diff --git a/package-lock.json b/package-lock.json index 3107ff9f04..2ebf8ef56f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5804,97 +5804,48 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.730.0.tgz", + "integrity": "sha512-iJt2pL6RqWg7R3pja1WfcC2+oTjwaKFYndNE9oUQqyc6RN24XWUtGy9JnWqTUOy8jYzaP2eoF00fGeasSBX+Dw==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/credential-provider-node": "3.730.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.730.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.730.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.730.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -5902,48 +5853,47 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.730.0.tgz", + "integrity": "sha512-mI8kqkSuVlZklewEmN7jcbBMyVODBld3MsTjCKSl5ztduuPX69JD7nXLnWWPkw1PX4aGTO24AEoRMGNxntoXUg==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/credential-provider-node": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.730.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.730.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.730.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", "@smithy/util-base64": "^4.0.0", "@smithy/util-body-length-browser": "^4.0.0", "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -5951,20 +5901,20 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.730.0.tgz", + "integrity": "sha512-jonKyR+2GcqbZj2WDICZS0c633keLc9qwXnePu83DfAoFXMMIMyoR/7FOGf8F3OrIdGh8KzE9VvST+nZCK9EJA==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/types": "3.723.0", + "@smithy/core": "^3.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/signature-v4": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, @@ -5972,195 +5922,235 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.730.0.tgz", + "integrity": "sha512-fFXgo3jBXLWqu8I07Hd96mS7RjrtpDgm3bZShm0F3lKtqDQF+hObFWq9A013SOE+RjMLVfbABhToXAYct3FcBw==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-logger": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.730.0.tgz", + "integrity": "sha512-1aF3elbCzpVhWLAuV63iFElfLOqLGGTp4fkf2VAFIDO3hjshpXUQssTgIWiBwwtJYJdOSxaFrCU7u8frjr/5aQ==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-stream": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.730.0.tgz", + "integrity": "sha512-zwsxkBuQuPp06o45ATAnznHzj3+ibop/EaTytNzSv0O87Q59K/jnS/bdtv1n6bhe99XCieRNTihvtS7YklzK7A==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/credential-provider-env": "3.730.0", + "@aws-sdk/credential-provider-http": "3.730.0", + "@aws-sdk/credential-provider-process": "3.730.0", + "@aws-sdk/credential-provider-sso": "3.730.0", + "@aws-sdk/credential-provider-web-identity": "3.730.0", + "@aws-sdk/nested-clients": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.730.0.tgz", + "integrity": "sha512-ztRjh1edY7ut2wwrj1XqHtqPY/NXEYIk5fYf04KKsp8zBi81ScVqP7C+Cst6PFKixjgLSG6RsqMx9GSAalVv0Q==", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.5", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/credential-provider-env": "3.730.0", + "@aws-sdk/credential-provider-http": "3.730.0", + "@aws-sdk/credential-provider-ini": "3.730.0", + "@aws-sdk/credential-provider-process": "3.730.0", + "@aws-sdk/credential-provider-sso": "3.730.0", + "@aws-sdk/credential-provider-web-identity": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/credential-provider-imds": "^4.0.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.730.0.tgz", + "integrity": "sha512-cNKUQ81eptfZN8MlSqwUq3+5ln8u/PcY57UmLZ+npxUHanqO1akpgcpNsLpmsIkoXGbtSQrLuDUgH86lS/SWOw==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.730.0.tgz", + "integrity": "sha512-SdI2xrTbquJLMxUh5LpSwB8zfiKq3/jso53xWRgrVfeDlrSzZuyV6QghaMs3KEEjcNzwEnTfSIjGQyRXG9VrEw==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/client-sso": "3.730.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/token-providers": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-endpoints": { - "version": "3.743.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.730.0.tgz", + "integrity": "sha512-l5vdPmvF/d890pbvv5g1GZrdjaSQkyPH/Bc8dO/ZqkWxkIP8JNgl48S2zgf4DkP3ik9K2axWO828L5RsMDQzdA==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "@smithy/util-endpoints": "^3.0.1", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/nested-clients": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.723.0.tgz", + "integrity": "sha512-LLVzLvk299pd7v4jN9yOSaWDZDfH0SnBPb6q+FDPaOCMGBY8kuwQso7e/ozIKSmZHRMGO3IZrflasHM+rI+2YQ==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-logger": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.723.0.tgz", + "integrity": "sha512-chASQfDG5NJ8s5smydOEnNK7N0gDMyuPbx7dYYcm1t/PKtnVfvWF+DHCTrRC2Ej76gLJVCVizlAJKM8v8Kg3cg==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.723.0.tgz", + "integrity": "sha512-7usZMtoynT9/jxL/rkuDOFQ0C2mhXl4yCm67Rg7GNTstl67u7w5WN1aIRImMeztaKlw8ExjoTyo6WTs1Kceh7A==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.723.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.730.0.tgz", + "integrity": "sha512-aPMZvNmf2a42B41au3bA3ODU4HfHka2nYT/SAIhhVXH1ENYfAmZo7FraFPxetKepFMCtL7j4QE6/LDucK6liIw==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.730.0", + "@smithy/core": "^3.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/hash-node": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/nested-clients": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.730.0.tgz", + "integrity": "sha512-vilIgf1/7kre8DdE5zAQkDOwHFb/TahMn/6j2RZwFLlK7cDk91r19deSiVYnKQkupDMtOfNceNqnorM4I3PDzw==", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-buffer-from": "^4.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.730.0", + "@aws-sdk/middleware-host-header": "3.723.0", + "@aws-sdk/middleware-logger": "3.723.0", + "@aws-sdk/middleware-recursion-detection": "3.723.0", + "@aws-sdk/middleware-user-agent": "3.730.0", + "@aws-sdk/region-config-resolver": "3.723.0", + "@aws-sdk/types": "3.723.0", + "@aws-sdk/util-endpoints": "3.730.0", + "@aws-sdk/util-user-agent-browser": "3.723.0", + "@aws-sdk/util-user-agent-node": "3.730.0", + "@smithy/config-resolver": "^4.0.0", + "@smithy/core": "^3.0.0", + "@smithy/fetch-http-handler": "^5.0.0", + "@smithy/hash-node": "^4.0.0", + "@smithy/invalid-dependency": "^4.0.0", + "@smithy/middleware-content-length": "^4.0.0", + "@smithy/middleware-endpoint": "^4.0.0", + "@smithy/middleware-retry": "^4.0.0", + "@smithy/middleware-serde": "^4.0.0", + "@smithy/middleware-stack": "^4.0.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/node-http-handler": "^4.0.0", + "@smithy/protocol-http": "^5.0.0", + "@smithy/smithy-client": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/url-parser": "^4.0.0", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.0", + "@smithy/util-defaults-mode-node": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "@smithy/util-middleware": "^4.0.0", + "@smithy/util-retry": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -6168,168 +6158,137 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz", + "integrity": "sha512-tGF/Cvch3uQjZIj34LY2mg8M2Dr4kYG8VU8Yd0dFnB1ybOEOveIK/9ypUo9ycZpB9oO6q01KRe5ijBaxNueUQg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.730.0.tgz", + "integrity": "sha512-BSPssGj54B/AABWXARIPOT/1ybFahM1ldlfmXy9gRmZi/afe9geWJGlFYCCt3PmqR+1Ny5XIjSfue+kMd//drQ==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/nested-clients": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/property-provider": "^4.0.0", + "@smithy/shared-ini-file-loader": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.723.0.tgz", + "integrity": "sha512-LmK3kwiMZG1y5g3LGihT9mNkeNOmwEyPk6HGcJqh0wOSV4QpWoKu2epyKE4MLQNUUlz2kOVbVbOrwmI6ZcteuA==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.730.0.tgz", + "integrity": "sha512-1KTFuVnk+YtLgWr6TwDiggcDqtPpOY2Cszt3r2lkXfaEAX6kHyOZi1vdvxXjPU5LsOBJem8HZ7KlkmrEi+xowg==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "@smithy/util-endpoints": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.723.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.723.0.tgz", + "integrity": "sha512-Wh9I6j2jLhNFq6fmXydIpqD1WyQLyTfSxjW9B+PXSnPyk3jtQW8AKQur7p97rO8LAUzVI0bv8kb3ZzDEVbquIg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.723.0", + "@smithy/types": "^4.0.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.730.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.730.0.tgz", + "integrity": "sha512-yBvkOAjqsDEl1va4eHNOhnFBk0iCY/DBFNyhvtTMqPF4NO+MITWpFs3J9JtZKzJlQ6x0Yb9TLQ8NhDjEISz5Ug==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/middleware-user-agent": "3.730.0", + "@aws-sdk/types": "3.723.0", + "@smithy/node-config-provider": "^4.0.0", + "@smithy/types": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/core": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", + "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.2", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -6337,52 +6296,42 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", "dependencies": { + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/fetch-http-handler": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", + "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", "dependencies": { + "@smithy/types": "^4.3.1", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -6391,330 +6340,269 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", "dependencies": { + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-config-provider": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-endpoints": { - "version": "3.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", + "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/core": "^3.5.3", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-retry": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", + "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.5", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-retry": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-http-handler": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", + "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-logger": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.5", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/service-error-classification": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", + "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@smithy/types": "^4.3.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { - "version": "3.743.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "@smithy/util-endpoints": "^3.0.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/smithy-client": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", + "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@smithy/core": "^3.5.3", + "@smithy/middleware-endpoint": "^4.1.11", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/hash-node": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dependencies": { - "@smithy/types": "^4.1.0", "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -6723,168 +6611,143 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", + "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", + "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-retry": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", + "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/service-error-classification": "^4.0.5", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", + "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/fetch-http-handler": "^5.0.4", + "@smithy/node-http-handler": "^4.0.6", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, @@ -6892,650 +6755,795 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8": { "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-datazone/-/client-datazone-3.848.0.tgz", + "integrity": "sha512-m9x9G6oQHUVJvt9JsTdU41/nimz11MMmQLptQVgIJcD6VHoHoVXppvPntK7GUkH0T6+0gw63RugGd7kB+xofBQ==", "dependencies": { - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/credential-provider-node": "3.848.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/client-sso": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.848.0.tgz", + "integrity": "sha512-mD+gOwoeZQvbecVLGoCmY6pS7kg02BHesbtIxUj+PeBqYoZV5uLvjUOmuGfw1SfoSobKvS11urxC9S7zxU/Maw==", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/core": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", + "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", "dependencies": { + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.7.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.846.0.tgz", + "integrity": "sha512-QuCQZET9enja7AWVISY+mpFrEIeHzvkx/JEEbHYzHhUkxcnC2Kq2c0bB7hDihGD0AZd3Xsm653hk1O97qu69zg==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.846.0.tgz", + "integrity": "sha512-Jh1iKUuepdmtreMYozV2ePsPcOF5W9p3U4tWhi3v6nDvz0GsBjzjAROW+BW8XMz9vAD3I9R+8VC3/aq63p5nlw==", "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-endpoints": { - "version": "3.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.848.0.tgz", + "integrity": "sha512-r6KWOG+En2xujuMhgZu7dzOZV3/M5U/5+PXrG8dLQ3rdPRB3vgp5tc56KMqLwm/EXKRzAOSuw/UE4HfNOAB8Hw==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/credential-provider-env": "3.846.0", + "@aws-sdk/credential-provider-http": "3.846.0", + "@aws-sdk/credential-provider-process": "3.846.0", + "@aws-sdk/credential-provider-sso": "3.848.0", + "@aws-sdk/credential-provider-web-identity": "3.848.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.848.0.tgz", + "integrity": "sha512-AblNesOqdzrfyASBCo1xW3uweiSro4Kft9/htdxLeCVU1KVOnFWA5P937MNahViRmIQm2sPBCqL8ZG0u9lnh5g==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/credential-provider-env": "3.846.0", + "@aws-sdk/credential-provider-http": "3.846.0", + "@aws-sdk/credential-provider-ini": "3.848.0", + "@aws-sdk/credential-provider-process": "3.846.0", + "@aws-sdk/credential-provider-sso": "3.848.0", + "@aws-sdk/credential-provider-web-identity": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-retry": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.846.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.846.0.tgz", + "integrity": "sha512-mEpwDYarJSH+CIXnnHN0QOe0MXI+HuPStD6gsv3z/7Q6ESl8KRWon3weFZCDnqpiJMUVavlDR0PPlAFg2MQoPg==", "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.848.0.tgz", + "integrity": "sha512-pozlDXOwJZL0e7w+dqXLgzVDB7oCx4WvtY0sk6l4i07uFliWF/exupb6pIehFWvTUcOvn5aFTTqcQaEzAD5Wsg==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@aws-sdk/client-sso": "3.848.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/token-providers": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.848.0.tgz", + "integrity": "sha512-D1fRpwPxtVDhcSc/D71exa2gYweV+ocp4D3brF0PgFd//JR3XahZ9W24rVnTQwYEcK9auiBZB89Ltv+WbWN8qw==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.848.0.tgz", + "integrity": "sha512-rjMuqSWJEf169/ByxvBqfdei1iaduAnfolTshsZxwcmLIUtbYrFUmts0HrLQqsAG8feGPpDLHA272oPl+NTCCA==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@smithy/core": "^3.7.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/nested-clients": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.848.0.tgz", + "integrity": "sha512-joLsyyo9u61jnZuyYzo1z7kmS7VgWRAkzSGESVzQHfOA1H2PYeUFek6vLT4+c9xMGrX/Z6B0tkRdzfdOPiatLg==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.848.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.15", + "@smithy/middleware-retry": "^4.1.16", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.7", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.23", + "@smithy/util-defaults-mode-node": "^4.0.23", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/token-providers": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.848.0.tgz", + "integrity": "sha512-oNPyM4+Di2Umu0JJRFSxDcKQ35+Chl/rAwD47/bS0cDPI8yrao83mLXLeDqpRPHyQW4sXlP763FZcuAibC0+mg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.846.0", + "@aws-sdk/nested-clients": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-endpoints": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", + "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.848.0.tgz", + "integrity": "sha512-Zz1ft9NiLqbzNj/M0jVNxaoxI2F4tGXN0ZbZIj+KJ+PbJo+w5+Jo6d0UDAtbj3AEd79pjcCaP4OA9NTVzItUdw==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.848.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/core": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.1.tgz", + "integrity": "sha512-ExRCsHnXFtBPnM7MkfKBPcBBdHw1h/QS/cbNw4ho95qnyNHvnpmGbR39MIAv9KggTr5qSPxRSEL+hRXlyGyGQw==", "dependencies": { + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/fetch-http-handler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", + "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-env/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.16.tgz", + "integrity": "sha512-plpa50PIGLqzMR2ANKAw2yOW5YKS626KYKqae3atwucbz4Ve4uQ9K9BEZxDLIFmCu7hKLcrq2zmj4a+PfmUV5w==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/core": "^3.7.1", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-retry": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.17.tgz", + "integrity": "sha512-gsCimeG6BApj0SBecwa1Be+Z+JOJe46iy3B3m3A8jKJHf7eIihP76Is4LwLrbJ1ygoS7Vg73lfqzejmLOrazUA==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.8", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/node-http-handler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", + "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "dependencies": { + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", "dependencies": { "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@smithy/util-middleware": "^4.0.4", "@smithy/util-uri-escape": "^4.0.0", "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" @@ -7544,27 +7552,27 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/smithy-client": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.8.tgz", + "integrity": "sha512-pcW691/lx7V54gE+dDGC26nxz8nrvnvRSCJaIYD6XLPpOInEZeKdV/SpSux+wqeQ4Ine7LJQu8uxMvobTIBK0w==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@smithy/core": "^3.7.1", + "@smithy/middleware-endpoint": "^4.1.16", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "dependencies": { "tslib": "^2.6.2" }, @@ -7572,23 +7580,23 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-base64": { "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dependencies": { "@smithy/util-buffer-from": "^4.0.0", "@smithy/util-utf8": "^4.0.0", @@ -7598,10 +7606,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-body-length-browser": { + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "dependencies": { "tslib": "^2.6.2" }, @@ -7609,92 +7617,89 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.758.0", - "@aws-sdk/credential-provider-http": "3.758.0", - "@aws-sdk/credential-provider-ini": "3.758.0", - "@aws-sdk/credential-provider-process": "3.758.0", - "@aws-sdk/credential-provider-sso": "3.758.0", - "@aws-sdk/credential-provider-web-identity": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.24", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.24.tgz", + "integrity": "sha512-UkQNgaQ+bidw1MgdgPO1z1k95W/v8Ej/5o/T/Is8PiVUYPspl/ZxV6WO/8DrzZQu5ULnmpB9CDdMSRwgRc21AA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.8", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.24", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.24.tgz", + "integrity": "sha512-phvGi/15Z4MpuQibTLOYIumvLdXb+XIJu8TA55voGgboln85jytA3wiD7CkUE8SNcWqkkb+uptZKPiuFouX/7g==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.8", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-node/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dependencies": { "tslib": "^2.6.2" }, @@ -7702,531 +7707,420 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-stream": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", + "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-datazone/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "strnum": "^2.1.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-datazone/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/@aws-sdk/client-ec2": { + "version": "3.695.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-sdk-ec2": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/property-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/protocol-http": { - "version": "5.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-process/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/client-sso": "3.758.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/token-providers": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/core": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/signature-v4": { - "version": "5.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/smithy-client": { - "version": "4.1.6", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/url-parser": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/util-middleware": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/credential-provider-sso/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-host-header": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8239,7 +8133,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-logger": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8251,7 +8145,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8264,7 +8158,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8280,7 +8174,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/region-config-resolver": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8295,7 +8189,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/token-providers": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8312,7 +8206,7 @@ "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-endpoints": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8325,7 +8219,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8335,7 +8229,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -8357,1122 +8251,1430 @@ } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/abort-controller": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/abort-controller/node_modules/@smithy/types": { - "version": "4.1.0", + "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.693.0.tgz", + "integrity": "sha512-qBI06wo2VaQI/+Pb4GmZRVQMnXFr9B983nWWNhM6xzcYmfJKXbCW29syDVojiwp8/HPMOSqcKJzqIOqCWtN1Ug==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds/node_modules/@smithy/property-provider": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/credential-provider-imds/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/core": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/querystring-builder": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/querystring-builder/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "dependencies": { + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/querystring-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/querystring-parser/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/service-error-classification": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "dependencies": { - "@smithy/types": "^4.1.0" + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/service-error-classification/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream": { - "version": "4.1.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-eks": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-eks/-/client-eks-3.936.0.tgz", + "integrity": "sha512-bdKxO/nj6VRiqHxgWBa/4fGdZOU5xyhRAZ4CB7Rn/oJy+PTYXZFNAQBQ3+aBZMvi5juNodKOBFInnOozZ+Hl1A==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-node": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/client-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", + "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-utf8": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/core": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", + "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-utf8/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", + "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-codecatalyst/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", + "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.730.0.tgz", - "integrity": "sha512-iJt2pL6RqWg7R3pja1WfcC2+oTjwaKFYndNE9oUQqyc6RN24XWUtGy9JnWqTUOy8jYzaP2eoF00fGeasSBX+Dw==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.730.0", - "@aws-sdk/credential-provider-node": "3.730.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.730.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.730.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.730.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", + "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-login": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/client-sso": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.730.0.tgz", - "integrity": "sha512-mI8kqkSuVlZklewEmN7jcbBMyVODBld3MsTjCKSl5ztduuPX69JD7nXLnWWPkw1PX4aGTO24AEoRMGNxntoXUg==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", + "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.730.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.730.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.730.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.730.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-ini": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/core": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.730.0.tgz", - "integrity": "sha512-jonKyR+2GcqbZj2WDICZS0c633keLc9qwXnePu83DfAoFXMMIMyoR/7FOGf8F3OrIdGh8KzE9VvST+nZCK9EJA==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", + "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/core": "^3.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/signature-v4": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", - "fast-xml-parser": "4.4.1", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.730.0.tgz", - "integrity": "sha512-fFXgo3jBXLWqu8I07Hd96mS7RjrtpDgm3bZShm0F3lKtqDQF+hObFWq9A013SOE+RjMLVfbABhToXAYct3FcBw==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", + "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/client-sso": "3.936.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/token-providers": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.730.0.tgz", - "integrity": "sha512-1aF3elbCzpVhWLAuV63iFElfLOqLGGTp4fkf2VAFIDO3hjshpXUQssTgIWiBwwtJYJdOSxaFrCU7u8frjr/5aQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", + "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-stream": "^4.0.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.730.0.tgz", - "integrity": "sha512-zwsxkBuQuPp06o45ATAnznHzj3+ibop/EaTytNzSv0O87Q59K/jnS/bdtv1n6bhe99XCieRNTihvtS7YklzK7A==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/credential-provider-env": "3.730.0", - "@aws-sdk/credential-provider-http": "3.730.0", - "@aws-sdk/credential-provider-process": "3.730.0", - "@aws-sdk/credential-provider-sso": "3.730.0", - "@aws-sdk/credential-provider-web-identity": "3.730.0", - "@aws-sdk/nested-clients": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.730.0.tgz", - "integrity": "sha512-ztRjh1edY7ut2wwrj1XqHtqPY/NXEYIk5fYf04KKsp8zBi81ScVqP7C+Cst6PFKixjgLSG6RsqMx9GSAalVv0Q==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.730.0", - "@aws-sdk/credential-provider-http": "3.730.0", - "@aws-sdk/credential-provider-ini": "3.730.0", - "@aws-sdk/credential-provider-process": "3.730.0", - "@aws-sdk/credential-provider-sso": "3.730.0", - "@aws-sdk/credential-provider-web-identity": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/credential-provider-imds": "^4.0.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.730.0.tgz", - "integrity": "sha512-cNKUQ81eptfZN8MlSqwUq3+5ln8u/PcY57UmLZ+npxUHanqO1akpgcpNsLpmsIkoXGbtSQrLuDUgH86lS/SWOw==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.730.0.tgz", - "integrity": "sha512-SdI2xrTbquJLMxUh5LpSwB8zfiKq3/jso53xWRgrVfeDlrSzZuyV6QghaMs3KEEjcNzwEnTfSIjGQyRXG9VrEw==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", + "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.730.0", - "@aws-sdk/core": "3.730.0", - "@aws-sdk/token-providers": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.730.0.tgz", - "integrity": "sha512-l5vdPmvF/d890pbvv5g1GZrdjaSQkyPH/Bc8dO/ZqkWxkIP8JNgl48S2zgf4DkP3ik9K2axWO828L5RsMDQzdA==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/nested-clients": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", + "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/nested-clients": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.723.0.tgz", - "integrity": "sha512-LLVzLvk299pd7v4jN9yOSaWDZDfH0SnBPb6q+FDPaOCMGBY8kuwQso7e/ozIKSmZHRMGO3IZrflasHM+rI+2YQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-logger": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.723.0.tgz", - "integrity": "sha512-chASQfDG5NJ8s5smydOEnNK7N0gDMyuPbx7dYYcm1t/PKtnVfvWF+DHCTrRC2Ej76gLJVCVizlAJKM8v8Kg3cg==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/token-providers": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", + "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.723.0.tgz", - "integrity": "sha512-7usZMtoynT9/jxL/rkuDOFQ0C2mhXl4yCm67Rg7GNTstl67u7w5WN1aIRImMeztaKlw8ExjoTyo6WTs1Kceh7A==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.730.0.tgz", - "integrity": "sha512-aPMZvNmf2a42B41au3bA3ODU4HfHka2nYT/SAIhhVXH1ENYfAmZo7FraFPxetKepFMCtL7j4QE6/LDucK6liIw==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.730.0", - "@smithy/core": "^3.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/types": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/nested-clients": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.730.0.tgz", - "integrity": "sha512-vilIgf1/7kre8DdE5zAQkDOwHFb/TahMn/6j2RZwFLlK7cDk91r19deSiVYnKQkupDMtOfNceNqnorM4I3PDzw==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.730.0", - "@aws-sdk/middleware-host-header": "3.723.0", - "@aws-sdk/middleware-logger": "3.723.0", - "@aws-sdk/middleware-recursion-detection": "3.723.0", - "@aws-sdk/middleware-user-agent": "3.730.0", - "@aws-sdk/region-config-resolver": "3.723.0", - "@aws-sdk/types": "3.723.0", - "@aws-sdk/util-endpoints": "3.730.0", - "@aws-sdk/util-user-agent-browser": "3.723.0", - "@aws-sdk/util-user-agent-node": "3.730.0", - "@smithy/config-resolver": "^4.0.0", - "@smithy/core": "^3.0.0", - "@smithy/fetch-http-handler": "^5.0.0", - "@smithy/hash-node": "^4.0.0", - "@smithy/invalid-dependency": "^4.0.0", - "@smithy/middleware-content-length": "^4.0.0", - "@smithy/middleware-endpoint": "^4.0.0", - "@smithy/middleware-retry": "^4.0.0", - "@smithy/middleware-serde": "^4.0.0", - "@smithy/middleware-stack": "^4.0.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/node-http-handler": "^4.0.0", - "@smithy/protocol-http": "^5.0.0", - "@smithy/smithy-client": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/url-parser": "^4.0.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.0", - "@smithy/util-defaults-mode-node": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", - "@smithy/util-middleware": "^4.0.0", - "@smithy/util-retry": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.723.0.tgz", - "integrity": "sha512-tGF/Cvch3uQjZIj34LY2mg8M2Dr4kYG8VU8Yd0dFnB1ybOEOveIK/9ypUo9ycZpB9oO6q01KRe5ijBaxNueUQg==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", + "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/token-providers": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.730.0.tgz", - "integrity": "sha512-BSPssGj54B/AABWXARIPOT/1ybFahM1ldlfmXy9gRmZi/afe9geWJGlFYCCt3PmqR+1Ny5XIjSfue+kMd//drQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/property-provider": "^4.0.0", - "@smithy/shared-ini-file-loader": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/types": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.723.0.tgz", - "integrity": "sha512-LmK3kwiMZG1y5g3LGihT9mNkeNOmwEyPk6HGcJqh0wOSV4QpWoKu2epyKE4MLQNUUlz2kOVbVbOrwmI6ZcteuA==", - "dependencies": { - "@smithy/types": "^4.0.0", - "tslib": "^2.6.2" - }, + "node_modules/@aws-sdk/client-eks/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.0.tgz", + "integrity": "sha512-D1jAmAZQYMoPiacfgNf7AWhg3DFN3Wq/vQv3WINt9znwjzHp2x+WzdJFxxj7xZL7V1U79As6G8f7PorMYWBKsQ==", + "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-endpoints": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.730.0.tgz", - "integrity": "sha512-1KTFuVnk+YtLgWr6TwDiggcDqtPpOY2Cszt3r2lkXfaEAX6kHyOZi1vdvxXjPU5LsOBJem8HZ7KlkmrEi+xowg==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "@smithy/util-endpoints": "^3.0.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.723.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.723.0.tgz", - "integrity": "sha512-Wh9I6j2jLhNFq6fmXydIpqD1WyQLyTfSxjW9B+PXSnPyk3jtQW8AKQur7p97rO8LAUzVI0bv8kb3ZzDEVbquIg==", - "dependencies": { - "@aws-sdk/types": "3.723.0", - "@smithy/types": "^4.0.0", - "bowser": "^2.11.0", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.730.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.730.0.tgz", - "integrity": "sha512-yBvkOAjqsDEl1va4eHNOhnFBk0iCY/DBFNyhvtTMqPF4NO+MITWpFs3J9JtZKzJlQ6x0Yb9TLQ8NhDjEISz5Ug==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/core": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", + "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.730.0", - "@aws-sdk/types": "3.723.0", - "@smithy/node-config-provider": "^4.0.0", - "@smithy/types": "^4.0.0", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/config-resolver": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", - "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/core": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.5.3.tgz", - "integrity": "sha512-xa5byV9fEguZNofCclv6v9ra0FYh5FATQW/da7FQUVTic94DfrN/NvmKZjrMyzbpqfot9ZjBaO8U1UeTbmSLuA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/hash-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.2", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", - "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.4.tgz", - "integrity": "sha512-AMtBR5pHppYMVD7z7G+OlHHAcgAN7v0kVKEpHuTO4Gb199Gowh0taYi9oDStFeUhetkeP55JLSVlTW1n9rFtUw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/hash-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", - "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/invalid-dependency": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", - "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", + "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/core": "^3.18.5", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-retry": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", + "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", + "license": "Apache-2.0", "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-content-length": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", - "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.11.tgz", - "integrity": "sha512-zDogwtRLzKl58lVS8wPcARevFZNBOOqnmzWWxVe9XiaXU2CADFjvJ9XfNibgkOWs08sxLuSr81NrpY4mgp9OwQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-middleware": "^4.0.4", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-retry": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.12.tgz", - "integrity": "sha512-wvIH70c4e91NtRxdaLZF+mbLZ/HcC6yg7ySKUiufL6ESp6zJUSnJucZ309AvG9nqCFHSRB5I6T3Ez1Q9wCh0Ww==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/service-error-classification": "^4.0.5", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.5", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/middleware-stack": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", - "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-config-provider": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", - "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/node-http-handler": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.6.tgz", - "integrity": "sha512-NqbmSz7AW2rvw4kXhKGrYTiJVDHnMsFnX4i+/FzcZAfbOBauPYs2ekuECkSbtqaxETLLTu9Rl/ex6+I2BKErPA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/property-provider": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", - "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@smithy/types": "^4.9.0" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/service-error-classification": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.5.tgz", - "integrity": "sha512-LvcfhrnCBvCmTee81pRlh1F39yTS/+kYleVeLCwNtkY8wtGg8V/ca9rbZZvYIl8OjlMtL6KIjaiL/lgVqHD2nA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/smithy-client": { + "version": "4.9.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", + "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1" + "@smithy/core": "^3.18.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", - "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/signature-v4": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", - "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/smithy-client": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.3.tgz", - "integrity": "sha512-xxzNYgA0HD6ETCe5QJubsxP0hQH3QK3kbpJz3QrosBCuIWyEXLR/CO5hFb2OeawEKUxMNhz3a1nuJNN2np2RMA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.5.3", - "@smithy/middleware-endpoint": "^4.1.11", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.2", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -9480,36 +9682,36 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -9517,166 +9719,184 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", + "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "license": "Apache-2.0", "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", + "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.19.tgz", - "integrity": "sha512-mvLMh87xSmQrV5XqnUYEPoiFFeEGYeAKIDDKdhE2ahqitm8OHM3aSvhqL6rrK6wm1brIk90JhxDf5lf2hbrLbQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.19", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.19.tgz", - "integrity": "sha512-8tYnx+LUfj6m+zkUUIrIQJxPM1xVxfRBvoGHua7R/i6qAxOMjqR6CpEpDwKoIs1o0+hOjGvkKE23CafKL0vJ9w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.3", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", "dependencies": { + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-retry": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.5.tgz", - "integrity": "sha512-V7MSjVDTlEt/plmOFBn1762Dyu5uqMrV2Pl2X0dYk4XvWfdWJNe9Bs5Bzb56wkCuiWjSfClVMGcsuKrGj7S/yg==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.5", - "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-stream": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.2.tgz", - "integrity": "sha512-aI+GLi7MJoVxg24/3J1ipwLoYzgkB4kUfogZfnslcYlynj3xsQ0e7vk4TnTro9hhsS5PvX1mwmkRqqHQjwcU7w==", + "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-waiter": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", + "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.4", - "@smithy/node-http-handler": "^4.0.6", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/abort-controller": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "node_modules/@aws-sdk/client-eks/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "strnum": "^2.1.0" }, - "engines": { - "node": ">=18.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-cognito-identity/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "node_modules/@aws-sdk/client-eks/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" }, - "node_modules/@aws-sdk/client-datazone": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-datazone/-/client-datazone-3.848.0.tgz", - "integrity": "sha512-m9x9G6oQHUVJvt9JsTdU41/nimz11MMmQLptQVgIJcD6VHoHoVXppvPntK7GUkH0T6+0gw63RugGd7kB+xofBQ==", + "node_modules/@aws-sdk/client-glue": { + "version": "3.852.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-glue/-/client-glue-3.852.0.tgz", + "integrity": "sha512-5IyZt/gKr0NoUHWGM112ikXrZs+VsA/09bwKDmp4/j250tfaZqgC1zhfBNFkyNisj1JQ0XYjwfzkLnYWlT3Pyw==", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -9715,17 +9935,14 @@ "@smithy/util-endpoints": "^3.0.6", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", - "@smithy/util-stream": "^4.2.3", "@smithy/util-utf8": "^4.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/client-sso": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.848.0.tgz", "integrity": "sha512-mD+gOwoeZQvbecVLGoCmY6pS7kg02BHesbtIxUj+PeBqYoZV5uLvjUOmuGfw1SfoSobKvS11urxC9S7zxU/Maw==", @@ -9773,7 +9990,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/core": { "version": "3.846.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", @@ -9798,7 +10015,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-env": { "version": "3.846.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.846.0.tgz", "integrity": "sha512-QuCQZET9enja7AWVISY+mpFrEIeHzvkx/JEEbHYzHhUkxcnC2Kq2c0bB7hDihGD0AZd3Xsm653hk1O97qu69zg==", @@ -9813,7 +10030,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-http": { "version": "3.846.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.846.0.tgz", "integrity": "sha512-Jh1iKUuepdmtreMYozV2ePsPcOF5W9p3U4tWhi3v6nDvz0GsBjzjAROW+BW8XMz9vAD3I9R+8VC3/aq63p5nlw==", @@ -9833,7 +10050,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.848.0.tgz", "integrity": "sha512-r6KWOG+En2xujuMhgZu7dzOZV3/M5U/5+PXrG8dLQ3rdPRB3vgp5tc56KMqLwm/EXKRzAOSuw/UE4HfNOAB8Hw==", @@ -9856,7 +10073,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-node": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.848.0.tgz", "integrity": "sha512-AblNesOqdzrfyASBCo1xW3uweiSro4Kft9/htdxLeCVU1KVOnFWA5P937MNahViRmIQm2sPBCqL8ZG0u9lnh5g==", @@ -9878,7 +10095,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-process": { "version": "3.846.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.846.0.tgz", "integrity": "sha512-mEpwDYarJSH+CIXnnHN0QOe0MXI+HuPStD6gsv3z/7Q6ESl8KRWon3weFZCDnqpiJMUVavlDR0PPlAFg2MQoPg==", @@ -9894,7 +10111,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.848.0.tgz", "integrity": "sha512-pozlDXOwJZL0e7w+dqXLgzVDB7oCx4WvtY0sk6l4i07uFliWF/exupb6pIehFWvTUcOvn5aFTTqcQaEzAD5Wsg==", @@ -9912,7 +10129,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.848.0.tgz", "integrity": "sha512-D1fRpwPxtVDhcSc/D71exa2gYweV+ocp4D3brF0PgFd//JR3XahZ9W24rVnTQwYEcK9auiBZB89Ltv+WbWN8qw==", @@ -9928,7 +10145,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-host-header": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", @@ -9942,7 +10159,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-logger": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", @@ -9955,7 +10172,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", @@ -9969,7 +10186,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.848.0.tgz", "integrity": "sha512-rjMuqSWJEf169/ByxvBqfdei1iaduAnfolTshsZxwcmLIUtbYrFUmts0HrLQqsAG8feGPpDLHA272oPl+NTCCA==", @@ -9986,7 +10203,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/nested-clients": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.848.0.tgz", "integrity": "sha512-joLsyyo9u61jnZuyYzo1z7kmS7VgWRAkzSGESVzQHfOA1H2PYeUFek6vLT4+c9xMGrX/Z6B0tkRdzfdOPiatLg==", @@ -10034,7 +10251,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/region-config-resolver": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", @@ -10050,7 +10267,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/token-providers": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.848.0.tgz", "integrity": "sha512-oNPyM4+Di2Umu0JJRFSxDcKQ35+Chl/rAwD47/bS0cDPI8yrao83mLXLeDqpRPHyQW4sXlP763FZcuAibC0+mg==", @@ -10067,7 +10284,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/types": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", @@ -10079,7 +10296,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-endpoints": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", @@ -10094,7 +10311,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.840.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", @@ -10105,7 +10322,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.848.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.848.0.tgz", "integrity": "sha512-Zz1ft9NiLqbzNj/M0jVNxaoxI2F4tGXN0ZbZIj+KJ+PbJo+w5+Jo6d0UDAtbj3AEd79pjcCaP4OA9NTVzItUdw==", @@ -10128,7 +10345,7 @@ } } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/xml-builder": { "version": "3.821.0", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", @@ -10140,7 +10357,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/abort-controller": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", @@ -10152,7 +10369,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/config-resolver": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/config-resolver": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", @@ -10167,10 +10384,10 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/core": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.1.tgz", - "integrity": "sha512-ExRCsHnXFtBPnM7MkfKBPcBBdHw1h/QS/cbNw4ho95qnyNHvnpmGbR39MIAv9KggTr5qSPxRSEL+hRXlyGyGQw==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/core": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", + "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", "dependencies": { "@smithy/middleware-serde": "^4.0.8", "@smithy/protocol-http": "^5.1.2", @@ -10186,7 +10403,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/credential-provider-imds": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/credential-provider-imds": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", @@ -10201,7 +10418,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/fetch-http-handler": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", @@ -10216,7 +10433,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/hash-node": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/hash-node": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", @@ -10230,7 +10447,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/invalid-dependency": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/invalid-dependency": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", @@ -10242,7 +10459,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/is-array-buffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", @@ -10253,7 +10470,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-content-length": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-content-length": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", @@ -10266,12 +10483,12 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.16.tgz", - "integrity": "sha512-plpa50PIGLqzMR2ANKAw2yOW5YKS626KYKqae3atwucbz4Ve4uQ9K9BEZxDLIFmCu7hKLcrq2zmj4a+PfmUV5w==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", + "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", "dependencies": { - "@smithy/core": "^3.7.1", + "@smithy/core": "^3.7.2", "@smithy/middleware-serde": "^4.0.8", "@smithy/node-config-provider": "^4.1.3", "@smithy/shared-ini-file-loader": "^4.0.4", @@ -10284,15 +10501,15 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-retry": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.17.tgz", - "integrity": "sha512-gsCimeG6BApj0SBecwa1Be+Z+JOJe46iy3B3m3A8jKJHf7eIihP76Is4LwLrbJ1ygoS7Vg73lfqzejmLOrazUA==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-retry": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", + "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", "dependencies": { "@smithy/node-config-provider": "^4.1.3", "@smithy/protocol-http": "^5.1.2", "@smithy/service-error-classification": "^4.0.6", - "@smithy/smithy-client": "^4.4.8", + "@smithy/smithy-client": "^4.4.9", "@smithy/types": "^4.3.1", "@smithy/util-middleware": "^4.0.4", "@smithy/util-retry": "^4.0.6", @@ -10303,7 +10520,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-serde": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", @@ -10316,7 +10533,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-stack": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", @@ -10328,7 +10545,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/node-config-provider": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", @@ -10342,7 +10559,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/node-http-handler": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/node-http-handler": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", @@ -10357,7 +10574,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/property-provider": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/property-provider": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", @@ -10369,7 +10586,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/protocol-http": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/protocol-http": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", @@ -10381,7 +10598,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/querystring-builder": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/querystring-builder": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", @@ -10394,7 +10611,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/querystring-parser": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/querystring-parser": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", @@ -10406,7 +10623,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/service-error-classification": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/service-error-classification": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", @@ -10417,7 +10634,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/shared-ini-file-loader": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/shared-ini-file-loader": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", @@ -10429,7 +10646,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/signature-v4": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/signature-v4": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", @@ -10447,13 +10664,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/smithy-client": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.8.tgz", - "integrity": "sha512-pcW691/lx7V54gE+dDGC26nxz8nrvnvRSCJaIYD6XLPpOInEZeKdV/SpSux+wqeQ4Ine7LJQu8uxMvobTIBK0w==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/smithy-client": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", + "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", "dependencies": { - "@smithy/core": "^3.7.1", - "@smithy/middleware-endpoint": "^4.1.16", + "@smithy/core": "^3.7.2", + "@smithy/middleware-endpoint": "^4.1.17", "@smithy/middleware-stack": "^4.0.4", "@smithy/protocol-http": "^5.1.2", "@smithy/types": "^4.3.1", @@ -10464,7 +10681,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/types": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/types": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", @@ -10475,7 +10692,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/url-parser": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/url-parser": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", @@ -10488,7 +10705,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-base64": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-base64": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", @@ -10501,7 +10718,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-body-length-browser": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-body-length-browser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", @@ -10512,7 +10729,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-body-length-node": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-body-length-node": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", @@ -10523,7 +10740,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-buffer-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", @@ -10535,7 +10752,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-config-provider": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-config-provider": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", @@ -10546,13 +10763,13 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.24", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.24.tgz", - "integrity": "sha512-UkQNgaQ+bidw1MgdgPO1z1k95W/v8Ej/5o/T/Is8PiVUYPspl/ZxV6WO/8DrzZQu5ULnmpB9CDdMSRwgRc21AA==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", + "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", "dependencies": { "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.8", + "@smithy/smithy-client": "^4.4.9", "@smithy/types": "^4.3.1", "bowser": "^2.11.0", "tslib": "^2.6.2" @@ -10561,16 +10778,16 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.24", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.24.tgz", - "integrity": "sha512-phvGi/15Z4MpuQibTLOYIumvLdXb+XIJu8TA55voGgboln85jytA3wiD7CkUE8SNcWqkkb+uptZKPiuFouX/7g==", + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", + "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", "dependencies": { "@smithy/config-resolver": "^4.1.4", "@smithy/credential-provider-imds": "^4.0.6", "@smithy/node-config-provider": "^4.1.3", "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.8", + "@smithy/smithy-client": "^4.4.9", "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, @@ -10578,7 +10795,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-endpoints": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-endpoints": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", @@ -10591,7 +10808,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-hex-encoding": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-hex-encoding": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", @@ -10602,7 +10819,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-middleware": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-middleware": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", @@ -10614,7 +10831,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-retry": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-retry": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", @@ -10627,7 +10844,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-stream": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-stream": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", @@ -10645,7 +10862,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-uri-escape": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-uri-escape": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", @@ -10656,7 +10873,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-utf8": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", @@ -10668,7 +10885,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/fast-xml-parser": { + "node_modules/@aws-sdk/client-glue/node_modules/fast-xml-parser": { "version": "5.2.5", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", @@ -10685,7 +10902,7 @@ "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/client-datazone/node_modules/strnum": { + "node_modules/@aws-sdk/client-glue/node_modules/strnum": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", @@ -10696,8 +10913,8 @@ } ] }, - "node_modules/@aws-sdk/client-ec2": { - "version": "3.695.0", + "node_modules/@aws-sdk/client-iam": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -10709,7 +10926,6 @@ "@aws-sdk/middleware-host-header": "3.693.0", "@aws-sdk/middleware-logger": "3.693.0", "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-sdk-ec2": "3.693.0", "@aws-sdk/middleware-user-agent": "3.693.0", "@aws-sdk/region-config-resolver": "3.693.0", "@aws-sdk/types": "3.692.0", @@ -10742,15 +10958,13 @@ "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", "@smithy/util-waiter": "^3.1.8", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10797,7 +11011,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10848,7 +11062,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10897,7 +11111,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/core": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10917,7 +11131,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-http": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10936,7 +11150,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10960,7 +11174,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-node": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10981,7 +11195,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -10998,7 +11212,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11015,7 +11229,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-host-header": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11028,7 +11242,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-logger": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11040,7 +11254,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11053,7 +11267,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11069,7 +11283,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/region-config-resolver": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11084,7 +11298,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/token-providers": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11101,7 +11315,7 @@ "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-endpoints": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11114,7 +11328,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11124,7 +11338,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { @@ -11146,7 +11360,7 @@ } } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -11156,7 +11370,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -11167,7 +11381,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ec2/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-iam/node_modules/@smithy/util-utf8": { "version": "3.0.0", "license": "Apache-2.0", "dependencies": { @@ -11178,10 +11392,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr": { + "node_modules/@aws-sdk/client-iot": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecr/-/client-ecr-3.693.0.tgz", - "integrity": "sha512-qBI06wo2VaQI/+Pb4GmZRVQMnXFr9B983nWWNhM6xzcYmfJKXbCW29syDVojiwp8/HPMOSqcKJzqIOqCWtN1Ug==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.693.0.tgz", + "integrity": "sha512-0EOKH6CjDHMdE1NSDdtZ8/zov+Xf1MovWvAeQGs76ec4mL2VWP5HvePjjdkGoOo0KC9k/AqOVVc0UOZjK0iCQw==", + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -11223,17 +11438,19 @@ "@smithy/util-middleware": "^3.0.9", "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "tslib": "^2.6.2" + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sso": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -11278,10 +11495,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -11330,10 +11548,11 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", + "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -11380,10 +11599,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/core": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/core": "^2.5.2", @@ -11401,10 +11621,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-http": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", "@aws-sdk/types": "3.692.0", @@ -11421,10 +11642,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", "@aws-sdk/credential-provider-env": "3.693.0", @@ -11446,10 +11668,11 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-node": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.693.0", "@aws-sdk/credential-provider-http": "3.693.0", @@ -11468,10 +11691,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.693.0", "@aws-sdk/core": "3.693.0", @@ -11486,10 +11710,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", "@aws-sdk/types": "3.692.0", @@ -11504,10 +11729,11 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-host-header": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/protocol-http": "^4.1.6", @@ -11518,10 +11744,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-logger": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/types": "^3.7.0", @@ -11531,10 +11758,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/protocol-http": "^4.1.6", @@ -11545,10 +11773,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", "@aws-sdk/types": "3.692.0", @@ -11562,10 +11791,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/region-config-resolver": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/node-config-provider": "^3.1.10", @@ -11578,10 +11808,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/token-providers": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/property-provider": "^3.1.9", @@ -11596,10 +11827,11 @@ "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-endpoints": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/types": "^3.7.0", @@ -11610,10 +11842,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", "@smithy/types": "^3.7.0", @@ -11621,10 +11854,11 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", + "license": "Apache-2.0", "dependencies": { "@aws-sdk/middleware-user-agent": "3.693.0", "@aws-sdk/types": "3.692.0", @@ -11644,10 +11878,11 @@ } } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-iot/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, @@ -11655,10 +11890,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-iot/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" @@ -11667,10 +11903,11 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ecr/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-iot/node_modules/@smithy/util-utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", "dependencies": { "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" @@ -11679,470 +11916,480 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-eks/-/client-eks-3.936.0.tgz", - "integrity": "sha512-bdKxO/nj6VRiqHxgWBa/4fGdZOU5xyhRAZ4CB7Rn/oJy+PTYXZFNAQBQ3+aBZMvi5juNodKOBFInnOozZ+Hl1A==", + "node_modules/@aws-sdk/client-iotsecuretunneling": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iotsecuretunneling/-/client-iotsecuretunneling-3.693.0.tgz", + "integrity": "sha512-f9p5/TgVQsko0FlYIj9UKAVSfgPF4GgoKGVOI3Gx6XpynYwideGxItq3v0ExoAzpaohq6zRKleqA68o/T1TqXQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.5", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/client-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", - "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", - "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", - "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/core": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", - "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", - "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", - "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", - "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.936.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/token-providers": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", - "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", - "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/nested-clients": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", - "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/token-providers": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", - "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", - "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -12153,6979 +12400,705 @@ } } }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.0.tgz", - "integrity": "sha512-D1jAmAZQYMoPiacfgNf7AWhg3DFN3Wq/vQv3WINt9znwjzHp2x+WzdJFxxj7xZL7V1U79As6G8f7PorMYWBKsQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/core": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", - "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", + "node_modules/@aws-sdk/client-lambda": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "node_modules/@aws-sdk/client-redshift": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift/-/client-redshift-3.693.0.tgz", + "integrity": "sha512-k+4emXXK7iOOYjTAU+Erj5RVxu68Hi6iI48h5r8iNMhWRUMqUq346tK5qkD4C4x9SzJu5j0WgPWpVUiHu8ufDw==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-endpoint": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", - "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", + "node_modules/@aws-sdk/client-redshift-data": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift-data/-/client-redshift-data-3.693.0.tgz", + "integrity": "sha512-uG5LdlXz80KcauRIucMdiRSQJ2WutewQRHpcTQW4vFUf/kEhUha5fD9FMn+/eJ1NFA2N8hv64vhpzGvu7EiP6Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-retry": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", - "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/uuid": "^1.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/core": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/@smithy/util-waiter": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", - "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@aws-sdk/client-eks/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/client-glue": { - "version": "3.852.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-glue/-/client-glue-3.852.0.tgz", - "integrity": "sha512-5IyZt/gKr0NoUHWGM112ikXrZs+VsA/09bwKDmp4/j250tfaZqgC1zhfBNFkyNisj1JQ0XYjwfzkLnYWlT3Pyw==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.846.0", - "@aws-sdk/credential-provider-node": "3.848.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.848.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.848.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.0", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.15", - "@smithy/middleware-retry": "^4.1.16", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.7", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.23", - "@smithy/util-defaults-mode-node": "^4.0.23", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/client-sso": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.848.0.tgz", - "integrity": "sha512-mD+gOwoeZQvbecVLGoCmY6pS7kg02BHesbtIxUj+PeBqYoZV5uLvjUOmuGfw1SfoSobKvS11urxC9S7zxU/Maw==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.846.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.848.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.848.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.0", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.15", - "@smithy/middleware-retry": "^4.1.16", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.7", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.23", - "@smithy/util-defaults-mode-node": "^4.0.23", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/core": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.846.0.tgz", - "integrity": "sha512-7CX0pM906r4WSS68fCTNMTtBCSkTtf3Wggssmx13gD40gcWEZXsU00KzPp1bYheNRyPlAq3rE22xt4wLPXbuxA==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.7.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.7", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.846.0.tgz", - "integrity": "sha512-QuCQZET9enja7AWVISY+mpFrEIeHzvkx/JEEbHYzHhUkxcnC2Kq2c0bB7hDihGD0AZd3Xsm653hk1O97qu69zg==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.846.0.tgz", - "integrity": "sha512-Jh1iKUuepdmtreMYozV2ePsPcOF5W9p3U4tWhi3v6nDvz0GsBjzjAROW+BW8XMz9vAD3I9R+8VC3/aq63p5nlw==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/types": "3.840.0", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.7", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.848.0.tgz", - "integrity": "sha512-r6KWOG+En2xujuMhgZu7dzOZV3/M5U/5+PXrG8dLQ3rdPRB3vgp5tc56KMqLwm/EXKRzAOSuw/UE4HfNOAB8Hw==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/credential-provider-env": "3.846.0", - "@aws-sdk/credential-provider-http": "3.846.0", - "@aws-sdk/credential-provider-process": "3.846.0", - "@aws-sdk/credential-provider-sso": "3.848.0", - "@aws-sdk/credential-provider-web-identity": "3.848.0", - "@aws-sdk/nested-clients": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.848.0.tgz", - "integrity": "sha512-AblNesOqdzrfyASBCo1xW3uweiSro4Kft9/htdxLeCVU1KVOnFWA5P937MNahViRmIQm2sPBCqL8ZG0u9lnh5g==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.846.0", - "@aws-sdk/credential-provider-http": "3.846.0", - "@aws-sdk/credential-provider-ini": "3.848.0", - "@aws-sdk/credential-provider-process": "3.846.0", - "@aws-sdk/credential-provider-sso": "3.848.0", - "@aws-sdk/credential-provider-web-identity": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.846.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.846.0.tgz", - "integrity": "sha512-mEpwDYarJSH+CIXnnHN0QOe0MXI+HuPStD6gsv3z/7Q6ESl8KRWon3weFZCDnqpiJMUVavlDR0PPlAFg2MQoPg==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.848.0.tgz", - "integrity": "sha512-pozlDXOwJZL0e7w+dqXLgzVDB7oCx4WvtY0sk6l4i07uFliWF/exupb6pIehFWvTUcOvn5aFTTqcQaEzAD5Wsg==", - "dependencies": { - "@aws-sdk/client-sso": "3.848.0", - "@aws-sdk/core": "3.846.0", - "@aws-sdk/token-providers": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.848.0.tgz", - "integrity": "sha512-D1fRpwPxtVDhcSc/D71exa2gYweV+ocp4D3brF0PgFd//JR3XahZ9W24rVnTQwYEcK9auiBZB89Ltv+WbWN8qw==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/nested-clients": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.848.0.tgz", - "integrity": "sha512-rjMuqSWJEf169/ByxvBqfdei1iaduAnfolTshsZxwcmLIUtbYrFUmts0HrLQqsAG8feGPpDLHA272oPl+NTCCA==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@smithy/core": "^3.7.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/nested-clients": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.848.0.tgz", - "integrity": "sha512-joLsyyo9u61jnZuyYzo1z7kmS7VgWRAkzSGESVzQHfOA1H2PYeUFek6vLT4+c9xMGrX/Z6B0tkRdzfdOPiatLg==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.846.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.848.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.848.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.0", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.15", - "@smithy/middleware-retry": "^4.1.16", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.7", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.23", - "@smithy/util-defaults-mode-node": "^4.0.23", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/token-providers": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.848.0.tgz", - "integrity": "sha512-oNPyM4+Di2Umu0JJRFSxDcKQ35+Chl/rAwD47/bS0cDPI8yrao83mLXLeDqpRPHyQW4sXlP763FZcuAibC0+mg==", - "dependencies": { - "@aws-sdk/core": "3.846.0", - "@aws-sdk/nested-clients": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-endpoints": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", - "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.848.0.tgz", - "integrity": "sha512-Zz1ft9NiLqbzNj/M0jVNxaoxI2F4tGXN0ZbZIj+KJ+PbJo+w5+Jo6d0UDAtbj3AEd79pjcCaP4OA9NTVzItUdw==", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.848.0", - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@aws-sdk/xml-builder": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", - "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/config-resolver": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", - "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", - "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/fetch-http-handler": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", - "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/hash-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", - "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/invalid-dependency": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", - "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-content-length": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", - "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", - "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", - "dependencies": { - "@smithy/core": "^3.7.2", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-retry": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", - "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/service-error-classification": "^4.0.6", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/middleware-stack": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", - "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/node-config-provider": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", - "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", - "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/node-http-handler": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", - "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/property-provider": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", - "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/service-error-classification": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", - "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", - "dependencies": { - "@smithy/types": "^4.3.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", - "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/signature-v4": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", - "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "dependencies": { - "@smithy/core": "^3.7.2", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", - "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", - "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", - "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", - "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", - "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-retry": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", - "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", - "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-stream": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", - "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", - "dependencies": { - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@aws-sdk/client-glue/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ] - }, - "node_modules/@aws-sdk/client-iam": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iam/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.693.0.tgz", - "integrity": "sha512-0EOKH6CjDHMdE1NSDdtZ8/zov+Xf1MovWvAeQGs76ec4mL2VWP5HvePjjdkGoOo0KC9k/AqOVVc0UOZjK0iCQw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iot/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iotsecuretunneling/-/client-iotsecuretunneling-3.693.0.tgz", - "integrity": "sha512-f9p5/TgVQsko0FlYIj9UKAVSfgPF4GgoKGVOI3Gx6XpynYwideGxItq3v0ExoAzpaohq6zRKleqA68o/T1TqXQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-iotsecuretunneling/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda": { - "version": "3.637.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.637.0", - "@aws-sdk/client-sts": "3.637.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/eventstream-serde-browser": "^3.0.6", - "@smithy/eventstream-serde-config-resolver": "^3.0.3", - "@smithy/eventstream-serde-node": "^3.0.5", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-stream": "^3.1.3", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@aws-sdk/types": { - "version": "3.609.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-lambda/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift/-/client-redshift-3.693.0.tgz", - "integrity": "sha512-k+4emXXK7iOOYjTAU+Erj5RVxu68Hi6iI48h5r8iNMhWRUMqUq346tK5qkD4C4x9SzJu5j0WgPWpVUiHu8ufDw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift-data/-/client-redshift-data-3.693.0.tgz", - "integrity": "sha512-uG5LdlXz80KcauRIucMdiRSQJ2WutewQRHpcTQW4vFUf/kEhUha5fD9FMn+/eJ1NFA2N8hv64vhpzGvu7EiP6Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift-serverless/-/client-redshift-serverless-3.693.0.tgz", - "integrity": "sha512-m6Bhw0Xx/x0KGKP9N7c+Jqs5VT6nkZbfwO+QTxllggsuNfAzGwluCw1hoY++/MQ9oFtioEu+ud7xWOlTIK8w/A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", - "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha1-browser": "5.2.0", - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-bucket-endpoint": "3.693.0", - "@aws-sdk/middleware-expect-continue": "3.693.0", - "@aws-sdk/middleware-flexible-checksums": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-location-constraint": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-sdk-s3": "3.693.0", - "@aws-sdk/middleware-ssec": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/signature-v4-multi-region": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@aws-sdk/xml-builder": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/eventstream-serde-browser": "^3.0.12", - "@smithy/eventstream-serde-config-resolver": "^3.0.9", - "@smithy/eventstream-serde-node": "^3.0.11", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-blob-browser": "^3.1.8", - "@smithy/hash-node": "^3.0.9", - "@smithy/hash-stream-node": "^3.1.8", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/md5-js": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-stream": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control": { - "version": "3.859.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3-control/-/client-s3-control-3.859.0.tgz", - "integrity": "sha512-vzhOtDH4BCdn30+Crg1QxGXbhZIh4Ia84/qNx2EtupkM2UrO6uaZ91qGl175QWU4TcG+mlf/yA/bvrwenhbF6w==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.858.0", - "@aws-sdk/credential-provider-node": "3.859.0", - "@aws-sdk/middleware-bucket-endpoint": "3.840.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-sdk-s3-control": "3.848.0", - "@aws-sdk/middleware-user-agent": "3.858.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.858.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.2", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-blob-browser": "^4.0.4", - "@smithy/hash-node": "^4.0.4", - "@smithy/hash-stream-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/md5-js": "^4.0.4", - "@smithy/middleware-apply-body-checksum": "^4.1.2", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-retry": "^4.1.18", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.25", - "@smithy/util-defaults-mode-node": "^4.0.25", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/client-sso": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.858.0.tgz", - "integrity": "sha512-iXuZQs4KH6a3Pwnt0uORalzAZ5EXRPr3lBYAsdNwkP8OYyoUz5/TE3BLyw7ceEh0rj4QKGNnNALYo1cDm0EV8w==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.858.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.858.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.858.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.2", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-retry": "^4.1.18", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.25", - "@smithy/util-defaults-mode-node": "^4.0.25", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/core": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.858.0.tgz", - "integrity": "sha512-iWm4QLAS+/XMlnecIU1Y33qbBr1Ju+pmWam3xVCPlY4CSptKpVY+2hXOnmg9SbHAX9C005fWhrIn51oDd00c9A==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/xml-builder": "3.821.0", - "@smithy/core": "^3.7.2", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/signature-v4": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-utf8": "^4.0.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.858.0.tgz", - "integrity": "sha512-kZsGyh2BoSRguzlcGtzdLhw/l/n3KYAC+/l/H0SlsOq3RLHF6tO/cRdsLnwoix2bObChHUp03cex63o1gzdx/Q==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.858.0.tgz", - "integrity": "sha512-GDnfYl3+NPJQ7WQQYOXEA489B212NinpcIDD7rpsB6IWUPo8yDjT5NceK4uUkIR3MFpNCGt9zd/z6NNLdB2fuQ==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.859.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.859.0.tgz", - "integrity": "sha512-KsccE1T88ZDNhsABnqbQj014n5JMDilAroUErFbGqu5/B3sXqUsYmG54C/BjvGTRUFfzyttK9lB9P9h6ddQ8Cw==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/credential-provider-env": "3.858.0", - "@aws-sdk/credential-provider-http": "3.858.0", - "@aws-sdk/credential-provider-process": "3.858.0", - "@aws-sdk/credential-provider-sso": "3.859.0", - "@aws-sdk/credential-provider-web-identity": "3.858.0", - "@aws-sdk/nested-clients": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.859.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.859.0.tgz", - "integrity": "sha512-ZRDB2xU5aSyTR/jDcli30tlycu6RFvQngkZhBs9Zoh2BiYXrfh2MMuoYuZk+7uD6D53Q2RIEldDHR9A/TPlRuA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.858.0", - "@aws-sdk/credential-provider-http": "3.858.0", - "@aws-sdk/credential-provider-ini": "3.859.0", - "@aws-sdk/credential-provider-process": "3.858.0", - "@aws-sdk/credential-provider-sso": "3.859.0", - "@aws-sdk/credential-provider-web-identity": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.858.0.tgz", - "integrity": "sha512-l5LJWZJMRaZ+LhDjtupFUKEC5hAjgvCRrOvV5T60NCUBOy0Ozxa7Sgx3x+EOwiruuoh3Cn9O+RlbQlJX6IfZIw==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.859.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.859.0.tgz", - "integrity": "sha512-BwAqmWIivhox5YlFRjManFF8GoTvEySPk6vsJNxDsmGsabY+OQovYxFIYxRCYiHzH7SFjd4Lcd+riJOiXNsvRw==", - "dependencies": { - "@aws-sdk/client-sso": "3.858.0", - "@aws-sdk/core": "3.858.0", - "@aws-sdk/token-providers": "3.859.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.858.0.tgz", - "integrity": "sha512-8iULWsH83iZDdUuiDsRb83M0NqIlXjlDbJUIddVsIrfWp4NmanKw77SV6yOZ66nuJjPsn9j7RDb9bfEPCy5SWA==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/nested-clients": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", - "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", - "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-logger": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", - "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", - "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.858.0.tgz", - "integrity": "sha512-pC3FT/sRZ6n5NyXiTVu9dpf1D9j3YbJz3XmeOOwJqO/Mib2PZyIQktvNMPgwaC5KMVB1zWqS5bmCwxpMOnq0UQ==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@smithy/core": "^3.7.2", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/nested-clients": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.858.0.tgz", - "integrity": "sha512-ChdIj80T2whoWbovmO7o8ICmhEB2S9q4Jes9MBnKAPm69PexcJAK2dQC8yI4/iUP8b3+BHZoUPrYLWjBxIProQ==", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.858.0", - "@aws-sdk/middleware-host-header": "3.840.0", - "@aws-sdk/middleware-logger": "3.840.0", - "@aws-sdk/middleware-recursion-detection": "3.840.0", - "@aws-sdk/middleware-user-agent": "3.858.0", - "@aws-sdk/region-config-resolver": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@aws-sdk/util-user-agent-browser": "3.840.0", - "@aws-sdk/util-user-agent-node": "3.858.0", - "@smithy/config-resolver": "^4.1.4", - "@smithy/core": "^3.7.2", - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/hash-node": "^4.0.4", - "@smithy/invalid-dependency": "^4.0.4", - "@smithy/middleware-content-length": "^4.0.4", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-retry": "^4.1.18", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.25", - "@smithy/util-defaults-mode-node": "^4.0.25", - "@smithy/util-endpoints": "^3.0.6", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", - "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/token-providers": { - "version": "3.859.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.859.0.tgz", - "integrity": "sha512-6P2wlvm9KBWOvRNn0Pt8RntnXg8fzOb5kEShvWsOsAocZeqKNaYbihum5/Onq1ZPoVtkdb++8eWDocDnM4k85Q==", - "dependencies": { - "@aws-sdk/core": "3.858.0", - "@aws-sdk/nested-clients": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.804.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", - "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-endpoints": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", - "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-endpoints": "^3.0.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", - "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", - "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.858.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.858.0.tgz", - "integrity": "sha512-T1m05QlN8hFpx5/5duMjS8uFSK5e6EXP45HQRkZULVkL3DK+jMaxsnh3KLl5LjUoHn/19M4HM0wNUBhYp4Y2Yw==", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.858.0", - "@aws-sdk/types": "3.840.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/xml-builder": { - "version": "3.821.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", - "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/abort-controller": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", - "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/chunked-blob-reader": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", - "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/chunked-blob-reader-native": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", - "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", - "dependencies": { - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/config-resolver": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", - "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/core": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", - "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", - "dependencies": { - "@smithy/middleware-serde": "^4.0.8", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-stream": "^4.2.3", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", - "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/fetch-http-handler": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", - "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-blob-browser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", - "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", - "dependencies": { - "@smithy/chunked-blob-reader": "^5.0.0", - "@smithy/chunked-blob-reader-native": "^4.0.0", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", - "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-stream-node": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", - "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/invalid-dependency": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", - "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/md5-js": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", - "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-content-length": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", - "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-endpoint": { - "version": "4.1.17", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", - "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", - "dependencies": { - "@smithy/core": "^3.7.2", - "@smithy/middleware-serde": "^4.0.8", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-middleware": "^4.0.4", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-retry": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", - "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/service-error-classification": "^4.0.6", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-retry": "^4.0.6", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-serde": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", - "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", - "dependencies": { - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-stack": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", - "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/node-config-provider": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", - "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", - "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/node-http-handler": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", - "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", - "dependencies": { - "@smithy/abort-controller": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/querystring-builder": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/property-provider": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", - "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/querystring-builder": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", - "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", - "dependencies": { - "@smithy/types": "^4.3.1", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/service-error-classification": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", - "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", - "dependencies": { - "@smithy/types": "^4.3.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", - "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/signature-v4": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", - "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.4", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/smithy-client": { - "version": "4.4.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", - "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", - "dependencies": { - "@smithy/core": "^3.7.2", - "@smithy/middleware-endpoint": "^4.1.17", - "@smithy/middleware-stack": "^4.0.4", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-stream": "^4.2.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", - "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", - "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", - "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", - "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.25", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", - "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", - "dependencies": { - "@smithy/config-resolver": "^4.1.4", - "@smithy/credential-provider-imds": "^4.0.6", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/property-provider": "^4.0.4", - "@smithy/smithy-client": "^4.4.9", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", - "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-middleware": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", - "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", - "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-retry": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", - "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", - "dependencies": { - "@smithy/service-error-classification": "^4.0.6", - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-stream": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", - "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", - "dependencies": { - "@smithy/fetch-http-handler": "^5.1.0", - "@smithy/node-http-handler": "^4.1.0", - "@smithy/types": "^4.3.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", - "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@aws-sdk/client-s3-control/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ] - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker/-/client-sagemaker-3.693.0.tgz", - "integrity": "sha512-iInrrb7V9f0CRBiVCaaxCbpoBRQ5BqxX4elRYI6gE/pSDD2tPqmRfm4reahMtTUcKg1jaSGuvqJLfOpp0HTozQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", - "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", - "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sts": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", - "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", - "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", - "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-stream": "^3.3.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", - "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", - "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.693.0", - "@aws-sdk/credential-provider-http": "3.693.0", - "@aws-sdk/credential-provider-ini": "3.693.0", - "@aws-sdk/credential-provider-process": "3.693.0", - "@aws-sdk/credential-provider-sso": "3.693.0", - "@aws-sdk/credential-provider-web-identity": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/credential-provider-imds": "^3.2.6", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", - "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/token-providers": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", - "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", - "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", - "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", - "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", - "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", - "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", - "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", - "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", - "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", - "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", - "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", @@ -19138,7 +13111,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-redshift-data/node_modules/@smithy/util-utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", @@ -19151,10 +13124,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn": { + "node_modules/@aws-sdk/client-redshift-serverless": { "version": "3.693.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sfn/-/client-sfn-3.693.0.tgz", - "integrity": "sha512-B2K3aXGnP7eD1ITEIx4kO43l1N5OLqHdLW4AUbwoopwU5qzicc9jADrthXpGxymJI8AhJz9T2WtLmceBU2EpNg==", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-redshift-serverless/-/client-redshift-serverless-3.693.0.tgz", + "integrity": "sha512-m6Bhw0Xx/x0KGKP9N7c+Jqs5VT6nkZbfwO+QTxllggsuNfAzGwluCw1hoY++/MQ9oFtioEu+ud7xWOlTIK8w/A==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -19197,15 +13170,13 @@ "@smithy/util-middleware": "^3.0.9", "@smithy/util-retry": "^3.0.9", "@smithy/util-utf8": "^3.0.0", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sso": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", @@ -19254,7 +13225,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", @@ -19307,7 +13278,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", @@ -19358,7 +13329,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/core": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", @@ -19380,7 +13351,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-http": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", @@ -19401,7 +13372,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", @@ -19427,7 +13398,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-node": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", @@ -19450,7 +13421,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", @@ -19469,7 +13440,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", @@ -19488,7 +13459,7 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-host-header": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", @@ -19503,7 +13474,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-logger": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", @@ -19517,7 +13488,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", @@ -19532,7 +13503,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", @@ -19550,7 +13521,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/region-config-resolver": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/region-config-resolver": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", @@ -19567,7 +13538,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/token-providers": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", @@ -19586,7 +13557,7 @@ "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-endpoints": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", @@ -19601,7 +13572,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", @@ -19613,7 +13584,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.693.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", @@ -19637,7 +13608,7 @@ } } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/is-array-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", @@ -19649,7 +13620,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/util-buffer-from": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/util-buffer-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", @@ -19662,7 +13633,7 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/util-utf8": { + "node_modules/@aws-sdk/client-redshift-serverless/node_modules/@smithy/util-utf8": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", @@ -19675,61 +13646,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.693.0", - "@aws-sdk/client-sts": "3.693.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/credential-provider-node": "3.693.0", - "@aws-sdk/middleware-host-header": "3.693.0", - "@aws-sdk/middleware-logger": "3.693.0", - "@aws-sdk/middleware-recursion-detection": "3.693.0", - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/region-config-resolver": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@aws-sdk/util-user-agent-browser": "3.693.0", - "@aws-sdk/util-user-agent-node": "3.693.0", - "@smithy/config-resolver": "^3.0.11", - "@smithy/core": "^2.5.2", - "@smithy/fetch-http-handler": "^4.1.0", - "@smithy/hash-node": "^3.0.9", - "@smithy/invalid-dependency": "^3.0.9", - "@smithy/middleware-content-length": "^3.0.11", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/middleware-retry": "^3.0.26", - "@smithy/middleware-serde": "^3.0.9", - "@smithy/middleware-stack": "^3.0.9", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/node-http-handler": "^3.3.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/url-parser": "^3.0.9", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.26", - "@smithy/util-defaults-mode-node": "^3.0.26", - "@smithy/util-endpoints": "^2.1.5", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-retry": "^3.0.9", - "@smithy/util-utf8": "^3.0.0", - "@smithy/util-waiter": "^3.1.8", - "@types/uuid": "^9.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sso": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -19775,8 +13695,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso-oidc": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -19826,8 +13748,10 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sts": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -19875,8 +13799,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/core": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", @@ -19895,8 +13821,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-http": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -19914,8 +13842,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -19938,8 +13868,10 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-node": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/credential-provider-env": "3.693.0", @@ -19959,8 +13891,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/client-sso": "3.693.0", @@ -19976,8 +13910,10 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/core": "3.693.0", @@ -19993,1982 +13929,2285 @@ "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-redshift/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-bucket-endpoint": "3.693.0", + "@aws-sdk/middleware-expect-continue": "3.693.0", + "@aws-sdk/middleware-flexible-checksums": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-location-constraint": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-sdk-s3": "3.693.0", + "@aws-sdk/middleware-ssec": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/signature-v4-multi-region": "3.693.0", "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@aws-sdk/xml-builder": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/eventstream-serde-browser": "^3.0.12", + "@smithy/eventstream-serde-config-resolver": "^3.0.9", + "@smithy/eventstream-serde-node": "^3.0.11", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-blob-browser": "^3.1.8", + "@smithy/hash-node": "^3.0.9", + "@smithy/hash-stream-node": "^3.1.8", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/md5-js": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-stream": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-logger": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3-control/-/client-s3-control-3.859.0.tgz", + "integrity": "sha512-vzhOtDH4BCdn30+Crg1QxGXbhZIh4Ia84/qNx2EtupkM2UrO6uaZ91qGl175QWU4TcG+mlf/yA/bvrwenhbF6w==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/credential-provider-node": "3.859.0", + "@aws-sdk/middleware-bucket-endpoint": "3.840.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-sdk-s3-control": "3.848.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-blob-browser": "^4.0.4", + "@smithy/hash-node": "^4.0.4", + "@smithy/hash-stream-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/md5-js": "^4.0.4", + "@smithy/middleware-apply-body-checksum": "^4.1.2", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/client-sso": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.858.0.tgz", + "integrity": "sha512-iXuZQs4KH6a3Pwnt0uORalzAZ5EXRPr3lBYAsdNwkP8OYyoUz5/TE3BLyw7ceEh0rj4QKGNnNALYo1cDm0EV8w==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/core": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.858.0.tgz", + "integrity": "sha512-iWm4QLAS+/XMlnecIU1Y33qbBr1Ju+pmWam3xVCPlY4CSptKpVY+2hXOnmg9SbHAX9C005fWhrIn51oDd00c9A==", "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-endpoints": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/xml-builder": "3.821.0", + "@smithy/core": "^3.7.2", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/signature-v4": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-utf8": "^4.0.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.858.0.tgz", + "integrity": "sha512-kZsGyh2BoSRguzlcGtzdLhw/l/n3KYAC+/l/H0SlsOq3RLHF6tO/cRdsLnwoix2bObChHUp03cex63o1gzdx/Q==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/token-providers": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.858.0.tgz", + "integrity": "sha512-GDnfYl3+NPJQ7WQQYOXEA489B212NinpcIDD7rpsB6IWUPo8yDjT5NceK4uUkIR3MFpNCGt9zd/z6NNLdB2fuQ==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sso-oidc": "^3.693.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-endpoints": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.859.0.tgz", + "integrity": "sha512-KsccE1T88ZDNhsABnqbQj014n5JMDilAroUErFbGqu5/B3sXqUsYmG54C/BjvGTRUFfzyttK9lB9P9h6ddQ8Cw==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "@smithy/util-endpoints": "^2.1.5", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/credential-provider-env": "3.858.0", + "@aws-sdk/credential-provider-http": "3.858.0", + "@aws-sdk/credential-provider-process": "3.858.0", + "@aws-sdk/credential-provider-sso": "3.859.0", + "@aws-sdk/credential-provider-web-identity": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.859.0.tgz", + "integrity": "sha512-ZRDB2xU5aSyTR/jDcli30tlycu6RFvQngkZhBs9Zoh2BiYXrfh2MMuoYuZk+7uD6D53Q2RIEldDHR9A/TPlRuA==", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", - "bowser": "^2.11.0", + "@aws-sdk/credential-provider-env": "3.858.0", + "@aws-sdk/credential-provider-http": "3.858.0", + "@aws-sdk/credential-provider-ini": "3.859.0", + "@aws-sdk/credential-provider-process": "3.858.0", + "@aws-sdk/credential-provider-sso": "3.859.0", + "@aws-sdk/credential-provider-web-identity": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.693.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.858.0.tgz", + "integrity": "sha512-l5LJWZJMRaZ+LhDjtupFUKEC5hAjgvCRrOvV5T60NCUBOy0Ozxa7Sgx3x+EOwiruuoh3Cn9O+RlbQlJX6IfZIw==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/types": "^3.7.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.859.0.tgz", + "integrity": "sha512-BwAqmWIivhox5YlFRjManFF8GoTvEySPk6vsJNxDsmGsabY+OQovYxFIYxRCYiHzH7SFjd4Lcd+riJOiXNsvRw==", "dependencies": { + "@aws-sdk/client-sso": "3.858.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/token-providers": "3.859.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.858.0.tgz", + "integrity": "sha512-8iULWsH83iZDdUuiDsRb83M0NqIlXjlDbJUIddVsIrfWp4NmanKw77SV6yOZ66nuJjPsn9j7RDb9bfEPCy5SWA==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", + "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.637.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.840.0.tgz", + "integrity": "sha512-ub+hXJAbAje94+Ya6c6eL7sYujoE8D4Bumu1NUI8TXjUhVVn0HzVWQjpRLshdLsUp1AW7XyeJaxyajRaJQ8+Xg==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.637.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-logger": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.840.0.tgz", + "integrity": "sha512-lSV8FvjpdllpGaRspywss4CtXV8M7NNNH+2/j86vMH+YCOZ6fu2T/TyFd/tHwZ92vDfHctWkRbQxg0bagqwovA==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.637.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { - "version": "3.609.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.840.0.tgz", + "integrity": "sha512-Gu7lGDyfddyhIkj1Z1JtrY5NHb5+x/CRiB87GjaSrKxkDaydtX2CU977JIABtt69l9wLbcGDIQ+W0uJ5xPof7g==", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.840.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.858.0.tgz", + "integrity": "sha512-pC3FT/sRZ6n5NyXiTVu9dpf1D9j3YbJz3XmeOOwJqO/Mib2PZyIQktvNMPgwaC5KMVB1zWqS5bmCwxpMOnq0UQ==", "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@smithy/core": "^3.7.2", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/nested-clients": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.858.0.tgz", + "integrity": "sha512-ChdIj80T2whoWbovmO7o8ICmhEB2S9q4Jes9MBnKAPm69PexcJAK2dQC8yI4/iUP8b3+BHZoUPrYLWjBxIProQ==", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/middleware-host-header": "3.840.0", + "@aws-sdk/middleware-logger": "3.840.0", + "@aws-sdk/middleware-recursion-detection": "3.840.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/region-config-resolver": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@aws-sdk/util-user-agent-browser": "3.840.0", + "@aws-sdk/util-user-agent-node": "3.858.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/core": "^3.7.2", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/hash-node": "^4.0.4", + "@smithy/invalid-dependency": "^4.0.4", + "@smithy/middleware-content-length": "^4.0.4", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-retry": "^4.1.18", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.25", + "@smithy/util-defaults-mode-node": "^4.0.25", + "@smithy/util-endpoints": "^3.0.6", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.840.0.tgz", + "integrity": "sha512-Qjnxd/yDv9KpIMWr90ZDPtRj0v75AqGC92Lm9+oHXZ8p1MjG5JE2CW0HL8JRgK9iKzgKBL7pPQRXI8FkvEVfrA==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/token-providers": { + "version": "3.859.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.859.0.tgz", + "integrity": "sha512-6P2wlvm9KBWOvRNn0Pt8RntnXg8fzOb5kEShvWsOsAocZeqKNaYbihum5/Onq1ZPoVtkdb++8eWDocDnM4k85Q==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@aws-sdk/core": "3.858.0", + "@aws-sdk/nested-clients": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { - "version": "3.609.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-endpoints": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", + "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.840.0.tgz", + "integrity": "sha512-JdyZM3EhhL4PqwFpttZu1afDpPJCCc3eyZOLi+srpX11LsGj6sThf47TYQN75HT1CarZ7cCdQHGzP2uy3/xHfQ==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.637.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.858.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.858.0.tgz", + "integrity": "sha512-T1m05QlN8hFpx5/5duMjS8uFSK5e6EXP45HQRkZULVkL3DK+jMaxsnh3KLl5LjUoHn/19M4HM0wNUBhYp4Y2Yw==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/client-sso-oidc": "3.637.0", - "@aws-sdk/core": "3.635.0", - "@aws-sdk/credential-provider-node": "3.637.0", - "@aws-sdk/middleware-host-header": "3.620.0", - "@aws-sdk/middleware-logger": "3.609.0", - "@aws-sdk/middleware-recursion-detection": "3.620.0", - "@aws-sdk/middleware-user-agent": "3.637.0", - "@aws-sdk/region-config-resolver": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@aws-sdk/util-user-agent-browser": "3.609.0", - "@aws-sdk/util-user-agent-node": "3.614.0", - "@smithy/config-resolver": "^3.0.5", - "@smithy/core": "^2.4.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/hash-node": "^3.0.3", - "@smithy/invalid-dependency": "^3.0.3", - "@smithy/middleware-content-length": "^3.0.5", - "@smithy/middleware-endpoint": "^3.1.0", - "@smithy/middleware-retry": "^3.0.15", - "@smithy/middleware-serde": "^3.0.3", - "@smithy/middleware-stack": "^3.0.3", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/url-parser": "^3.0.3", - "@smithy/util-base64": "^3.0.0", - "@smithy/util-body-length-browser": "^3.0.0", - "@smithy/util-body-length-node": "^3.0.0", - "@smithy/util-defaults-mode-browser": "^3.0.15", - "@smithy/util-defaults-mode-node": "^3.0.15", - "@smithy/util-endpoints": "^2.0.5", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-retry": "^3.0.3", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/middleware-user-agent": "3.858.0", + "@aws-sdk/types": "3.840.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { - "version": "3.609.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@aws-sdk/xml-builder": { + "version": "3.821.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.821.0.tgz", + "integrity": "sha512-DIIotRnefVL6DiaHtO6/21DhJ4JZnnIwdNbpwiAhdt/AVbttcE4yw925gsjur0OGv5BTYXQXU3YnANBYnZjuQA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/abort-controller": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.4.tgz", + "integrity": "sha512-gJnEjZMvigPDQWHrW3oPrFhQtkrgqBkyjj3pCIdF3A5M6vsZODG93KNlfJprv6bp4245bdT32fsHK4kkH3KYDA==", "dependencies": { + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/chunked-blob-reader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.0.0.tgz", + "integrity": "sha512-+sKqDBQqb036hh4NPaUiEkYFkTUGYzRsn3EuFhyfQfMy6oGHEUJDurLP9Ufb5dasr/XiAmPNMr6wa9afjQB+Gw==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/chunked-blob-reader-native": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.0.0.tgz", + "integrity": "sha512-R9wM2yPmfEMsUmlMlIgSzOyICs0x9uu7UTHoccMyt7BWw8shcGM8HqB355+BZCPBcySvbTYMs62EgEQkNxz2ig==", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.635.0", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/config-resolver": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.1.4.tgz", + "integrity": "sha512-prmU+rDddxHOH0oNcwemL+SwnzcG65sBF2yXRO7aeXIn/xTlq2pX7JLVbkBnVLowHLg4/OL4+jBmv9hVrVGS+w==", "dependencies": { - "@smithy/core": "^2.4.0", - "@smithy/node-config-provider": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/signature-v4": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-middleware": "^3.0.3", - "fast-xml-parser": "4.4.1", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.936.0.tgz", - "integrity": "sha512-+aSC59yiD4M5RcYp9Gx3iwX/n4hO3ZWA2Mxmkzmt9gYFBbJ9umx2LpBdrV64y57AtOvfGeo0h7PAXniIufagxw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/core": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.7.2.tgz", + "integrity": "sha512-JoLw59sT5Bm8SAjFCYZyuCGxK8y3vovmoVbZWLDPTH5XpPEIwpFd9m90jjVMwoypDuB/SdVgje5Y4T7w50lJaw==", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-stream": "^4.2.3", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.936.0.tgz", - "integrity": "sha512-AkJZ426y0G8Lsyi9p7mWudacMKeo8XLZOfxUmeThMkDa3GxGQ1y6BTrOj6ZcvqQ1Hz7Abb3QWPC+EMqhu1Lncw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/credential-provider-imds": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.6.tgz", + "integrity": "sha512-hKMWcANhUiNbCJouYkZ9V3+/Qf9pteR1dnwgdyzR09R4ODEYx8BbUysHwRSyex4rZ9zapddZhLFTnT4ZijR4pw==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/client-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", - "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/fetch-http-handler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.1.0.tgz", + "integrity": "sha512-mADw7MS0bYe2OGKkHYMaqarOXuDwRbO6ArD91XhHcl2ynjGCFF+hvqf0LyQcYxkA1zaWjefSkU7Ne9mqgApSgQ==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-blob-browser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.0.4.tgz", + "integrity": "sha512-WszRiACJiQV3QG6XMV44i5YWlkrlsM5Yxgz4jvsksuu7LDXA6wAtypfPajtNTadzpJy3KyJPoWehYpmZGKUFIQ==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@smithy/chunked-blob-reader": "^5.0.0", + "@smithy/chunked-blob-reader-native": "^4.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", - "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.4.tgz", + "integrity": "sha512-qnbTPUhCVnCgBp4z4BUJUhOEkVwxiEi1cyFM+Zj6o+aY8OFGxUQleKWq8ltgp3dujuhXojIvJWdoqpm6dVO3lQ==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", - "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/hash-stream-node": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.0.4.tgz", + "integrity": "sha512-wHo0d8GXyVmpmMh/qOR0R7Y46/G1y6OR8U+bSTB4ppEzRxd1xVAQ9xOE9hOc0bSjhz0ujCPAbfNLkLrpa6cevg==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", - "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/invalid-dependency": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.4.tgz", + "integrity": "sha512-bNYMi7WKTJHu0gn26wg8OscncTt1t2b8KcsZxvOv56XA6cyXtOAAAaNP7+m45xfppXfOatXF3Sb1MNsLUgVLTw==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", - "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/is-array-buffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", + "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", "dependencies": { - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", - "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/md5-js": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.0.4.tgz", + "integrity": "sha512-uGLBVqcOwrLvGh/v/jw423yWHq/ofUGK1W31M2TNspLQbUV1Va0F5kTxtirkoHawODAZcjXTSGi7JwbnPcDPJg==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", - "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-content-length": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.4.tgz", + "integrity": "sha512-F7gDyfI2BB1Kc+4M6rpuOLne5LOcEknH1n6UQB69qv+HucXBR1rkzXBnQTB2q46sFy1PM/zuSJOB532yc8bg3w==", "dependencies": { - "@aws-sdk/client-sso": "3.936.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/token-providers": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", - "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-endpoint": { + "version": "4.1.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.1.17.tgz", + "integrity": "sha512-S3hSGLKmHG1m35p/MObQCBCdRsrpbPU8B129BVzRqRfDvQqPMQ14iO4LyRw+7LNizYc605COYAcjqgawqi+6jA==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/core": "^3.7.2", + "@smithy/middleware-serde": "^4.0.8", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-middleware": "^4.0.4", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-retry": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.1.18.tgz", + "integrity": "sha512-bYLZ4DkoxSsPxpdmeapvAKy7rM5+25gR7PGxq2iMiecmbrRGBHj9s75N74Ylg+aBiw9i5jIowC/cLU2NR0qH8w==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-retry": "^4.0.6", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-serde": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.8.tgz", + "integrity": "sha512-iSSl7HJoJaGyMIoNn2B7czghOVwJ9nD7TMvLhMWeSB5vt0TnEYyRRqPJu/TqW76WScaNvYYB8nRoiBHR9S1Ddw==", + "dependencies": { + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/middleware-stack": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.4.tgz", + "integrity": "sha512-kagK5ggDrBUCCzI93ft6DjteNSfY8Ulr83UtySog/h09lTIOAJ/xUSObutanlPT0nhoHAkpmW9V5K8oPyLh+QA==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", - "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/nested-clients": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", - "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/node-http-handler": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.1.0.tgz", + "integrity": "sha512-vqfSiHz2v8b3TTTrdXi03vNz1KLYYS3bhHCDv36FYDqxT7jvTll1mMnCrkD+gOvgwybuunh/2VmvOMqwBegxEg==", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@smithy/abort-controller": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/querystring-builder": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/token-providers": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", - "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/querystring-builder": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.4.tgz", + "integrity": "sha512-SwREZcDnEYoh9tLNgMbpop+UTGq44Hl9tdj3rf+yeLcfH7+J8OXEBaMc2kDxtyRHu8BhSg9ADEx0gFHvpJgU8w==", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", + "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/service-error-classification": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.6.tgz", + "integrity": "sha512-RRoTDL//7xi4tn5FrN2NzH17jbgmnKidUqd4KvquT0954/i6CXXkh1884jBiunq24g9cGtPBEXlU40W6EpNOOg==", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@smithy/types": "^4.3.1" + }, + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", - "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/signature-v4": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.1.2.tgz", + "integrity": "sha512-d3+U/VpX7a60seHziWnVZOHuEgJlclufjkS6zhXvxcJgkJq4UWdH5eOBLzHRMx6gXjsdT9h6lfpmLzbrdupHgQ==", "dependencies": { - "@smithy/types": "^4.9.0", - "fast-xml-parser": "5.2.5", + "@smithy/is-array-buffer": "^4.0.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-middleware": "^4.0.4", + "@smithy/util-uri-escape": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", - "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/smithy-client": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.4.9.tgz", + "integrity": "sha512-mbMg8mIUAWwMmb74LoYiArP04zWElPzDoA1jVOp3or0cjlDMgoS6WTC3QXK0Vxoc9I4zdrX0tq6qsOmaIoTWEQ==", + "dependencies": { + "@smithy/core": "^3.7.2", + "@smithy/middleware-endpoint": "^4.1.17", + "@smithy/middleware-stack": "^4.0.4", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-stream": "^4.2.3", + "tslib": "^2.6.2" + }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "dependencies": { - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/core": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", - "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-base64": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", + "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-body-length-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", + "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-body-length-node": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", + "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-buffer-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", + "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dependencies": { - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.25.tgz", + "integrity": "sha512-pxEWsxIsOPLfKNXvpgFHBGFC3pKYKUFhrud1kyooO9CJai6aaKDHfT10Mi5iiipPXN/JhKAu3qX9o75+X85OdQ==", "dependencies": { + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", + "bowser": "^2.11.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.25.tgz", + "integrity": "sha512-+w4n4hKFayeCyELZLfsSQG5mCC3TwSkmRHv4+el5CzFU8ToQpYGhpV7mrRzqlwKkntlPilT1HJy1TVeEvEjWOQ==", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/config-resolver": "^4.1.4", + "@smithy/credential-provider-imds": "^4.0.6", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/property-provider": "^4.0.4", + "@smithy/smithy-client": "^4.4.9", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-endpoint": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", - "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-retry": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", - "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-hex-encoding": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", + "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-middleware": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.4.tgz", + "integrity": "sha512-9MLKmkBmf4PRb0ONJikCbCwORACcil6gUWojwARCClT7RmLzF04hUR4WdRprIXal7XVyrddadYNfp2eF3nrvtQ==", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-retry": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.6.tgz", + "integrity": "sha512-+YekoF2CaSMv6zKrA6iI/N9yva3Gzn4L6n35Luydweu5MMPYpiGZlWqehPHDHyNbnyaYlz/WJyYAZnC+loBDZg==", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/service-error-classification": "^4.0.6", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-stream": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.2.3.tgz", + "integrity": "sha512-cQn412DWHHFNKrQfbHY8vSFI3nTROY1aIKji9N0tpp8gUABRilr7wdf8fqBbSlXresobM+tQFNk6I+0LXK/YZg==", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/fetch-http-handler": "^5.1.0", + "@smithy/node-http-handler": "^4.1.0", + "@smithy/types": "^4.3.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^4.0.0", + "@smithy/util-hex-encoding": "^4.0.0", + "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-uri-escape": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", + "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", - "license": "Apache-2.0", + "node_modules/@aws-sdk/client-s3-control/node_modules/@smithy/util-utf8": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", + "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "node_modules/@aws-sdk/client-s3-control/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/client-s3-control/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ] + }, + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.9.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "node_modules/@aws-sdk/client-s3/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "node_modules/@aws-sdk/client-sagemaker": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sagemaker/-/client-sagemaker-3.693.0.tgz", + "integrity": "sha512-iInrrb7V9f0CRBiVCaaxCbpoBRQ5BqxX4elRYI6gE/pSDD2tPqmRfm4reahMtTUcKg1jaSGuvqJLfOpp0HTozQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", + "license": "Apache-2.0", "dependencies": { - "strnum": "^2.1.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sso-oidc": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/core": { + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/client-sts": { "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", + "@smithy/node-http-handler": "^3.3.0", "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", "@smithy/smithy-client": "^3.4.3", "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.635.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/core": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/fetch-http-handler": "^3.2.4", - "@smithy/node-http-handler": "^3.1.4", - "@smithy/property-provider": "^3.1.3", - "@smithy/protocol-http": "^4.1.0", - "@smithy/smithy-client": "^3.2.0", - "@smithy/types": "^3.3.0", - "@smithy/util-stream": "^3.1.3", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { - "version": "3.2.4", - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^4.1.0", - "@smithy/querystring-builder": "^3.0.3", - "@smithy/types": "^3.3.0", - "@smithy/util-base64": "^3.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/credential-provider-env": "3.758.0", - "@aws-sdk/credential-provider-http": "3.758.0", - "@aws-sdk/credential-provider-process": "3.758.0", - "@aws-sdk/credential-provider-sso": "3.758.0", - "@aws-sdk/credential-provider-web-identity": "3.758.0", - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/client-sso": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/core": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/client-sso": "3.758.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/token-providers": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.734.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-logger": { - "version": "3.734.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.734.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.5", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.734.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/token-providers": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-endpoints": { - "version": "3.743.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "@smithy/util-endpoints": "^3.0.1", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.734.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.758.0", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -21979,758 +16218,1007 @@ } } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/abort-controller": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/core": { - "version": "3.1.5", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", + "node_modules/@aws-sdk/client-sagemaker/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/hash-node": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sfn/-/client-sfn-3.693.0.tgz", + "integrity": "sha512-B2K3aXGnP7eD1ITEIx4kO43l1N5OLqHdLW4AUbwoopwU5qzicc9jADrthXpGxymJI8AhJz9T2WtLmceBU2EpNg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.693.0.tgz", + "integrity": "sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.693.0.tgz", + "integrity": "sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.693.0.tgz", + "integrity": "sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/core": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.693.0.tgz", + "integrity": "sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.693.0.tgz", + "integrity": "sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.693.0.tgz", + "integrity": "sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/property-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.693.0.tgz", + "integrity": "sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/protocol-http": { - "version": "5.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.693.0.tgz", + "integrity": "sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/querystring-builder": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.693.0.tgz", + "integrity": "sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/querystring-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/service-error-classification": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.693.0.tgz", + "integrity": "sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0" + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.693.0.tgz", + "integrity": "sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/signature-v4": { - "version": "5.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.693.0.tgz", + "integrity": "sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/smithy-client": { - "version": "4.1.6", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.693.0.tgz", + "integrity": "sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/types": { - "version": "4.1.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.693.0.tgz", + "integrity": "sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/url-parser": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.693.0.tgz", + "integrity": "sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-base64": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.693.0.tgz", + "integrity": "sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.693.0.tgz", + "integrity": "sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.693.0.tgz", + "integrity": "sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", + "node_modules/@aws-sdk/client-sfn/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", + "node_modules/@aws-sdk/client-ssm": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/client-sts": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.8", + "@types/uuid": "^9.0.1", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-endpoints": { - "version": "3.0.1", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-retry": { - "version": "4.0.1", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/client-sts": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-node": "3.693.0", + "@aws-sdk/middleware-host-header": "3.693.0", + "@aws-sdk/middleware-logger": "3.693.0", + "@aws-sdk/middleware-recursion-detection": "3.693.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/region-config-resolver": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@aws-sdk/util-user-agent-browser": "3.693.0", + "@aws-sdk/util-user-agent-node": "3.693.0", + "@smithy/config-resolver": "^3.0.11", + "@smithy/core": "^2.5.2", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/hash-node": "^3.0.9", + "@smithy/invalid-dependency": "^3.0.9", + "@smithy/middleware-content-length": "^3.0.11", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/middleware-retry": "^3.0.26", + "@smithy/middleware-serde": "^3.0.9", + "@smithy/middleware-stack": "^3.0.9", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/url-parser": "^3.0.9", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.26", + "@smithy/util-defaults-mode-node": "^3.0.26", + "@smithy/util-endpoints": "^2.1.5", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-retry": "^3.0.9", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-stream": { - "version": "4.1.2", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/fetch-http-handler": "^4.1.0", + "@smithy/node-http-handler": "^3.3.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-stream": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini/node_modules/@smithy/util-utf8": { - "version": "4.0.0", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.936.0.tgz", - "integrity": "sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "3.693.0", + "@aws-sdk/credential-provider-http": "3.693.0", + "@aws-sdk/credential-provider-ini": "3.693.0", + "@aws-sdk/credential-provider-process": "3.693.0", + "@aws-sdk/credential-provider-sso": "3.693.0", + "@aws-sdk/credential-provider-web-identity": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/credential-provider-imds": "^3.2.6", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/client-sso": "3.693.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/token-providers": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-logger": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", - "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", - "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-endpoints": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/token-providers": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.693.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-endpoints": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", + "@smithy/util-endpoints": "^2.1.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", - "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", + "node_modules/@aws-sdk/client-ssm/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -22741,334 +17229,375 @@ } } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.0.tgz", - "integrity": "sha512-D1jAmAZQYMoPiacfgNf7AWhg3DFN3Wq/vQv3WINt9znwjzHp2x+WzdJFxxj7xZL7V1U79As6G8f7PorMYWBKsQ==", + "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "node_modules/@aws-sdk/client-ssm/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.637.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/core": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", - "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", - "license": "Apache-2.0", - "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-endpoint": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", - "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-retry": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", - "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "node_modules/@aws-sdk/client-sts": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "node_modules/@aws-sdk/client-sts/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "node_modules/@aws-sdk/core": { + "version": "3.635.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.936.0.tgz", + "integrity": "sha512-+aSC59yiD4M5RcYp9Gx3iwX/n4hO3ZWA2Mxmkzmt9gYFBbJ9umx2LpBdrV64y57AtOvfGeo0h7PAXniIufagxw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-cognito-identity": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23076,18 +17605,49 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.936.0.tgz", + "integrity": "sha512-AkJZ426y0G8Lsyi9p7mWudacMKeo8XLZOfxUmeThMkDa3GxGQ1y6BTrOj6ZcvqQ1Hz7Abb3QWPC+EMqhu1Lncw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-node": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -23095,121 +17655,193 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/client-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", + "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", - "license": "Apache-2.0", - "dependencies": { + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/core": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", + "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", + "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", + "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", + "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-login": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", + "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-ini": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", + "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", + "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-sso": "3.936.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/token-providers": "3.936.0", + "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", + "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23217,17 +17849,17 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", + "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", + "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23235,13 +17867,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23249,24 +17882,29 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23274,13 +17912,17 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", + "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23288,18 +17930,48 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/nested-clients": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", + "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -23307,534 +17979,534 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/token-providers": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", + "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", "dependencies": { - "strnum": "^2.1.0" + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, - "bin": { - "fxparser": "src/cli/cli.js" + "engines": { + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-login/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-ini": "3.637.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.637.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.620.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", + "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.620.1", - "@aws-sdk/credential-provider-http": "3.635.0", - "@aws-sdk/credential-provider-process": "3.620.1", - "@aws-sdk/credential-provider-sso": "3.637.0", - "@aws-sdk/credential-provider-web-identity": "3.621.0", - "@aws-sdk/types": "3.609.0", - "@smithy/credential-provider-imds": "^3.2.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-sts": "^3.637.0" + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.620.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.621.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", + "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/types": "^3.3.0", - "tslib": "^2.6.2" - }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-sts": "^3.621.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/property-provider": "^3.1.9", - "@smithy/shared-ini-file-loader": "^3.1.10", - "@smithy/types": "^3.7.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/core": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/core": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", + "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.637.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.637.0", - "@aws-sdk/token-providers": "3.614.0", - "@aws-sdk/types": "3.609.0", - "@smithy/property-provider": "^3.1.3", - "@smithy/shared-ini-file-loader": "^3.1.4", - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.758.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/hash-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/core": { - "version": "3.758.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { - "version": "3.734.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/abort-controller": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/core": { - "version": "3.1.5", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", + "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", + "@smithy/core": "^3.18.5", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-retry": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", + "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/property-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/protocol-http": { - "version": "5.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/querystring-builder": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@smithy/types": "^4.9.0" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/querystring-parser": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/signature-v4": { - "version": "5.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/smithy-client": { + "version": "4.9.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", + "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/core": "^3.18.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/smithy-client": { - "version": "4.1.6", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/types": { - "version": "4.1.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/url-parser": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-base64": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23842,22 +18514,24 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.6.2" }, @@ -23865,83 +18539,71 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-middleware": { - "version": "4.0.1", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", + "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-stream": { - "version": "4.1.2", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", + "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@smithy/util-utf8": { - "version": "4.0.0", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.936.0.tgz", - "integrity": "sha512-RWiX6wuReeEU7/P7apGwWMNO7nrai/CXmMMaho3+pJW7i6ImosgsjSe5tetdv1r4djOtM1b4J4WAbHPKJUahUg==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-cognito-identity": "3.936.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-cognito-identity": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -23949,98 +18611,32 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.936.0.tgz", - "integrity": "sha512-AkJZ426y0G8Lsyi9p7mWudacMKeo8XLZOfxUmeThMkDa3GxGQ1y6BTrOj6ZcvqQ1Hz7Abb3QWPC+EMqhu1Lncw==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-node": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", + "@smithy/service-error-classification": "^4.2.5", "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", - "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.936.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.936.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -24048,143 +18644,146 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", - "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", - "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", - "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/credential-provider-cognito-identity/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", - "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-login": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", - "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.936.0", - "@aws-sdk/credential-provider-http": "3.936.0", - "@aws-sdk/credential-provider-ini": "3.936.0", - "@aws-sdk/credential-provider-process": "3.936.0", - "@aws-sdk/credential-provider-sso": "3.936.0", - "@aws-sdk/credential-provider-web-identity": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", - "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", + "license": "Apache-2.0", + "dependencies": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", - "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.936.0.tgz", + "integrity": "sha512-8DVrdRqPyUU66gfV7VZNToh56ZuO5D6agWrkLQE/xbLJOm2RbeRgh6buz7CqV8ipRd6m+zCl9mM4F3osQLZn8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.936.0", "@aws-sdk/core": "3.936.0", - "@aws-sdk/token-providers": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" @@ -24193,25 +18792,31 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", - "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", + "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-host-header": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-host-header": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", @@ -24226,7 +18831,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-logger": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-logger": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", @@ -24240,7 +18845,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-recursion-detection": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-recursion-detection": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", @@ -24256,7 +18861,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/middleware-user-agent": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", @@ -24274,7 +18879,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/nested-clients": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/nested-clients": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", @@ -24312,44 +18917,26 @@ "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/region-config-resolver": { "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", - "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.936.0", - "@aws-sdk/nested-clients": "3.936.0", "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24357,7 +18944,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/types": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", @@ -24370,7 +18957,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-endpoints": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-endpoints": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", @@ -24386,7 +18973,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-browser": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", @@ -24398,7 +18985,7 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/util-user-agent-node": { "version": "3.936.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", @@ -24422,7 +19009,7 @@ } } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/xml-builder": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": { "version": "3.930.0", "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", @@ -24436,16 +19023,16 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@aws/lambda-invoke-store": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", - "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.0.tgz", + "integrity": "sha512-D1jAmAZQYMoPiacfgNf7AWhg3DFN3Wq/vQv3WINt9znwjzHp2x+WzdJFxxj7xZL7V1U79As6G8f7PorMYWBKsQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/abort-controller": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/abort-controller": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", @@ -24458,7 +19045,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/config-resolver": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/config-resolver": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", @@ -24475,7 +19062,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/core": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/core": { "version": "3.18.5", "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", @@ -24496,7 +19083,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/credential-provider-imds": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/credential-provider-imds": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", @@ -24512,7 +19099,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/fetch-http-handler": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/fetch-http-handler": { "version": "5.3.6", "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", @@ -24528,7 +19115,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/hash-node": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/hash-node": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", @@ -24543,7 +19130,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/invalid-dependency": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/invalid-dependency": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", @@ -24556,7 +19143,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/is-array-buffer": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/is-array-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", @@ -24568,7 +19155,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-content-length": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-content-length": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", @@ -24582,7 +19169,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-endpoint": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-endpoint": { "version": "4.3.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", @@ -24601,7 +19188,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-retry": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-retry": { "version": "4.4.12", "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", @@ -24621,7 +19208,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-serde": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-serde": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", @@ -24635,7 +19222,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-stack": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/middleware-stack": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", @@ -24648,108 +19235,585 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-config-provider": { + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/node-config-provider": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/smithy-client": { + "version": "4.9.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", + "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.18.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", + "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", + "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.637.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.637.0", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/property-provider": "^3.1.9", + "@smithy/shared-ini-file-loader": "^3.1.10", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", - "@smithy/util-uri-escape": "^4.2.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.637.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "node_modules/@aws-sdk/credential-providers": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.936.0.tgz", + "integrity": "sha512-RWiX6wuReeEU7/P7apGwWMNO7nrai/CXmMMaho3+pJW7i6ImosgsjSe5tetdv1r4djOtM1b4J4WAbHPKJUahUg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-cognito-identity": "3.936.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-cognito-identity": "3.936.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-ini": "3.936.0", + "@aws-sdk/credential-provider-login": "3.936.0", + "@aws-sdk/credential-provider-node": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24757,18 +19821,49 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.936.0.tgz", + "integrity": "sha512-AkJZ426y0G8Lsyi9p7mWudacMKeo8XLZOfxUmeThMkDa3GxGQ1y6BTrOj6ZcvqQ1Hz7Abb3QWPC+EMqhu1Lncw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-node": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", - "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", - "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -24776,121 +19871,193 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/client-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.936.0.tgz", + "integrity": "sha512-0G73S2cDqYwJVvqL08eakj79MZG2QRaB56Ul8/Ps9oQxllr7DMI1IQ/N3j3xjxgpq/U36pkoFZ8aK1n7Sbr3IQ==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", - "license": "Apache-2.0", - "dependencies": { + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/core": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.936.0.tgz", + "integrity": "sha512-eGJ2ySUMvgtOziHhDRDLCrj473RJoL4J1vPjVM3NrKC/fF3/LoHjkut8AAnKmrW6a2uTzNKubigw8dEnpmpERw==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.936.0.tgz", + "integrity": "sha512-dKajFuaugEA5i9gCKzOaVy9uTeZcApE+7Z5wdcZ6j40523fY1a56khDAUYkCfwqa7sHci4ccmxBkAo+fW1RChA==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-http": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.936.0.tgz", + "integrity": "sha512-5FguODLXG1tWx/x8fBxH+GVrk7Hey2LbXV5h9SFzYCx/2h50URBm0+9hndg0Rd23+xzYe14F6SI9HA9c1sPnjg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.936.0.tgz", + "integrity": "sha512-TbUv56ERQQujoHcLMcfL0Q6bVZfYF83gu/TjHkVkdSlHPOIKaG/mhE2XZSQzXv1cud6LlgeBbfzVAxJ+HPpffg==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-login": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.936.0.tgz", + "integrity": "sha512-rk/2PCtxX9xDsQW8p5Yjoca3StqmQcSfkmD7nQ61AqAHL1YgpSQWqHE+HjfGGiHDYKG7PvE33Ku2GyA7lEIJAw==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", + "@aws-sdk/credential-provider-env": "3.936.0", + "@aws-sdk/credential-provider-http": "3.936.0", + "@aws-sdk/credential-provider-ini": "3.936.0", + "@aws-sdk/credential-provider-process": "3.936.0", + "@aws-sdk/credential-provider-sso": "3.936.0", + "@aws-sdk/credential-provider-web-identity": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.936.0.tgz", + "integrity": "sha512-GpA4AcHb96KQK2PSPUyvChvrsEKiLhQ5NWjeef2IZ3Jc8JoosiedYqp6yhZR+S8cTysuvx56WyJIJc8y8OTrLA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.936.0.tgz", + "integrity": "sha512-wHlEAJJvtnSyxTfNhN98JcU4taA1ED2JvuI2eePgawqBwS/Tzi0mhED1lvNIaWOkjfLd+nHALwszGrtJwEq4yQ==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/client-sso": "3.936.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/token-providers": "3.936.0", + "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", + "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24898,17 +20065,17 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.936.0.tgz", + "integrity": "sha512-v3qHAuoODkoRXsAF4RG+ZVO6q2P9yYBT4GMpMEfU9wXVNn7AIfwZgTwzSUfnjNiGva5BKleWVpRpJ9DeuLFbUg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", + "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24916,13 +20083,14 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24930,24 +20098,29 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24955,13 +20128,17 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.936.0.tgz", + "integrity": "sha512-YB40IPa7K3iaYX0lSnV9easDOLPLh+fJyUDF3BH8doX4i1AOSsYn86L4lVldmOaSX+DwiaqKHpvk4wPBdcIPWw==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, @@ -24969,18 +20146,48 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/nested-clients": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.936.0.tgz", + "integrity": "sha512-eyj2tz1XmDSLSZQ5xnB7cLTVKkSJnYAEoNDSUNhzWPxrBDYeJzIbatecOKceKCU8NBf8gWWZCK/CSY0mDxMO0A==", "license": "Apache-2.0", "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -24988,835 +20195,869 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-providers/node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/@aws-sdk/credential-providers/node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, - "node_modules/@aws-sdk/lib-storage": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/token-providers": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.936.0.tgz", + "integrity": "sha512-vvw8+VXk0I+IsoxZw0mX9TMJawUJvEsg3EF7zcCSetwhNPAU8Xmlhv7E/sN/FgSmm7b7DsqKoW6rVtQiCs1PWQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^3.1.7", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/smithy-client": "^3.4.3", - "buffer": "5.6.0", - "events": "3.3.0", - "stream-browserify": "3.0.0", + "@aws-sdk/core": "3.936.0", + "@aws-sdk/nested-clients": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@aws-sdk/client-s3": "^3.693.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { - "version": "5.6.0", - "license": "MIT", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "node_modules/@aws-sdk/lib-storage/node_modules/events": { - "version": "3.3.0", - "license": "MIT", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, "engines": { - "node": ">=0.8.x" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-arn-parser": "3.693.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.936.0.tgz", + "integrity": "sha512-XOEc7PF9Op00pWV2AYCGDSu5iHgYjIO53Py2VUQTIvP7SRCaCsXmA33mjBvC2Ms6FhSyWNa4aK4naUGIz0hQcw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@aws-crypto/crc32c": "5.2.0", - "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-stream": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", + "@aws-sdk/middleware-user-agent": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@aws/lambda-invoke-store": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", + "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/abort-controller": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-utf8": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/config-resolver": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.620.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/core": { + "version": "3.18.5", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", + "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/credential-provider-imds": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/fetch-http-handler": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/types": "^3.7.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/hash-node": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/invalid-dependency": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.620.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/is-array-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-content-length": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-api-gateway": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-endpoint": { + "version": "4.3.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", + "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/protocol-http": "^4.1.6", - "@smithy/types": "^3.7.0", + "@smithy/core": "^3.18.5", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-ec2": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-retry": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", + "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-format-url": "3.693.0", - "@smithy/middleware-endpoint": "^3.2.2", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.693.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-serde": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.693.0", - "@aws-sdk/types": "3.692.0", - "@aws-sdk/util-arn-parser": "3.693.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-config-provider": "^3.0.0", - "@smithy/util-middleware": "^3.0.9", - "@smithy/util-stream": "^3.3.0", - "@smithy/util-utf8": "^3.0.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3-control/-/middleware-sdk-s3-control-3.848.0.tgz", - "integrity": "sha512-1zozD+IKFzFE9RLOCBOGPjhi+jUj0bLxf0ntqBMBJKX9Cf5zqvVuck7mCY19+m0/B+GuSAoiQm2yPV6dcgN17g==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/middleware-stack": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-bucket-endpoint": "3.840.0", - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@aws-sdk/util-endpoints": "3.848.0", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-endpoints": "^3.0.6", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", - "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-config-provider": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@aws-sdk/util-arn-parser": "3.804.0", - "@smithy/node-config-provider": "^4.1.3", - "@smithy/protocol-http": "^5.1.2", - "@smithy/types": "^4.3.1", - "@smithy/util-config-provider": "^4.0.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/types": { - "version": "3.840.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", - "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/node-http-handler": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/util-arn-parser": { - "version": "3.804.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", - "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/property-provider": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "license": "Apache-2.0", "dependencies": { + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/util-endpoints": { - "version": "3.848.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", - "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/protocol-http": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.840.0", - "@smithy/types": "^4.3.1", - "@smithy/url-parser": "^4.0.4", - "@smithy/util-endpoints": "^3.0.6", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/node-config-provider": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", - "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-builder": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.4", - "@smithy/shared-ini-file-loader": "^4.0.4", - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/property-provider": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", - "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/querystring-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/protocol-http": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", - "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/service-error-classification": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", - "tslib": "^2.6.2" + "@smithy/types": "^4.9.0" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/querystring-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", - "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", - "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/signature-v4": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.3.1", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/types": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", - "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/smithy-client": { + "version": "4.9.8", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", + "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "license": "Apache-2.0", "dependencies": { + "@smithy/core": "^3.18.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/url-parser": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", - "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/types": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.4", - "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/url-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "license": "Apache-2.0", "dependencies": { + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/util-endpoints": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", - "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-base64": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.1.3", - "@smithy/types": "^4.3.1", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { - "version": "3.693.0", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.692.0", - "@smithy/core": "^2.5.2", - "@smithy/node-config-provider": "^3.1.10", - "@smithy/property-provider": "^3.1.9", - "@smithy/protocol-http": "^4.1.6", - "@smithy/signature-v4": "^4.2.2", - "@smithy/smithy-client": "^3.4.3", - "@smithy/types": "^3.7.0", - "@smithy/util-middleware": "^3.0.9", - "fast-xml-parser": "4.4.1", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-browser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "license": "Apache-2.0", + "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/is-array-buffer": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-body-length-node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-buffer-from": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-buffer-from": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-utf8": { - "version": "3.0.0", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-config-provider": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", - "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", + "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-defaults-mode-node": { + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", + "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/@smithy/types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", - "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-endpoints": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", - "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-hex-encoding": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-middleware": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/eventstream-codec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.1.0.tgz", - "integrity": "sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw==", - "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^1.2.0", - "@smithy/util-hex-encoding": "^1.1.0", - "tslib": "^2.5.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/is-array-buffer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.1.0.tgz", - "integrity": "sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-retry": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/property-provider": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.2.0.tgz", - "integrity": "sha512-qlJd9gT751i4T0t/hJAyNGfESfi08Fek8QiLcysoKPgR05qHhG0OYhlaCJHhpXy4ECW0lHyjvFM1smrCLIXVfw==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-stream": { + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^1.2.0", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/protocol-http": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.2.0.tgz", - "integrity": "sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-uri-escape": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^1.2.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/signature-v4": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.1.0.tgz", - "integrity": "sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q==", + "node_modules/@aws-sdk/credential-providers/node_modules/@smithy/util-utf8": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^1.1.0", - "@smithy/is-array-buffer": "^1.1.0", - "@smithy/types": "^1.2.0", - "@smithy/util-hex-encoding": "^1.1.0", - "@smithy/util-middleware": "^1.1.0", - "@smithy/util-uri-escape": "^1.1.0", - "@smithy/util-utf8": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", - "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "node_modules/@aws-sdk/credential-providers/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", "dependencies": { - "tslib": "^2.5.0" + "strnum": "^2.1.0" }, - "engines": { - "node": ">=14.0.0" + "bin": { + "fxparser": "src/cli/cli.js" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-buffer-from": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.1.0.tgz", - "integrity": "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw==", + "node_modules/@aws-sdk/credential-providers/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws-sdk/lib-storage": { + "version": "3.693.0", + "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.7", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/smithy-client": "^3.4.3", + "buffer": "5.6.0", + "events": "3.3.0", + "stream-browserify": "3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-s3": "^3.693.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-hex-encoding": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.1.0.tgz", - "integrity": "sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg==", + "node_modules/@aws-sdk/lib-storage/node_modules/buffer": { + "version": "5.6.0", + "license": "MIT", "dependencies": { - "tslib": "^2.5.0" - }, + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/@aws-sdk/lib-storage/node_modules/events": { + "version": "3.3.0", + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=0.8.x" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-middleware": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.1.0.tgz", - "integrity": "sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ==", + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.693.0", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-uri-escape": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.1.0.tgz", - "integrity": "sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==", + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.693.0", + "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.1.0.tgz", - "integrity": "sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A==", + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.693.0", + "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^1.1.0", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-stream": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-ssec": { + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": { "version": "3.693.0", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-stack": { - "version": "3.342.0", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.637.0", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.609.0", - "@aws-sdk/util-endpoints": "3.637.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { - "version": "3.609.0", + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", "license": "Apache-2.0", "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, @@ -25824,733 +21065,635 @@ "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.758.0", + "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { - "version": "3.758.0", + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-host-header": { - "version": "3.734.0", + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-logger": { - "version": "3.734.0", + "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.734.0", + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.758.0", + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/core": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@smithy/core": "^3.1.5", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/region-config-resolver": { - "version": "3.734.0", + "node_modules/@aws-sdk/middleware-sdk-api-gateway": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@aws-sdk/types": "3.692.0", + "@smithy/protocol-http": "^4.1.6", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { - "version": "3.734.0", + "node_modules/@aws-sdk/middleware-sdk-ec2": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-format-url": "3.693.0", + "@smithy/middleware-endpoint": "^3.2.2", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { - "version": "3.743.0", + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "@smithy/util-endpoints": "^3.0.1", + "@aws-sdk/core": "3.693.0", + "@aws-sdk/types": "3.692.0", + "@aws-sdk/util-arn-parser": "3.693.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.9", + "@smithy/util-stream": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.734.0", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.758.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3-control/-/middleware-sdk-s3-control-3.848.0.tgz", + "integrity": "sha512-1zozD+IKFzFE9RLOCBOGPjhi+jUj0bLxf0ntqBMBJKX9Cf5zqvVuck7mCY19+m0/B+GuSAoiQm2yPV6dcgN17g==", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/middleware-bucket-endpoint": "3.840.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@aws-sdk/util-endpoints": "3.848.0", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/abort-controller": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.840.0.tgz", + "integrity": "sha512-+gkQNtPwcSMmlwBHFd4saVVS11In6ID1HczNzpM3MXKXRBfSlbZJbCt6wN//AZ8HMklZEik4tcEOG0qa9UY8SQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.840.0", + "@aws-sdk/util-arn-parser": "3.804.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/protocol-http": "^5.1.2", + "@smithy/types": "^4.3.1", + "@smithy/util-config-provider": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/types": { + "version": "3.840.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.840.0.tgz", + "integrity": "sha512-xliuHaUFZxEx1NSXeLLZ9Dyu6+EJVQKEoD+yM+zqUo3YDZ7medKJWY6fIOKiPX/N7XbLdBYwajb15Q7IL8KkeA==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/core": { - "version": "3.1.5", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/util-arn-parser": { + "version": "3.804.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.804.0.tgz", + "integrity": "sha512-wmBJqn1DRXnZu3b4EkE6CWnoWMo1ZMvlfkqU5zPz67xx1GMaXlDCchFvKAXMjk4jn/L1O3tKnoFDNsoLV1kgNQ==", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/credential-provider-imds": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@aws-sdk/util-endpoints": { + "version": "3.848.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.848.0.tgz", + "integrity": "sha512-fY/NuFFCq/78liHvRyFKr+aqq1aA/uuVSANjzr5Ym8c+9Z3HRPE9OrExAHoMrZ6zC8tHerQwlsXYYH5XZ7H+ww==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@aws-sdk/types": "3.840.0", + "@smithy/types": "^4.3.1", + "@smithy/url-parser": "^4.0.4", + "@smithy/util-endpoints": "^3.0.6", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/node-config-provider": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.1.3.tgz", + "integrity": "sha512-HGHQr2s59qaU1lrVH6MbLlmOBxadtzTsoO4c+bF5asdgVik3I8o7JIOzoeqWc5MjVa+vD36/LWE0iXKpNqooRw==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/property-provider": "^4.0.4", + "@smithy/shared-ini-file-loader": "^4.0.4", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/hash-node": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/property-provider": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.4.tgz", + "integrity": "sha512-qHJ2sSgu4FqF4U/5UUp4DhXNmdTrgmoAai6oQiM+c5RZ/sbDwJ12qxB1M6FnP+Tn/ggkPZf9ccn4jqKSINaquw==", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/protocol-http": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.1.2.tgz", + "integrity": "sha512-rOG5cNLBXovxIrICSBm95dLqzfvxjEmuZx4KK3hWwPFHGdW3lxY0fZNXfv2zebfRO7sJZ5pKJYHScsqopeIWtQ==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/querystring-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.4.tgz", + "integrity": "sha512-6yZf53i/qB8gRHH/l2ZwUG5xgkPgQF15/KxH0DdXMDHjesA9MeZje/853ifkSY0x4m5S+dfDZ+c4x439PF0M2w==", "dependencies": { + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/shared-ini-file-loader": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.4.tgz", + "integrity": "sha512-63X0260LoFBjrHifPDs+nM9tV0VMkOTl4JRMYNuKh/f5PauSjowTfvF3LogfkWdcPoxsA9UjqEOgjeYIbhb7Nw==", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/types": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.3.1.tgz", + "integrity": "sha512-UqKOQBL2x6+HWl3P+3QqFD4ncKq0I8Nuz9QItGv5WuKuMHuuwlhvqcZCoXGfc+P1QmfJE7VieykoYYmrOoFJxA==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/url-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.4.tgz", + "integrity": "sha512-eMkc144MuN7B0TDA4U2fKs+BqczVbk3W+qIvcoCY6D1JY3hnAdCuhCZODC+GAeaxj0p6Jroz4+XMUn3PCxQQeQ==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/querystring-parser": "^4.0.4", + "@smithy/types": "^4.3.1", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/util-config-provider": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", + "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", "dependencies": { - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-s3-control/node_modules/@smithy/util-endpoints": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.6.tgz", + "integrity": "sha512-YARl3tFL3WgPuLzljRUnrS2ngLiUtkwhQtj8PAL13XZSyUiNLQxwG3fBBq3QXFqGFUXepIN73pINp3y8c2nBmA==", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.1.3", + "@smithy/types": "^4.3.1", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-config-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/core": "^2.5.2", + "@smithy/node-config-provider": "^3.1.10", + "@smithy/property-provider": "^3.1.9", + "@smithy/protocol-http": "^4.1.6", + "@smithy/signature-v4": "^4.2.2", + "@smithy/smithy-client": "^3.4.3", + "@smithy/types": "^3.7.0", + "@smithy/util-middleware": "^3.0.9", + "fast-xml-parser": "4.4.1", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.0.3", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/property-provider": { - "version": "4.0.1", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/is-array-buffer": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/protocol-http": { - "version": "5.0.1", + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@smithy/util-utf8": { + "version": "3.0.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/util-buffer-from": "^3.0.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-builder": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.363.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", + "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", - "tslib": "^2.6.2" + "@aws-sdk/middleware-signing": "3.363.0", + "@aws-sdk/types": "3.357.0", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/querystring-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/@aws-sdk/types": { + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", + "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/service-error-classification": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", "dependencies": { - "@smithy/types": "^4.1.0" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing": { + "version": "3.363.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", + "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@aws-sdk/types": "3.357.0", + "@smithy/property-provider": "^1.0.1", + "@smithy/protocol-http": "^1.1.0", + "@smithy/signature-v4": "^1.0.1", + "@smithy/types": "^1.1.0", + "@smithy/util-middleware": "^1.0.1", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/types": { - "version": "4.1.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@aws-sdk/middleware-signing/node_modules/@aws-sdk/types": { + "version": "3.357.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", + "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", "dependencies": { - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/url-parser": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/eventstream-codec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.1.0.tgz", + "integrity": "sha512-3tEbUb8t8an226jKB6V/Q2XU/J53lCwCzULuBPEaF4JjSh+FlCMp7TmogE/Aij5J9DwlsZ4VAD/IRDuQ/0ZtMw==", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^1.2.0", + "@smithy/util-hex-encoding": "^1.1.0", + "tslib": "^2.5.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-base64": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/is-array-buffer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.1.0.tgz", + "integrity": "sha512-twpQ/n+3OWZJ7Z+xu43MJErmhB/WO/mMTnqR6PwWQShvSJ/emx5d1N59LQZk6ZpTAeuRWrc+eHhkzTp9NFjNRQ==", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/property-provider": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.2.0.tgz", + "integrity": "sha512-qlJd9gT751i4T0t/hJAyNGfESfi08Fek8QiLcysoKPgR05qHhG0OYhlaCJHhpXy4ECW0lHyjvFM1smrCLIXVfw==", "dependencies": { - "tslib": "^2.6.2" + "@smithy/types": "^1.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/protocol-http": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.2.0.tgz", + "integrity": "sha512-GfGfruksi3nXdFok5RhgtOnWe5f6BndzYfmEXISD+5gAGdayFGpjWu5pIqIweTudMtse20bGbc+7MFZXT1Tb8Q==", "dependencies": { - "tslib": "^2.6.2" + "@smithy/types": "^1.2.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/signature-v4": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.1.0.tgz", + "integrity": "sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q==", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "tslib": "^2.6.2" + "@smithy/eventstream-codec": "^1.1.0", + "@smithy/is-array-buffer": "^1.1.0", + "@smithy/types": "^1.2.0", + "@smithy/util-hex-encoding": "^1.1.0", + "@smithy/util-middleware": "^1.1.0", + "@smithy/util-uri-escape": "^1.1.0", + "@smithy/util-utf8": "^1.1.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", "dependencies": { - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.1.0.tgz", + "integrity": "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw==", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "@smithy/is-array-buffer": "^1.1.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-hex-encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.1.0.tgz", + "integrity": "sha512-7UtIE9eH0u41zpB60Jzr0oNCQ3hMJUabMcKRUVjmyHTXiWDE4vjSqN6qlih7rCNeKGbioS7f/y2Jgym4QZcKFg==", "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-endpoints": { - "version": "3.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-middleware": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.1.0.tgz", + "integrity": "sha512-6hhckcBqVgjWAqLy2vqlPZ3rfxLDhFWEmM7oLh2POGvsi7j0tHkbN7w4DFhuBExVJAbJ/qqxqZdRY6Fu7/OezQ==", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-uri-escape": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.1.0.tgz", + "integrity": "sha512-/jL/V1xdVRt5XppwiaEU8Etp5WHZj609n0xMTuehmCqdoOFbId1M+aEeDWZsQ+8JbEB/BJ6ynY2SlYmOaKtt8w==", "dependencies": { - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "license": "Apache-2.0", - "peer": true, + "node_modules/@aws-sdk/middleware-signing/node_modules/@smithy/util-utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.1.0.tgz", + "integrity": "sha512-p/MYV+JmqmPyjdgyN2UxAeYDj9cBqCjp0C/NsTWnnjoZUVqoeZ6IrW915L9CAKWVECgv9lVQGc4u/yz26/bI1A==", "dependencies": { - "@smithy/types": "^4.1.0", - "tslib": "^2.6.2" + "@smithy/util-buffer-from": "^1.1.0", + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-retry": { - "version": "4.0.1", + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.693.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/types": "3.692.0", + "@smithy/types": "^3.7.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-stream": { - "version": "4.1.2", + "node_modules/@aws-sdk/middleware-stack": { + "version": "3.342.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", - "tslib": "^2.6.2" + "tslib": "^2.5.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.637.0", "license": "Apache-2.0", - "peer": true, "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, - "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/util-utf8": { - "version": "4.0.0", + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": { + "version": "3.609.0", "license": "Apache-2.0", - "peer": true, "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", + "@smithy/types": "^3.3.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=18.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/property-provider": { @@ -42582,7 +37725,6 @@ "@aws-sdk/client-cloudcontrol": "<3.731.0", "@aws-sdk/client-cloudformation": "<3.731.0", "@aws-sdk/client-cloudwatch-logs": "<3.731.0", - "@aws-sdk/client-codecatalyst": "<3.731.0", "@aws-sdk/client-cognito-identity": "<3.731.0", "@aws-sdk/client-datazone": "^3.848.0", "@aws-sdk/client-docdb": "<3.731.0", diff --git a/packages/amazonq/src/app/chat/node/activateAgents.ts b/packages/amazonq/src/app/chat/node/activateAgents.ts index cd0309d7f2..2ba954b4af 100644 --- a/packages/amazonq/src/app/chat/node/activateAgents.ts +++ b/packages/amazonq/src/app/chat/node/activateAgents.ts @@ -10,7 +10,6 @@ import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' export function activateAgents() { const appInitContext = DefaultAmazonQAppInitContext.instance - amazonqNode.cwChatAppInit(appInitContext) amazonqNode.gumbyChatAppInit(appInitContext) scanChatAppInit(appInitContext) } diff --git a/packages/amazonq/src/extensionNode.ts b/packages/amazonq/src/extensionNode.ts index d42fafea05..e3a6d47f0d 100644 --- a/packages/amazonq/src/extensionNode.ts +++ b/packages/amazonq/src/extensionNode.ts @@ -5,17 +5,9 @@ import * as vscode from 'vscode' import { activateAmazonQCommon, amazonQContextPrefix, deactivateCommon } from './extension' -import { DefaultAmazonQAppInitContext, AmazonQChatViewProvider } from 'aws-core-vscode/amazonq' +import { DefaultAmazonQAppInitContext } from 'aws-core-vscode/amazonq' import { activate as activateTransformationHub } from 'aws-core-vscode/amazonqGumby' -import { - ExtContext, - globals, - CrashMonitoring, - getLogger, - isNetworkError, - isSageMaker, - Experiments, -} from 'aws-core-vscode/shared' +import { ExtContext, globals, CrashMonitoring, getLogger, isNetworkError, isSageMaker } from 'aws-core-vscode/shared' import { filetypes, SchemaService } from 'aws-core-vscode/sharedNode' import { updateDevMode } from 'aws-core-vscode/dev' import { CommonAuthViewProvider } from 'aws-core-vscode/login' @@ -24,7 +16,6 @@ import { registerSubmitFeedback } from 'aws-core-vscode/feedback' import { DevOptions } from 'aws-core-vscode/dev' import { Auth, AuthUtils, getTelemetryMetadataForConn, isAnySsoConnection } from 'aws-core-vscode/auth' import api from './api' -import { activate as activateCWChat } from './app/chat/activation' import { beta } from 'aws-core-vscode/dev' import { activate as activateNotifications, NotificationsController } from 'aws-core-vscode/notifications' import { AuthState, AuthUtil } from 'aws-core-vscode/codewhisperer' @@ -52,24 +43,6 @@ async function activateAmazonQNode(context: vscode.ExtensionContext) { extensionContext: context, } - if (!Experiments.instance.get('amazonqChatLSP', true)) { - const appInitContext = DefaultAmazonQAppInitContext.instance - const provider = new AmazonQChatViewProvider( - context, - appInitContext.getWebViewToAppsMessagePublishers(), - appInitContext.getAppsToWebViewMessageListener(), - appInitContext.onDidChangeAmazonQVisibility - ) - context.subscriptions.push( - vscode.window.registerWebviewViewProvider(AmazonQChatViewProvider.viewType, provider, { - webviewOptions: { - retainContextWhenHidden: true, - }, - }) - ) - // this is registered inside of lsp/chat/activation.ts when the chat experiment is enabled - await activateCWChat(context) - } activateAgents() await activateTransformationHub(extContext as ExtContext) diff --git a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts index 6bcbd99bc2..8a06b56f62 100644 --- a/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts +++ b/packages/amazonq/test/unit/codewhispererChat/controllers/chat/chatRequest/converter.test.ts @@ -4,12 +4,7 @@ */ import assert from 'assert' -import { - ChatTriggerType, - filePathSizeLimit, - TriggerPayload, - triggerPayloadToChatRequest, -} from 'aws-core-vscode/codewhispererChat' +import { ChatTriggerType, TriggerPayload, triggerPayloadToChatRequest } from 'aws-core-vscode/codewhispererChat' describe('triggerPayloadToChatRequest', () => { const mockBasicPayload: TriggerPayload = { @@ -185,7 +180,7 @@ describe('triggerPayloadToChatRequest', () => { assert.strictEqual( result.conversationState.currentMessage?.userInputMessage?.userInputMessageContext?.editorState?.document ?.relativeFilePath?.length, - filePathSizeLimit + 4_000 ) }) diff --git a/packages/core/package.json b/packages/core/package.json index 7d0d08721b..42a12d7d5b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -588,7 +588,6 @@ "@aws-sdk/client-cloudcontrol": "<3.731.0", "@aws-sdk/client-cloudformation": "<3.731.0", "@aws-sdk/client-cloudwatch-logs": "<3.731.0", - "@aws-sdk/client-codecatalyst": "<3.731.0", "@aws-sdk/client-cognito-identity": "<3.731.0", "@aws-sdk/client-datazone": "^3.848.0", "@aws-sdk/client-docdb": "<3.731.0", diff --git a/packages/core/scripts/build/copyFiles.ts b/packages/core/scripts/build/copyFiles.ts index 6a4131172b..7d3e1116e8 100644 --- a/packages/core/scripts/build/copyFiles.ts +++ b/packages/core/scripts/build/copyFiles.ts @@ -26,7 +26,6 @@ interface CopyTask { } const tasks: CopyTask[] = [ - { target: path.join('src', 'templates') }, { target: path.join('src', 'test', 'shared', 'cloudformation', 'yaml') }, { target: path.join('src', 'testFixtures') }, { target: 'src/auth/sso/vue' }, @@ -36,19 +35,6 @@ const tasks: CopyTask[] = [ target: path.join('../../node_modules', 'vue', 'dist', 'vue.global.prod.js'), destination: path.join('libs', 'vue.min.js'), }, - // SSM - { - target: path.join('../../node_modules', 'aws-ssm-document-language-service', 'dist', 'server.js'), - destination: path.join('src', 'ssmDocument', 'ssm', 'ssmServer.js'), - }, - { - target: path.join('../../node_modules', 'aws-ssm-document-language-service', 'dist', 'server.js.LICENSE.txt'), - destination: path.join('src', 'ssmDocument', 'ssm', 'ssmServer.js.LICENSE.txt'), - }, - { - target: path.join('../../node_modules', 'aws-ssm-document-language-service', 'dist', 'server.js.map'), - destination: path.join('src', 'ssmDocument', 'ssm', 'server.js.map'), - }, ] function copy(task: CopyTask): void { diff --git a/packages/core/scripts/build/generateServiceClient.ts b/packages/core/scripts/build/generateServiceClient.ts index 6e6829fd1e..5d1854527b 100644 --- a/packages/core/scripts/build/generateServiceClient.ts +++ b/packages/core/scripts/build/generateServiceClient.ts @@ -241,14 +241,6 @@ void (async () => { serviceJsonPath: 'src/codewhisperer/client/user-service-2.json', serviceName: 'CodeWhispererUserClient', }, - { - serviceJsonPath: 'src/sagemakerunifiedstudio/shared/client/sqlworkbench.json', - serviceName: 'SQLWorkbench', - }, - { - serviceJsonPath: 'src/sagemakerunifiedstudio/shared/client/datazonecustomclient.json', - serviceName: 'DataZoneCustomClient', - }, ] await generateServiceClients(serviceClientDefinitions) })() diff --git a/packages/core/src/amazonq/commons/model.ts b/packages/core/src/amazonq/commons/model.ts index 3c5b228d2f..e047a09016 100644 --- a/packages/core/src/amazonq/commons/model.ts +++ b/packages/core/src/amazonq/commons/model.ts @@ -4,7 +4,16 @@ */ import { LicenseUtil } from '../../codewhisperer/util/licenseUtil' -import { CodeReference } from '../../codewhispererChat/view/connector/connector' + +export interface CodeReference { + licenseName?: string + repository?: string + url?: string + recommendationContentSpan?: { + start?: number + end?: number + } +} // Currently importing ChatItemType in Mynah UI from the vscode side causes an error // TODO remove this once the import stops failing diff --git a/packages/core/src/amazonq/index.ts b/packages/core/src/amazonq/index.ts index aa266ce39d..2d4b830b5e 100644 --- a/packages/core/src/amazonq/index.ts +++ b/packages/core/src/amazonq/index.ts @@ -15,7 +15,6 @@ export { walkthroughInlineSuggestionsExample, } from './onboardingPage/walkthrough' export { api } from './extApi' -export { AmazonQChatViewProvider } from './webview/webView' export { amazonQHelpUrl } from '../shared/constants' export * as webviewConstants from './webview/ui/texts/constants' export * as webviewTabConstants from './webview/ui/tabs/constants' @@ -31,9 +30,8 @@ export { computeDiff, } from './commons/diff' export { AuthFollowUpType, AuthMessageDataMap } from './auth/model' -export { ChatItemType, referenceLogText } from './commons/model' +export { ChatItemType, CodeReference, referenceLogText } from './commons/model' export { ExtensionMessage } from '../amazonq/webview/ui/commands' -export { CodeReference } from '../codewhispererChat/view/connector/connector' export { extractAuthFollowUp } from './util/authUtils' export * as secondaryAuth from '../auth/secondaryAuth' export * as authConnection from '../auth/connection' diff --git a/packages/core/src/amazonq/indexNode.ts b/packages/core/src/amazonq/indexNode.ts index ccc01dc283..b9eff9cf14 100644 --- a/packages/core/src/amazonq/indexNode.ts +++ b/packages/core/src/amazonq/indexNode.ts @@ -6,5 +6,4 @@ /** * These agents have underlying requirements on node dependencies (e.g. jsdom, admzip) */ -export { init as cwChatAppInit } from '../codewhispererChat/app' export { init as gumbyChatAppInit } from '../amazonqGumby/app' diff --git a/packages/core/src/amazonq/webview/generators/webViewContent.ts b/packages/core/src/amazonq/webview/generators/webViewContent.ts deleted file mode 100644 index bc637a8f6c..0000000000 --- a/packages/core/src/amazonq/webview/generators/webViewContent.ts +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import path from 'path' -import { Uri, Webview } from 'vscode' -import { AuthUtil } from '../../../codewhisperer/util/authUtil' -import globals from '../../../shared/extensionGlobals' -import { isSageMaker } from '../../../shared/extensionUtilities' -import { AmazonQPromptSettings } from '../../../shared/settings' -import { getFeatureConfigs, serialize } from './featureConfig' - -export class WebViewContentGenerator { - public async generate(extensionURI: Uri, webView: Webview): Promise { - const entrypoint = process.env.WEBPACK_DEVELOPER_SERVER - ? 'http: localhost' - : 'https: file+.vscode-resources.vscode-cdn.net' - - const contentPolicy = `default-src ${entrypoint} data: blob: 'unsafe-inline'; - script-src ${entrypoint} filesystem: ws: wss: 'unsafe-inline';` - - const configs = await getFeatureConfigs() - const featureDataAttributes = serialize(configs) - return ` - - - - Amazon Q (Preview) - ${await this.generateJS(extensionURI, webView, configs)} - - - - ` - } - - private async generateJS(extensionURI: Uri, webView: Webview, featureConfigsString: string): Promise { - const source = path.join('vue', 'src', 'amazonq', 'webview', 'ui', 'amazonq-ui.js') // Sent to dist/vue folder in webpack. - const assetsPath = Uri.joinPath(extensionURI) - const javascriptUri = Uri.joinPath(assetsPath, 'dist', source) - - const serverHostname = process.env.WEBPACK_DEVELOPER_SERVER - - const javascriptEntrypoint = - serverHostname !== undefined - ? Uri.parse(serverHostname).with({ path: `/${source}` }) - : webView.asWebviewUri(javascriptUri) - - const cssEntrypoints = [ - Uri.joinPath(globals.context.extensionUri, 'resources', 'css', 'amazonq-webview.css'), - Uri.joinPath(globals.context.extensionUri, 'resources', 'css', 'amazonq-chat.css'), - ] - - const cssEntrypointsMap = cssEntrypoints.map((item) => webView.asWebviewUri(item)) - const cssLinks = cssEntrypointsMap.map((uri) => ``).join('\n') - - const isSM = isSageMaker('SMAI') - const isSMUS = isSageMaker('SMUS') - - const disabledCommandsString = isSM ? `['/dev', '/transform', '/test', '/review', '/doc']` : '[]' - const disclaimerAcknowledged = !AmazonQPromptSettings.instance.isPromptEnabled('amazonQChatDisclaimer') - const welcomeLoadCount = globals.globalState.tryGet('aws.amazonq.welcomeChatShowCount', Number, 0) - - // only show profile card when the two conditions - // 1. profile count >= 2 - // 2. not default (fallback) which has empty arn - const regionProfile = - AuthUtil.instance.regionProfileManager.profiles.length === 1 - ? undefined - : AuthUtil.instance.regionProfileManager.activeRegionProfile - - const regionProfileString: string = JSON.stringify(regionProfile) - const authState = (await AuthUtil.instance.getChatAuthState()).amazonQ - - return ` - - ${cssLinks} - - ` - } -} diff --git a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts index 56d4c8503b..38f583c35e 100644 --- a/packages/core/src/amazonq/webview/messages/messageDispatcher.ts +++ b/packages/core/src/amazonq/webview/messages/messageDispatcher.ts @@ -3,11 +3,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { Webview, Uri } from 'vscode' +import { Uri } from 'vscode' import { MessagePublisher } from '../../messages/messagePublisher' -import { MessageListener } from '../../messages/messageListener' import { TabType } from '../ui/storages/tabsStorage' -import { getLogger } from '../../../shared/logger/logger' import { amazonqMark } from '../../../shared/performance/marks' import { telemetry } from '../../../shared/telemetry/telemetry' import { AmazonQChatMessageDuration } from '../../messages/chatMessageDuration' @@ -19,15 +17,6 @@ import { AmazonQPromptSettings } from '../../../shared/settings' const qChatModuleName = 'amazonqChat' -export function dispatchWebViewMessagesToApps( - webview: Webview, - webViewToAppsMessagePublishers: Map> -) { - webview.onDidReceiveMessage((msg) => { - handleWebviewEvent(msg, webViewToAppsMessagePublishers) - }) -} - export function isLegacyEvent(value: string): boolean { return ( value === 'ui-is-ready' || @@ -129,11 +118,3 @@ export function handleWebviewEvent(msg: any, webViewToAppsMessagePublishers: Map } appMessagePublisher.publish(msg) } - -export function dispatchAppsMessagesToWebView(webView: Webview, appsMessageListener: MessageListener) { - appsMessageListener.onMessage((msg) => { - webView.postMessage(JSON.stringify(msg)).then(undefined, (e) => { - getLogger().error('webView.postMessage failed: %s', (e as Error).message) - }) - }) -} diff --git a/packages/core/src/amazonq/webview/webView.ts b/packages/core/src/amazonq/webview/webView.ts deleted file mode 100644 index 70683e65be..0000000000 --- a/packages/core/src/amazonq/webview/webView.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - WebviewViewProvider, - ExtensionContext, - WebviewView, - WebviewViewResolveContext, - CancellationToken, - Uri, - Webview, - EventEmitter, -} from 'vscode' -import { registerAssetsHttpsFileSystem } from './assets/assetsHandler' -import { WebViewContentGenerator } from './generators/webViewContent' -import { dispatchAppsMessagesToWebView, dispatchWebViewMessagesToApps } from './messages/messageDispatcher' -import { MessageListener } from '../messages/messageListener' -import { MessagePublisher } from '../messages/messagePublisher' -import { TabType } from './ui/storages/tabsStorage' -import { amazonqMark } from '../../shared/performance/marks' -import { AuthUtil } from '../../codewhisperer/util/authUtil' - -export class AmazonQChatViewProvider implements WebviewViewProvider { - public static readonly viewType = 'aws.amazonq.AmazonQChatView' - - webViewContentGenerator: WebViewContentGenerator - webView: Webview | undefined - - constructor( - private readonly extensionContext: ExtensionContext, - private readonly webViewToAppsMessagesPublishers: Map>, - private readonly appsMessagesListener: MessageListener, - private readonly onDidChangeAmazonQVisibility: EventEmitter - ) { - registerAssetsHttpsFileSystem(extensionContext) - this.webViewContentGenerator = new WebViewContentGenerator() - - AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(async () => { - if (this.webView) { - this.webView.html = await this.webViewContentGenerator.generate( - this.extensionContext.extensionUri, - this.webView - ) - } - }) - } - - public async resolveWebviewView( - webviewView: WebviewView, - context: WebviewViewResolveContext, - _token: CancellationToken - ) { - webviewView.onDidChangeVisibility(() => { - this.onDidChangeAmazonQVisibility.fire(webviewView.visible) - }) - - const dist = Uri.joinPath(this.extensionContext.extensionUri, 'dist') - const resources = Uri.joinPath(this.extensionContext.extensionUri, 'resources') - webviewView.webview.options = { - enableScripts: true, - enableCommandUris: true, - localResourceRoots: [dist, resources], - } - - dispatchWebViewMessagesToApps(webviewView.webview, this.webViewToAppsMessagesPublishers) - - dispatchAppsMessagesToWebView(webviewView.webview, this.appsMessagesListener) - - webviewView.webview.html = await this.webViewContentGenerator.generate( - this.extensionContext.extensionUri, - webviewView.webview - ) - - this.webView = webviewView.webview - performance.mark(amazonqMark.open) - } -} diff --git a/packages/core/src/applicationcomposer/activation.ts b/packages/core/src/applicationcomposer/activation.ts deleted file mode 100644 index 28af3f47d0..0000000000 --- a/packages/core/src/applicationcomposer/activation.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ApplicationComposerManager } from './webviewManager' -import { ApplicationComposerCodeLensProvider } from './codeLensProvider' -import { openTemplateInComposerCommand } from './commands/openTemplateInComposer' -import { openInComposerDialogCommand } from './commands/openInComposerDialog' - -export async function activate(extensionContext: vscode.ExtensionContext): Promise { - const visualizationManager = await ApplicationComposerManager.create(extensionContext) - - extensionContext.subscriptions.push( - openTemplateInComposerCommand.register(visualizationManager), - openInComposerDialogCommand.register(visualizationManager) - ) - - extensionContext.subscriptions.push( - vscode.languages.registerCodeLensProvider( - ['yaml', 'json', { scheme: 'file' }], - new ApplicationComposerCodeLensProvider() - ) - ) -} diff --git a/packages/core/src/applicationcomposer/codeLensProvider.ts b/packages/core/src/applicationcomposer/codeLensProvider.ts deleted file mode 100644 index 844f6ddb9e..0000000000 --- a/packages/core/src/applicationcomposer/codeLensProvider.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import vscode from 'vscode' -import * as CloudFormation from '../shared/cloudformation/cloudformation' -import * as nls from 'vscode-nls' -import { openTemplateInComposerCommand } from './commands/openTemplateInComposer' - -const localize = nls.loadMessageBundle() - -export class ApplicationComposerCodeLensProvider implements vscode.CodeLensProvider { - public async provideCodeLenses( - document: vscode.TextDocument, - _token: vscode.CancellationToken - ): Promise { - const cfnTemplate = CloudFormation.isValidFilename(document.uri) - ? await CloudFormation.tryLoad(document.uri) - : undefined - if (!cfnTemplate?.template) { - return [] - } - - let codeLensLine = 0 - for (let i = 0; i < document.getText().length; i++) { - const line = document.lineAt(i) - const lineContents = line.text.substring(line.firstNonWhitespaceCharacterIndex) - if (lineContents.length > 0 && !lineContents.startsWith('#')) { - codeLensLine = i - break - } - } - const resourcesLoc = new vscode.Range(codeLensLine, 0, codeLensLine, 0) - const codeLens = openTemplateInComposerCommand.build().asCodeLens(resourcesLoc, { - title: localize('AWS.applicationComposer.codeLens.title', 'Visualize with Infrastructure Composer'), - tooltip: localize( - 'AWS.applicationComposer.codeLens.tooltip', - 'Visually design and build modern applications quickly' - ), - }) - return [codeLens] - } -} diff --git a/packages/core/src/applicationcomposer/commands/openInComposerDialog.ts b/packages/core/src/applicationcomposer/commands/openInComposerDialog.ts deleted file mode 100644 index e3e1bc9b74..0000000000 --- a/packages/core/src/applicationcomposer/commands/openInComposerDialog.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Commands } from '../../shared/vscode/commands2' -import { ApplicationComposerManager } from '../webviewManager' -import vscode from 'vscode' - -export const openInComposerDialogCommand = Commands.declare( - 'aws.openInApplicationComposerDialog', - (manager: ApplicationComposerManager) => async () => { - const fileUri = await vscode.window.showOpenDialog({ - filters: { - Templates: ['yml', 'yaml', 'json', 'template'], - }, - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - }) - if (fileUri && fileUri[0]) { - return await manager.visualizeTemplate(fileUri[0]) - } - } -) diff --git a/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts b/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts deleted file mode 100644 index 351673754c..0000000000 --- a/packages/core/src/applicationcomposer/commands/openTemplateInComposer.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Commands } from '../../shared/vscode/commands2' -import { ApplicationComposerManager } from '../webviewManager' -import vscode from 'vscode' -import { telemetry } from '../../shared/telemetry/telemetry' -import { ToolkitError } from '../../shared/errors' -import { isTreeNode, TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import { SamAppLocation } from '../../awsService/appBuilder/explorer/samProject' -import { getAmazonqApi } from '../../amazonq/extApi' - -export const openTemplateInComposerCommand = Commands.declare( - 'aws.openInApplicationComposer', - (manager: ApplicationComposerManager) => async (arg?: vscode.TextEditor | vscode.Uri | TreeNode) => { - let result: vscode.WebviewPanel | undefined - await telemetry.appcomposer_openTemplate.run(async (span) => { - const amazonqApi = await getAmazonqApi() - - let hasChatAuth = false - if (amazonqApi) { - const authState = await amazonqApi.authApi.getChatAuthState() - hasChatAuth = authState.codewhispererChat === 'connected' || authState.codewhispererChat === 'expired' - } - - span.record({ - hasChatAuth, - }) - let input = undefined - if (arg instanceof vscode.Uri) { - input = arg - } else if (isTreeNode(arg)) { - input = ((arg as TreeNode).resource as SamAppLocation).samTemplateUri - } else { - input = vscode.window.activeTextEditor?.document - } - - if (!input) { - throw new ToolkitError('No active text editor or document found') - } - - result = await manager.visualizeTemplate(input) - }) - return result - } -) diff --git a/packages/core/src/applicationcomposer/composerWebview.ts b/packages/core/src/applicationcomposer/composerWebview.ts deleted file mode 100644 index f43775df3c..0000000000 --- a/packages/core/src/applicationcomposer/composerWebview.ts +++ /dev/null @@ -1,154 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { join } from 'path' -import * as nls from 'vscode-nls' -import * as path from 'path' -import * as vscode from 'vscode' -import { handleMessage } from './handleMessage' -import { FileWatchInfo } from './types' - -const localize = nls.loadMessageBundle() - -export class ApplicationComposer { - public readonly documentUri: vscode.Uri - public webviewPanel: vscode.WebviewPanel = {} as vscode.WebviewPanel - protected readonly disposables: vscode.Disposable[] = [] - protected isPanelDisposed = false - private readonly onVisualizationDisposeEmitter = new vscode.EventEmitter() - public workSpacePath: string - public defaultTemplatePath: string - public defaultTemplateName: string - - private constructor( - textDocument: vscode.TextDocument, - private getWebviewContent: () => Promise, - public fileWatches: Record = {} // fileWatches is used to monitor template file changes and achieve bi-direction sync - ) { - this.getWebviewContent = getWebviewContent - this.documentUri = textDocument.uri - this.workSpacePath = path.dirname(textDocument.uri.fsPath) - this.defaultTemplatePath = textDocument.uri.fsPath - this.defaultTemplateName = path.basename(this.defaultTemplatePath) - } - - public static async create( - textDocument: vscode.TextDocument, - context: vscode.ExtensionContext, - getWebviewContent: () => Promise - ) { - const obj = new ApplicationComposer(textDocument, getWebviewContent) - obj.webviewPanel = await obj.setupWebviewPanel(textDocument, context) - return obj - } - - public get onVisualizationDisposeEvent(): vscode.Event { - return this.onVisualizationDisposeEmitter.event - } - - public getPanel(): vscode.WebviewPanel | undefined { - if (!this.isPanelDisposed) { - return this.webviewPanel - } - } - - public showPanel(): void { - this.getPanel()?.reveal() - } - - public async refreshPanel(context: vscode.ExtensionContext) { - if (!this.isPanelDisposed) { - this.webviewPanel.dispose() - const document = await vscode.workspace.openTextDocument(this.documentUri) - this.webviewPanel = await this.setupWebviewPanel(document, context) - } - } - - protected getText(textDocument: vscode.TextDocument): string { - return textDocument.getText() - } - - private async setupWebviewPanel( - textDocument: vscode.TextDocument, - context: vscode.ExtensionContext - ): Promise { - const documentUri = textDocument.uri - - // Create and show panel - const panel = this.createVisualizationWebviewPanel(documentUri, context) - - // Set the initial html for the webpage - panel.webview.html = await this.getWebviewContent() - - // Handle messages from the webview - this.disposables.push( - panel.webview.onDidReceiveMessage((message) => - handleMessage(message, { - panel: panel, - textDocument: textDocument, - disposables: this.disposables, - workSpacePath: this.workSpacePath, - defaultTemplatePath: this.defaultTemplatePath, - defaultTemplateName: this.defaultTemplateName, - fileWatches: this.fileWatches, - }) - ) - ) - - // When the panel is closed, dispose of any disposables/remove subscriptions - const disposePanel = () => { - if (this.isPanelDisposed) { - return - } - this.isPanelDisposed = true - this.onVisualizationDisposeEmitter.fire() - for (const disposable of this.disposables) { - disposable.dispose() - } - this.onVisualizationDisposeEmitter.dispose() - } - - this.disposables.push( - panel.onDidDispose(() => { - disposePanel() - }) - ) - - return panel - } - - private createVisualizationWebviewPanel( - documentUri: vscode.Uri, - context: vscode.ExtensionContext - ): vscode.WebviewPanel { - const panel = vscode.window.createWebviewPanel( - 'applicationComposer', - localize( - 'AWS.applicationComposer.title', - '{0} (Infrastructure Composer)', - path.basename(documentUri.fsPath) - ), - { - preserveFocus: true, - viewColumn: vscode.ViewColumn.Active, - }, - { - enableScripts: true, - localResourceRoots: [], - retainContextWhenHidden: true, - } - ) - if (vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Light) { - panel.iconPath = vscode.Uri.file( - context.asAbsolutePath(join('resources', 'icons', 'aws', 'applicationcomposer', 'icon.svg')) - ) - } else { - panel.iconPath = vscode.Uri.file( - context.asAbsolutePath(join('resources', 'icons', 'aws', 'applicationcomposer', 'icon-dark.svg')) - ) - } - return panel - } -} diff --git a/packages/core/src/applicationcomposer/constants.ts b/packages/core/src/applicationcomposer/constants.ts deleted file mode 100644 index 1eb3852ad6..0000000000 --- a/packages/core/src/applicationcomposer/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -const isLocalDev = false -const localhost = 'http://127.0.0.1:3000' -const cdn = 'https://ide-toolkits.app-composer.aws.dev' - -export { isLocalDev, localhost, cdn } diff --git a/packages/core/src/applicationcomposer/handleMessage.ts b/packages/core/src/applicationcomposer/handleMessage.ts deleted file mode 100644 index d967cb744d..0000000000 --- a/packages/core/src/applicationcomposer/handleMessage.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { loadFileMessageHandler } from './messageHandlers/loadFileMessageHandler' -import { initMessageHandler } from './messageHandlers/initMessageHandler' -import { - Command, - EmitTelemetryMessage, - LoadFileRequestMessage, - LogMessage, - Message, - WebviewContext, - SaveFileRequestMessage, - AddFileWatchRequestMessage, - GenerateResourceRequestMessage, - MessageType, - DeployRequestMessage, -} from './types' -import { saveFileMessageHandler } from './messageHandlers/saveFileMessageHandler' -import { addFileWatchMessageHandler } from './messageHandlers/addFileWatchMessageHandler' -import { deployMessageHandler } from './messageHandlers/deployMessageHandler' -import { generateResourceHandler } from './messageHandlers/generateResourceHandler' -import { logMessageHandler } from './messageHandlers/logMessageHandler' -import { emitTelemetryMessageHandler } from './messageHandlers/emitTelemetryMessageHandler' -import { openFeedbackMessageHandler } from './messageHandlers/openFeedbackMessageHandler' - -export async function handleMessage(message: unknown, context: WebviewContext) { - const composerMessage = message as Message - - const { command, messageType } = composerMessage - - if (messageType === MessageType.REQUEST) { - switch (command) { - case Command.INIT: - void initMessageHandler(context) - break - case Command.LOAD_FILE: - void loadFileMessageHandler(message as LoadFileRequestMessage, context) - break - case Command.SAVE_FILE: - void saveFileMessageHandler(message as SaveFileRequestMessage, context) - break - case Command.ADD_FILE_WATCH: - void addFileWatchMessageHandler(message as AddFileWatchRequestMessage, context) - break - case Command.DEPLOY: - void deployMessageHandler(message as DeployRequestMessage, context) - break - case Command.GENERATE_RESOURCE: - void generateResourceHandler(message as GenerateResourceRequestMessage, context) - break - } - } else if (messageType === MessageType.BROADCAST) { - switch (command) { - case Command.LOG: - logMessageHandler(message as LogMessage) - break - case Command.EMIT_TELEMETRY: - emitTelemetryMessageHandler(message as EmitTelemetryMessage) - break - case Command.OPEN_FEEDBACK: - openFeedbackMessageHandler() - break - } - } -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/addFileWatchMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/addFileWatchMessageHandler.ts deleted file mode 100644 index d23dabd4d3..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/addFileWatchMessageHandler.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AddFileWatchRequestMessage, - AddFileWatchResponseMessage, - MessageType, - FileChangedMessage, - Command, - WebviewContext, -} from '../types' -import vscode from 'vscode' - -export async function addFileWatchMessageHandler(request: AddFileWatchRequestMessage, context: WebviewContext) { - let addFileWatchResponseMessage: AddFileWatchResponseMessage - try { - // we only file watch on default template file now - if (context.defaultTemplateName !== request.fileName) { - throw new Error('file watching is only allowed on default template file') - } - const filePath = context.defaultTemplatePath - const fileName = context.defaultTemplateName - const fileWatch = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(filePath, '*')) - context.disposables.push(fileWatch) - - fileWatch.onDidChange(async () => { - const fileContents = (await vscode.workspace.fs.readFile(vscode.Uri.file(filePath))).toString() - if (fileContents !== context.fileWatches[filePath].fileContents) { - const fileChangedMessage: FileChangedMessage = { - messageType: MessageType.BROADCAST, - command: Command.FILE_CHANGED, - fileName: fileName, - fileContents: fileContents, - } - - await context.panel.webview.postMessage(fileChangedMessage) - context.fileWatches[filePath] = { fileContents: fileContents } - } - }) - - addFileWatchResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.ADD_FILE_WATCH, - eventId: request.eventId, - isSuccess: true, - } - } catch (e) { - addFileWatchResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.ADD_FILE_WATCH, - eventId: request.eventId, - isSuccess: false, - failureReason: (e as Error).message, - } - } - - await context.panel.webview.postMessage(addFileWatchResponseMessage) -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/deployMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/deployMessageHandler.ts deleted file mode 100644 index 26cd38cafe..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/deployMessageHandler.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import vscode from 'vscode' -import { Command, DeployRequestMessage, DeployResponseMessage, MessageType, WebviewContext } from '../types' -import { SamSyncResult } from '../../shared/sam/sync' -import { telemetry } from '../../shared/telemetry/telemetry' - -export async function deployMessageHandler(message: DeployRequestMessage, context: WebviewContext) { - // SAM already handles success/failure, so we only log that the user clicked the deploy button - telemetry.appcomposer_deployClicked.emit({ - result: 'Succeeded', - }) - const args = context.textDocument.uri - /* TODO Rework this command so that a failure case is returned - * We don't want to override the SAM Sync telemetry. The SAM telemetry catches all errors, - * so we instead check for an undefined response to determine failure. The downside is that - * we don't get failure reasons. - */ - const result = (await vscode.commands.executeCommand('aws.samcli.sync', args, false)) as SamSyncResult - if (result?.isSuccess) { - void vscode.window.showInformationMessage('SAM Sync succeeded!') - telemetry.appcomposer_deployCompleted.emit({ result: 'Succeeded' }) - } else { - telemetry.appcomposer_deployCompleted.emit({ result: 'Failed' }) - } - const response: DeployResponseMessage = { - command: Command.DEPLOY, - messageType: MessageType.RESPONSE, - eventId: message.eventId, - isSuccess: result?.isSuccess, - } - await context.panel.webview.postMessage(response) -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts deleted file mode 100644 index 8210f126a4..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/emitTelemetryMessageHandler.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EmitTelemetryMessage } from '../types' -import { - AppcomposerAddConnection, - AppcomposerAddResource, - AppcomposerCloseWfs, - AppcomposerCustomerReady, - AppcomposerFps, - AppcomposerGenerateAccepted, - AppcomposerGenerateClicked, - AppcomposerGenerateRejected, - AppcomposerInvalidGeneration, - AppcomposerOpenWfs, - AppcomposerPostProcess, - AppcomposerRegenerateClicked, - AppcomposerInitializeProject, - telemetry, -} from '../../shared/telemetry/telemetry' -import { getLogger } from '../../shared/logger/logger' - -export function emitTelemetryMessageHandler(message: EmitTelemetryMessage) { - try { - const parsedData = message.metadata ? JSON.parse(message.metadata) : {} - switch (message.eventType) { - case 'GENERATE_CLICKED': - sendGenerateClicked(parsedData as AppcomposerGenerateClicked) - return - case 'REGENERATE_CLICKED': - sendRegenerateClicked(parsedData as AppcomposerRegenerateClicked) - return - case 'GENERATE_ACCEPTED': - sendGenerateAccepted(parsedData as AppcomposerGenerateAccepted) - return - case 'GENERATE_REJECTED': - sendGenerateRejected(parsedData as AppcomposerGenerateRejected) - return - case 'INVALID_GENERATION': - sendInvalidGeneration(parsedData as AppcomposerInvalidGeneration) - return - case 'POST_PROCESS': - sendPostProcess(parsedData as AppcomposerPostProcess) - return - case 'CUSTOMER_READY': - sendCustomerReady(parsedData as AppcomposerCustomerReady) - return - case 'FPS': - sendFps(parsedData as AppcomposerFps) - return - case 'ADD_RESOURCE': - sendAddResource(parsedData as AppcomposerAddResource) - return - case 'ADD_CONNECTION': - sendAddConnection(parsedData as AppcomposerAddConnection) - return - case 'OPEN_WFS': - sendOpenWfs(parsedData as AppcomposerOpenWfs) - return - case 'CLOSE_WFS': - sendCloseWfs(parsedData as AppcomposerCloseWfs) - return - case 'TEMPLATE_OPENED': - sendInitializeProject(parsedData as AppcomposerInitializeProject) - return - } - } catch (e) { - getLogger().error('Could not log telemetry for App Composer %O', e) - } -} - -function sendGenerateClicked(metadata: AppcomposerGenerateClicked) { - telemetry.appcomposer_generateClicked.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - }) -} - -function sendRegenerateClicked(metadata: AppcomposerRegenerateClicked) { - telemetry.appcomposer_regenerateClicked.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - }) -} - -function sendGenerateAccepted(metadata: AppcomposerGenerateAccepted) { - telemetry.appcomposer_generateAccepted.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - numAttempts: metadata.numAttempts, - }) -} - -function sendGenerateRejected(metadata: AppcomposerGenerateRejected) { - telemetry.appcomposer_generateRejected.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - numAttempts: metadata.numAttempts, - }) -} - -function sendInvalidGeneration(metadata: AppcomposerInvalidGeneration) { - telemetry.appcomposer_invalidGeneration.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - generateFailure: metadata.generateFailure, - }) -} - -function sendPostProcess(metadata: AppcomposerPostProcess) { - telemetry.appcomposer_postProcess.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - pathsScrubbed: metadata.pathsScrubbed, - }) -} - -function sendCustomerReady(metadata: AppcomposerCustomerReady) { - telemetry.appcomposer_customerReady.emit({ - result: metadata.result ?? 'Succeeded', - loadFileTime: metadata.loadFileTime, - initializeTime: metadata.initializeTime, - saveFileTime: metadata.saveFileTime, - }) -} - -function sendFps(metadata: AppcomposerFps) { - telemetry.appcomposer_fps.emit({ - result: metadata.result ?? 'Succeeded', - fps: metadata.fps, - }) -} - -function sendAddResource(metadata: AppcomposerAddResource) { - telemetry.appcomposer_addResource.emit({ - result: metadata.result ?? 'Succeeded', - resourceType: metadata.resourceType, - }) -} - -function sendAddConnection(metadata: AppcomposerAddConnection) { - telemetry.appcomposer_addConnection.emit({ - result: metadata.result ?? 'Succeeded', - sourceResourceType: metadata.sourceResourceType, - sourceFacetType: metadata.sourceFacetType, - destResourceType: metadata.destResourceType, - destFacetType: metadata.destFacetType, - }) -} - -function sendOpenWfs(metadata: AppcomposerOpenWfs) { - telemetry.appcomposer_openWfs.emit({ - result: metadata.result ?? 'Succeeded', - }) -} - -function sendCloseWfs(metadata: AppcomposerCloseWfs) { - telemetry.appcomposer_closeWfs.emit({ - result: metadata.result ?? 'Succeeded', - didSave: metadata.didSave, - }) -} - -function sendInitializeProject(metadata: AppcomposerInitializeProject) { - telemetry.appcomposer_initializeProject.emit({ - resourceCounts: metadata.resourceCounts, - totalResourceCount: metadata.totalResourceCount, - }) -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts deleted file mode 100644 index fe31e40ef2..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/generateResourceHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - GenerateResourceRequestMessage, - GenerateResourceResponseMessage, - WebviewContext, - Command, - MessageType, -} from '../types' -import { getLogger } from '../../shared/logger/logger' -import request from '../../shared/request' -import { isLocalDev, localhost, cdn } from '../constants' - -export async function generateResourceHandler(request: GenerateResourceRequestMessage, context: WebviewContext) { - try { - const { chatResponse, references, metadata, isSuccess } = await fetchExampleResource(request.cfnType) - - const responseMessage: GenerateResourceResponseMessage = { - command: Command.GENERATE_RESOURCE, - messageType: MessageType.RESPONSE, - chatResponse, - references, - metadata, - isSuccess, - traceId: request.traceId, - } - await context.panel.webview.postMessage(responseMessage) - } catch (error: any) { - getLogger().error(`Error in generateResourceHandler: ${error.message}`, error) - - const responseMessage: GenerateResourceResponseMessage = { - command: Command.GENERATE_RESOURCE, - messageType: MessageType.RESPONSE, - isSuccess: false, - errorMessage: error.message, - traceId: request.traceId, - chatResponse: '', - references: [], - metadata: {}, - } - - await context.panel.webview.postMessage(responseMessage) - } -} - -async function fetchExampleResource(cfnType: string) { - try { - const source = isLocalDev ? localhost : cdn - const resp = request.fetch('GET', `${source}/examples/${convertCFNType(cfnType)}.json`, {}) - return { - chatResponse: await (await resp.response).text(), - references: [], - metadata: {}, - isSuccess: true, - } - } catch (error: any) { - getLogger().debug(`Resource fetch error: ${error.name} - ${error.message}`) - if (error.$metadata) { - const { requestId, cfId, extendedRequestId } = error.$metadata - getLogger().debug('%O', { requestId, cfId, extendedRequestId }) - } - - throw error - } -} - -function convertCFNType(cfnType: string): string { - const resourceParts = cfnType.split('::') - if (resourceParts.length !== 3) { - throw new Error('CFN type did not contain three parts') - } - - return resourceParts.join('_') -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/initMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/initMessageHandler.ts deleted file mode 100644 index 0bcca34300..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/initMessageHandler.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import path from 'path' -import { InitResponseMessage, MessageType, WebviewContext, Command } from '../types' -import { getAmazonqApi } from '../../amazonq/extApi' - -export async function initMessageHandler(context: WebviewContext) { - const filePath = context.defaultTemplatePath - const amazonqApi = await getAmazonqApi() - let isConnectedToCodeWhisperer = false - if (amazonqApi) { - const authState = await amazonqApi.authApi.getChatAuthState() - isConnectedToCodeWhisperer = - authState.codewhispererChat === 'connected' || authState.codewhispererChat === 'expired' - } - - const responseMessage: InitResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.INIT, - templateFileName: path.basename(filePath), - templateFilePath: filePath, - isConnectedToCodeWhisperer, - } - - await context.panel.webview.postMessage(responseMessage) -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/loadFileMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/loadFileMessageHandler.ts deleted file mode 100644 index ad5e1bbf60..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/loadFileMessageHandler.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import path from 'path' -import { MessageType, LoadFileRequestMessage, LoadFileResponseMessage, Command, WebviewContext } from '../types' -import vscode from 'vscode' - -export async function loadFileMessageHandler(request: LoadFileRequestMessage, context: WebviewContext) { - let loadFileResponseMessage: LoadFileResponseMessage - try { - switch (request.fileName) { - case '': { - // load default template file when 'fileName' is empty - const initFileContents = ( - await vscode.workspace.fs.readFile(vscode.Uri.file(context.defaultTemplatePath)) - ).toString() - if (initFileContents === undefined) { - throw new Error(`Cannot read file contents from ${context.defaultTemplatePath}`) - } - context.fileWatches[context.defaultTemplatePath] = { fileContents: initFileContents } - loadFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.LOAD_FILE, - eventId: request.eventId, - fileName: context.defaultTemplateName, - fileContents: initFileContents, - isSuccess: true, - } - break - } - default: { - const filePath = path.join(context.workSpacePath, request.fileName) - const fileContents = (await vscode.workspace.fs.readFile(vscode.Uri.file(filePath))).toString() - loadFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.LOAD_FILE, - eventId: request.eventId, - fileName: request.fileName, - fileContents: fileContents, - isSuccess: true, - } - break - } - } - } catch (e) { - loadFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.LOAD_FILE, - eventId: request.eventId, - fileName: request.fileName, - fileContents: '', - isSuccess: false, - failureReason: (e as Error).message, - } - } - await context.panel.webview.postMessage(loadFileResponseMessage) -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/logMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/logMessageHandler.ts deleted file mode 100644 index 2565a2b811..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/logMessageHandler.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LogMessage } from '../types' -import { getLogger } from '../../shared/logger/logger' - -export function logMessageHandler(message: LogMessage) { - const logger = getLogger() - switch (message.logType) { - case 'INFO': - logger.info(message.logMessage) - return - case 'WARNING': - logger.warn(message.logMessage) - return - case 'ERROR': - logger.error(message.logMessage) - return - } -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/notifyUser.ts b/packages/core/src/applicationcomposer/messageHandlers/notifyUser.ts deleted file mode 100644 index 4c5a288681..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/notifyUser.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export type NotifyUserRequest = { - command: 'NOTIFY_USER' - notification: any - notificationType: 'INFO' | 'WARNING' | 'ERROR' -} -export function notifyUser(request: NotifyUserRequest) { - // TODO -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts deleted file mode 100644 index 85ddd677c2..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/openFeedbackMessageHandler.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { submitFeedback } from '../../feedback/vue/submitFeedback' -import { placeholder } from '../../shared/vscode/commands2' - -export function openFeedbackMessageHandler() { - void submitFeedback(placeholder, 'Infrastructure Composer') -} diff --git a/packages/core/src/applicationcomposer/messageHandlers/saveFileMessageHandler.ts b/packages/core/src/applicationcomposer/messageHandlers/saveFileMessageHandler.ts deleted file mode 100644 index e62aefdec9..0000000000 --- a/packages/core/src/applicationcomposer/messageHandlers/saveFileMessageHandler.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import vscode from 'vscode' -import { SaveFileRequestMessage, SaveFileResponseMessage, WebviewContext, Command, MessageType } from '../types' -import path from 'path' - -export async function saveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) { - let saveFileResponseMessage: SaveFileResponseMessage - // If filePath is empty, save contents in default template file - const filePath = - request.filePath === '' ? context.defaultTemplatePath : path.join(context.workSpacePath, request.filePath) - try { - if (!context.textDocument.isDirty) { - const contents = Buffer.from(request.fileContents, 'utf8') - context.fileWatches[filePath] = { fileContents: request.fileContents } - const uri = vscode.Uri.file(filePath) - await vscode.workspace.fs.writeFile(uri, contents) - saveFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.SAVE_FILE, - eventId: request.eventId, - filePath: filePath, - isSuccess: true, - } - } else { - // TODO: If the template file is dirty, do we pop out a warning window? - throw new Error(`Cannot save latest contents in ${path.basename(request.filePath)}`) - } - } catch (e) { - saveFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.SAVE_FILE, - eventId: request.eventId, - filePath: filePath, - isSuccess: false, - failureReason: (e as Error).message, - } - } - - await context.panel.webview.postMessage(saveFileResponseMessage) -} diff --git a/packages/core/src/applicationcomposer/types.ts b/packages/core/src/applicationcomposer/types.ts deleted file mode 100644 index 75d38179e9..0000000000 --- a/packages/core/src/applicationcomposer/types.ts +++ /dev/null @@ -1,162 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' - -export type WebviewContext = { - panel: vscode.WebviewPanel - textDocument: vscode.TextDocument - disposables: vscode.Disposable[] - workSpacePath: string - defaultTemplatePath: string - defaultTemplateName: string - fileWatches: Record -} - -export type FileWatchInfo = { - fileContents: string -} - -export interface Message { - command: Command - messageType: MessageType -} - -export enum Command { - INIT = 'INIT', - LOAD_FILE = 'LOAD_FILE', - SAVE_FILE = 'SAVE_FILE', - ADD_FILE_WATCH = 'ADD_FILE_WATCH', - DEPLOY = 'DEPLOY', - FILE_CHANGED = 'FILE_CHANGED', - GENERATE_RESOURCE = 'GENERATE_RESOURCE', - LOG = 'LOG', - EMIT_TELEMETRY = 'EMIT_TELEMETRY', - OPEN_FEEDBACK = 'OPEN_FEEDBACK', -} - -export enum MessageType { - REQUEST = 'REQUEST', - RESPONSE = 'RESPONSE', - BROADCAST = 'BROADCAST', -} - -type TelemetryType = - | 'GENERATE_CLICKED' - | 'REGENERATE_CLICKED' - | 'GENERATE_ACCEPTED' - | 'GENERATE_REJECTED' - | 'INVALID_GENERATION' - | 'POST_PROCESS' - | 'CUSTOMER_READY' - | 'FPS' - | 'ADD_RESOURCE' - | 'ADD_CONNECTION' - | 'OPEN_WFS' - | 'CLOSE_WFS' - | 'TEMPLATE_OPENED' - -export interface InitResponseMessage extends Message { - templateFileName: string - templateFilePath: string - isConnectedToCodeWhisperer: boolean -} - -export interface LoadFileResponseMessage extends Message { - eventId: string - fileName: string - fileContents: string - isSuccess: boolean - failureReason?: string -} - -export interface SaveFileResponseMessage extends Message { - eventId: string - filePath: string - isSuccess: boolean - failureReason?: string -} - -export interface AddFileWatchResponseMessage extends Message { - eventId: string - isSuccess: boolean - failureReason?: string -} - -export interface FileChangedMessage extends Message { - fileName: string - fileContents: string -} - -export interface DeployRequestMessage extends Message { - eventId: string -} - -export interface DeployResponseMessage extends Message { - eventId: string - isSuccess: boolean -} - -export interface RequestMessage extends Message { - command: Command -} - -export interface InitRequestMessage extends Message { - eventId?: string -} - -export interface LoadFileRequestMessage extends Message { - eventId: string - fileName: string -} - -export interface SaveFileRequestMessage extends Message { - eventId: string - filePath: string - fileContents: string -} - -export interface AddFileWatchRequestMessage extends Message { - eventId: string - fileName: string -} - -export interface NotifyUserRequestMessage extends Message { - eventId?: string - notification: string - notificationType: 'INFO' | 'WARNING' | 'ERROR' -} - -export interface LogMessage extends Message { - eventId?: string - logMessage: string - logType: 'INFO' | 'WARNING' | 'ERROR' -} - -export interface EmitTelemetryMessage extends Message { - eventId?: string - eventType: TelemetryType - metadata?: string -} - -export interface ReferenceDetails { - title: string | undefined - url: string | undefined - snippet?: string -} - -export interface GenerateResourceRequestMessage extends Message { - cfnType: string - prompt: string - traceId: string -} - -export interface GenerateResourceResponseMessage extends Message { - chatResponse: string - references: ReferenceDetails[] - metadata: object - isSuccess: boolean - errorMessage?: string - traceId: string -} diff --git a/packages/core/src/applicationcomposer/webviewManager.ts b/packages/core/src/applicationcomposer/webviewManager.ts deleted file mode 100644 index 92bcbb5559..0000000000 --- a/packages/core/src/applicationcomposer/webviewManager.ts +++ /dev/null @@ -1,142 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import request from '../shared/request' -import { ApplicationComposer } from './composerWebview' -import { isLocalDev, localhost, cdn } from './constants' -import { getLogger } from '../shared/logger/logger' - -const localize = nls.loadMessageBundle() - -const enabledFeatures = ['ide-only', 'anything-resource', 'sfnV2', 'starling'] - -export class ApplicationComposerManager { - protected readonly name: string = 'ApplicationComposerManager' - - protected readonly managedVisualizations = new Map() - protected webviewHtml?: string - protected readonly logger = getLogger() - - private constructor(protected extensionContext: vscode.ExtensionContext) {} - - public static async create(extensionContext: vscode.ExtensionContext): Promise { - const obj = new ApplicationComposerManager(extensionContext) - await obj.fetchWebviewHtml() - return obj - } - - private async fetchWebviewHtml() { - const source = isLocalDev ? localhost : cdn - const response = await request.fetch('GET', `${source}/index.html`).response - this.webviewHtml = await response.text() - for (const visualization of this.managedVisualizations.values()) { - await visualization.refreshPanel(this.extensionContext) - } - } - - private getWebviewContent = async () => { - if (!this.webviewHtml) { - void this.fetchWebviewHtml() - return '' - } - const htmlFileSplit = this.webviewHtml.split('') - - // Set asset source to CDN - const source = isLocalDev ? localhost : cdn - const baseTag = '' - - // Set dark mode, locale, and feature flags - const locale = vscode.env.language - const localeTag = `` - const theme = vscode.window.activeColorTheme.kind - const isDarkMode = theme === vscode.ColorThemeKind.Dark || theme === vscode.ColorThemeKind.HighContrast - const darkModeTag = `` - const featuresTag = `` - - return htmlFileSplit[0] + '' + baseTag + localeTag + darkModeTag + featuresTag + htmlFileSplit[1] - } - - public async visualizeTemplate(target: vscode.TextDocument | vscode.Uri): Promise { - const document = target instanceof vscode.Uri ? await vscode.workspace.openTextDocument(target) : target - - // Attempt to retrieve existing visualization if it exists. - const existingVisualization = this.getExistingVisualization(document.uri.fsPath) - if (existingVisualization) { - existingVisualization.showPanel() - - return existingVisualization.getPanel() - } - - // Existing visualization does not exist, construct new visualization - try { - const newVisualization = await ApplicationComposer.create( - document, - this.extensionContext, - this.getWebviewContent - ) - this.handleNewVisualization(document.uri.fsPath, newVisualization) - - if (vscode.version === '1.91.0') { - void vscode.window.showWarningMessage( - localize( - 'AWS.applicationComposer.visualisation.warnings.draganddrop', - 'This version of Visual Studio Code has a bug preventing normal drag and drop functionality. ' + - 'As a temporary workaround, hold the Shift key before releasing a resource onto the visual canvas.' - ) - ) - } - return newVisualization.getPanel() - } catch (err) { - this.handleErr(err as Error) - } - } - - public async createTemplate(): Promise { - try { - const document = await vscode.workspace.openTextDocument({ - language: 'yaml', - }) - const newVisualization = await ApplicationComposer.create( - document, - this.extensionContext, - this.getWebviewContent - ) - this.handleNewVisualization(document.uri.fsPath, newVisualization) - - return newVisualization.getPanel() - } catch (err) { - this.handleErr(err as Error) - } - } - - protected getExistingVisualization(key: string): ApplicationComposer | undefined { - return this.managedVisualizations.get(key) - } - - protected handleErr(err: Error): void { - void vscode.window.showInformationMessage( - localize( - 'AWS.applicationComposer.visualisation.errors.rendering', - 'There was an error rendering Infrastructure Composer, check logs for details.' - ) - ) - this.logger.error(`${this.name}: Unable to show App Composer webview: ${err}`) - } - - protected handleNewVisualization(key: string, visualization: ApplicationComposer): void { - this.managedVisualizations.set(key, visualization) - - const visualizationDisposable = visualization.onVisualizationDisposeEvent(() => { - this.managedVisualizations.delete(key) - }) - this.pushToExtensionContextSubscriptions(visualizationDisposable) - } - - protected pushToExtensionContextSubscriptions(visualizationDisposable: vscode.Disposable): void { - this.extensionContext.subscriptions.push(visualizationDisposable) - } -} diff --git a/packages/core/src/auth/auth.ts b/packages/core/src/auth/auth.ts index 18f4fdd5b1..d2f6f72812 100644 --- a/packages/core/src/auth/auth.ts +++ b/packages/core/src/auth/auth.ts @@ -23,17 +23,10 @@ import { CredentialsProviderManager } from './providers/credentialsProviderManag import { asString, CredentialsId, CredentialsProvider, fromString } from './providers/credentials' import { keyedDebounce, once } from '../shared/utilities/functionUtils' import { CredentialsSettings } from './credentials/utils' -import { - extractDataFromSection, - getSectionOrThrow, - loadSharedCredentialsSections, -} from './credentials/sharedCredentials' -import { getCodeCatalystDevEnvId } from '../shared/vscode/env' import { getEnvironmentSpecificMemento } from '../shared/utilities/mementos' import { SsoCredentialsProvider } from './providers/ssoCredentialsProvider' import { AsyncCollection, toCollection } from '../shared/utilities/asyncCollection' import { join, toStream } from '../shared/utilities/collectionUtils' -import { getConfigFilename } from './credentials/sharedCredentialsFile' import { SharedCredentialsKeys, StaticProfile, StaticProfileKeyErrorMessage } from './credentials/types' import { TempCredentialProvider } from './providers/tempCredentialsProvider' import { @@ -49,15 +42,11 @@ import { SsoProfile, StatefulConnection, StoredProfile, - scopesCodeCatalyst, - createBuilderIdProfile, - createSsoProfile, hasScopes, loadIamProfilesIntoStore, loadLinkedProfilesIntoStore, scopesSsoAccountAccess, AwsConnection, - scopesCodeWhispererCore, ProfileNotFoundError, isSsoConnection, } from './connection' @@ -586,7 +575,7 @@ export class Auth implements AuthService, ConnectionManager { } // XXX: never drop tokens in a dev environment, unless you are Amazon Q! - if (getCodeCatalystDevEnvId() === undefined || isAmazonQ()) { + if (isAmazonQ()) { // TODO: Require invalidateConnection() to require a reason and then pass it in to the following instead await provider.invalidate('explicitInvalidation') } @@ -746,54 +735,8 @@ export class Auth implements AuthService, ConnectionManager { return credentialsProvider } - // XXX: always read from the same location in a dev environment - // This detection is fuzzy if an sso-session section exists for any other reason. - private detectSsoSessionNameForCodeCatalyst = once((): string => { - try { - const configFile = getConfigFilename() - // `require('fs')` is workaround for web mode: - const contents: string = require('fs').readFileSync(configFile, 'utf-8') - const identifier = contents.match(/\[sso\-session (.*)\]/)?.[1] - if (!identifier) { - throw new ToolkitError('No sso-session name found in ~/.aws/config', { code: 'NoSsoSessionName' }) - } - - return identifier - } catch (err) { - const identifier = 'codecatalyst' - getLogger().warn(`auth: unable to get an sso session name, defaulting to "${identifier}": %s`, err) - return identifier - } - }) - - private createCodeCatalystDevEnvProfile = async (): Promise<[id: string, profile: SsoProfile]> => { - const identifier = this.detectSsoSessionNameForCodeCatalyst() - - const { sections } = await loadSharedCredentialsSections() - const { sso_region: region, sso_start_url: startUrl } = extractDataFromSection( - getSectionOrThrow(sections, identifier, 'sso-session') - ) - - if ([region, startUrl].some((prop) => typeof prop !== 'string')) { - throw new ToolkitError('sso-session data missing in ~/.aws/config', { code: 'NoSsoSession' }) - } - - return startUrl === builderIdStartUrl - ? [identifier, createBuilderIdProfile(scopesCodeCatalyst)] - : [identifier, createSsoProfile(startUrl, region, scopesCodeCatalyst)] - } - private getSsoTokenProvider(id: Connection['id'], profile: StoredProfile) { - // XXX: Use the token created by Dev Environments if and only if the profile is strictly - // for CodeCatalyst, as indicated by its set of scopes. A consequence of these semantics is - // that any profile will be coerced to use this token if that profile exclusively contains - // CodeCatalyst scopes. Similarly, if additional scopes are added to a profile, the profile - // no longer matches this condition. - const shouldUseSoftwareStatement = - getCodeCatalystDevEnvId() !== undefined && - profile.scopes?.every((scope) => scopesCodeCatalyst.includes(scope)) - - const tokenIdentifier = shouldUseSoftwareStatement ? this.detectSsoSessionNameForCodeCatalyst() : id + const tokenIdentifier = id return this.createSsoTokenProvider( { @@ -1012,33 +955,6 @@ export class Auth implements AuthService, ConnectionManager { }) ) - // When opening a Dev Environment, use the environment token if no other CodeCatalyst - // credential is in use. This token only has CC permissions currently! - if (!isAmazonQ() && getCodeCatalystDevEnvId() !== undefined) { - const connections = await this.listConnections() - const shouldInsertDevEnvCredential = !connections.some( - (c) => c.type === 'sso' && hasScopes(c, scopesCodeCatalyst) && !hasScopes(c, scopesCodeWhispererCore) - ) - - if (shouldInsertDevEnvCredential) { - // Insert a profile based on the `~/.aws/config` sso-session: - try { - // After creating a CodeCatalyst Dev Environment profile based on sso-session, - // we discard the actual key, and we insert the profile with an arbitrary key. - // The cache key for any strictly-CodeCatalyst profile is overriden to the - // sso-session identifier on read. If the coerce-on-read semantics ever become - // an issue, it should be possible to use the actual key here However, this - // would require deleting existing profiles to avoid inserting duplicates. - const [_dangerousDiscardActualKey, devEnvProfile] = await this.createCodeCatalystDevEnvProfile() - const key = randomUUID() - await this.store.addProfile(key, devEnvProfile) - await this.store.setCurrentProfileId(key) - } catch (err) { - getLogger().warn(`auth: failed to insert dev env profile: %s`, err) - } - } - } - const conn = await this.restorePreviousSession() if (conn !== undefined) { return diff --git a/packages/core/src/auth/connection.ts b/packages/core/src/auth/connection.ts index fea929fc8a..7e2fbe3c71 100644 --- a/packages/core/src/auth/connection.ts +++ b/packages/core/src/auth/connection.ts @@ -25,7 +25,6 @@ const warnOnce = onceChanged((s: string, url: string) => { }) // TODO: Refactor all scopes to a central file with minimal dependencies. -export const scopesCodeCatalyst = ['codecatalyst:read_write'] export const scopesSsoAccountAccess = ['sso:account:access'] /** These are the non-chat scopes for CW. */ export const scopesCodeWhispererCore = ['codewhisperer:completions', 'codewhisperer:analysis'] @@ -68,9 +67,6 @@ export const isAnySsoConnection = (conn?: Connection): conn is SsoConnection => export const isIdcSsoConnection = (conn?: Connection): conn is SsoConnection => isSsoConnection(conn, 'idc') export const isBuilderIdConnection = (conn?: Connection): conn is SsoConnection => isSsoConnection(conn, 'builderId') -export const isValidCodeCatalystConnection = (conn?: Connection): conn is SsoConnection => - isSsoConnection(conn) && hasScopes(conn, scopesCodeCatalyst) - export const areCredentialsEqual = (creds1: any, creds2: any): boolean => { if (!creds1 || !creds2) { return creds1 === creds2 diff --git a/packages/core/src/auth/utils.ts b/packages/core/src/auth/utils.ts index 28e2bc1123..aef5c1b6c4 100644 --- a/packages/core/src/auth/utils.ts +++ b/packages/core/src/auth/utils.ts @@ -36,7 +36,6 @@ import { isIdcSsoConnection, isBuilderIdConnection, isIamConnection, - isValidCodeCatalystConnection, hasScopes, scopesSsoAccountAccess, isSsoConnection, @@ -650,7 +649,7 @@ export async function hasIamCredentials( return (await allConnections()).some(isIamConnection) } -export type SsoKind = 'any' | 'codewhisperer' | 'codecatalyst' +export type SsoKind = 'any' | 'codewhisperer' /** * Returns true if an Identity Center SSO connection exists. @@ -676,18 +675,13 @@ export async function findSsoConnections( return isIdcSsoConnection(conn) && isValidCodeWhispererCoreConnection(conn) } break - case 'codecatalyst': - predicate = (conn?: Connection) => { - return isIdcSsoConnection(conn) && isValidCodeCatalystConnection(conn) - } - break case 'any': predicate = isIdcSsoConnection } return (await allConnections()).filter(predicate).filter(isIdcSsoConnection) } -export type BuilderIdKind = 'any' | 'codewhisperer' | 'codecatalyst' +export type BuilderIdKind = 'any' | 'codewhisperer' /** * Returns true if a Builder ID connection exists. @@ -713,11 +707,6 @@ async function findBuilderIdConnections( return isBuilderIdConnection(conn) && isValidCodeWhispererCoreConnection(conn) } break - case 'codecatalyst': - predicate = (conn?: Connection) => { - return isBuilderIdConnection(conn) && isValidCodeCatalystConnection(conn) - } - break case 'any': predicate = isBuilderIdConnection } @@ -865,9 +854,6 @@ export function getAuthFormIdsFromConnection(conn?: Connection): AuthFormId[] { return ['unknown'] } - if (isValidCodeCatalystConnection(conn)) { - authIds.push(`${connType}CodeCatalyst`) - } if (isValidAmazonQConnection(conn)) { authIds.push(`${connType}CodeWhisperer`) } diff --git a/packages/core/src/awsService/accessanalyzer/activation.ts b/packages/core/src/awsService/accessanalyzer/activation.ts deleted file mode 100644 index 6c8d7a74bd..0000000000 --- a/packages/core/src/awsService/accessanalyzer/activation.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ExtContext } from '../../shared/extensions' -import { Commands } from '../../shared/vscode/commands2' -import { renderIamPolicyChecks } from './vue/iamPolicyChecks' - -/** - * Activate Policy Checks functionality for the extension. - */ -export async function activate(extContext: ExtContext): Promise { - const extensionContext = extContext.extensionContext - - extensionContext.subscriptions.push( - Commands.register('aws.accessanalyzer.iamPolicyChecks', async () => await renderIamPolicyChecks(extContext)) - ) -} diff --git a/packages/core/src/awsService/accessanalyzer/vue/constants.ts b/packages/core/src/awsService/accessanalyzer/vue/constants.ts deleted file mode 100644 index 2c0ddb0585..0000000000 --- a/packages/core/src/awsService/accessanalyzer/vue/constants.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export abstract class IamPolicyChecksConstants { - static readonly ValidatePolicySuccessWithFindings = - 'Review the findings for your policy document in the problems panel. We recommend that you update your policy document and re-run the policy checks until no findings are generated.' - static readonly ValidatePolicySuccessNoFindings = 'Policy checks did not generate any findings for your policy.' - static readonly CustomCheckSuccessWithFindings = - 'Result: FAIL. Review the details for the check failure for your policy document in the problems panel. We recommend that you update your policy document and re-run the policy check until the check returns a PASS result.' - static readonly CustomCheckSuccessNoFindings = 'Result: PASS.' - static readonly CheckNoNewAccessFilePathSetting = 'aws.accessAnalyzer.policyChecks.checkNoNewAccessFilePath' - static readonly CheckAccessNotGrantedFilePathSetting = - 'aws.accessAnalyzer.policyChecks.checkAccessNotGrantedFilePath' - static readonly CfnParameterFilePathSetting = 'aws.accessAnalyzer.policyChecks.cloudFormationParameterFilePath' - static readonly MissingReferenceDocError = 'Reference document is missing.' - static readonly IncorrectFileExtension = - 'The file extension does not match the selected document type. Please select the correct document type, or use the proper file extension.' - static readonly InvalidAwsCredentials = - 'AWS Role Credentials are invalid or expired. Update AWS credentials and try again.' - static readonly MissingActionsOrResourcesError = 'Please provide at least one of actions or resources.' -} - -export type PolicyChecksErrorCode = 'FileReadError' | 'ValidatePolicyError' | 'CustomChecksError' - -export type PolicyChecksDocumentType = 'Terraform Plan' | 'CloudFormation' | 'JSON Policy Language' - -export type PolicyChecksCheckType = 'CheckNoNewAccess' | 'CheckAccessNotGranted' | 'CheckNoPublicAccess' - -export type PolicyChecksPolicyType = 'Identity' | 'Resource' - -export type PolicyChecksResult = 'Success' | 'Warning' | 'Error' - -export type PolicyChecksUiClick = - | 'accessanalyzer_selectDocumentType' - | 'accessanalyzer_selectInputPolicyType' - | 'accessanalyzer_selectReferencePolicyType' - | 'accessanalyzer_selectCustomCheckType' - | 'accessanalyzer_selectCheckAccessNotGrantedFilePath' - | 'accessanalyzer_selectCheckNoNewAccessFilePath' - | 'accessanalyzer_selectCfnParameterFilePath' - | 'accessanalyzer_runValidatePolicy' - | 'accessanalyzer_runCustomPolicyCheck' diff --git a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts b/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts deleted file mode 100644 index 42521e73ec..0000000000 --- a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts +++ /dev/null @@ -1,970 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as fs from 'fs' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import { getLogger, Logger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { VueWebview, VueWebviewPanel } from '../../../webviews/main' -import { ExtContext } from '../../../shared/extensions' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { AccessAnalyzerClient, ValidatePolicyCommand } from '@aws-sdk/client-accessanalyzer' -import { fromIni } from '@aws-sdk/credential-providers' -import { ToolkitError } from '../../../shared/errors' -import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities' -import globals from '../../../shared/extensionGlobals' -import { - IamPolicyChecksConstants, - PolicyChecksCheckType, - PolicyChecksDocumentType, - PolicyChecksErrorCode, - PolicyChecksPolicyType, - PolicyChecksResult, - PolicyChecksUiClick, -} from './constants' -import { S3Client, parseS3Uri } from '../../../shared/clients/s3' -import { ExpiredTokenException } from '@aws-sdk/client-sso-oidc' -import { ChildProcess } from '../../../shared/utilities/processUtils' - -const defaultTerraformConfigPath = 'resources/policychecks-tf-default.yaml' -// Diagnostics for Custom checks are shared -export const customPolicyCheckDiagnosticCollection = vscode.languages.createDiagnosticCollection('customPolicyCheck') -export const validatePolicyDiagnosticCollection = vscode.languages.createDiagnosticCollection('validatePolicy') - -export interface IamPolicyChecksInitialData { - checkNoNewAccessFilePath: string - checkNoNewAccessTextArea: string - checkAccessNotGrantedFilePath: string - checkAccessNotGrantedActionsTextArea: string - checkAccessNotGrantedResourcesTextArea: string - customChecksFileErrorMessage: string - cfnParameterPath: string - pythonToolsInstalled: boolean -} - -type PolicyCommandOpts = { - command: string - args: string[] - cfnParameterPathExists: boolean - documentType: PolicyChecksDocumentType -} - -export class IamPolicyChecksWebview extends VueWebview { - public static readonly sourcePath: string = 'src/awsService/accessanalyzer/vue/index.js' - public readonly id = 'iamPolicyChecks' - private static editedDocumentUri: vscode.Uri - public static editedDocumentFileName: string - private static editedDocument: string - - public constructor( - private readonly data: IamPolicyChecksInitialData, - private client: AccessAnalyzerClient, - private readonly region: string, - public readonly onChangeInputPath = new vscode.EventEmitter(), - public readonly onChangeCheckNoNewAccessFilePath = new vscode.EventEmitter(), - public readonly onChangeCheckAccessNotGrantedFilePath = new vscode.EventEmitter(), - public readonly onChangeCloudformationParameterFilePath = new vscode.EventEmitter(), - public readonly onValidatePolicyResponse = new vscode.EventEmitter<[string, string]>(), - public readonly onCustomPolicyCheckResponse = new vscode.EventEmitter<[string, string]>(), - public readonly onFileReadError = new vscode.EventEmitter() - ) { - super(IamPolicyChecksWebview.sourcePath) - this._setActiveTextEditorListener() - this._setActiveConfigurationListener() - if (vscode.window.activeTextEditor) { - IamPolicyChecksWebview.editedDocumentFileName = vscode.window.activeTextEditor.document.uri.path - IamPolicyChecksWebview.editedDocument = vscode.window.activeTextEditor.document.getText() - IamPolicyChecksWebview.editedDocumentUri = vscode.window.activeTextEditor.document.uri - } - } - - public init(): typeof this.data { - return this.data - } - - public async readCustomChecksFile(path: string): Promise { - try { - const customChecksTextArea = await _readCustomChecksFile(path) - this.onFileReadError.fire('') // Reset error message display if no error is found - return customChecksTextArea - } catch (err: any) { - if (err instanceof PolicyChecksError && (err.code as PolicyChecksErrorCode) === 'FileReadError') { - this.onFileReadError.fire(err.message) - } - return '' - } - } - - public async readCustomChecksJsonFile(path: string): Promise { - try { - const rawString = await _readCustomChecksFile(path) - this.onFileReadError.fire('') // Reset error message display if no error is found - return JSON.parse(rawString) - } catch (err: any) { - if (err instanceof PolicyChecksError && (err.code as PolicyChecksErrorCode) === 'FileReadError') { - this.onFileReadError.fire(err.message) - } else if (err instanceof SyntaxError) { - this.onFileReadError.fire(`JSON Parse Error: ${err.message}`) - } else { - this.onFileReadError.fire(`Unexpected Error: ${err.message}`) - } - } - return {} - } - - public _setActiveTextEditorListener() { - // Send the current active text editor to Webview to show what is being targeted by the user - vscode.window.onDidChangeActiveTextEditor((message: any) => { - const editedFile = vscode.window.activeTextEditor?.document - if (editedFile !== undefined) { - IamPolicyChecksWebview.editedDocumentFileName = editedFile.uri.path - IamPolicyChecksWebview.editedDocument = editedFile.getText() - IamPolicyChecksWebview.editedDocumentUri = editedFile.uri - this.onChangeInputPath.fire(editedFile.uri.path) - } - }) - vscode.workspace.onDidChangeTextDocument((message: any) => { - const editedFile = vscode.window.activeTextEditor?.document - if (editedFile !== undefined) { - IamPolicyChecksWebview.editedDocument = editedFile.getText() - } - }) - } - - public _setActiveConfigurationListener() { - vscode.workspace.onDidChangeConfiguration((config: vscode.ConfigurationChangeEvent) => { - // If settings change, we want to update the Webview to reflect the change in inputs - if (config.affectsConfiguration(IamPolicyChecksConstants.CheckNoNewAccessFilePathSetting)) { - this.onChangeCheckNoNewAccessFilePath.fire( - vscode.workspace.getConfiguration().get(IamPolicyChecksConstants.CheckNoNewAccessFilePathSetting)! - ) - } else if (config.affectsConfiguration(IamPolicyChecksConstants.CheckAccessNotGrantedFilePathSetting)) { - this.onChangeCheckAccessNotGrantedFilePath.fire( - vscode.workspace - .getConfiguration() - .get(IamPolicyChecksConstants.CheckAccessNotGrantedFilePathSetting)! - ) - } else if (config.affectsConfiguration(IamPolicyChecksConstants.CfnParameterFilePathSetting)) { - this.onChangeCloudformationParameterFilePath.fire( - vscode.workspace.getConfiguration().get(IamPolicyChecksConstants.CfnParameterFilePathSetting)! - ) - } - }) - } - - public emitUiClick(elementId: PolicyChecksUiClick) { - telemetry.ui_click.emit({ elementId }) - } - - /* Uses A2 SDK and Python CLI tools to call ValidatePolicy API - * Responses are exposed in the Webview, including errors - * Diagnostic objects are created to expose findings, which appear in the problems panel - */ - public async validatePolicy( - documentType: PolicyChecksDocumentType, - policyType: PolicyChecksPolicyType, - cfnParameterPath?: string - ) { - const document = IamPolicyChecksWebview.editedDocumentFileName - validatePolicyDiagnosticCollection.clear() - const diagnostics: vscode.Diagnostic[] = [] - switch (documentType) { - case 'JSON Policy Language': { - if (isJsonPolicyLanguage(document)) { - telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run((span) => { - span.record({ - documentType, - inputPolicyType: policyType ? policyType : 'None', - }) - this.client.config.credentials = fromIni({ - profile: `${getProfileName()}`, - }) // We need to detect changes in the user's credentials - this.client - .send( - new ValidatePolicyCommand({ - policyDocument: IamPolicyChecksWebview.editedDocument, - policyType: policyType === 'Identity' ? 'IDENTITY_POLICY' : 'RESOURCE_POLICY', - }) - ) - .then((data) => { - if (data.findings && data.findings.length > 0) { - span.record({ - findingsCount: data.findings.length, - }) - // eslint-disable-next-line unicorn/no-array-for-each - data.findings.forEach((finding) => { - const locationSpan = finding.locations?.[0].span - if ( - !locationSpan?.start?.line || - !locationSpan.start.offset || - !locationSpan.end?.line || - !locationSpan.end.offset - ) { - return - } - const message = `${finding.findingType}: ${finding.issueCode} - ${finding.findingDetails} Learn more: ${finding.learnMoreLink}` - if (finding.findingType === 'ERROR') { - diagnostics.push( - new vscode.Diagnostic( - new vscode.Range( - locationSpan.start.line, - locationSpan.start.offset, - locationSpan.end.line, - locationSpan.end.offset - ), - message, - vscode.DiagnosticSeverity.Error - ) - ) - validatePolicyDiagnosticCollection.set( - IamPolicyChecksWebview.editedDocumentUri, - diagnostics - ) - } else { - diagnostics.push( - new vscode.Diagnostic( - new vscode.Range( - locationSpan.start.line, - locationSpan.start.offset, - locationSpan.end.line, - locationSpan.end.offset - ), - message, - vscode.DiagnosticSeverity.Warning - ) - ) - validatePolicyDiagnosticCollection.set( - IamPolicyChecksWebview.editedDocumentUri, - diagnostics - ) - } - }) - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.ValidatePolicySuccessWithFindings, - getResultCssColor('Warning'), - ]) - void vscode.commands.executeCommand('workbench.actions.view.problems') - } else { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.ValidatePolicySuccessNoFindings, - getResultCssColor('Success'), - ]) - } - }) - .catch((err) => { - span.record({ - findingsCount: 0, - }) - if (err instanceof ExpiredTokenException) { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.InvalidAwsCredentials, - getResultCssColor('Error'), - ]) - } else { - this.onValidatePolicyResponse.fire([err.message, getResultCssColor('Error')]) - } - }) - }) - return - } else { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - case 'Terraform Plan': { - if (isTerraformPlan(document)) { - const command = 'tf-policy-validator' - const args = [ - 'validate', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - `${getProfileName()}`, - ] - await this.executeValidatePolicyCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - policyType, - }) - return - } else { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - case 'CloudFormation': { - if (isCloudFormationTemplate(document)) { - const command = 'cfn-policy-validator' - const args = [ - 'validate', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--profile', - `${getProfileName()}`, - ] - if (cfnParameterPath !== '') { - args.push('--template-configuration-file', `${cfnParameterPath}`) - } - await this.executeValidatePolicyCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - policyType, - }) - return - } else { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - } - } - - public async checkNoNewAccess( - documentType: PolicyChecksDocumentType, - policyType: PolicyChecksPolicyType, - referenceDocument: string, - cfnParameterPath?: string - ) { - const tempFolder = await makeTemporaryToolkitFolder() - const tempFilePath = path.join(tempFolder, 'policyChecksDocument') - - const document = IamPolicyChecksWebview.editedDocumentFileName - customPolicyCheckDiagnosticCollection.clear() - if (referenceDocument !== '') { - fs.writeFileSync(tempFilePath, referenceDocument) - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.MissingReferenceDocError, - getResultCssColor('Error'), - ]) - return - } - - switch (documentType) { - case 'Terraform Plan': { - if (isTerraformPlan(document)) { - const command = 'tf-policy-validator' - const args = [ - 'check-no-new-access', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--reference-policy', - `${tempFilePath}`, - '--reference-policy-type', - `${policyType}`, - '--profile', - `${getProfileName()}`, - ] - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoNewAccess', - referencePolicyType: policyType, - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - case 'CloudFormation': { - if (isCloudFormationTemplate(document)) { - const command = 'cfn-policy-validator' - const args = [ - 'check-no-new-access', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--reference-policy', - `${tempFilePath}`, - '--reference-policy-type', - `${policyType}`, - '--profile', - `${getProfileName()}`, - ] - if (cfnParameterPath !== '') { - args.push('--template-configuration-file', `${cfnParameterPath}`) - } - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoNewAccess', - referencePolicyType: policyType, - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - } - await tryRemoveFolder(tempFolder) - } - - public async checkAccessNotGranted( - documentType: PolicyChecksDocumentType, - actions: string, - resources: string, - cfnParameterPath?: string - ) { - const document = IamPolicyChecksWebview.editedDocumentFileName - customPolicyCheckDiagnosticCollection.clear() - if (actions !== '') { - // Remove spaces, line breaks, carriage returns, and tabs - actions = actions.replace(/\s*|\t|\r|\n/gm, '') - } - if (resources !== '') { - // Remove spaces, line breaks, carriage returns, and tabs - resources = resources.replace(/\s*|\t|\r|\n/gm, '') - } - if (!(actions || resources)) { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.MissingActionsOrResourcesError, - getResultCssColor('Error'), - ]) - return - } - switch (documentType) { - case 'Terraform Plan': { - if (isTerraformPlan(document)) { - const command = 'tf-policy-validator' - const args = [ - 'check-access-not-granted', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - `${getProfileName()}`, - ] - if (actions !== '') { - args.push('--actions', `${actions}`) - } - if (resources !== '') { - args.push('--resources', `${resources}`) - } - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckAccessNotGranted', - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - case 'CloudFormation': { - if (isCloudFormationTemplate(document)) { - const command = 'cfn-policy-validator' - const args = [ - 'check-access-not-granted', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--profile', - `${getProfileName()}`, - ] - if (actions !== '') { - args.push('--actions', `${actions}`) - } - if (resources !== '') { - args.push('--resources', `${resources}`) - } - if (cfnParameterPath !== '') { - args.push('--template-configuration-file', `${cfnParameterPath}`) - } - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckAccessNotGranted', - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - } - } - - public async checkNoPublicAccess(documentType: PolicyChecksDocumentType, cfnParameterPath?: string) { - const document = IamPolicyChecksWebview.editedDocumentFileName - customPolicyCheckDiagnosticCollection.clear() - - switch (documentType) { - case 'Terraform Plan': { - if (isTerraformPlan(document)) { - const command = 'tf-policy-validator' - const args = [ - 'check-no-public-access', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - `${getProfileName()}`, - ] - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoPublicAccess', - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - case 'CloudFormation': { - if (isCloudFormationTemplate(document)) { - const command = 'cfn-policy-validator' - const args = [ - 'check-no-public-access', - '--template-path', - `${document}`, - '--region', - `${this.region}`, - '--profile', - `${getProfileName()}`, - ] - if (cfnParameterPath !== '') { - args.push('--template-configuration-file', `${cfnParameterPath}`) - } - await this.executeCustomPolicyChecksCommand({ - command, - args, - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoPublicAccess', - }) - return - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - return - } - } - } - } - - public async executeValidatePolicyCommand(opts: PolicyCommandOpts & { policyType?: PolicyChecksPolicyType }) { - await telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run(async (span) => { - try { - span.record({ - cfnParameterFileUsed: opts.cfnParameterPathExists, - documentType: opts.documentType, - inputPolicyType: opts.policyType ?? 'None', - }) - const result = await ChildProcess.run(opts.command, opts.args, { collect: true }) - const findingsCount = this.handleValidatePolicyCliResponse(result.stdout) - span.record({ - findingsCount: findingsCount, - }) - } catch (err: any) { - if (err.status === 2) { - // CLI responds with a status code of 2 when findings are discovered - const findingsCount = this.handleValidatePolicyCliResponse(err.stdout.toString()) - span.record({ - findingsCount: findingsCount, - }) - } else { - span.record({ - findingsCount: 0, - }) - this.onValidatePolicyResponse.fire([ - parseCliErrorMessage(err.message, opts.documentType), - getResultCssColor('Error'), - ]) - } - } - }) - } - - public handleValidatePolicyCliResponse(response: string): number { - let findingsCount = 0 - const diagnostics: vscode.Diagnostic[] = [] - const jsonOutput = JSON.parse(response) - if (jsonOutput.BlockingFindings.length === 0 && jsonOutput.NonBlockingFindings.length === 0) { - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.ValidatePolicySuccessNoFindings, - getResultCssColor('Success'), - ]) - } else { - // eslint-disable-next-line unicorn/no-array-for-each - jsonOutput.BlockingFindings.forEach((finding: any) => { - this.pushValidatePolicyDiagnostic(diagnostics, finding, true) - findingsCount++ - }) - // eslint-disable-next-line unicorn/no-array-for-each - jsonOutput.NonBlockingFindings.forEach((finding: any) => { - this.pushValidatePolicyDiagnostic(diagnostics, finding, false) - findingsCount++ - }) - this.onValidatePolicyResponse.fire([ - IamPolicyChecksConstants.ValidatePolicySuccessWithFindings, - getResultCssColor('Warning'), - ]) - void vscode.commands.executeCommand('workbench.actions.view.problems') - } - return findingsCount - } - - public async executeCustomPolicyChecksCommand( - opts: PolicyCommandOpts & { checkType: PolicyChecksCheckType; referencePolicyType?: PolicyChecksPolicyType } - ) { - await telemetry.accessanalyzer_iamPolicyChecksCustomChecks.run(async (span) => { - try { - span.record({ - cfnParameterFileUsed: opts.cfnParameterPathExists, - checkType: opts.checkType, - documentType: opts.documentType, - inputPolicyType: 'None', // Note: This will change once JSON policy language is enabled for Custom policy checks - referencePolicyType: opts.referencePolicyType ?? 'None', - }) - const resp = await ChildProcess.run(opts.command, opts.args) - const findingsCount = this.handleCustomPolicyChecksCliResponse(resp.stdout) - span.record({ - findingsCount: findingsCount, - }) - } catch (err: any) { - if (err.status === 2) { - // CLI responds with a status code of 2 when findings are discovered - const findingsCount = this.handleCustomPolicyChecksCliResponse(err.stdout.toString()) - span.record({ - findingsCount: findingsCount, - }) - } else { - span.record({ - findingsCount: 0, - }) - this.onCustomPolicyCheckResponse.fire([ - parseCliErrorMessage(err.message, opts.documentType), - getResultCssColor('Error'), - ]) - } - } - }) - } - - public handleCustomPolicyChecksCliResponse(response: string): number { - let findingsCount = 0 - let errorMessage: string | undefined - const diagnostics: vscode.Diagnostic[] = [] - try { - const jsonOutput = JSON.parse(response) - if (jsonOutput.BlockingFindings.length === 0 && jsonOutput.NonBlockingFindings.length === 0) { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.CustomCheckSuccessNoFindings, - getResultCssColor('Success'), - ]) - } else { - // eslint-disable-next-line unicorn/no-array-for-each - jsonOutput.BlockingFindings.forEach((finding: any) => { - this.pushCustomCheckDiagnostic(diagnostics, finding, true) - errorMessage = getCheckNoNewAccessErrorMessage(finding) - findingsCount++ - }) - // eslint-disable-next-line unicorn/no-array-for-each - jsonOutput.NonBlockingFindings.forEach((finding: any) => { - this.pushCustomCheckDiagnostic(diagnostics, finding, false) - findingsCount++ - }) - if (errorMessage) { - this.onCustomPolicyCheckResponse.fire([errorMessage, getResultCssColor('Error')]) - } else { - this.onCustomPolicyCheckResponse.fire([ - IamPolicyChecksConstants.CustomCheckSuccessWithFindings, - getResultCssColor('Warning'), - ]) - } - void vscode.commands.executeCommand('workbench.actions.view.problems') - } - } catch (err: any) { - this.onCustomPolicyCheckResponse.fire([err.message, getResultCssColor('Error')]) - } - return findingsCount - } - - public pushValidatePolicyDiagnostic(diagnostics: vscode.Diagnostic[], finding: any, isBlocking: boolean) { - diagnostics.push( - new vscode.Diagnostic( - new vscode.Range(0, 0, 0, 0), - `${finding.findingType}: ${finding.code} - ${finding.details.findingDetails} Resource name: ${finding.resourceName}, Policy name: ${finding.policyName}. Learn more: ${finding.details.learnMoreLink}`, - isBlocking ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning - ) - ) - validatePolicyDiagnosticCollection.set(IamPolicyChecksWebview.editedDocumentUri, diagnostics) - } - - public pushCustomCheckDiagnostic(diagnostics: vscode.Diagnostic[], finding: any, isBlocking: boolean) { - const findingMessage: string = finding.message.includes('existingPolicyDocument') - ? finding.message.replace('existingPolicyDocument', 'reference document') - : finding.message - const message = `${finding.findingType}: ${findingMessage} - Resource name: ${finding.resourceName}, Policy name: ${finding.policyName}` - if (finding.details.reasons) { - // eslint-disable-next-line unicorn/no-array-for-each - finding.details.reasons.forEach((reason: any) => { - diagnostics.push( - new vscode.Diagnostic( - new vscode.Range(0, 0, 0, 0), - message + ` - ${reason.description}`, - isBlocking ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning - ) - ) - }) - } else { - diagnostics.push( - new vscode.Diagnostic( - new vscode.Range(0, 0, 0, 0), - message, - isBlocking ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Warning - ) - ) - } - customPolicyCheckDiagnosticCollection.set(IamPolicyChecksWebview.editedDocumentUri, diagnostics) - } -} - -const Panel = VueWebview.compilePanel(IamPolicyChecksWebview) - -export async function renderIamPolicyChecks(context: ExtContext): Promise { - const logger: Logger = getLogger() - try { - const client = new AccessAnalyzerClient({ region: context.regionProvider.defaultRegionId }) - // Read from settings to auto-fill some inputs - const checkNoNewAccessFilePath: string = vscode.workspace - .getConfiguration() - .get(IamPolicyChecksConstants.CheckNoNewAccessFilePathSetting)! - const checkAccessNotGrantedFilePath: string = vscode.workspace - .getConfiguration() - .get(IamPolicyChecksConstants.CheckAccessNotGrantedFilePathSetting)! - const cfnParameterPath: string = vscode.workspace - .getConfiguration() - .get(IamPolicyChecksConstants.CfnParameterFilePathSetting)! - let checkNoNewAccessTextArea: string = '' - let checkAccessNotGrantedActionsTextArea: string = '' - let checkAccessNotGrantedResourcesTextArea: string = '' - let customChecksFileErrorMessage: string = '' - try { - if (checkNoNewAccessFilePath) { - checkNoNewAccessTextArea = await _readCustomChecksFile(checkNoNewAccessFilePath) - } - if (checkAccessNotGrantedFilePath) { - // Parse JSON into actions & resources here - const accessJson = JSON.parse(await _readCustomChecksFile(checkAccessNotGrantedFilePath)) - checkAccessNotGrantedActionsTextArea = accessJson.actions || '' - checkAccessNotGrantedResourcesTextArea = accessJson.resources || '' - } - } catch (err: any) { - customChecksFileErrorMessage = err.message - } - - const wv = new Panel( - context.extensionContext, - { - checkNoNewAccessFilePath: checkNoNewAccessFilePath ? checkNoNewAccessFilePath : '', - checkNoNewAccessTextArea, - checkAccessNotGrantedFilePath: checkAccessNotGrantedFilePath ? checkAccessNotGrantedFilePath : '', - checkAccessNotGrantedActionsTextArea, - checkAccessNotGrantedResourcesTextArea, - customChecksFileErrorMessage, - cfnParameterPath: cfnParameterPath ? cfnParameterPath : '', - pythonToolsInstalled: await arePythonToolsInstalled(), - }, - client, - context.regionProvider.defaultRegionId - ) - await wv.show({ - viewColumn: vscode.ViewColumn.Beside, - title: localize('AWS.iamPolicyChecks.title', 'IAM Policy Checks'), - }) - return wv - } catch (err) { - logger.error(err as Error) - } -} - -// Helper function to get document contents from a path -export async function _readCustomChecksFile(input: string): Promise { - if (fs.existsSync(input)) { - return fs.readFileSync(input).toString() - } else { - try { - const [region, bucket, key] = parseS3Uri(input) - const s3Client = new S3Client(region) - const resp = await s3Client.getObject({ bucketName: bucket, key }) - // Lint warning: this may evaluate to '[object Object]'. @typescript-eslint/no-base-to-string - // eslint-disable-next-line @typescript-eslint/no-base-to-string - return resp.objectBody.toString() - } catch (e: any) { - if (e.message.includes('Invalid S3 URI')) { - throw new PolicyChecksError('Invalid file path or S3 URI', 'FileReadError') - } else { - throw new PolicyChecksError(e.message, 'FileReadError') - } - } - } -} - -// Check if Cfn and Tf tools are installed -export async function arePythonToolsInstalled(): Promise { - const logger: Logger = getLogger() - let cfnToolInstalled = true - let tfToolInstalled = true - try { - await ChildProcess.run('tf-policy-validator') - } catch (err: any) { - if (isProcessNotFoundErr(err.message)) { - tfToolInstalled = false - logger.error('Terraform Policy Validator is not found') - } - } - try { - await ChildProcess.run('cfn-policy-validator') - } catch (err: any) { - if (isProcessNotFoundErr(err.message)) { - cfnToolInstalled = false - logger.error('Cloudformation Policy Validator is not found') - } - } - return cfnToolInstalled && tfToolInstalled -} - -export function isProcessNotFoundErr(errMsg: string) { - return errMsg.includes('command not found') || errMsg.includes('ENOENT') -} - -// Since TypeScript can only get the CLI tool's error output as a string, we have to parse and sanitize it ourselves -export function parseCliErrorMessage(message: string, documentType: PolicyChecksDocumentType): string { - const cfnMatch = message.match(/ERROR: .*/) - const botoMatch = message.match(/(?<=botocore\.exceptions\.).*/) // Boto errors have a special match - const terraformMatch = message.match(/AttributeError:.*/) // Terraform CLI responds with a different error schema... this catches invalid .json plans - if ( - message.includes('The security token included in the request is invalid') || - message.includes('The security token included in the request is expired') - ) { - return IamPolicyChecksConstants.InvalidAwsCredentials - } else if (message.includes(`Unexpected error occurred. 'AccessAnalyzer' object has no attribute 'check`)) { - return 'ERROR: The dependencies of the Python CLI tools are outdated.' - } else if (cfnMatch?.[0]) { - return cfnMatch[0] - } else if (botoMatch?.[0]) { - return botoMatch[0] - } else if (isProcessNotFoundErr(message)) { - return `Command not found, please install the ${documentType} Python CLI tool` - } else if (terraformMatch?.[0]) { - return 'ERROR: Unable to parse Terraform plan. Invalid Terraform plan schema detected.' - } else if (message.includes('Unexpected end of JSON input')) { - return 'ERROR: Unexpected input. Please enter a list of AWS actions, separated by commas.' - } - return message -} - -export function getCheckNoNewAccessErrorMessage(finding: any) { - if (finding.findingType === 'ERROR') { - if ( - finding.message.includes( - 'The policy in existingPolicyDocument is invalid. Principal is a prohibited policy element.' - ) - ) { - return "ERROR: The policy in reference document is invalid. Principal is a prohibited policy element. Review the reference document's policy type and try again." - } - } -} - -export function getResultCssColor(resultType: PolicyChecksResult): string { - switch (resultType) { - case 'Success': - return 'var(--vscode-terminal-ansiGreen)' - case 'Warning': - return 'var(--vscode-debugConsole-warningForeground)' - case 'Error': - return 'var(--vscode-errorForeground)' - } -} - -export function isCloudFormationTemplate(document: string): boolean { - const cfnFileTypes = ['.yaml', '.yml', '.json'] - return cfnFileTypes.some((t) => document.endsWith(t)) -} - -export function isTerraformPlan(document: string) { - const terraformPlanFileTypes = ['.json'] - return terraformPlanFileTypes.some((t) => document.endsWith(t)) -} - -export function isJsonPolicyLanguage(document: string) { - const policyLanguageFileTypes = ['.json'] - return policyLanguageFileTypes.some((t) => document.endsWith(t)) -} - -export function getProfileName(): string | undefined { - // We neeed to split the name on 'profile:' to extract the correct profile name - return globals.awsContext.getCredentialProfileName()?.split('profile:')[1] -} - -export class PolicyChecksError extends ToolkitError { - constructor(message: string, code: PolicyChecksErrorCode) { - super(message, { code }) - } -} diff --git a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.vue b/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.vue deleted file mode 100644 index 866e177eea..0000000000 --- a/packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.vue +++ /dev/null @@ -1,510 +0,0 @@ -/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - - - - diff --git a/packages/core/src/awsService/accessanalyzer/vue/index.ts b/packages/core/src/awsService/accessanalyzer/vue/index.ts deleted file mode 100644 index 153c86daa6..0000000000 --- a/packages/core/src/awsService/accessanalyzer/vue/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createApp } from 'vue' -import component from './iamPolicyChecks.vue' - -const create = () => createApp(component) -const app = create() -app.mount('#vue-app') - -window.addEventListener('remount', () => { - app.unmount() - create().mount('#vue-app') -}) diff --git a/packages/core/src/awsService/apigateway/activation.ts b/packages/core/src/awsService/apigateway/activation.ts deleted file mode 100644 index 78add0d3e6..0000000000 --- a/packages/core/src/awsService/apigateway/activation.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { RestApiNode } from './explorer/apiNodes' -import { invokeRemoteRestApi } from './vue/invokeRemoteRestApi' -import { copyUrlCommand } from './commands/copyUrl' -import { ExtContext } from '../../shared/extensions' -import { Commands } from '../../shared/vscode/commands2' -import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import { getSourceNode } from '../../shared/utilities/treeNodeUtils' - -/** - * Activate API Gateway functionality for the extension. - */ -export async function activate(activateArguments: { - extContext: ExtContext - outputChannel: vscode.OutputChannel -}): Promise { - const extensionContext = activateArguments.extContext.extensionContext - const regionProvider = activateArguments.extContext.regionProvider - extensionContext.subscriptions.push( - Commands.register('aws.apig.copyUrl', async (node: RestApiNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await copyUrlCommand(sourceNode, regionProvider) - }), - Commands.register('aws.apig.invokeRemoteRestApi', async (node: RestApiNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await invokeRemoteRestApi(activateArguments.extContext, { - apiNode: sourceNode, - outputChannel: activateArguments.outputChannel, - }) - }) - ) -} diff --git a/packages/core/src/awsService/apigateway/commands/copyUrl.ts b/packages/core/src/awsService/apigateway/commands/copyUrl.ts deleted file mode 100644 index 7c19ffa925..0000000000 --- a/packages/core/src/awsService/apigateway/commands/copyUrl.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { copyToClipboard } from '../../../shared/utilities/messages' -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() -import { RestApiNode } from '../explorer/apiNodes' -import * as picker from '../../../shared/ui/picker' -import * as vscode from 'vscode' -import { ProgressLocation } from 'vscode' - -import { Stage } from '@aws-sdk/client-api-gateway' -import { ApiGatewayClient } from '../../../shared/clients/apiGateway' -import { defaultDnsSuffix, RegionProvider } from '../../../shared/regions/regionProvider' -import { getLogger } from '../../../shared/logger/logger' -import { telemetry } from '../../../shared/telemetry/telemetry' - -interface StageInvokeUrlQuickPick extends vscode.QuickPickItem { - // override declaration so this can't be undefined - detail: string -} - -export async function copyUrlCommand(node: RestApiNode, regionProvider: RegionProvider): Promise { - const region = node.regionCode - const dnsSuffix = regionProvider.getDnsSuffixForRegion(region) || defaultDnsSuffix - const client = new ApiGatewayClient(region) - - let stages: Stage[] - try { - stages = await vscode.window.withProgress( - { - cancellable: false, - location: ProgressLocation.Window, - }, - async (progress) => { - progress.report({ - message: localize('AWS.apig.loadingStages', 'Loading stage list for API: {0}', node.name), - }) - return (await client.getStages(node.id)).item || [] - } - ) - } catch (e) { - getLogger().error(`Failed to load stages: %s`, e) - telemetry.apigateway_copyUrl.emit({ result: 'Failed' }) - return - } - - const quickPickItems = stages.map((stage) => ({ - label: stage.stageName!, - detail: buildDefaultApiInvokeUrl(node.id, region, dnsSuffix, stage.stageName!), - })) - - if (quickPickItems.length === 0) { - void vscode.window.showInformationMessage( - localize('AWS.apig.copyUrlNoStages', "Failed to copy URL because '{0}' has no stages", node.name) - ) - telemetry.apigateway_copyUrl.emit({ result: 'Failed' }) - return - } else if (quickPickItems.length === 1) { - const url = quickPickItems[0].detail - await copyToClipboard(url, 'URL') - telemetry.apigateway_copyUrl.emit({ result: 'Succeeded' }) - return - } - - const quickPick = picker.createQuickPick({ - options: { - ignoreFocusOut: true, - title: localize('AWS.apig.selectStage', 'Select an API stage'), - }, - items: quickPickItems, - }) - - const choices = await picker.promptUser({ - picker: quickPick, - onDidTriggerButton: (button, resolve, reject) => { - if (button === vscode.QuickInputButtons.Back) { - resolve(undefined) - } - }, - }) - const pickerResponse = picker.verifySinglePickerOutput(choices) - - if (!pickerResponse) { - telemetry.apigateway_copyUrl.emit({ result: 'Cancelled' }) - return - } - - const url = pickerResponse.detail - await copyToClipboard(url, 'URL') - telemetry.apigateway_copyUrl.emit({ result: 'Succeeded' }) -} - -export function buildDefaultApiInvokeUrl(apiId: string, region: string, dnsSuffix: string, stage: string): string { - return `https://${apiId}.execute-api.${region}.${dnsSuffix}/${stage}` -} diff --git a/packages/core/src/awsService/apigateway/explorer/apiGatewayNodes.ts b/packages/core/src/awsService/apigateway/explorer/apiGatewayNodes.ts deleted file mode 100644 index c23b6cb972..0000000000 --- a/packages/core/src/awsService/apigateway/explorer/apiGatewayNodes.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' - -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { compareTreeItems, makeChildrenNodes } from '../../../shared/treeview/utils' -import { ApiGatewayClient } from '../../../shared/clients/apiGateway' -import { RestApi } from '@aws-sdk/client-api-gateway' -import { toArrayAsync, toMap, updateInPlace } from '../../../shared/utilities/collectionUtils' -import { RestApiNode } from './apiNodes' - -/** - * An AWS Explorer node representing the API Gateway (v1) service. - */ -export class ApiGatewayNode extends AWSTreeNodeBase { - private readonly apiNodes: Map - - public constructor( - private readonly partitionId: string, - public override readonly regionCode: string, - private readonly client = new ApiGatewayClient(regionCode) - ) { - super('API Gateway', vscode.TreeItemCollapsibleState.Collapsed) - this.apiNodes = new Map() - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.apiNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.apigateway.noApis', '[No API Gateway REST APIs found]') - ), - sort: (nodeA, nodeB) => compareTreeItems(nodeA, nodeB), - }) - } - - public async updateChildren(): Promise { - const apis: Map = toMap( - await toArrayAsync(this.client.listApis()), - (configuration) => `${configuration.name} (${configuration.id})` - ) - - updateInPlace( - this.apiNodes, - apis.keys(), - (key) => this.apiNodes.get(key)!.update(apis.get(key)!), - (key) => new RestApiNode(this, this.partitionId, this.regionCode, apis.get(key)!) - ) - } -} diff --git a/packages/core/src/awsService/apigateway/explorer/apiNodes.ts b/packages/core/src/awsService/apigateway/explorer/apiNodes.ts deleted file mode 100644 index 02280a7436..0000000000 --- a/packages/core/src/awsService/apigateway/explorer/apiNodes.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { RestApi } from '@aws-sdk/client-api-gateway' - -export class RestApiNode extends AWSTreeNodeBase implements AWSResourceNode { - public override id!: string - - public constructor( - public readonly parent: AWSTreeNodeBase, - public readonly partitionId: string, - public override readonly regionCode: string, - private api: RestApi - ) { - super('') - this.update(api) - this.contextValue = 'awsApiGatewayNode' - } - - public update(api: RestApi): void { - this.api = api - this.label = `${this.api.name} (${this.api.id})` || '' - this.tooltip = this.api.description - - if (api.id === undefined) { - throw new Error('REST API id expected but not found') - } - - this.id = api.id - } - - public get name(): string { - if (this.api.name === undefined) { - throw new Error('REST API name expected but not found') - } - - return this.api.name - } - - public get arn(): string { - return `arn:${this.partitionId}:apigateway:${this.regionCode}::/apis/${this.api.id}` - } -} diff --git a/packages/core/src/awsService/apigateway/vue/index.ts b/packages/core/src/awsService/apigateway/vue/index.ts deleted file mode 100644 index fad0b0d6ce..0000000000 --- a/packages/core/src/awsService/apigateway/vue/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createApp } from 'vue' -import component from './invokeApi.vue' - -const create = () => createApp(component) -const app = create() -app.mount('#vue-app') - -window.addEventListener('remount', () => { - app.unmount() - create().mount('#vue-app') -}) diff --git a/packages/core/src/awsService/apigateway/vue/invokeApi.vue b/packages/core/src/awsService/apigateway/vue/invokeApi.vue deleted file mode 100644 index 8588109d55..0000000000 --- a/packages/core/src/awsService/apigateway/vue/invokeApi.vue +++ /dev/null @@ -1,121 +0,0 @@ -/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - - - - diff --git a/packages/core/src/awsService/apigateway/vue/invokeRemoteRestApi.ts b/packages/core/src/awsService/apigateway/vue/invokeRemoteRestApi.ts deleted file mode 100644 index 4e8389c89b..0000000000 --- a/packages/core/src/awsService/apigateway/vue/invokeRemoteRestApi.ts +++ /dev/null @@ -1,166 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { RestApiNode } from '../explorer/apiNodes' -import { getLogger, Logger } from '../../../shared/logger/logger' - -import { toArrayAsync } from '../../../shared/utilities/collectionUtils' -import { Resource } from '@aws-sdk/client-api-gateway' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { Result } from '../../../shared/telemetry/telemetry' -import { VueWebview } from '../../../webviews/main' -import { ExtContext } from '../../../shared/extensions' -import { ApiGatewayClient } from '../../../shared/clients/apiGateway' -import { telemetry } from '../../../shared/telemetry/telemetry' - -interface InvokeApiMessage { - region: string - body: string - selectedApiResource: Resource - selectedMethod: string - queryString: string - api: string -} - -interface ResourceMap { - [key: string]: Resource -} - -export interface InvokeRemoteRestApiInitialData { - ApiName: string - ApiId: string - ApiArn: string - Resources: ResourceMap - Region: string - localizedMessages: { - noApiResource: string - noMethod: string - } -} - -export class RemoteRestInvokeWebview extends VueWebview { - public static readonly sourcePath: string = 'src/awsService/apigateway/vue/index.js' - public readonly id = 'remoteInvoke' - - private readonly logger = getLogger() - - public constructor( - private readonly data: InvokeRemoteRestApiInitialData, - private readonly channel: vscode.OutputChannel, - private readonly client = new ApiGatewayClient(data.Region) - ) { - super(RemoteRestInvokeWebview.sourcePath) - } - - public init(): typeof this.data { - return this.data - } - - public listValidMethods(resource: Resource): string[] { - return listValidMethods(resource) - } - - public async invokeApi(message: InvokeApiMessage): Promise { - let result: Result = 'Succeeded' - - this.logger.info('Invoking API Gateway resource:') - this.logger.info(String(message.body)) - - this.channel.show() - this.channel.appendLine('Loading response...') - - const path = message.selectedApiResource.path - const pathWithQueryString = path && message.queryString ? `${path}?${message.queryString}` : undefined - try { - const response = await this.client.testInvokeMethod( - message.api, - message.selectedApiResource.id!, - message.selectedMethod, - message.body, - pathWithQueryString - ) - - this.channel.appendLine(response.log!) - this.channel.appendLine('') - this.channel.appendLine(`Request returned status: ${response.status}:`) - this.channel.appendLine(response.body!) - } catch (e) { - const error = e as Error - result = 'Failed' - this.channel.appendLine(`There was an error invoking`) - this.channel.appendLine(error.toString()) - this.channel.appendLine('') - } finally { - // only set method if it is not empty or undefined - const method = message.selectedMethod ? message.selectedMethod.toUpperCase() : undefined - telemetry.apigateway_invokeRemote.emit({ - result: result, - httpMethod: method, - }) - } - - return result - } -} - -const Panel = VueWebview.compilePanel(RemoteRestInvokeWebview) - -export async function invokeRemoteRestApi( - context: ExtContext, - params: { outputChannel: vscode.OutputChannel; apiNode: RestApiNode } -): Promise { - const logger: Logger = getLogger() - - try { - const client = new ApiGatewayClient(params.apiNode.regionCode) - logger.info(`Loading API Resources for API ${params.apiNode.name} (id: ${params.apiNode.id})`) - const resources = (await toArrayAsync(client.getResourcesForApi(params.apiNode.id))) - .sort((a, b) => a.path!.localeCompare(b.path!)) - .reduce<{ [key: string]: Resource }>((prev, curr) => { - return { - ...prev, - [curr.id!]: curr, - } - }, {}) - logger.debug(`Loaded: %O`, resources) - - // something is wrong if the paths aren't defined... - // const sortResources = (a: [string, Resource], b: [string, Resource]) => a[1].path!.localeCompare(b[1].path!) - const wv = new Panel( - context.extensionContext, - { - ApiName: params.apiNode.name, - ApiId: params.apiNode.id, - ApiArn: params.apiNode.arn, - Resources: resources, - Region: params.apiNode.regionCode, - localizedMessages: { - noApiResource: localize('AWS.apig.remoteInvoke.noApiResource', 'Select an API Resource'), - noMethod: localize('AWS.apig.remoteInvoke.noMethod', 'Select a HTTP method'), - }, - }, - context.outputChannel - ) - - await wv.show({ title: localize('AWS.invokeApi.title', 'Invoke Remote API') }) - } catch (err) { - logger.error(err as Error) - } -} - -export function listValidMethods(resource: Resource): string[] { - // OpenAPI 2 (swagger) valid methods - const supportedOperations = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'] - - // you can simultaneously declare a resource that supports ANY in conjunction with conventional methods - const isAny = (method: string) => method.toUpperCase() === 'ANY' - const methods = resource.resourceMethods !== undefined ? Object.keys(resource.resourceMethods) : [] - if (methods.some(isAny)) { - return supportedOperations - } - - return methods.sort() -} diff --git a/packages/core/src/awsService/appBuilder/activation.ts b/packages/core/src/awsService/appBuilder/activation.ts deleted file mode 100644 index 93cf111944..0000000000 --- a/packages/core/src/awsService/appBuilder/activation.ts +++ /dev/null @@ -1,228 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import globals from '../../shared/extensionGlobals' -import { ExtContext } from '../../shared/extensions' -import { Commands, VsCodeCommandArg } from '../../shared/vscode/commands2' -import { ToolView } from '../../awsexplorer/toolView' -import { telemetry } from '../../shared/telemetry/telemetry' -import { activateViewsShared, registerToolView } from '../../awsexplorer/activationShared' -import { setContext } from '../../shared/vscode/setContext' -import { fs } from '../../shared/fs/fs' -import { AppBuilderRootNode } from './explorer/nodes/rootNode' -import { - initWalkthroughProjectCommand, - walkthroughContextString, - getOrInstallCliWrapper, - installLocalStackExtension, -} from './walkthrough' -import { getLogger } from '../../shared/logger/logger' -import path from 'path' -import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import { runBuild } from '../../shared/sam/build' -import { runOpenHandler, runOpenTemplate } from './utils' -import { ResourceNode } from './explorer/nodes/resourceNode' -import { getSyncWizard, runSync } from '../../shared/sam/sync' -import { getDeployWizard, runDeploy } from '../../shared/sam/deploy' -import { DeployTypeWizard } from './wizards/deployTypeWizard' -import { createNewServerlessLandProject } from './serverlessLand/main' -import { lambdaToSam } from './lambda2sam/lambda2sam' -export const templateToOpenAppComposer = 'aws.toolkit.appComposer.templateToOpenOnStart' - -/** - * Activates the AWS Explorer UI and related functionality. - * - * IMPORTANT: Views that should work in all vscode environments (node or web) - * should be setup in {@link activateViewsShared}. - */ -export async function activate(context: ExtContext): Promise { - // recover context variables from global state when activate - const walkthroughSelected = globals.globalState.get(walkthroughContextString) - if (walkthroughSelected !== undefined) { - await setContext(walkthroughContextString, walkthroughSelected) - } - - await registerAppBuilderCommands(context) - - const appBuilderNode: ToolView[] = [ - { - nodes: [AppBuilderRootNode.instance], - view: 'aws.appBuilder', - refreshCommands: [AppBuilderRootNode.instance.refreshAppBuilderExplorer], - }, - { - nodes: [AppBuilderRootNode.instance], - view: 'aws.appBuilderForFileExplorer', - refreshCommands: [AppBuilderRootNode.instance.refreshAppBuilderForFileExplorer], - }, - ] - - const watcher = vscode.workspace.createFileSystemWatcher('**/{template.yaml,template.yml,samconfig.toml}') - watcher.onDidChange(async (uri) => runRefreshAppBuilder(uri, 'changed')) - watcher.onDidCreate(async (uri) => runRefreshAppBuilder(uri, 'created')) - watcher.onDidDelete(async (uri) => runRefreshAppBuilder(uri, 'deleted')) - - for (const viewNode of appBuilderNode) { - registerToolView(viewNode, context.extensionContext) - } - - await openApplicationComposerAfterReload() -} - -async function runRefreshAppBuilder(uri: vscode.Uri, event: string) { - getLogger().debug(`${uri.fsPath} ${event}, refreshing appBuilder`) - await vscode.commands.executeCommand('aws.appBuilderForFileExplorer.refresh') - await vscode.commands.executeCommand('aws.appBuilder.refresh') -} - -/** - * To support open template in AppComposer after extension reload. - * This typically happens when user create project from walkthrough - * and added a new folder to an empty workspace. - * - * Checkes templateToOpenAppComposer in global and opens template - * Directly return if templateToOpenAppComposer is undefined - */ -export async function openApplicationComposerAfterReload(): Promise { - const templatesToOpen = globals.globalState.get<[string]>(templateToOpenAppComposer) - // undefined - if (!templatesToOpen) { - return - } - - for (const template of templatesToOpen) { - const templateUri = vscode.Uri.file(template) - const templateFolder = vscode.Uri.file(path.dirname(template)) - const basename = path.basename(template) - // ignore templates that doesn't belong to current workspace, ignore if not template - if ( - !vscode.workspace.getWorkspaceFolder(templateFolder) || - (basename !== 'template.yaml' && basename !== 'template.yml') - ) { - continue - } - - await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') - await vscode.commands.executeCommand('aws.openInApplicationComposer', templateUri) - - if (await fs.exists(vscode.Uri.joinPath(templateFolder, 'README.md'))) { - await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') - await vscode.commands.executeCommand( - 'markdown.showPreview', - vscode.Uri.joinPath(templateFolder, 'README.md') - ) - } - } - // set to undefined - await globals.globalState.update(templateToOpenAppComposer, undefined) -} - -async function setWalkthrough(walkthroughSelected: string = 'S3'): Promise { - await setContext(walkthroughContextString, walkthroughSelected) - await globals.globalState.update(walkthroughContextString, walkthroughSelected) -} - -/** - * - * @param context VScode Context - */ -async function registerAppBuilderCommands(context: ExtContext): Promise { - const source = 'AppBuilderWalkthrough' - context.extensionContext.subscriptions.push( - Commands.register({ id: 'aws.toolkit.lambda.convertToSam', autoconnect: true }, async (lambdaNode) => { - await telemetry.appbuilder_lambda2sam.run(async () => { - telemetry.record({ source: 'explorer' }) - await lambdaToSam(lambdaNode) - }) - }), - Commands.register('aws.toolkit.installSAMCLI', async () => { - await getOrInstallCliWrapper('sam-cli', source) - }), - Commands.register('aws.toolkit.installAWSCLI', async () => { - await getOrInstallCliWrapper('aws-cli', source) - }), - Commands.register('aws.toolkit.installDocker', async () => { - await getOrInstallCliWrapper('docker', source) - }), - Commands.register('aws.toolkit.installLocalStack', async () => { - await installLocalStackExtension(source) - }), - Commands.register('aws.toolkit.installFinch', async () => { - await getOrInstallCliWrapper('finch', source) - }), - Commands.register('aws.toolkit.lambda.setWalkthroughToAPI', async () => { - await setWalkthrough('API') - }), - Commands.register('aws.toolkit.lambda.setWalkthroughToS3', async () => { - await setWalkthrough('S3') - }), - Commands.register('aws.toolkit.lambda.setWalkthroughToVisual', async () => { - await setWalkthrough('Visual') - }), - Commands.register('aws.toolkit.lambda.setWalkthroughToCustomTemplate', async () => { - await setWalkthrough('CustomTemplate') - }), - Commands.register('aws.toolkit.lambda.initializeWalkthroughProject', async (): Promise => { - await telemetry.appBuilder_selectWalkthroughTemplate.run(async () => await initWalkthroughProjectCommand()) - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', true) - }), - Commands.register('aws.toolkit.lambda.walkthrough.credential', async (): Promise => { - await vscode.commands.executeCommand('aws.toolkit.auth.manageConnections', source) - }), - Commands.register( - { id: `aws.toolkit.lambda.openWalkthrough`, compositeKey: { 1: 'source' } }, - async (_: VsCodeCommandArg, source?: string) => { - telemetry.appBuilder_startWalkthrough.emit({ source: source }) - await vscode.commands.executeCommand( - 'workbench.action.openWalkthrough', - 'amazonwebservices.aws-toolkit-vscode#aws.toolkit.lambda.walkthrough' - ) - } - ), - Commands.register( - { - id: 'aws.appBuilder.build', - autoconnect: false, - }, - async (arg?: TreeNode | undefined) => await telemetry.sam_build.run(async () => await runBuild(arg)) - ), - Commands.register({ id: 'aws.appBuilder.openTemplate', autoconnect: false }, async (arg: TreeNode) => - telemetry.appBuilder_openTemplate.run(async (span) => { - if (arg) { - span.record({ source: 'AppBuilderOpenTemplate' }) - } else { - span.record({ source: 'commandPalette' }) - } - await runOpenTemplate(arg) - }) - ), - Commands.register({ id: 'aws.appBuilder.openHandler', autoconnect: false }, async (arg: ResourceNode) => - telemetry.lambda_goToHandler.run(async (span) => { - span.record({ source: 'AppBuilderOpenHandler' }) - await runOpenHandler(arg) - }) - ), - Commands.register({ id: 'aws.appBuilder.deploy', autoconnect: true }, async (arg) => { - const wizard = new DeployTypeWizard( - await getSyncWizard('infra', arg, undefined, false), - await getDeployWizard(arg, false) - ) - const choices = await wizard.run() - if (choices) { - if (choices.choice === 'deploy' && choices.deployParam) { - await runDeploy(arg, choices.deployParam) - } else if (choices.choice === 'sync' && choices.syncParam) { - await runSync('infra', arg, undefined, choices.syncParam) - } - } - }), - Commands.register({ id: 'aws.toolkit.lambda.createServerlessLandProject', autoconnect: false }, async () => { - await telemetry.lambda_createServerlessLandProject.run(async () => { - await createNewServerlessLandProject(context) - }) - }) - ) -} diff --git a/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts b/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts deleted file mode 100644 index 511217481b..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/detectSamProjects.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SamAppLocation } from './samProject' -import { getLogger } from '../../../shared/logger/logger' -import { getProjectRootUri } from '../../../shared/sam/utils' - -export async function detectSamProjects(): Promise { - const workspaceFolders = vscode.workspace.workspaceFolders - - if (!workspaceFolders) { - return [] - } - - const results = new Map() - const projects = (await Promise.all(workspaceFolders.map(detectSamProjectsFromWorkspaceFolder))).reduce( - (a, b) => a.concat(b), - [] - ) - - for (const p of projects) { - results.set(p.samTemplateUri.toString(), p) - } - - return Array.from(results.values()) -} - -async function detectSamProjectsFromWorkspaceFolder( - workspaceFolder: vscode.WorkspaceFolder -): Promise { - const result: SamAppLocation[] = [] - const samTemplateFiles = await getFiles(workspaceFolder, '**/template.{yml,yaml}', '**/.aws-sam/**') - for (const samTemplateFile of samTemplateFiles) { - const project = { - samTemplateUri: samTemplateFile, - workspaceFolder: workspaceFolder, - projectRoot: getProjectRootUri(samTemplateFile), - } - result.push(project) - } - return result -} - -export async function getFiles( - workspaceFolder: vscode.WorkspaceFolder, - pattern: string, - buildArtifactFolderPattern?: string -): Promise { - try { - const globPattern = new vscode.RelativePattern(workspaceFolder, pattern) - const excludePattern = buildArtifactFolderPattern - ? new vscode.RelativePattern(workspaceFolder, buildArtifactFolderPattern) - : undefined - - return await vscode.workspace.findFiles(globPattern, excludePattern) - } catch (error) { - getLogger().error(`Failed to find files with pattern ${pattern}:`, error) - return [] - } -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts deleted file mode 100644 index b9c00d59c7..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/appNode.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { getLogger } from '../../../../shared/logger/logger' -import { ResourceTreeEntity, SamAppLocation, getApp, getStackName } from '../samProject' -import { ResourceNode, generateResourceNodes } from './resourceNode' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { createPlaceholderItem } from '../../../../shared/treeview/utils' -import { getIcon } from '../../../../shared/icons' -import { getSamCliContext } from '../../../../shared/sam/cli/samCliContext' -import { SamCliListResourcesParameters } from '../../../../shared/sam/cli/samCliListResources' -import { getDeployedResources, StackResource } from '../../../../lambda/commands/listSamResources' -import * as path from 'path' -import { generateStackNode } from './deployedStack' - -export class AppNode implements TreeNode { - public readonly id = this.location.samTemplateUri.toString() - public readonly resource = this.location - public readonly label = path.join( - this.location.workspaceFolder.name, - path.relative(this.location.workspaceFolder.uri.fsPath, path.dirname(this.location.samTemplateUri.fsPath)) - ) - private stackName: string = '' - public constructor(private readonly location: SamAppLocation) {} - - public async getChildren(): Promise<(ResourceNode | TreeNode)[]> { - const resources = [] - try { - const successfulApp = await getApp(this.location) - const templateResources: ResourceTreeEntity[] = successfulApp.resourceTree - const { stackName, region } = await getStackName(this.location.projectRoot) - this.stackName = stackName - - const listStackResourcesArguments: SamCliListResourcesParameters = { - stackName: this.stackName, - templateFile: this.location.samTemplateUri.fsPath, - region: region, - projectRoot: this.location.projectRoot, - } - - const deployedResources: StackResource[] | undefined = this.stackName - ? await getDeployedResources({ - listResourcesParams: listStackResourcesArguments, - invoker: getSamCliContext().invoker, - }) - : undefined - // Skip generating stack node if stack does not exist in region or other errors - if (deployedResources && deployedResources.length > 0) { - resources.push(...(await generateStackNode(this.stackName, region))) - } - resources.push( - ...generateResourceNodes(this.location, templateResources, this.stackName, region, deployedResources) - ) - - // indicate that App exists, but it is empty - if (resources.length === 0) { - return [ - createPlaceholderItem( - localize('AWS.appBuilder.explorerNode.app.noResource', '[No resource found in SAM template]') - ), - ] - } - return resources - } catch (error) { - getLogger().error(`Could not load the construct tree located at '${this.id}': %O`, error as Error) - return [ - createPlaceholderItem( - localize( - 'AWS.appBuilder.explorerNode.app.noResourceTree', - '[Unable to load resource tree for this app. Ensure SAM template is correct.]' - ) - ), - ] - } - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.Collapsed) - - item.contextValue = 'awsAppBuilderAppNode' - item.iconPath = getIcon('vscode-folder') - item.resourceUri = this.location.samTemplateUri - item.tooltip = this.location.samTemplateUri.path - - return item - } -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts deleted file mode 100644 index d135185e71..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedNode.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getIcon } from '../../../../shared/icons' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { createPlaceholderItem } from '../../../../shared/treeview/utils' -import * as nls from 'vscode-nls' - -import { getLogger } from '../../../../shared/logger/logger' -import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import globals from '../../../../shared/extensionGlobals' -import { defaultPartition } from '../../../../shared/regions/regionProvider' -import { FunctionConfiguration } from '@aws-sdk/client-lambda' -import { RestApi } from '@aws-sdk/client-api-gateway' -import { LambdaNode } from '../../../../lambda/explorer/lambdaNodes' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import { S3Client, toBucket } from '../../../../shared/clients/s3' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { ApiGatewayNode } from '../../../../awsService/apigateway/explorer/apiGatewayNodes' -import { RestApiNode } from '../../../../awsService/apigateway/explorer/apiNodes' -import { - SERVERLESS_FUNCTION_TYPE, - SERVERLESS_API_TYPE, - s3BucketType, - SERVERLESS_CAPACITY_PROVIDER_TYPE, -} from '../../../../shared/cloudformation/cloudformation' -import { ToolkitError } from '../../../../shared/errors' -import { ResourceTreeEntity, isFunctionResource } from '../samProject' -import { LambdaCapacityProviderNode } from '../../../../lambda/explorer/lambdaCapacityProviderNode' - -const localize = nls.loadMessageBundle() -export interface DeployedResource { - stackName: string - regionCode: string - explorerNode: any - arn: string - contextValue: string -} - -export const DeployedResourceContextValues: Record = { - [SERVERLESS_FUNCTION_TYPE]: 'awsRegionFunctionNodeDownloadable', - [SERVERLESS_API_TYPE]: 'awsApiGatewayNode', - [s3BucketType]: 'awsS3BucketNode', - [SERVERLESS_CAPACITY_PROVIDER_TYPE]: 'awsCapacityProviderNode', -} - -export class DeployedResourceNode implements TreeNode { - public readonly id: string - public readonly contextValue: string - - public constructor(public readonly resource: DeployedResource) { - if (this.resource.arn) { - this.id = this.resource.arn - this.contextValue = this.resource.contextValue - } else { - getLogger().warn('Cannot create DeployedResourceNode, the ARN does not exist.') - this.id = '' - this.contextValue = '' - } - } - - public async getChildren(): Promise { - return [] - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.id) - - item.contextValue = this.contextValue - item.iconPath = getIcon('vscode-cloud') - item.collapsibleState = vscode.TreeItemCollapsibleState.None - item.tooltip = this.resource.arn - return item - } -} - -export async function generateDeployedNode( - deployedResource: any, - regionCode: string, - stackName: string, - resourceTreeEntity: ResourceTreeEntity, - location?: vscode.Uri -): Promise { - let newDeployedResource: any - const partitionId = globals.regionProvider.getPartitionId(regionCode) ?? defaultPartition - try { - switch (resourceTreeEntity.Type) { - case SERVERLESS_FUNCTION_TYPE: { - const defaultClient = new DefaultLambdaClient(regionCode) - const lambdaNode = new LambdaNode(regionCode, defaultClient) - let configuration: FunctionConfiguration - try { - configuration = (await defaultClient.getFunction(deployedResource.PhysicalResourceId)) - .Configuration as FunctionConfiguration - const codeUri = isFunctionResource(resourceTreeEntity) ? resourceTreeEntity.CodeUri : undefined - newDeployedResource = new LambdaFunctionNode( - lambdaNode, - regionCode, - configuration, - undefined, - location ? vscode.Uri.joinPath(location, codeUri ?? '').fsPath : undefined, - location, - deployedResource.LogicalResourceId - ) - } catch (error: any) { - getLogger().error('Error getting Lambda configuration: %O', error) - throw ToolkitError.chain(error, 'Error getting Lambda configuration', { - code: 'lambdaClientError', - }) - } - break - } - case s3BucketType: { - const s3Client = new S3Client(regionCode) - const s3Node = new S3Node(s3Client) - const s3Bucket = toBucket(deployedResource.PhysicalResourceId, regionCode, partitionId) - newDeployedResource = new S3BucketNode(s3Bucket, s3Node, s3Client) - break - } - case SERVERLESS_API_TYPE: { - const apiParentNode = new ApiGatewayNode(partitionId, regionCode) - const apiNodes = await apiParentNode.getChildren() - const apiNode = apiNodes.find((node) => node.id === deployedResource.PhysicalResourceId) - newDeployedResource = new RestApiNode(apiParentNode, partitionId, regionCode, apiNode as RestApi) - break - } - case SERVERLESS_CAPACITY_PROVIDER_TYPE: { - newDeployedResource = new LambdaCapacityProviderNode(regionCode, deployedResource) - break - } - default: - newDeployedResource = new DeployedResourceNode(deployedResource) - getLogger().info('Details are missing or are incomplete for: %O', deployedResource) - return [ - createPlaceholderItem( - localize( - 'AWS.appBuilder.explorerNode.noApps', - '[This resource is not yet supported in AppBuilder.]' - ) - ), - ] - } - } catch (error: any) { - void vscode.window.showErrorMessage(error.messages) - return [ - createPlaceholderItem( - localize( - 'AWS.appBuilder.explorerNode.unavailableDeployedResource', - '[Failed to retrieve deployed resource. Ensure correct stack name and region are in the samconfig.toml, and that your account is connected.]' - ) - ), - ] - } - newDeployedResource.contextValue = DeployedResourceContextValues[resourceTreeEntity.Type] - const finalDeployedResource = { - stackName, - regionCode, - explorerNode: newDeployedResource, - arn: newDeployedResource.arn, - contextValue: newDeployedResource.contextValue, - } - return [new DeployedResourceNode(finalDeployedResource)] -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts deleted file mode 100644 index 3a533c722f..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/deployedStack.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { getIcon } from '../../../../shared/icons' -import { CloudFormationClient, DescribeStacksCommand, CloudFormationClientConfig } from '@aws-sdk/client-cloudformation' -import { ToolkitError } from '../../../../shared/errors' -import { getIAMConnection } from '../../../../auth/utils' -import globals from '../../../../shared/extensionGlobals' - -export class StackNameNode implements TreeNode { - public readonly id = this.stackName - public readonly resource = this - public arn: string | undefined - public readonly link = `command:aws.explorer.cloudformation.showStack?${encodeURIComponent(JSON.stringify({ stackName: this.stackName, region: this.regionCode }))}` - - public constructor( - public stackName: string, - public regionCode: string - ) { - this.stackName = stackName - this.regionCode = regionCode - } - - public async getChildren(): Promise { - // This stack name node is a leaf node that does not have any children. - return [] - } - public get value(): string { - return `Stack: ${this.stackName} (${this.regionCode})` - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.value) - - item.contextValue = 'awsAppBuilderStackNode' - item.iconPath = getIcon('vscode-cloud') - return item - } -} - -export async function generateStackNode(stackName?: string, regionCode?: string): Promise { - const connection = await getIAMConnection({ prompt: false }) - if (!connection || connection.type !== 'iam') { - return [] - } - const cred = await connection.getCredentials() - const endpointUrl = globals.awsContext.getCredentialEndpointUrl() - const opts: CloudFormationClientConfig = { region: regionCode, credentials: cred } - if (endpointUrl !== undefined) { - opts.endpoint = endpointUrl - } - const client = new CloudFormationClient(opts) - try { - const command = new DescribeStacksCommand({ StackName: stackName }) - const response = await client.send(command) - if (response.Stacks && response.Stacks[0]) { - const stackArn = response.Stacks[0].StackId - if (stackName === undefined || regionCode === undefined) { - return [] - } - const stackNode = new StackNameNode(stackName || '', regionCode || '') - stackNode.arn = stackArn - return [stackNode] - } - } catch (error) { - throw new ToolkitError(`Failed to generate stack node ${stackName} in region ${regionCode}: ${error}`) - } - return [] -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts deleted file mode 100644 index e6eb92ff22..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/propertyNode.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getIcon } from '../../../../shared/icons' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' - -/** - * Formats CloudFormation intrinsic functions into readable strings - */ -function formatIntrinsicFunction(value: any): string | undefined { - if (typeof value !== 'object' || value === null || Object.keys(value).length !== 1) { - return undefined - } - return JSON.stringify(value) -} - -export class PropertyNode implements TreeNode { - public readonly id = this.key - public readonly resource = this.value - - public constructor( - private readonly key: string, - private readonly value: unknown - ) {} - - public async getChildren(): Promise { - if (this.value instanceof Array || this.value instanceof Object) { - return generatePropertyNodes(this.value) - } else { - return [] - } - } - - public getTreeItem() { - const intrinsicFormat = formatIntrinsicFunction(this.value) - const displayValue = intrinsicFormat ?? this.value - - const item = new vscode.TreeItem(`${this.key}: ${displayValue}`) - - item.contextValue = 'awsAppBuilderPropertyNode' - item.iconPath = getIcon('vscode-gear') - - if (!intrinsicFormat && (this.value instanceof Array || this.value instanceof Object)) { - item.label = this.key - item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - } - - return item - } -} - -export function generatePropertyNodes(properties: { [key: string]: any }): TreeNode[] { - return Object.entries(properties) - .filter(([key, value]) => key !== 'Id' && key !== 'Type' && key !== 'Events' && value !== undefined) - .map(([key, value]) => new PropertyNode(key, value)) -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts deleted file mode 100644 index 134302b041..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/resourceNode.ts +++ /dev/null @@ -1,185 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { IconPath, getIcon } from '../../../../shared/icons' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { ResourceTreeEntity, SamAppLocation } from '../samProject' -import { - SERVERLESS_FUNCTION_TYPE, - s3BucketType, - appRunnerType, - ecrRepositoryType, - SERVERLESS_CAPACITY_PROVIDER_TYPE, -} from '../../../../shared/cloudformation/cloudformation' -import { generatePropertyNodes } from './propertyNode' -import { generateDeployedNode } from './deployedNode' -import { StackResource } from '../../../../lambda/commands/listSamResources' -import { DeployedResourceNode } from './deployedNode' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import { ToolkitError } from '../../../../shared/errors' - -enum ResourceTypeId { - Function = 'function', - DeployedFunction = 'deployed-function', - Api = 'api', - CapacityProvider = 'capacityprovider', - Other = '', -} - -export async function generateLambdaNodeFromResource(resource: ResourceNode['resource']): Promise { - if (!resource.deployedResource || !resource.region || !resource.stackName || !resource.resource) { - throw new ToolkitError('Error getting Lambda info from Appbuilder Node, please check your connection') - } - const nodes = (await generateDeployedNode( - resource.deployedResource, - resource.region, - resource.stackName, - resource.resource, - resource.projectRoot - )) as DeployedResourceNode[] - if (nodes.length !== 1) { - throw new ToolkitError('Error getting Lambda info from Appbuilder Node, please check your connection') - } - // lambda function node or undefined - return nodes[0].resource?.explorerNode -} - -// from here, we should have a helper function to detect if lambda is deployed -// then return deployed node/normal node on each condition. -export class ResourceNode implements TreeNode { - public readonly id = this.resourceTreeEntity.Id - private readonly type = this.resourceTreeEntity.Type - public readonly resourceLogicalId = this.deployedResource?.LogicalResourceId - - public constructor( - private readonly location: SamAppLocation, - private readonly resourceTreeEntity: ResourceTreeEntity, - private readonly stackName?: string, - private readonly region?: string, - private readonly deployedResource?: StackResource, - // TODO: cleanup or rename functionArn parameter as type can be differ from Lambda; value never set in generateResourceNodes() - private readonly functionArn?: string - ) {} - - public get resource() { - return { - resource: this.resourceTreeEntity, - location: this.location.samTemplateUri, - projectRoot: this.location.projectRoot, - workspaceFolder: this.location.workspaceFolder, - region: this.region, - stackName: this.stackName, - deployedResource: this.deployedResource, - functionArn: this.functionArn, - } - } - - public async getChildren() { - let deployedNodes: DeployedResourceNode[] = [] - let propertyNodes: TreeNode[] = [] - - if (this.deployedResource && this.region && this.stackName) { - deployedNodes = (await generateDeployedNode( - this.deployedResource, - this.region, - this.stackName, - this.resourceTreeEntity, - this.location.projectRoot - )) as DeployedResourceNode[] - } - if ( - this.resourceTreeEntity.Type === SERVERLESS_FUNCTION_TYPE || - this.resourceTreeEntity.Type === SERVERLESS_CAPACITY_PROVIDER_TYPE - ) { - propertyNodes = generatePropertyNodes(this.resourceTreeEntity) - } - - return [...propertyNodes, ...deployedNodes] - } - - public getTreeItem(): vscode.TreeItem { - const collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - - // Create the TreeItem with the determined collapsible state - const item = new vscode.TreeItem(this.resourceTreeEntity.Id, collapsibleState) - - // Set the tooltip to the URI of the SAM template - item.tooltip = this.location.samTemplateUri.toString() - - item.iconPath = this.getIconPath() - - // Set the resource URI to the SAM template URI - item.resourceUri = this.location.samTemplateUri - - // Define the context value for the item - item.contextValue = `awsAppBuilderResourceNode.${this.getResourceId()}` - - return item - } - - // Additional resources and corresponding icons will be added in the future. - // When adding support for new resources, ensure that each new resource - // has an appropriate mapping in place. - private getIconPath(): IconPath | undefined { - switch (this.type) { - case SERVERLESS_FUNCTION_TYPE: - if (this.deployedResource) { - return getIcon('aws-lambda-deployed-function') - } - return getIcon('aws-lambda-function') - // add deployed lambda function type - case s3BucketType: - return getIcon('aws-s3-bucket') - case appRunnerType: - return getIcon('aws-apprunner-service') - case ecrRepositoryType: - return getIcon('aws-ecr-registry') - case SERVERLESS_CAPACITY_PROVIDER_TYPE: - return getIcon('vscode-gear') - default: - return getIcon('vscode-info') - } - } - - private getResourceId(): ResourceTypeId { - switch (this.type) { - case SERVERLESS_FUNCTION_TYPE: - if (this.deployedResource) { - return ResourceTypeId.DeployedFunction - } - return ResourceTypeId.Function - case 'Api': - return ResourceTypeId.Api - case SERVERLESS_CAPACITY_PROVIDER_TYPE: - return ResourceTypeId.CapacityProvider - default: - return ResourceTypeId.Other - } - } -} - -export function generateResourceNodes( - app: SamAppLocation, - resources: NonNullable, - stackName?: string, - region?: string, - deployedResources?: StackResource[] -): ResourceNode[] { - if (!deployedResources) { - return resources.map((resource) => new ResourceNode(app, resource, stackName, region)) - } - - return resources.map((resource) => { - if (resource.Type) { - const deployedResource = deployedResources.find( - (deployedResource) => resource.Id === deployedResource.LogicalResourceId - ) - return new ResourceNode(app, resource, stackName, region, deployedResource) - } else { - return new ResourceNode(app, resource, stackName, region) - } - }) -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts deleted file mode 100644 index 941ad8ec0f..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/rootNode.ts +++ /dev/null @@ -1,110 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { debugNewSamAppDocUrl } from '../../../../shared/constants' -import { telemetry } from '../../../../shared/telemetry/telemetry' -import { ResourceTreeDataProvider, TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { createPlaceholderItem } from '../../../../shared/treeview/utils' -import { localize, openUrl } from '../../../../shared/utilities/vsCodeUtils' -import { Commands } from '../../../../shared/vscode/commands2' -import { AppNode } from './appNode' -import { detectSamProjects } from '../detectSamProjects' -import globals from '../../../../shared/extensionGlobals' -import { WalkthroughNode } from './walkthroughNode' - -export async function getAppNodes(): Promise { - // no active workspace, show buttons in welcomeview - if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { - return [] - } - - const appsFound = await detectSamProjects() - - if (appsFound.length === 0) { - return [ - createPlaceholderItem( - localize('AWS.appBuilder.explorerNode.noApps', '[No SAM templates found in Workspaces]') - ), - ] - } - - const nodesToReturn: TreeNode[] = appsFound - .map((appLocation) => new AppNode(appLocation)) - .sort((a, b) => a.label.localeCompare(b.label) ?? 0) - - return nodesToReturn -} - -export class AppBuilderRootNode implements TreeNode { - public readonly id = 'appBuilder' - public readonly resource = this - private readonly onDidChangeChildrenEmitter = new vscode.EventEmitter() - public readonly onDidChangeChildren = this.onDidChangeChildrenEmitter.event - private readonly _refreshAppBuilderExplorer - private readonly _refreshAppBuilderForFileExplorer - - constructor() { - Commands.register('aws.appBuilder.viewDocs', () => { - void openUrl(debugNewSamAppDocUrl.toolkit) - telemetry.aws_help.emit({ name: 'appBuilder' }) - }) - this._refreshAppBuilderExplorer = (provider?: ResourceTreeDataProvider) => - Commands.register('aws.appBuilder.refresh', () => { - this.refresh() - if (provider) { - provider.refresh() - } - }) - - this._refreshAppBuilderForFileExplorer = (provider?: ResourceTreeDataProvider) => - Commands.register('aws.appBuilderForFileExplorer.refresh', () => { - this.refresh() - if (provider) { - provider.refresh() - } - }) - } - - public get refreshAppBuilderExplorer() { - return this._refreshAppBuilderExplorer - } - - public get refreshAppBuilderForFileExplorer() { - return this._refreshAppBuilderForFileExplorer - } - - public async getChildren() { - const nodesToReturn = await getAppNodes() - if (nodesToReturn.length === 0) { - return [] - } - - const walkthroughCompleted = globals.globalState.get('aws.toolkit.lambda.walkthroughCompleted') - // show walkthrough node if walkthrough not completed yet - if (!walkthroughCompleted) { - nodesToReturn.unshift(new WalkthroughNode()) - } - return nodesToReturn - } - - public refresh(): void { - this.onDidChangeChildrenEmitter.fire() - } - - public getTreeItem() { - const item = new vscode.TreeItem('APPLICATION BUILDER') - item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - item.contextValue = 'awsAppBuilderRootNode' - - return item - } - - static #instance: AppBuilderRootNode - - static get instance(): AppBuilderRootNode { - return (this.#instance ??= new AppBuilderRootNode()) - } -} diff --git a/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts b/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts deleted file mode 100644 index 8f3432075d..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/nodes/walkthroughNode.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { localize } from '../../../../shared/utilities/vsCodeUtils' - -/** - * Create Open Walkthrough Node in App builder sidebar - * - */ -export class WalkthroughNode implements TreeNode { - public readonly id = 'walkthrough' - public readonly resource: WalkthroughNode = this - - // Constructor left empty intentionally for future extensibility - public constructor() {} - - /** - * Generates the TreeItem for the Walkthrough Node. - * This item will appear in the sidebar with a label and command to open the walkthrough. - */ - public getTreeItem() { - const itemLabel = localize('AWS.appBuilder.openWalkthroughTitle', 'Walkthrough of Application Builder') - - const item = new vscode.TreeItem(itemLabel) - item.contextValue = 'awsWalkthroughNode' - item.command = { - title: localize('AWS.appBuilder.openWalkthroughTitle', 'Walkthrough of Application Builder'), - command: 'aws.toolkit.lambda.openWalkthrough', - } - - return item - } -} diff --git a/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts b/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts deleted file mode 100644 index c29bdeffce..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/openTemplate.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { samSyncUrl } from '../../../shared/constants' -import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' -import { syncMementoRootKey } from '../../../shared/sam/constants' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -import { createTemplatePrompter, TemplateItem } from '../../../shared/ui/sam/templatePrompter' -import { Wizard } from '../../../shared/wizards/wizard' - -export interface OpenTemplateParams { - readonly template: TemplateItem -} - -export class OpenTemplateWizard extends Wizard { - public constructor(state: Partial, registry: CloudFormationTemplateRegistry) { - super({ initState: state, exitPrompterProvider: createExitPrompter }) - this.form.template.bindPrompter(() => createTemplatePrompter(registry, syncMementoRootKey, samSyncUrl)) - } -} diff --git a/packages/core/src/awsService/appBuilder/explorer/samProject.ts b/packages/core/src/awsService/appBuilder/explorer/samProject.ts deleted file mode 100644 index eedf19ccdc..0000000000 --- a/packages/core/src/awsService/appBuilder/explorer/samProject.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { SamConfig, SamConfigErrorCode } from '../../../shared/sam/config' -import { getLogger } from '../../../shared/logger/logger' -import { ToolkitError } from '../../../shared/errors' -import { showViewLogsMessage } from '../../../shared/utilities/messages' - -export interface SamApp { - location: SamAppLocation - resourceTree: ResourceTreeEntity[] -} - -export interface SamAppLocation { - samTemplateUri: vscode.Uri - workspaceFolder: vscode.WorkspaceFolder - projectRoot: vscode.Uri -} - -export interface BaseResourceEntity { - Id: string - Type: string -} - -export interface EventEntity extends BaseResourceEntity { - Path?: string - Method?: string -} - -export interface FunctionResourceEntity extends BaseResourceEntity { - Runtime?: string - CodeUri?: string - Handler?: string - Events?: EventEntity[] - Environment?: { - Variables: Record - } - CapacityProviderConfig?: string - Architectures?: string -} - -export interface CapacityProviderResourceEntity extends BaseResourceEntity { - Architectures?: string -} - -export type ResourceTreeEntity = FunctionResourceEntity | CapacityProviderResourceEntity | BaseResourceEntity - -export function isFunctionResource(resource: ResourceTreeEntity): resource is FunctionResourceEntity { - return resource.Type === CloudFormation.SERVERLESS_FUNCTION_TYPE -} - -export function isCapacityProviderResource(resource: ResourceTreeEntity): resource is CapacityProviderResourceEntity { - return resource.Type === CloudFormation.SERVERLESS_CAPACITY_PROVIDER_TYPE -} - -export async function getStackName(projectRoot: vscode.Uri): Promise { - try { - const samConfig = await SamConfig.fromProjectRoot(projectRoot) - const stackName = await samConfig.getCommandParam('global', 'stack_name') - const region = await samConfig.getCommandParam('global', 'region') - - return { stackName, region } - } catch (error: any) { - switch (error.code) { - case SamConfigErrorCode.samNoConfigFound: - getLogger().info('Stack name and/or region information not found in samconfig.toml: %O', error) - break - case SamConfigErrorCode.samConfigParseError: - getLogger().error( - `Error parsing stack name and/or region information from samconfig.toml: ${error.message}. Ensure the information is correct.`, - error - ) - void showViewLogsMessage('Encountered an issue reading samconfig.toml') - break - default: - getLogger().warn(`Error parsing stack name and/or region information: ${error.message}`, error) - } - return {} - } -} - -export async function getApp(location: SamAppLocation): Promise { - const samTemplate = await CloudFormation.tryLoad(location.samTemplateUri) - if (!samTemplate.template) { - throw new ToolkitError(`Template at ${location.samTemplateUri.fsPath} is not valid`) - } - const templateResources = getResourceEntity(samTemplate.template) - - const resourceTree = [...templateResources] - - return { location, resourceTree } -} - -function getResourceEntity(template: any): ResourceTreeEntity[] { - const resourceTree: ResourceTreeEntity[] = [] - - for (const [logicalId, resource] of Object.entries(template?.Resources ?? []) as [string, any][]) { - const resourceEntity = createResourceEntity(logicalId, resource, template) - resourceTree.push(resourceEntity) - } - return resourceTree -} - -function createResourceEntity(logicalId: string, resource: any, template: any): ResourceTreeEntity { - const baseEntity: BaseResourceEntity = { - Id: logicalId, - Type: resource.Type, - } - - // Create type-specific entities - if (resource.Type === CloudFormation.SERVERLESS_FUNCTION_TYPE) { - const functionEntity: FunctionResourceEntity = { - ...baseEntity, - Runtime: resource.Properties?.Runtime ?? template?.Globals?.Function?.Runtime, - Handler: resource.Properties?.Handler ?? template?.Globals?.Function?.Handler, - Events: resource.Properties?.Events ? getEvents(resource.Properties.Events) : undefined, - CodeUri: resource.Properties?.CodeUri ?? template?.Globals?.Function?.CodeUri, - Environment: resource.Properties?.Environment ?? template?.Globals?.Function?.Environment, - CapacityProviderConfig: - resource.Properties?.CapacityProviderConfig ?? template?.Globals?.Function?.CapacityProviderConfig, - Architectures: resource.Properties?.Architectures?.[0] ?? template?.Globals?.Function?.Architectures?.[0], - } - return functionEntity - } - - if (resource.Type === CloudFormation.SERVERLESS_CAPACITY_PROVIDER_TYPE) { - const capacityProviderEntity: CapacityProviderResourceEntity = { - ...baseEntity, - Architectures: - resource.Properties?.InstanceRequirements?.Architectures?.[0] ?? - template?.Globals?.CapacityProvider?.InstanceRequirements?.Architectures?.[0], - } - return capacityProviderEntity - } - - // Generic resource for unsupported types - return baseEntity -} - -function getEvents(events: Record): EventEntity[] { - const eventResources: EventEntity[] = [] - - for (const [eventsLogicalId, event] of Object.entries(events)) { - const eventProperties = event.Properties - const eventResource: EventEntity = { - Id: eventsLogicalId, - Type: event.Type, - Path: eventProperties?.Path, - Method: eventProperties?.Method, - } - eventResources.push(eventResource) - } - - return eventResources -} diff --git a/packages/core/src/awsService/appBuilder/lambda2sam/lambda2sam.ts b/packages/core/src/awsService/appBuilder/lambda2sam/lambda2sam.ts deleted file mode 100644 index 75722842bc..0000000000 --- a/packages/core/src/awsService/appBuilder/lambda2sam/lambda2sam.ts +++ /dev/null @@ -1,1007 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import fs from '../../../shared/fs/fs' -import { getLogger } from '../../../shared/logger/logger' -import * as os from 'os' -import { - LAMBDA_FUNCTION_TYPE, - LAMBDA_LAYER_TYPE, - LAMBDA_URL_TYPE, - SERVERLESS_FUNCTION_TYPE, - SERVERLESS_LAYER_TYPE, - Template, - TemplateResources, - loadByContents, - save, - tryLoad, - ZipResourceProperties, - Resource, -} from '../../../shared/cloudformation/cloudformation' - -import { downloadUnzip, getLambdaClient, getCFNClient, isPermissionError } from '../utils' -import { openProjectInWorkspace } from '../walkthrough' -import { ToolkitError } from '../../../shared/errors' -import { ResourceToImport, StackResource } from '@aws-sdk/client-cloudformation' -import { SignatureV4 } from '@smithy/signature-v4' -import { Sha256 } from '@aws-crypto/sha256-js' -import { getIAMConnection } from '../../../auth/utils' -import globals from '../../../shared/extensionGlobals' -import { Runtime, telemetry } from '../../../shared/telemetry/telemetry' - -/** - * Information about a CloudFormation stack - */ -export interface StackInfo { - stackId: string - stackName: string - isSamTemplate: boolean - template: Template -} - -/** - * Main entry point for converting a Lambda function to a SAM project - */ -export async function lambdaToSam(lambdaNode: LambdaFunctionNode): Promise { - try { - // Show progress notification for the overall process - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: `Converting ${lambdaNode.name} to SAM project`, - cancellable: false, - }, - async (progress) => { - // 0. Prompt user for project location - const saveUri = await promptForProjectLocation() - if (!saveUri) { - getLogger().info('User canceled project location selection') - return - } - progress.report({ increment: 0, message: 'Checking stack association...' }) - - // 1. Determine which scenario applies to this Lambda function - telemetry.record({ runtime: lambdaNode?.configuration?.Runtime as Runtime | undefined }) - let stackInfo = await determineStackAssociation(lambdaNode) - - // 2. Handle the appropriate scenario - let samTemplate: Template - let sourceType: 'LambdaFunction' | 'SAMStack' | 'CFNStack' - let stackName: string | undefined - if (!stackInfo) { - telemetry.record({ action: 'deployStack' }) - // Scenario 1: Lambda doesn't belong to any stack - sourceType = 'LambdaFunction' - progress.report({ increment: 30, message: 'Generating template...' }) - // 2.1 call api to get CFN - let cfnTemplate: Template - let resourcesToImport: ResourceToImport[] - try { - ;[cfnTemplate, resourcesToImport] = await callExternalApiForCfnTemplate(lambdaNode) - } catch (error) { - throw new ToolkitError(`Failed to generate template: ${error}`) - } - - // 2.2. Deploy the CFN template to create a stack - progress.report({ increment: 20, message: 'Deploying template...' }) - stackName = await promptForStackName(lambdaNode.name.replaceAll('_', '-')) - if (!stackName) { - throw new ToolkitError('Stack name not provided') - } - - stackInfo = await deployCfnTemplate( - cfnTemplate, - resourcesToImport, - stackName, - lambdaNode.regionCode - ) - samTemplate = { - AWSTemplateFormatVersion: stackInfo.template.AWSTemplateFormatVersion, - Transform: 'AWS::Serverless-2016-10-31', - Parameters: stackInfo.template.Parameters, - Globals: stackInfo.template.Globals, - Resources: stackInfo.template.Resources, - } - } else if (stackInfo.isSamTemplate) { - // Scenario 3: Lambda belongs to a stack deployed by SAM - sourceType = 'SAMStack' - progress.report({ increment: 50, message: 'Processing SAM template...' }) - samTemplate = stackInfo.template - stackName = stackInfo.stackName - } else { - // Scenario 2: Lambda belongs to a CFN stack - sourceType = 'CFNStack' - progress.report({ increment: 50, message: 'Creating SAM project from CFN...' }) - samTemplate = { - AWSTemplateFormatVersion: stackInfo.template.AWSTemplateFormatVersion, - Transform: 'AWS::Serverless-2016-10-31', - Parameters: stackInfo.template.Parameters, - Globals: stackInfo.template.Globals, - Resources: stackInfo.template.Resources, - } - stackName = stackInfo.stackName - } - - const projectUri = vscode.Uri.joinPath(saveUri[0], stackName) - - telemetry.record({ iac: sourceType }) - - // 3. Process Lambda functions in the template - if (!samTemplate.Resources) { - throw new ToolkitError('Template does not contain any resource, please retry') - } - - progress.report({ message: 'Downloading Lambda function code...' }) - await cfn2sam(samTemplate.Resources, projectUri, stackInfo, lambdaNode.regionCode) - - // 4. Save the SAM template - progress.report({ message: 'Saving SAM template...' }) - await save(samTemplate, vscode.Uri.joinPath(projectUri, 'template.yaml').fsPath) - - // 5. Create a basic README.md - // Use stack name from stackInfo if available, otherwise use the Lambda function name - progress.report({ message: 'Creating Readme...' }) - await createReadme(stackName, sourceType, projectUri) - - // 6. Create samconfig.toml - progress.report({ message: 'Creating SAM configuration...' }) - await createSAMConfig(stackName, lambdaNode.regionCode, projectUri) - - // 7. Open the project in VS Code - await openProjectInWorkspace(projectUri) - - // 8. Show success message - void vscode.window.showInformationMessage(`SAM project created successfully at ${projectUri.fsPath}`) - progress.report({ increment: 100, message: 'Done!' }) - } - ) - } catch (err) { - throw new ToolkitError(`Failed to convert Lambda to SAM: ${err instanceof Error ? err.message : String(err)}`) - } -} - -export async function createReadme( - stackName: string, - sourceType: 'LambdaFunction' | 'SAMStack' | 'CFNStack', - projectUri: vscode.Uri -) { - const warningSection = - sourceType !== 'LambdaFunction' - ? '' - : `**[Warning**: Currently only a subset of resource support converting to SAM, For any missing resources, please check the Lambda Console and add them manually to your SAM template. ]` - const lambda2SAMReadmeSource = 'resources/markdown/lambda2sam.md' - const readme = (await fs.readFileText(globals.context.asAbsolutePath(lambda2SAMReadmeSource))) - .replace(/\$\{sourceType\}/g, sourceType) - .replace(/\$\{stackName\}/g, stackName) - .replace(/\$\{warning\}/g, warningSection) - - await fs.writeFile(vscode.Uri.joinPath(projectUri, 'README.md'), readme) -} - -export async function createSAMConfig(stackName: string, region: string, projectUri: vscode.Uri) { - const samConfigContent = `# More information about the configuration file can be found here: -# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html -version = 0.1 - -[default] -[default.global.parameters] -stack_name = "${stackName}" -region = "${region}"` - await fs.writeFile(vscode.Uri.joinPath(projectUri, 'samconfig.toml'), samConfigContent) -} - -/** - * Determines if the Lambda function is associated with a CloudFormation stack - * and if that stack was deployed using SAM - */ -export async function determineStackAssociation(lambdaNode: LambdaFunctionNode): Promise { - try { - // Get Lambda function details including tags - const lambdaClient = getLambdaClient(lambdaNode.regionCode) - const functionDetails = await lambdaClient.getFunction(lambdaNode.name) - - // Check if the Lambda function has CloudFormation stack tags - if (!functionDetails.Tags) { - // Lambda doesn't have any tags, so it's not part of a stack - return undefined - } - - // Look for the CloudFormation stack ID tag - const stackIdTag = functionDetails.Tags['aws:cloudformation:stack-id'] - if (!stackIdTag) { - // Lambda doesn't have a CloudFormation stack ID tag - return undefined - } - - // Get the stack name tag if available, otherwise extract from stack ID - let stackName = functionDetails.Tags['aws:cloudformation:stack-name'] - if (!stackName) { - // Extract stack name from stack ID - const stackIdParts = stackIdTag.split('/') - stackName = stackIdParts.length > 1 ? stackIdParts[1] : '' - } - - // Create CloudFormation client - const cfn = await getCFNClient(lambdaNode.regionCode) - - // stack could be in DELETE_COMPLETE status or doesn't exist - const describeStacksResult = await cfn.describeStacks({ StackName: stackIdTag }) - if (!describeStacksResult.Stacks || describeStacksResult.Stacks.length === 0) { - return undefined - } - if (describeStacksResult.Stacks![0].StackStatus === 'DELETE_COMPLETE') { - return undefined - } - // Get the original stack template - const templateResponse = await cfn.getTemplate({ - StackName: stackIdTag, - TemplateStage: 'Original', // Critical to get the original SAM template - }) - - const templateBody = templateResponse.TemplateBody || '{}' - const template = await loadByContents(templateBody, false) - - // Determine if it's a SAM template by checking for the transform - const isSamTemplate = ifSamTemplate(template) - - return { - stackId: stackIdTag, - stackName, - isSamTemplate, - template, - } - } catch (err) { - throw new ToolkitError(`Error determining stack association: ${err}, please try again`) - } -} - -/** - * Checks if a template is a SAM template by looking for the SAM transform - */ -export function ifSamTemplate(template: Template): boolean { - // Check for SAM transform - if (template.Transform) { - if (typeof template.Transform === 'string') { - return template.Transform.startsWith('AWS::Serverless') - } else if (typeof template.Transform === 'object' && Array.isArray(template.Transform)) { - // Handle case where Transform might be an array - return template.Transform.some((t: string) => typeof t === 'string' && t.startsWith('AWS::Serverless')) - } - } - - return false -} - -/** - * Calls the external API to generate a CloudFormation template for a Lambda function - * Note: This is a placeholder for the actual API call - */ -export async function callExternalApiForCfnTemplate( - lambdaNode: LambdaFunctionNode -): Promise<[Template, ResourceToImport[]]> { - const conn = await getIAMConnection() - if (!conn || conn.type !== 'iam') { - return [{}, []] - } - - const cred = await conn.getCredentials() - const signer = new SignatureV4({ - credentials: cred, - region: lambdaNode.regionCode, - service: 'lambdaconsole', - sha256: Sha256, - }) - - // TODO: govcloud URL is in a slightly different format - const url = new URL( - `https://${lambdaNode.regionCode}.prod.topology.console.lambda.aws.a2z.com/lambda-api/topology/topology?lambdaArn=${lambdaNode.arn}` - ) - - const signedRequest = await signer.sign({ - method: 'GET', - headers: { - host: url.hostname, - }, - hostname: url.hostname, - path: url.pathname, - query: Object.fromEntries(url.searchParams), - protocol: url.protocol, - }) - - const response = await fetch(url.toString(), { - method: 'GET', - headers: { - Accept: 'application/xml', - 'Content-Type': 'application/json', - ...signedRequest.headers, - }, - }) - - if (!response.ok) { - getLogger().error('Failed to retrieve generated CloudFormation template: %O', await response.json()) - throw new ToolkitError(`Failed to retrieve generated CloudFormation template ID: ${response.statusText}`) - } - - const data = await response.json() - if (!data.cloudFormationTemplateId) { - throw new ToolkitError('No template ID returned') - } - - let status: string | undefined = 'CREATE_IN_PROGRESS' - let getGeneratedTemplateResponse - let resourcesToImport: ResourceToImport[] = [] - const cfn = await getCFNClient(lambdaNode.regionCode) - - // Wait for template generation to complete - while (status !== 'COMPLETE') { - getGeneratedTemplateResponse = await cfn.getGeneratedTemplate({ - Format: 'YAML', - GeneratedTemplateName: data.cloudFormationTemplateId, - }) - - status = getGeneratedTemplateResponse.Status - if (status === 'FAILED') { - throw new ToolkitError('CloudFormation template create status FAILED') - } - - // Add a small delay to avoid hitting API rate limits - if (status !== 'COMPLETE') { - await new Promise((resolve) => setTimeout(resolve, 1000)) - } - } - - // Get the generated template details to extract resource information - const describeGeneratedTemplateResponse = await cfn.describeGeneratedTemplate({ - GeneratedTemplateName: data.cloudFormationTemplateId, - }) - - if (describeGeneratedTemplateResponse.Status === 'FAILED') { - throw new ToolkitError('CloudFormation template describe request failed') - } - - // Build resourcesToImport from the generated template resources - if (describeGeneratedTemplateResponse.Resources) { - resourcesToImport = describeGeneratedTemplateResponse.Resources.filter( - (resource) => resource.LogicalResourceId && resource.ResourceType && resource.ResourceIdentifier - ).map((resource) => { - const resourceIdentifier = { ...resource.ResourceIdentifier! } - - // Fix Lambda function identifiers - extract function name from ARN - if (resource.ResourceType === 'AWS::Lambda::Function' && resourceIdentifier.FunctionName) { - // FunctionName might be returned as 'arn:aws:lambda:region:account:function:name' - // We need to extract just the function name - const functionNameOrArn = resourceIdentifier.FunctionName - if (functionNameOrArn.startsWith('arn:')) { - const arnParts = functionNameOrArn.split(':') - // ARN format: arn:aws:lambda:region:account:function:function-name - if (arnParts.length >= 7 && arnParts[5] === 'function') { - resourceIdentifier.FunctionName = arnParts[6] - } - } - } - - return { - ResourceType: resource.ResourceType!, - LogicalResourceId: resource.LogicalResourceId!, - ResourceIdentifier: resourceIdentifier, - } - }) - } - - const cfnTemplate = getGeneratedTemplateResponse!.TemplateBody - - const load = await tryLoad(vscode.Uri.from({ scheme: 'untitled' }), cfnTemplate) - if (!load.template || !load.template.Resources) { - throw new ToolkitError('Failed to load CloudFormation template') - } - - return [load.template, resourcesToImport] -} - -/** - * Prompts the user for a stack name - */ -export async function promptForStackName(defaultName: string): Promise { - return vscode.window.showInputBox({ - title: 'Enter Stack Name', - prompt: 'Enter a name for the CloudFormation stack', - value: `${defaultName}-stack`, - validateInput: (value) => { - if (!value) { - return 'Stack name is required' - } - if (!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(value)) { - return 'Stack name must start with a letter and contain only letters, numbers, and hyphens' - } - return undefined - }, - }) -} - -async function promptForProjectLocation(): Promise { - return vscode.window.showOpenDialog({ - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - openLabel: 'Select SAM project location', - // if not workspace, use home dir - defaultUri: vscode.workspace.workspaceFolders?.[0]?.uri ?? vscode.Uri.file(os.homedir()), - }) -} - -/** - * Deploys a CloudFormation template to create a stack and imports the existing Lambda function - */ -export async function deployCfnTemplate( - template: Template, - resourcesToImport: ResourceToImport[], - stackName: string, - region: string -): Promise { - const cfn = await getCFNClient(region) - - removeUnwantedCodeParameters(template) - - // Convert template object to JSON string - const templateBody = JSON.stringify(template) - - // Create a change set to import the existing resources - const changeSetName = `ImportLambda-${Date.now()}` - const changeSetResponse = await cfn.createChangeSet({ - StackName: stackName, - ChangeSetName: changeSetName, - ChangeSetType: 'IMPORT', - TemplateBody: templateBody, - Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'], - ResourcesToImport: resourcesToImport, - }) - - if (!changeSetResponse.Id) { - throw new ToolkitError('Failed to create change set') - } - - // Wait for change set creation to complete - await cfn - .waitFor('changeSetCreateComplete', { - StackName: stackName, - ChangeSetName: changeSetName, - $waiter: { - delay: 2, - }, - }) - .catch(async (err: any) => { - // If the change set failed to create, get the status reason - const describeResponse = await cfn.describeChangeSet({ - StackName: stackName, - ChangeSetName: changeSetName, - }) - - throw new ToolkitError(`Change set creation failed: ${describeResponse.StatusReason || err.message}`) - }) - - // Execute the change set - await cfn.executeChangeSet({ - StackName: stackName, - ChangeSetName: changeSetName, - }) - - // Wait for stack import to complete - await cfn - .waitFor('stackImportComplete', { - StackName: stackName, - $waiter: { - delay: 2, - }, - }) - .catch(async () => { - // If the stack import failed, wait for stack update complete instead - // (AWS SDK might not have stackImportComplete waiter) - await cfn.waitFor('stackUpdateComplete', { - StackName: stackName, - $waiter: { - delay: 2, - }, - }) - }) - - // Get the stack ID - const describeStackResponse = await cfn.describeStacks({ - StackName: stackName, - }) - - if (!describeStackResponse.Stacks || !describeStackResponse.Stacks[0].StackId) { - throw new ToolkitError('Failed to get stack information') - } - - // Return information about the deployed stack - return { - stackId: describeStackResponse.Stacks[0].StackId, - stackName, - template, - isSamTemplate: false, - } -} - -export function removeUnwantedCodeParameters(template: Template) { - if (!template.Resources) { - throw new Error('No Resources found in template') - } - - const lambdaKey = Object.keys(template.Resources).find( - (key) => template.Resources![key]?.Type === 'AWS::Lambda::Function' - ) - - if (!lambdaKey) { - throw new Error('No Lambda function found in template') - } - - template.Resources[lambdaKey]!.Properties!.Code = { - ZipFile: '', - } - - template.Parameters = {} -} - -/** - * Extracts the logical ID from an intrinsic function like !Ref or !GetAtt - * Returns undefined if the value is not an intrinsic function - */ -export function extractLogicalIdFromIntrinsic(value: any): string | undefined { - // Check for Ref: { "Ref": "logicalId" } - if (typeof value === 'object' && value !== null && Object.keys(value).length === 1 && value.Ref) { - return value.Ref - } - - // Check for GetAtt: { "Fn::GetAtt": ["logicalId", "Arn"] } - if ( - typeof value === 'object' && - value !== null && - Object.keys(value).length === 1 && - value['Fn::GetAtt'] && - Array.isArray(value['Fn::GetAtt']) && - value['Fn::GetAtt'].length === 2 && - value['Fn::GetAtt'][1] === 'Arn' - ) { - return value['Fn::GetAtt'][0] - } - - return undefined -} - -/** - * the main tansform to convert a CFN template to a sam project - * @param resources the parsed CFN template - * @param projectDir selected local location for project - * @param stackInfo - * @param region - */ -export async function cfn2sam( - resources: TemplateResources, - projectDir: vscode.Uri, - stackInfo: StackInfo, - region: string -): Promise { - const lambdaProcess = processLambdaResources(resources, projectDir, stackInfo, region) - const lambdaLayerProcess = processLambdaLayerResources(resources, projectDir, stackInfo, region) - await Promise.all([lambdaProcess, lambdaLayerProcess]) - await processLambdaUrlResources(resources) -} - -/** - * Processes Lambda resources in a template, transforming AWS::Lambda::Function to AWS::Serverless::Function - */ -export function Lambda2Serverless(resourceProp: ZipResourceProperties, key: string): Resource { - const dlqConfig = resourceProp.DeadLetterConfig - return { - Type: SERVERLESS_FUNCTION_TYPE, - Metadata: resourceProp.Metadata, - Properties: { - ...resourceProp, - // Transform Tags from array to object format - Tags: resourceProp.Tags - ? Object.fromEntries( - resourceProp.Tags.filter((item: { Key: any; Value: any }) => item.Key !== 'lambda:createdBy').map( - (item: { Key: any; Value: any }) => [item.Key, item.Value] - ) - ) - : undefined, - // Remove Code property (S3 reference) - Code: undefined, - // Map TracingConfig.Mode to Tracing property - Tracing: resourceProp.TracingConfig?.Mode, - TracingConfig: undefined, - // Transform DeadLetterConfig to DeadLetterQueue - DeadLetterQueue: dlqConfig - ? { - Type: dlqConfig.TargetArn.split(':')[2] === 'sqs' ? 'SQS' : 'SNS', - TargetArn: dlqConfig.TargetArn, - } - : undefined, - // Set CodeUri to the local path - CodeUri: key, - }, - } -} - -/** - * Processes Lambda URL resources in a template, transforming AWS::Lambda::Url to AWS::Serverless::Function.FunctionUrlConfig - */ -export async function processLambdaResources( - resources: TemplateResources, - projectDir: vscode.Uri, - stackInfo: StackInfo, - region: string -): Promise { - await Promise.all( - Object.entries(resources).map(async ([key, resource]) => { - if (!resource) { - return - } - - const resourceProp = resource.Properties - if (!resourceProp || resourceProp.PackageType === 'Image') { - return - } - - if (resource.Type === LAMBDA_FUNCTION_TYPE) { - // Transform AWS::Lambda::Function to AWS::Serverless::Function - try { - await downloadLambdaFunctionCode(key, stackInfo, projectDir, region, resourceProp.FunctionName) - - // Transform to Serverless Function - resources[key] = Lambda2Serverless(resourceProp, key) - } catch (err) { - throw new ToolkitError( - `Failed to process Lambda function ${key}: ${err instanceof Error ? err.message : String(err)}` - ) - } - } else if (resource.Type === SERVERLESS_FUNCTION_TYPE) { - // Update CodeUri for AWS::Serverless::Function - try { - await downloadLambdaFunctionCode(key, stackInfo, projectDir, region, resourceProp.FunctionName) - // Update the CodeUri to point to the local directory - resourceProp.CodeUri = key - } catch (err) { - throw new ToolkitError( - `Failed to process Serverless function ${key}: ${err instanceof Error ? err.message : String(err)}` - ) - } - } - }) - ) -} - -/** - * Processes Lambda Layer resources in a template, transforming AWS::Lambda::LayerVersion to AWS::Serverless::LayerVersion - */ -export async function processLambdaLayerResources( - resources: TemplateResources, - projectDir: vscode.Uri, - stackInfo: StackInfo, - region: string -): Promise { - // Process each resource - await Promise.all( - Object.entries(resources).map(async ([key, resource]) => { - if (!resource || (resource.Type !== LAMBDA_LAYER_TYPE && resource.Type !== SERVERLESS_LAYER_TYPE)) { - return - } - - const resourceProp = resource.Properties - if (!resourceProp) { - return - } - - try { - // Download the layer code - await downloadLayerVersionResourceByName(key, stackInfo, projectDir, region) - - // Transform to Serverless LayerVersion - resources[key] = { - Type: SERVERLESS_LAYER_TYPE, - Properties: { - ...resourceProp, - // Remove Content property (S3 reference) - Content: undefined, - // Set ContentUri to the local path - ContentUri: key, - }, - } - - getLogger().info(`Successfully transformed Lambda Layer ${key} to Serverless LayerVersion`) - } catch (err) { - throw new ToolkitError( - `Failed to process Lambda Layer ${key}: ${err instanceof Error ? err.message : String(err)}` - ) - } - }) - ) -} - -/** - * Processes Lambda URL resources in a template, transforming AWS::Lambda::Url to AWS::Serverless::Function.FunctionUrlConfig - */ -export async function processLambdaUrlResources(resources: TemplateResources): Promise { - for (const [key, resource] of Object.entries(resources)) { - if (resource && resource.Type === LAMBDA_URL_TYPE) { - try { - const resourceProp = resource.Properties - if (!resourceProp) { - continue - } - - // Skip if Qualifier is present (not supported in FunctionUrlConfig) - if (resourceProp.Qualifier) { - getLogger().info( - `Skipping Lambda URL ${key} because Qualifier is not supported in FunctionUrlConfig` - ) - continue - } - - // Find the target function using TargetFunctionArn - const targetFunctionArn = resourceProp.TargetFunctionArn - if (!targetFunctionArn) { - getLogger().warn(`Lambda URL ${key} does not have a TargetFunctionArn`) - continue - } - - const targetFunctionKey = extractLogicalIdFromIntrinsic(targetFunctionArn) - if (!targetFunctionKey) { - getLogger().debug(`Could not extract logical ID from TargetFunctionArn in Lambda URL ${key}`) - continue - } - - const targetFunction = resources[targetFunctionKey] - // if MyLambdaFunction 's url is not formated as MyLambdaFunctionUrl, then we shouldn't transform it - if ( - !targetFunction || - targetFunction.Type !== SERVERLESS_FUNCTION_TYPE || - targetFunctionKey + 'Url' !== key - ) { - getLogger().debug(`Target function ${targetFunctionKey} not found or not a Serverless Function`) - continue - } - - // Add FunctionUrlConfig to the Serverless Function - if (!targetFunction.Properties) { - // skip if target function is not correctly setup - continue - } - - // Now we can safely add FunctionUrlConfig - if (targetFunction.Properties) { - targetFunction.Properties.FunctionUrlConfig = { - AuthType: resourceProp.AuthType, - Cors: resourceProp.Cors, - InvokeMode: resourceProp.InvokeMode, - } - } - - // Remove the original Lambda URL resource - delete resources[key] - - getLogger().info( - `Successfully transformed Lambda URL ${key} to FunctionUrlConfig in ${targetFunctionKey}` - ) - } catch (err) { - throw new ToolkitError( - `Failed to process Lambda URL ${key}: ${err instanceof Error ? err.message : String(err)}` - ) - } - } - } -} - -/** - * Download lambda function code based on logical resource ID or physical resrouce ID - * If logical id is given, it will try to find the physical id first and then download the code - * If physical id is given, it will download the code directly - * @param resourceName logical name of Lambda function in CFN template - * @param stackInfo - * @param targetDir Local location to store the code - * @param region - * @param physicalResourceId Physical name of Lambda function - */ -export async function downloadLambdaFunctionCode( - resourceName: string, // This is the logical name from CFN - stackInfo: StackInfo, - targetDir: vscode.Uri, - region: string, - physicalResourceId?: string -) { - try { - if (!physicalResourceId || typeof physicalResourceId !== 'string') { - physicalResourceId = await getPhysicalIdfromCFNResourceName( - resourceName, - region, - stackInfo.stackId, - LAMBDA_FUNCTION_TYPE - ) - if (!physicalResourceId) { - throw new ToolkitError(`Could not find physical resource ID for ${resourceName}`) - } - } - - const lambdaClient = getLambdaClient(region) - const functionDetails = await lambdaClient.getFunction(physicalResourceId) - - if (!functionDetails.Code || !functionDetails.Code.Location) { - throw new ToolkitError(`Could not determine code location for function: ${physicalResourceId}`) - } - - const outputPath = vscode.Uri.joinPath(targetDir, resourceName) - await downloadUnzip(functionDetails.Code.Location, outputPath) - - getLogger().info(`Successfully downloaded and extracted: ${resourceName}`) - } catch (err) { - throw new ToolkitError( - `Failed to download resource ${resourceName}: ${err instanceof Error ? err.message : String(err)}` - ) - } -} - -/** - * Get physical resource ID from CFN resource name - * @param name CFN resource name - * @param region - * @param stackId - * @returns Physical resrouce ID - */ -export async function getPhysicalIdfromCFNResourceName( - name: string, - region: string, - stackId: string, - resourceType: string -): Promise { - // Create CloudFormation client - const cfn = await getCFNClient(region) - - try { - // First try the exact match approach - let describeResult - try { - describeResult = await cfn.describeStackResource({ - StackName: stackId, - LogicalResourceId: name, - }) - } catch (error) { - // If it's a permission error, re-throw it immediately - if (isPermissionError(error)) { - throw error - } - // For other errors (like ResourceNotFound), continue to fuzzy matching - describeResult = undefined - } - - if (describeResult?.StackResourceDetail?.PhysicalResourceId) { - const physicalResourceId = describeResult.StackResourceDetail.PhysicalResourceId - getLogger().debug(`Resource ${name} found with exact match, physical ID: ${physicalResourceId}`) - return physicalResourceId - } - - // only do fuzzy matching for layer, function doesn't have random suffix - if (resourceType === LAMBDA_FUNCTION_TYPE || resourceType === SERVERLESS_FUNCTION_TYPE) { - throw new ToolkitError(`Could not find physical resource ID for ${name}`) - } - - // If exact match fails, get all resources and try fuzzy matching - getLogger().debug(`Resource ${name} not found with exact match, trying fuzzy match...`) - const resources = await cfn.describeStackResources({ - StackName: stackId, - }) - - if (!resources.StackResources || resources.StackResources.length === 0) { - getLogger().debug(`No resources found in stack ${stackId}`) - return undefined - } - - // Find resources that start with the given name (SAM transform often adds suffixes) - const matchingResources = resources.StackResources.filter((resource: StackResource) => - resource.LogicalResourceId?.startsWith(name) - ) - - if (matchingResources.length === 0) { - // Try a more flexible approach - check if the resource name is a substring - const substringMatches = resources.StackResources.filter((resource: StackResource) => - resource.LogicalResourceId?.includes(name) - ) - - if (substringMatches.length === 0) { - getLogger().debug(`No fuzzy matches found for resource ${name}`) - return undefined - } - - // Use the first substring match - const match = substringMatches[0] - getLogger().debug( - `Resource ${name} matched with ${match.LogicalResourceId} using substring match, physical ID: ${match.PhysicalResourceId}` - ) - return match.PhysicalResourceId - } - - // If we have multiple matches, prefer exact prefix match - // Sort by length to get the closest match (shortest additional suffix) - matchingResources.sort( - (a: StackResource, b: StackResource) => - (a.LogicalResourceId?.length ?? 0) - (b.LogicalResourceId?.length ?? 0) - ) - - const bestMatch = matchingResources[0] - getLogger().debug( - `Resource ${name} matched with ${bestMatch.LogicalResourceId} using prefix match, physical ID: ${bestMatch.PhysicalResourceId}` - ) - return bestMatch.PhysicalResourceId - } catch (err) { - throw ToolkitError.chain(err, `Error finding physical ID for resource ${name}, please retry`) - } -} - -/** - * Download a Lambda Layer resource by name and stack info - * @param resourceName Layer's Logical name from CFN - * @param stackInfo - * @param targetDir local location to store - * @param region - */ -export async function downloadLayerVersionResourceByName( - resourceName: string, // This is the logical name from CFN - stackInfo: StackInfo, - targetDir: vscode.Uri, - region: string -) { - try { - const physicalResourceId = await getPhysicalIdfromCFNResourceName( - resourceName, - region, - stackInfo.stackId, - LAMBDA_LAYER_TYPE - ) - if (!physicalResourceId) { - throw new ToolkitError(`Could not find physical resource ID for ${resourceName}`) - } - - getLogger().debug(`Resource ${resourceName} has physical ID ${physicalResourceId} and type LayerVersion`) - - // Parse the ARN to extract layer name and version - // Format: arn:aws:lambda:region:account-id:layer:layer-name:version - const arnParts = physicalResourceId.split(':') - if (arnParts.length < 8) { - throw new ToolkitError(`Invalid layer ARN format: ${physicalResourceId}`) - } - - const layerName = arnParts[6] - const version = parseInt(arnParts[7], 10) - - if (isNaN(version)) { - throw new ToolkitError(`Invalid version number in layer ARN: ${physicalResourceId}`) - } - - getLogger().debug(`Extracted layer name: ${layerName}, version: ${version} from ARN`) - - const lambdaClient = getLambdaClient(region) - - // Get the layer version details directly using the extracted name and version - const layerDetails = await lambdaClient.getLayerVersion(layerName, version) - - if (!layerDetails.Content || !layerDetails.Content.Location) { - throw new ToolkitError(`Could not determine code location for layer: ${layerName}:${version}`) - } - - // Download Lambda layer code using the presigned URL - const presignedUrl = layerDetails.Content.Location - - // Use node-fetch to download from the presigned URL - const outputPath = vscode.Uri.joinPath(targetDir, resourceName) - await downloadUnzip(presignedUrl, outputPath) - - getLogger().info(`Successfully downloaded and extracted layer ${layerName}:${version} to: ${resourceName}`) - } catch (err) { - throw new ToolkitError( - `Failed to download resource ${resourceName}: ${err instanceof Error ? err.message : String(err)}, please retry` - ) - } -} diff --git a/packages/core/src/awsService/appBuilder/serverlessLand/main.ts b/packages/core/src/awsService/appBuilder/serverlessLand/main.ts deleted file mode 100644 index aac352b976..0000000000 --- a/packages/core/src/awsService/appBuilder/serverlessLand/main.ts +++ /dev/null @@ -1,140 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() -import * as path from 'path' -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { checklogs } from '../../../shared/localizedText' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { CreateServerlessLandWizardForm, CreateServerlessLandWizard } from './wizard' -import { ExtContext } from '../../../shared/extensions' -import { addFolderToWorkspace } from '../../../shared/utilities/workspaceUtils' -import { ToolkitError } from '../../../shared/errors' -import { fs } from '../../../shared/fs/fs' -import { handleOverwriteConflict } from '../../../shared/utilities/messages' -import { getPattern } from '../../../shared/utilities/downloadPatterns' -import { MetadataManager } from './metadataManager' - -export const readmeFile: string = 'README.md' -const serverlessLandOwner = 'aws-samples' -const serverlessLandRepo = 'serverless-patterns' - -/** - * Creates a new Serverless Land project using the provided extension context - * @param extContext Extension context containing AWS credentials and region information - * @returns Promise that resolves when the project creation is complete - * - * This function: - * 1. Validates AWS credentials and regions - * 2. Launches the Serverless Land project creation wizard - * 3. Creates the project structure - * 4. Adds the project folder to the workspace - * 5. Opens the README.md file if available - * 6. Handles errors and emits telemetry - */ -export async function createNewServerlessLandProject(extContext: ExtContext): Promise { - let metadataManager: MetadataManager - - try { - metadataManager = MetadataManager.getInstance() - // Launch the project creation wizard - const config = await launchProjectCreationWizard(extContext) - if (!config) { - return - } - const assetName = metadataManager.getAssetName(config.pattern, config.runtime, config.iac) - - await downloadPatternCode(config, assetName) - await openReadmeFile(config) - await addFolderToWorkspace( - { - uri: vscode.Uri.joinPath(config.location, config.name), - name: path.basename(config.name), - }, - true - ) - telemetry.record({ - templateName: config.pattern, - runtimeString: config.runtime, - iac: config.iac, - }) - } catch (err) { - getLogger().error( - localize( - 'AWS.serverlessland.initWizard.general.error', - 'Error creating new Serverless Land Application. {0}', - checklogs() - ) - ) - getLogger().error('Error creating new Serverless Land Application: %O', err as Error) - } -} - -export async function launchProjectCreationWizard( - extContext: ExtContext -): Promise { - const awsContext = extContext.awsContext - const credentials = await awsContext.getCredentials() - const defaultRegion = awsContext.getCredentialDefaultRegion() - - return new CreateServerlessLandWizard({ - credentials, - defaultRegion, - }).run() -} - -export async function downloadPatternCode(config: CreateServerlessLandWizardForm, assetName: string): Promise { - const fullAssetName = assetName + '.zip' - const location = vscode.Uri.joinPath(config.location, config.name) - - await handleOverwriteConflict(location) - - try { - await getPattern(serverlessLandOwner, serverlessLandRepo, fullAssetName, location, true) - } catch (error) { - if (error instanceof ToolkitError) { - throw error - } - throw new ToolkitError(`Failed to download pattern: ${error}`) - } -} - -export async function openReadmeFile(config: CreateServerlessLandWizardForm): Promise { - try { - const readmeUri = await getProjectUri(config, readmeFile) - if (!readmeUri) { - getLogger().warn('README.md file not found in the project directory') - return - } - await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') - await vscode.commands.executeCommand('markdown.showPreview', readmeUri) - } catch (err) { - getLogger().error(`Error in openReadmeFile: ${err}`) - throw new ToolkitError('Error processing README file') - } -} - -export async function getProjectUri( - config: Pick, - file: string -): Promise { - if (!file) { - throw new ToolkitError('expected "file" parameter to have at least one item') - } - const cfnTemplatePath = path.resolve(config.location.fsPath, config.name, file) - if (await fs.exists(cfnTemplatePath)) { - return vscode.Uri.file(cfnTemplatePath) - } - void vscode.window.showWarningMessage( - localize( - 'AWS.serverlessLand.initWizard.source.error.notFound', - 'Project created successfully, but {0} file not found: {1}', - file!, - cfnTemplatePath! - ) - ) -} diff --git a/packages/core/src/awsService/appBuilder/serverlessLand/metadata.json b/packages/core/src/awsService/appBuilder/serverlessLand/metadata.json deleted file mode 100644 index 06ab2efc52..0000000000 --- a/packages/core/src/awsService/appBuilder/serverlessLand/metadata.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "patterns": { - "Image Resizing": { - "name": "Resizing image", - "description": "Lambda, S3 • Python, Javascript, Java, .NET • SAM", - "implementation": [ - { - "iac": "sam", - "runtime": "python", - "assetName": "s3-lambda-resizing-python" - }, - { - "iac": "sam", - "runtime": "javascript", - "assetName": "s3-lambda" - }, - { - "iac": "sam", - "runtime": "java", - "assetName": "s3-lambda-resizing-java" - }, - { - "iac": "sam", - "runtime": "dotnet", - "assetName": "s3-lambda-dotnet" - } - ] - }, - "Hello World Lambda with API": { - "name": "Rest API", - "description": "Lambda, API Gateway • Python, Javascript, Java, .NET • SAM", - "implementation": [ - { - "iac": "sam", - "runtime": "python", - "assetName": "apigw-rest-api-lambda-python" - }, - { - "iac": "sam", - "runtime": "javascript", - "assetName": "apigw-rest-api-lambda-node" - }, - { - "iac": "sam", - "runtime": "java", - "assetName": "apigw-rest-api-lambda-java" - }, - { - "iac": "sam", - "runtime": "dotnet", - "assetName": "apigw-rest-api-lambda-dotnet" - } - ] - }, - "Process SQS Records with Lambda": { - "name": "Process SQS Records with Lambda", - "description": "Lambda, SQS • Python, Javascript, Java, .NET • SAM", - "implementation": [ - { - "iac": "sam", - "runtime": "python", - "assetName": "sqs-lambda-python-sam" - }, - { - "iac": "sam", - "runtime": "javascript", - "assetName": "sqs-lambda-nodejs-sam" - }, - { - "iac": "sam", - "runtime": "java", - "assetName": "sqs-lambda-java-sam" - }, - { - "iac": "sam", - "runtime": "dotnet", - "assetName": "sqs-lambda-dotnet-sam" - } - ] - }, - "Process SNS notification messages with Lambda": { - "name": "Process SNS notification messages with Lambda", - "description": "Lambda, SNS • Python, Javascript, Java, .NET • SAM", - "implementation": [ - { - "iac": "sam", - "runtime": "python", - "assetName": "sns-lambda-python-sam" - }, - { - "iac": "sam", - "runtime": "javascript", - "assetName": "sns-lambda-nodejs-sam" - }, - { - "iac": "sam", - "runtime": "java", - "assetName": "sns-lambda-java-sam" - }, - { - "iac": "sam", - "runtime": "dotnet", - "assetName": "sns-lambda-dotnet-sam" - } - ] - } - } -} diff --git a/packages/core/src/awsService/appBuilder/serverlessLand/metadataManager.ts b/packages/core/src/awsService/appBuilder/serverlessLand/metadataManager.ts deleted file mode 100644 index 35d0b25450..0000000000 --- a/packages/core/src/awsService/appBuilder/serverlessLand/metadataManager.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import { ToolkitError } from '../../../shared/errors' -import globals from '../../../shared/extensionGlobals' - -interface Implementation { - iac: string - runtime: string - assetName: string -} -interface PatternData { - name: string - description: string - implementation: Implementation[] -} - -export interface ProjectMetadata { - patterns: Record -} - -/** - * Manages metadata for serverless application patterns - */ - -export class MetadataManager { - private static instance: MetadataManager - private metadata: ProjectMetadata | undefined - - private constructor() {} - - public static getInstance(): MetadataManager { - if (!MetadataManager.instance) { - MetadataManager.instance = new MetadataManager() - } - return MetadataManager.instance - } - - public static initialize(): MetadataManager { - const instance = MetadataManager.getInstance() - const metadataPath = instance.getMetadataPath() - instance.loadMetadata(metadataPath).catch((err) => { - throw new ToolkitError(`Failed to load metadata: ${err}`) - }) - return instance - } - - public getMetadataPath(): string { - return globals.context.asAbsolutePath(path.join('dist', 'src', 'serverlessLand', 'metadata.json')) - } - - /** - * Loads metadata from a JSON file - * @param metadataPath Path to the metadata JSON file - * @returns Promise containing the parsed ProjectMetadata - */ - public async loadMetadata(metadataPath: string): Promise { - try { - if (!this.metadata) { - const metadataContent = nodefs.readFileSync(metadataPath, { encoding: 'utf-8' }) - const parseMetadata = JSON.parse(metadataContent) as ProjectMetadata - this.metadata = parseMetadata - } - return this.metadata - } catch (err) { - throw new ToolkitError(`Failed to load metadata: ${err instanceof Error ? err.message : String(err)}`) - } - } - - /** - * Retrieves available patterns with their descriptions - * @returns Array of pattern objects containing labels and descriptions - */ - public getPatterns(): { label: string; description?: string }[] { - if (!this.metadata) { - return [] - } - return Object.entries(this.metadata.patterns).map(([patternName, patternData]) => { - let description: string | undefined = undefined - if (typeof patternData === 'string') { - description = patternData - } else if (Array.isArray(patternData)) { - // If description is an array, join it into a single string - description = patternData.join(' ') - } - if (!patternData || !patternData.name) { - return { - label: patternName, - description: description || 'No description available', - } - } - return { - label: patternName, - description: patternData.description, - } - }) - } - - /** - * Gets available runtimes for a specific pattern - * @param pattern The pattern name to get runtimes for - * @returns Array of runtime options with labels - */ - public getRuntimes(pattern: string): { label: string }[] { - const patternData = this.metadata?.patterns?.[pattern] - if (!patternData || !patternData.implementation) { - return [] - } - const uniqueRuntimes = new Set(patternData.implementation.map((item) => item.runtime)) - return Array.from(uniqueRuntimes).map((runtime) => ({ - label: runtime, - })) - } - - public getUrl(pattern: string): string { - const patternData = this.metadata?.patterns?.[pattern] - if (!patternData || !patternData.implementation) { - return '' - } - const asset = patternData.implementation[0].assetName - - return `https://serverlessland.com/patterns/${asset}` - } - - /** - * Gets available Infrastructure as Code options for a specific pattern - * @param pattern The pattern name to get IaC options for - * @returns Array of IaC options with labels - */ - public getIacOptions(pattern: string): { label: string }[] { - const patternData = this.metadata?.patterns?.[pattern] - if (!patternData || !patternData.implementation) { - return [] - } - const uniqueIaCs = new Set(patternData.implementation.map((item) => item.iac)) - return Array.from(uniqueIaCs).map((iac) => ({ - label: iac, - })) - } - public getAssetName(selectedPattern: string, selectedRuntime: string, selectedIaC: string): string { - const patternData = this.metadata?.patterns?.[selectedPattern] - if (!patternData || !patternData.implementation) { - return '' - } - const matchingImplementation = patternData.implementation.find( - (impl) => impl.runtime === selectedRuntime && impl.iac === selectedIaC - ) - return matchingImplementation?.assetName ?? '' - } -} diff --git a/packages/core/src/awsService/appBuilder/serverlessLand/wizard.ts b/packages/core/src/awsService/appBuilder/serverlessLand/wizard.ts deleted file mode 100644 index 42b3d28330..0000000000 --- a/packages/core/src/awsService/appBuilder/serverlessLand/wizard.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as nls from 'vscode-nls' -import * as AWS from '@aws-sdk/types' -import * as vscode from 'vscode' -import { Wizard } from '../../../shared/wizards/wizard' -import * as path from 'path' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { createQuickPick } from '../../../shared/ui/pickerPrompter' -import { createFolderPrompt } from '../../../shared/ui/common/location' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -import { MetadataManager } from './metadataManager' -import { ToolkitError } from '../../../shared/errors' - -const localize = nls.loadMessageBundle() -export interface CreateServerlessLandWizardForm { - name: string - location: vscode.Uri - pattern: string - runtime: string - iac: string -} - -function promptPattern(metadataManager: MetadataManager) { - const patterns = metadataManager.getPatterns() - if (patterns.length === 0) { - throw new ToolkitError('No patterns found in metadata') - } - - return createQuickPick( - patterns.map((p) => ({ - label: p.label, - detail: p.description, - data: p.label, - buttons: [ - { - iconPath: new vscode.ThemeIcon('link-external'), - tooltip: 'Open in Serverless Land', - }, - ], - })), - { - title: localize('AWS.serverlessLand.initWizard.pattern.prompt', 'Select a Pattern for your application'), - placeholder: 'Choose a pattern for your project', - buttons: createCommonButtons(), - matchOnDescription: true, - matchOnDetail: true, - } - ) -} - -function promptRuntime(metadataManager: MetadataManager, pattern: string | undefined) { - if (!pattern || typeof pattern !== 'string') { - throw new ToolkitError('Pattern not selected') - } - - const runtimes = metadataManager.getRuntimes(pattern) - if (runtimes.length === 0) { - throw new ToolkitError('No runtimes found for the selected pattern') - } - - return createQuickPick( - runtimes.map((r) => ({ - label: r.label, - data: r.label, - })), - { - title: localize('AWS.serverlessLand.initWizard.runtime.prompt', 'Select Runtime'), - placeholder: 'Choose a runtime for your project', - buttons: createCommonButtons(), - } - ) -} - -function promptIac(metadataManager: MetadataManager, pattern: string | undefined) { - if (!pattern || typeof pattern !== 'string') { - throw new ToolkitError('Pattern not selected') - } - - const iacOptions = metadataManager.getIacOptions(pattern) - if (iacOptions.length === 0) { - throw new ToolkitError('No IAC options found for the selected pattern') - } - - return createQuickPick( - iacOptions.map((i) => ({ - label: i.label, - data: i.label, - })), - { - title: localize('AWS.serverlessLand.initWizard.iac.prompt', 'Select IaC'), - placeholder: 'Choose an IaC option for your project', - buttons: createCommonButtons(), - } - ) -} - -function promptLocation() { - return createFolderPrompt(vscode.workspace.workspaceFolders ?? [], { - title: localize('AWS.serverlessLand.initWizard.location.prompt', 'Select Project Location'), - buttons: createCommonButtons(), - browseFolderDetail: 'Select a parent folder for your project', - }) -} - -function promptName(location: vscode.Uri | undefined) { - const folderName = location ? path.basename(location.fsPath) : '' - return createInputBox({ - title: localize('AWS.serverlessLand.initWizard.name.prompt', 'Enter Project Name'), - placeholder: ` ${folderName}/: Enter a name for your new application`, - buttons: createCommonButtons(), - validateInput: (value: string): string | undefined => { - if (!value) { - return 'Application name cannot be empty' - } - if (value.includes(path.sep)) { - return `The path separator (${path.sep}) is not allowed in application names` - } - return undefined - }, - }) -} - -/** - * Wizard for creating Serverless Land applications - * Guides users through the project creation process - */ -export class CreateServerlessLandWizard extends Wizard { - private metadataManager: MetadataManager - - public constructor(context: { defaultRegion?: string; credentials?: AWS.Credentials }) { - super({ - exitPrompterProvider: createExitPrompter, - }) - this.metadataManager = MetadataManager.initialize() - this.form.pattern.bindPrompter(() => promptPattern(this.metadataManager)) - this.form.runtime.bindPrompter((state) => promptRuntime(this.metadataManager, state.pattern)) - this.form.iac.bindPrompter((state) => promptIac(this.metadataManager, state.pattern)) - this.form.location.bindPrompter(() => promptLocation()) - this.form.name.bindPrompter((state) => promptName(state.location)) - } -} diff --git a/packages/core/src/awsService/appBuilder/utils.ts b/packages/core/src/awsService/appBuilder/utils.ts deleted file mode 100644 index bf65059ebc..0000000000 --- a/packages/core/src/awsService/appBuilder/utils.ts +++ /dev/null @@ -1,763 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import * as nls from 'vscode-nls' -import { ResourceNode } from './explorer/nodes/resourceNode' -import type { SamAppLocation } from './explorer/samProject' -import { isFunctionResource } from './explorer/samProject' -import { ToolkitError } from '../../shared/errors' -import globals from '../../shared/extensionGlobals' -import { OpenTemplateParams, OpenTemplateWizard } from './explorer/openTemplate' -import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter' -import { createCommonButtons } from '../../shared/ui/buttons' -import { samDeployUrl } from '../../shared/constants' -import path from 'path' -import fs from '../../shared/fs/fs' -import { getLogger } from '../../shared/logger/logger' -import { RuntimeFamily, getFamily } from '../../lambda/models/samLambdaRuntime' -import { showMessage } from '../../shared/utilities/messages' -import { DefaultLambdaClient } from '../../shared/clients/lambdaClient' -import AdmZip from 'adm-zip' -import { - CloudFormationClient, - CreateChangeSetCommand, - CreateChangeSetInput, - CreateChangeSetOutput, - DescribeChangeSetCommand, - DescribeChangeSetInput, - DescribeChangeSetOutput, - DescribeGeneratedTemplateCommand, - DescribeGeneratedTemplateInput, - DescribeGeneratedTemplateOutput, - DescribeStackResourceCommand, - DescribeStackResourceInput, - DescribeStackResourceOutput, - DescribeStackResourcesCommand, - DescribeStackResourcesInput, - DescribeStackResourcesOutput, - DescribeStacksCommand, - DescribeStacksInput, - DescribeStacksOutput, - ExecuteChangeSetCommand, - ExecuteChangeSetInput, - ExecuteChangeSetOutput, - GetGeneratedTemplateCommand, - GetGeneratedTemplateInput, - GetGeneratedTemplateOutput, - GetTemplateCommand, - GetTemplateInput, - GetTemplateOutput, - waitUntilChangeSetCreateComplete, - waitUntilStackImportComplete, - waitUntilStackUpdateComplete, -} from '@aws-sdk/client-cloudformation' -import { - FunctionConfiguration, - FunctionUrlConfig, - GetFunctionResponse, - GetLayerVersionResponse, - InvocationRequest, - InvocationResponse, - LayerVersionsListItem, - Runtime, -} from '@aws-sdk/client-lambda' -import { isAwsError, UnknownError } from '../../shared/errors' -import { WaiterConfiguration } from '@aws-sdk/types' -const localize = nls.loadMessageBundle() - -/** - * Interface for mapping AWS service actions to their required permissions - */ -interface PermissionMapping { - service: 'cloudformation' | 'lambda' - action: string - requiredPermissions: string[] - documentation?: string -} - -/** - * Comprehensive mapping of AWS service actions to their required permissions - */ -const PermissionMappings: PermissionMapping[] = [ - // CloudFormation permissions - { - service: 'cloudformation', - action: 'describeStacks', - requiredPermissions: ['cloudformation:DescribeStacks'], - documentation: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStacks.html', - }, - { - service: 'cloudformation', - action: 'getTemplate', - requiredPermissions: ['cloudformation:GetTemplate'], - documentation: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_GetTemplate.html', - }, - { - service: 'cloudformation', - action: 'createChangeSet', - requiredPermissions: ['cloudformation:CreateChangeSet'], - documentation: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateChangeSet.html', - }, - { - service: 'cloudformation', - action: 'executeChangeSet', - requiredPermissions: ['cloudformation:ExecuteChangeSet'], - documentation: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_ExecuteChangeSet.html', - }, - { - service: 'cloudformation', - action: 'describeChangeSet', - requiredPermissions: ['cloudformation:DescribeChangeSet'], - documentation: 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeChangeSet.html', - }, - { - service: 'cloudformation', - action: 'describeStackResources', - requiredPermissions: ['cloudformation:DescribeStackResources'], - documentation: - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStackResources.html', - }, - { - service: 'cloudformation', - action: 'describeStackResource', - requiredPermissions: ['cloudformation:DescribeStackResource'], - documentation: - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeStackResource.html', - }, - { - service: 'cloudformation', - action: 'getGeneratedTemplate', - requiredPermissions: ['cloudformation:GetGeneratedTemplate'], - documentation: - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_GetGeneratedTemplate.html', - }, - { - service: 'cloudformation', - action: 'describeGeneratedTemplate', - requiredPermissions: ['cloudformation:DescribeGeneratedTemplate'], - documentation: - 'https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_DescribeGeneratedTemplate.html', - }, - // Lambda permissions - { - service: 'lambda', - action: 'getFunction', - requiredPermissions: ['lambda:GetFunction'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_GetFunction.html', - }, - { - service: 'lambda', - action: 'listFunctions', - requiredPermissions: ['lambda:ListFunctions'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_ListFunctions.html', - }, - { - service: 'lambda', - action: 'getLayerVersion', - requiredPermissions: ['lambda:GetLayerVersion'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_GetLayerVersion.html', - }, - { - service: 'lambda', - action: 'listLayerVersions', - requiredPermissions: ['lambda:ListLayerVersions'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_ListLayerVersions.html', - }, - { - service: 'lambda', - action: 'listFunctionUrlConfigs', - requiredPermissions: ['lambda:GetFunctionUrlConfig'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_GetFunctionUrlConfig.html', - }, - { - service: 'lambda', - action: 'updateFunctionCode', - requiredPermissions: ['lambda:UpdateFunctionCode'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_UpdateFunctionCode.html', - }, - { - service: 'lambda', - action: 'deleteFunction', - requiredPermissions: ['lambda:DeleteFunction'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_DeleteFunction.html', - }, - { - service: 'lambda', - action: 'invoke', - requiredPermissions: ['lambda:InvokeFunction'], - documentation: 'https://docs.aws.amazon.com/lambda/latest/api/API_Invoke.html', - }, -] - -/** - * Creates an enhanced error message for permission-related failures - */ -function createEnhancedPermissionError( - originalError: unknown, - service: 'cloudformation' | 'lambda', - action: string, - resourceArn?: string -): ToolkitError { - const mapping = PermissionMappings.find((m) => m.service === service && m.action === action) - - if (!mapping) { - return ToolkitError.chain(originalError, `Permission denied for ${service}:${action}`) - } - - const permissionsList = mapping.requiredPermissions.map((p) => ` - ${p}`).join('\n') - const resourceInfo = resourceArn ? `\nResource: ${resourceArn}` : '' - - const message = `Permission denied: Missing required permissions for ${service}:${action} - -Required permissions: -${permissionsList}${resourceInfo} - -To fix this issue: -1. Contact your AWS administrator to add the missing permissions -2. Add these permissions to your IAM user/role policy -3. If using IAM roles, ensure the role has these permissions attached - -${mapping.documentation ? `Documentation: ${mapping.documentation}` : ''}` - - return new ToolkitError(message, { - code: 'InsufficientPermissions', - cause: UnknownError.cast(originalError), - details: { - service, - action, - requiredPermissions: mapping.requiredPermissions, - resourceArn, - }, - }) -} - -/** - * Checks if an error is a permission-related error - */ -export function isPermissionError(error: unknown): boolean { - return ( - isAwsError(error) && - (error.code === 'AccessDeniedException' || - error.code === 'UnauthorizedOperation' || - error.code === 'Forbidden' || - error.code === 'AccessDenied' || - (error as any).statusCode === 403) - ) -} - -/** - * Enhanced Lambda client wrapper that provides better error messages for permission issues - */ -export class EnhancedLambdaClient { - constructor( - private readonly client: DefaultLambdaClient, - private readonly regionCode: string - ) {} - - async deleteFunction(name: string): Promise { - try { - return await this.client.deleteFunction(name) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'deleteFunction', - `arn:aws:lambda:${this.regionCode}:*:function:${name}` - ) - } - throw error - } - } - - async invoke(name: string, payload?: InvocationRequest['Payload']): Promise { - try { - return await this.client.invoke(name, payload) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'invoke', - `arn:aws:lambda:${this.regionCode}:*:function:${name}` - ) - } - throw error - } - } - - async *listFunctions(): AsyncIterableIterator { - try { - yield* this.client.listFunctions() - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError(error, 'lambda', 'listFunctions') - } - throw error - } - } - - async getFunction(name: string): Promise { - try { - return await this.client.getFunction(name) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'getFunction', - `arn:aws:lambda:${this.regionCode}:*:function:${name}` - ) - } - throw error - } - } - - async getLayerVersion(name: string, version: number): Promise { - try { - return await this.client.getLayerVersion(name, version) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'getLayerVersion', - `arn:aws:lambda:${this.regionCode}:*:layer:${name}:${version}` - ) - } - throw error - } - } - - async *listLayerVersions(name: string): AsyncIterableIterator { - try { - yield* this.client.listLayerVersions(name) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'listLayerVersions', - `arn:aws:lambda:${this.regionCode}:*:layer:${name}` - ) - } - throw error - } - } - - async getFunctionUrlConfigs(name: string): Promise { - try { - return await this.client.getFunctionUrlConfigs(name) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'listFunctionUrlConfigs', - `arn:aws:lambda:${this.regionCode}:*:function:${name}` - ) - } - throw error - } - } - - async updateFunctionCode(name: string, zipFile: Uint8Array): Promise { - try { - return await this.client.updateFunctionCode(name, zipFile) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError( - error, - 'lambda', - 'updateFunctionCode', - `arn:aws:lambda:${this.regionCode}:*:function:${name}` - ) - } - throw error - } - } -} - -/** - * Enhanced CloudFormation client wrapper that provides better error messages for permission issues - */ -export class EnhancedCloudFormationClient { - constructor( - private readonly client: CloudFormationClient, - private readonly regionCode: string - ) {} - - async describeStacks(params: DescribeStacksInput): Promise { - try { - return await this.client.send(new DescribeStacksCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'describeStacks', stackArn) - } - throw error - } - } - - async getTemplate(params: GetTemplateInput): Promise { - try { - return await this.client.send(new GetTemplateCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'getTemplate', stackArn) - } - throw error - } - } - - async createChangeSet(params: CreateChangeSetInput): Promise { - try { - return await this.client.send(new CreateChangeSetCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'createChangeSet', stackArn) - } - throw error - } - } - - async executeChangeSet(params: ExecuteChangeSetInput): Promise { - try { - return await this.client.send(new ExecuteChangeSetCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'executeChangeSet', stackArn) - } - throw error - } - } - - async describeChangeSet(params: DescribeChangeSetInput): Promise { - try { - return await this.client.send(new DescribeChangeSetCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'describeChangeSet', stackArn) - } - throw error - } - } - - async describeStackResources(params: DescribeStackResourcesInput): Promise { - try { - return await this.client.send(new DescribeStackResourcesCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'describeStackResources', stackArn) - } - throw error - } - } - - async describeStackResource(params: DescribeStackResourceInput): Promise { - try { - return await this.client.send(new DescribeStackResourceCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - const stackArn = params.StackName - ? `arn:aws:cloudformation:${this.regionCode}:*:stack/${params.StackName}/*` - : undefined - throw createEnhancedPermissionError(error, 'cloudformation', 'describeStackResource', stackArn) - } - throw error - } - } - - async getGeneratedTemplate(params: GetGeneratedTemplateInput): Promise { - try { - return await this.client.send(new GetGeneratedTemplateCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError(error, 'cloudformation', 'getGeneratedTemplate') - } - throw error - } - } - - async describeGeneratedTemplate(params: DescribeGeneratedTemplateInput): Promise { - try { - return await this.client.send(new DescribeGeneratedTemplateCommand(params)) - } catch (error) { - if (isPermissionError(error)) { - throw createEnhancedPermissionError(error, 'cloudformation', 'describeGeneratedTemplate') - } - throw error - } - } - - async waitFor(state: string, params: any): Promise { - try { - const waiterConfig = { - client: this.client, - maxWaitTime: 900, - } satisfies WaiterConfiguration - switch (state) { - case 'changeSetCreateComplete': - return await waitUntilChangeSetCreateComplete(waiterConfig, params) - case 'stackImportComplete': - return await waitUntilStackImportComplete(waiterConfig, params) - case 'stackUpdateComplete': - return await waitUntilStackUpdateComplete(waiterConfig, params) - default: - throw new Error(`Unsupported waiter state: ${state}`) - } - } catch (error) { - if (isPermissionError(error)) { - // For waitFor operations, we'll provide a generic permission error since the specific action varies - throw createEnhancedPermissionError(error, 'cloudformation', 'describeStacks') - } - throw error - } - } -} - -export async function runOpenTemplate(arg?: TreeNode) { - const templateUri = arg ? (arg.resource as SamAppLocation).samTemplateUri : await promptUserForTemplate() - if (!templateUri || !(await fs.exists(templateUri))) { - throw new ToolkitError('SAM Template not found, cannot open template', { code: 'NoTemplateProvided' }) - } - const document = await vscode.workspace.openTextDocument(templateUri) - await vscode.window.showTextDocument(document) -} - -/** - * Find and open the lambda handler with given ResourceNode - * If not found, a NoHandlerFound error will be raised - * @param arg ResourceNode - */ -export async function runOpenHandler(arg: ResourceNode): Promise { - const folderUri = path.dirname(arg.resource.location.fsPath) - const resource = arg.resource.resource - - if (!isFunctionResource(resource)) { - throw new ToolkitError('Resource is not a Lambda function', { code: 'NotAFunction' }) - } - - if (!resource.CodeUri) { - throw new ToolkitError('No CodeUri provided in template, cannot open handler', { code: 'NoCodeUriProvided' }) - } - - if (!resource.Handler) { - throw new ToolkitError('No Handler provided in template, cannot open handler', { code: 'NoHandlerProvided' }) - } - - if (!resource.Runtime) { - throw new ToolkitError('No Runtime provided in template, cannot open handler', { code: 'NoRuntimeProvided' }) - } - - const handlerFile = await getLambdaHandlerFile( - vscode.Uri.file(folderUri), - resource.CodeUri, - resource.Handler, - resource.Runtime as Runtime - ) - if (!handlerFile) { - throw new ToolkitError( - `No handler file found with name "${resource.Handler}". Ensure the file exists in the expected location."`, - { - code: 'NoHandlerFound', - } - ) - } - await vscode.workspace.openTextDocument(handlerFile).then(async (doc) => await vscode.window.showTextDocument(doc)) -} - -// create a set to store all supported runtime in the following function -const supportedRuntimeForHandler = new Set([ - RuntimeFamily.Ruby, - RuntimeFamily.Python, - RuntimeFamily.NodeJS, - RuntimeFamily.DotNet, - RuntimeFamily.Java, -]) - -/** - * Get the actual Lambda handler file, in vscode.Uri format, from the template - * file and handler name. If not found, return undefined. - * - * @param folderUri The root folder for sam project - * @param codeUri codeUri prop in sam template - * @param handler handler prop in sam template - * @param runtime runtime prop in sam template - * @returns - */ -export async function getLambdaHandlerFile( - folderUri: vscode.Uri, - codeUri: string, - handler: string, - runtime: Runtime -): Promise { - const family = getFamily(runtime) - if (!supportedRuntimeForHandler.has(family)) { - throw new ToolkitError(`Runtime ${runtime} is not supported for the 'Open handler' button`, { - code: 'RuntimeNotSupported', - }) - } - - // if this function is used to get handler from a just downloaded lambda function zip. codeUri will be '' - if (codeUri !== '') { - folderUri = vscode.Uri.joinPath(folderUri, codeUri) - } - - const handlerParts = handler.split('.') - // sample: app.lambda_handler -> app.rb - if (family === RuntimeFamily.Ruby) { - // Ruby supports namespace/class handlers as well, but the path is - // guaranteed to be slash-delimited so we can assume the first part is - // the path - return vscode.Uri.joinPath(folderUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.rb') - } - - // sample:app.lambda_handler -> app.py - if (family === RuntimeFamily.Python) { - // Otherwise (currently Node.js and Python) handle dot-delimited paths - return vscode.Uri.joinPath(folderUri, handlerParts.slice(0, handlerParts.length - 1).join('/') + '.py') - } - - // sample: app.handler -> app.mjs/app.js - // More likely to be mjs if NODEJS version>=18, now searching for both - if (family === RuntimeFamily.NodeJS) { - const handlerName = handlerParts.slice(0, handlerParts.length - 1).join('/') - const handlerPath = path.dirname(handlerName) - const handlerFile = path.basename(handlerName) - const pattern = new vscode.RelativePattern( - vscode.Uri.joinPath(folderUri, handlerPath), - `${handlerFile}.{js,mjs,cjs,ts}` - ) - return searchHandlerFile(folderUri, pattern) - } - // search directly under Code uri for Dotnet and java - // sample: ImageResize::ImageResize.Function::FunctionHandler -> Function.cs - if (family === RuntimeFamily.DotNet) { - const handlerName = path.basename(handler.split('::')[1].replaceAll('.', '/')) - const pattern = new vscode.RelativePattern(folderUri, `${handlerName}.cs`) - return searchHandlerFile(folderUri, pattern) - } - - // sample: resizer.App::handleRequest -> App.java - if (family === RuntimeFamily.Java) { - const handlerName = handler.split('::')[0].replaceAll('.', '/') - const pattern = new vscode.RelativePattern(folderUri, `**/${handlerName}.java`) - return searchHandlerFile(folderUri, pattern) - } -} - -/** - Searches for a handler file in the given pattern and returns the first match. - If no match is found, returns undefined. -*/ -export async function searchHandlerFile( - folderUri: vscode.Uri, - pattern: vscode.RelativePattern -): Promise { - const handlerFile = await vscode.workspace.findFiles(pattern, new vscode.RelativePattern(folderUri, '.aws-sam')) - if (handlerFile.length === 0) { - return undefined - } - if (handlerFile.length > 1) { - getLogger().warn(`Multiple handler files found with name "${path.basename(handlerFile[0].fsPath)}"`) - void showMessage('warn', `Multiple handler files found with name "${path.basename(handlerFile[0].fsPath)}"`) - } - if (await fs.exists(handlerFile[0])) { - return handlerFile[0] - } - return undefined -} - -async function promptUserForTemplate() { - const registry = await globals.templateRegistry - const openTemplateParams: Partial = {} - - const param = await new OpenTemplateWizard(openTemplateParams, registry).run() - return param?.template.uri -} - -export async function deployTypePrompt() { - const items: DataQuickPickItem[] = [ - { - label: 'Sync', - data: 'sync', - detail: 'Speed up your development and testing experience in the AWS Cloud. With the --watch parameter, sync will build, deploy and watch for local changes', - description: 'Development environments', - }, - { - label: 'Deploy', - data: 'deploy', - detail: 'Deploys your template through CloudFormation', - description: 'Production environments', - }, - ] - - const selected = await createQuickPick(items, { - title: localize('AWS.appBuilder.deployType.title', 'Select deployment command'), - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samDeployUrl), - }).prompt() - - if (!selected) { - getLogger().info('Operation cancelled.') - return - } - return selected -} - -export async function downloadUnzip(url: string, destination: vscode.Uri) { - const response = await fetch(url) - if (!response.ok) { - throw new Error(`Failed to download Lambda layer code: ${response.statusText}`) - } - - // Get the response as an ArrayBuffer - const arrayBuffer = await response.arrayBuffer() - const zipBuffer = Buffer.from(arrayBuffer) - - // Create AdmZip instance with the buffer - const zip = new AdmZip(zipBuffer) - - // Create output directory if it doesn't exist - if (!(await fs.exists(destination))) { - await fs.mkdir(destination) - } - - // Extract zip contents to output path - zip.extractAllTo(destination.fsPath, true) -} - -export function getLambdaClient(region: string): EnhancedLambdaClient { - const originalClient = new DefaultLambdaClient(region) - return new EnhancedLambdaClient(originalClient, region) -} - -export async function getCFNClient(regionCode: string): Promise { - const originalClient = globals.sdkClientBuilderV3.createAwsService({ - serviceClient: CloudFormationClient, - region: regionCode, - }) - return new EnhancedCloudFormationClient(originalClient, regionCode) -} diff --git a/packages/core/src/awsService/appBuilder/walkthrough.ts b/packages/core/src/awsService/appBuilder/walkthrough.ts deleted file mode 100644 index e1a0954864..0000000000 --- a/packages/core/src/awsService/appBuilder/walkthrough.ts +++ /dev/null @@ -1,380 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as semver from 'semver' -import * as vscode from 'vscode' -import globals from '../../shared/extensionGlobals' -import { getLogger } from '../../shared/logger/logger' - -import { Wizard } from '../../shared/wizards/wizard' -import { createQuickPick } from '../../shared/ui/pickerPrompter' -import { createCommonButtons } from '../../shared/ui/buttons' -import * as nls from 'vscode-nls' -import { ToolkitError } from '../../shared/errors' -import { createSingleFileDialog } from '../../shared/ui/common/openDialog' -import { fs } from '../../shared/fs/fs' -import path from 'path' -import { telemetry, ToolId } from '../../shared/telemetry/telemetry' - -import { minSamCliVersionForAppBuilderSupport } from '../../shared/sam/cli/samCliValidator' -import { SamCliInfoInvocation } from '../../shared/sam/cli/samCliInfo' -import { openUrl } from '../../shared/utilities/vsCodeUtils' -import { getOrInstallCli, awsClis, AwsClis } from '../../shared/utilities/cliUtils' -import { getPattern } from '../../shared/utilities/downloadPatterns' -import { addFolderToWorkspace } from '../../shared/utilities/workspaceUtils' - -const localize = nls.loadMessageBundle() -const serverlessLandUrl = 'https://serverlessland.com/' -export const walkthroughContextString = 'aws.toolkit.lambda.walkthroughSelected' -export const templateToOpenAppComposer = 'aws.toolkit.appComposer.templateToOpenOnStart' -const defaultTemplateName = 'template.yaml' -const serverlessLandOwner = 'aws-samples' -const serverlessLandRepo = 'serverless-patterns' - -type WalkthroughOptions = 'CustomTemplate' | 'Visual' | 'S3' | 'API' -type TutorialRuntimeOptions = 'python' | 'node' | 'java' | 'dotnet' | 'skipped' - -interface IServerlessLandProject { - asset: string - handler?: string -} - -export const appMap = new Map([ - ['APIdotnet', { asset: 'apigw-rest-api-lambda-dotnet.zip', handler: 'src/HelloWorld/Function.cs' }], - ['APInode', { asset: 'apigw-rest-api-lambda-node.zip', handler: 'hello_world/app.mjs' }], - ['APIpython', { asset: 'apigw-rest-api-lambda-python.zip', handler: 'hello_world/app.py' }], - [ - 'APIjava', - { - asset: 'apigw-rest-api-lambda-java.zip', - handler: 'HelloWorldFunction/src/main/java/helloworld/App.java', - }, - ], - ['S3dotnet', { asset: 's3-lambda-resizing-dotnet.zip', handler: 'ImageResize/Function.cs' }], - ['S3node', { asset: 's3-lambda-resizing-node.zip', handler: 'src/app.js' }], - ['S3python', { asset: 's3-lambda-resizing-python.zip', handler: 'src/app.py' }], - [ - 'S3java', - { - asset: 's3-lambda-resizing-java.zip', - handler: 'ResizerFunction/src/main/java/resizer/App.java', - }, - ], -]) - -export class RuntimeLocationWizard extends Wizard<{ - runtime: TutorialRuntimeOptions - dir: string - realDir: vscode.Uri -}> { - public constructor(skipRuntime: boolean, labelValue: string, existingTemplates?: vscode.Uri[]) { - super() - const form = this.form - - // step1: choose runtime - const items: { label: string; data: TutorialRuntimeOptions }[] = [ - { label: 'Python', data: 'python' }, - { label: 'Node JS', data: 'node' }, - { label: 'Java', data: 'java' }, - { label: 'Dot Net', data: 'dotnet' }, - ] - form.runtime.bindPrompter( - () => { - return createQuickPick(items, { - title: localize('AWS.toolkit.lambda.walkthroughSelectRuntime', 'Select a runtime'), - buttons: createCommonButtons(serverlessLandUrl), - }) - }, - { showWhen: () => !skipRuntime } - ) - - // step2: choose location for project - const wsFolders = vscode.workspace.workspaceFolders - const items2 = [ - { - label: localize('AWS.toolkit.lambda.walkthroughOpenExplorer', 'Open file explorer'), - data: 'file-selector', - }, - ] - - // if at least one open workspace, add all opened workspace as options - if (wsFolders && labelValue !== 'Open existing Project') { - for (const wsFolder of wsFolders) { - items2.push({ label: wsFolder.uri.fsPath, data: wsFolder.uri.fsPath }) - } - } - - if (wsFolders && existingTemplates && labelValue === 'Open existing Project') { - existingTemplates.map((file) => { - items2.push({ label: file.fsPath, data: path.dirname(file.fsPath) }) - }) - } - - form.dir.bindPrompter(() => { - return createQuickPick(items2, { - title: - labelValue === 'Open existing Project' - ? localize('AWS.toolkit.lambda.walkthroughOpenExistProject', 'Select an existing template file') - : localize('AWS.toolkit.lambda.walkthroughProjectLocation', 'Select a location for project'), - buttons: createCommonButtons(labelValue === 'Open existing Project' ? undefined : serverlessLandUrl), - }) - }) - - const options: vscode.OpenDialogOptions = { - canSelectMany: false, - openLabel: labelValue, - canSelectFiles: false, - canSelectFolders: true, - } - if (wsFolders) { - options.defaultUri = wsFolders[0]?.uri - } - - form.realDir.bindPrompter((state) => createSingleFileDialog(options), { - showWhen: (state) => state.dir !== undefined && state.dir === 'file-selector', - setDefault: (state) => (state.dir ? vscode.Uri.file(state.dir) : undefined), - }) - } -} - -export async function getTutorial( - runtime: TutorialRuntimeOptions, - project: WalkthroughOptions, - outputDir: vscode.Uri, - source?: string -): Promise { - const appSelected = appMap.get(project + runtime) - telemetry.record({ action: project + runtime, source: source ?? 'AppBuilderWalkthrough' }) - if (!appSelected) { - throw new ToolkitError(`Template '${project}+${runtime}' does not exist, choose another template.`) - } - - try { - await getPattern(serverlessLandOwner, serverlessLandRepo, appSelected.asset, outputDir, true) - } catch (error) { - throw new ToolkitError(`An error occurred while fetching this pattern from Serverless Land: ${error}`) - } -} - -/** - * Takes projectUri and runtime then generate matching project - * @param walkthroughSelected the selected walkthrough - * @param projectUri The choosen project uri to generate proejct - * @param runtime The runtime choosen - */ -export async function genWalkthroughProject( - walkthroughSelected: WalkthroughOptions, - projectUri: vscode.Uri, - runtime: TutorialRuntimeOptions | undefined -): Promise { - // create project here - // TODO update with file fetching from serverless land - if (walkthroughSelected === 'CustomTemplate') { - // customer already have a project, no need to generate - return - } - - // check if template.yaml already exists - const templateUri = vscode.Uri.joinPath(projectUri, defaultTemplateName) - if (await fs.exists(templateUri)) { - // ask if want to overwrite - const choice = await vscode.window.showInformationMessage( - localize( - 'AWS.toolkit.lambda.walkthroughCreateProjectPrompt', - '{0} already exist in the selected directory, overwrite?', - defaultTemplateName - ), - 'Yes', - 'No' - ) - if (choice !== 'Yes') { - throw new ToolkitError(`A file named ${defaultTemplateName} already exists in this path.`) - } - } - - // if Yes, or template not found, continue to generate - if (walkthroughSelected === 'Visual') { - // create an empty template.yaml, open it in appcomposer later - await fs.writeFile(templateUri, Buffer.from('')) - return - } - // start fetching project - if (runtime && runtime !== 'skipped') { - await getTutorial(runtime, walkthroughSelected, projectUri, 'AppBuilderWalkthrough') - } -} - -/** - * check if the selected project Uri exist in current workspace. If not, add Project folder to Workspace - * @param projectUri uri for the selected project - */ -export async function openProjectInWorkspace(projectUri: vscode.Uri): Promise { - let templateUri: vscode.Uri | undefined = vscode.Uri.joinPath(projectUri, defaultTemplateName) - if (!(await fs.exists(templateUri))) { - // no template.yaml, trying yml - templateUri = vscode.Uri.joinPath(projectUri, 'template.yml') - if (!(await fs.exists(templateUri))) { - templateUri = undefined - } - } - - // Open template file, and after update workspace folder - if (templateUri) { - await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') - await vscode.window.showTextDocument(await vscode.workspace.openTextDocument(templateUri)) - // set global key to template to be opened, appComposer will open them upon reload - await globals.globalState.update(templateToOpenAppComposer, [templateUri.fsPath]) - } - - // if extension is reloaded here, this function will never return (killed) - await addFolderToWorkspace({ uri: projectUri, name: path.basename(projectUri.fsPath) }, true) - - // Open template file - if (templateUri) { - // extension not reloaded, set to false - await globals.globalState.update(templateToOpenAppComposer, undefined) - await vscode.commands.executeCommand('aws.openInApplicationComposer', templateUri) - } - - // Open Readme if exist - if (await fs.exists(vscode.Uri.joinPath(projectUri, 'README.md'))) { - await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup') - await vscode.commands.executeCommand('markdown.showPreview', vscode.Uri.joinPath(projectUri, 'README.md')) - } -} - -/** - * Used in Toolkit Appbuilder Walkthrough. - * 1: Customer select a template - * 2: Create project / Or don't create if customer choose use my own template - * 3: Add project to workspace, Open template.yaml, open template.yaml in AppComposer - */ -export async function initWalkthroughProjectCommand() { - const walkthroughSelected = globals.globalState.get(walkthroughContextString) - let runtimeSelected: TutorialRuntimeOptions | undefined = undefined - try { - if (!walkthroughSelected || !(typeof walkthroughSelected === 'string')) { - getLogger().info('No walkthrough selected - exiting') - void vscode.window.showErrorMessage( - localize('AWS.toolkit.lambda.walkthroughNotSelected', 'Select a template in the walkthrough.') - ) - return - } - let labelValue = 'Create Project' - if (walkthroughSelected === 'CustomTemplate') { - labelValue = 'Open existing Project' - } - // if these two, skip runtime choice - const skipRuntimeChoice = walkthroughSelected === 'Visual' || walkthroughSelected === 'CustomTemplate' - const templates: vscode.Uri[] = - walkthroughSelected === 'CustomTemplate' - ? await vscode.workspace.findFiles('**/{template.yaml,template.yml}', '**/.aws-sam/*') - : [] - const result = await new RuntimeLocationWizard(skipRuntimeChoice, labelValue, templates).run() - if (!result) { - getLogger().info('User canceled the runtime selection process via quickpick') - return - } - - if (!result.realDir || !fs.exists(result.realDir)) { - // exit for non-vaild uri - getLogger().info('exit on customer fileselector cancellation') - return - } - - runtimeSelected = result.runtime - - // generate project - await genWalkthroughProject(walkthroughSelected, result.realDir, runtimeSelected) - // open a workspace if no workspace yet - await openProjectInWorkspace(result.realDir) - } finally { - telemetry.record({ action: `${walkthroughSelected}:${runtimeSelected}`, source: 'AppBuilderWalkthrough' }) - } -} - -export async function getOrUpdateOrInstallSAMCli(source: string) { - try { - // find sam - const samPath = await getOrInstallCli('sam-cli', true, true) - // check version - const samCliVersion = (await new SamCliInfoInvocation(samPath).execute()).version - - if (semver.lt(samCliVersion, minSamCliVersionForAppBuilderSupport)) { - // sam found but version too low - const updateInstruction = localize( - 'AWS.toolkit.updateSAMInstruction', - 'View AWS SAM CLI update instructions' - ) - const selection = await vscode.window.showInformationMessage( - localize( - 'AWS.toolkit.samOutdatedPrompt', - 'AWS SAM CLI version {0} or greater is required ({1} currently installed).', - minSamCliVersionForAppBuilderSupport, - samCliVersion - ), - updateInstruction - ) - if (selection === updateInstruction) { - void openUrl(vscode.Uri.parse(awsClis['sam-cli'].manualInstallLink)) - } - } - } catch (err) { - throw ToolkitError.chain(err, 'Failed to install or detect SAM.') - } finally { - telemetry.record({ source: source, toolId: 'sam-cli' }) - } -} - -/** - * wraps getOrinstallCli and send telemetry - * @param toolId to install/check - * @param source to be added in telemetry - */ -export async function getOrInstallCliWrapper(toolId: AwsClis, source: string) { - await telemetry.appBuilder_installTool.run(async (span) => { - span.record({ source: source, toolId: toolId }) - if (toolId === 'sam-cli') { - await getOrUpdateOrInstallSAMCli(source) - return - } - try { - await getOrInstallCli(toolId, true, true) - } finally { - telemetry.record({ source: source, toolId: toolId }) - } - }) -} - -export async function installLocalStackExtension(source: string) { - await telemetry.appBuilder_installTool.run(async (span) => { - // TODO: Update `ToolId` accepted values: https://github.com/aws/aws-toolkit-common/blob/8c88537fae2ac7e6524fb2b29ae336c606850eeb/telemetry/definitions/commonDefinitions.json#L2215-L2221 - // @ts-ignore - const toolId: ToolId = 'localstack' - span.record({ source, toolId }) - const extensionId = 'localstack.localstack' - const extension = vscode.extensions.getExtension(extensionId) - if (extension) { - void vscode.window.showInformationMessage( - localize( - 'AWS.toolkit.lambda.walkthrough.localStackExtension.alreadyInstalled', - 'LocalStack extension is already installed' - ) - ) - } else { - try { - await vscode.commands.executeCommand('workbench.extensions.installExtension', extensionId) - void vscode.window.showInformationMessage( - localize( - 'AWS.toolkit.lambda.walkthrough.localStackExtension.installSuccessful', - 'LocalStack extension has been installed' - ) - ) - } catch (err) { - throw ToolkitError.chain(err, 'Failed to install LocalStack extension') - } - } - }) -} diff --git a/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts b/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts deleted file mode 100644 index fbaec4657c..0000000000 --- a/packages/core/src/awsService/appBuilder/wizards/deployTypeWizard.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { samDeployUrl } from '../../../shared/constants' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { DataQuickPickItem, createQuickPick } from '../../../shared/ui/pickerPrompter' -import * as nls from 'vscode-nls' -import { Wizard } from '../../../shared/wizards/wizard' -import { DeployParams, DeployWizard } from '../../../shared/sam/deploy' -import { SyncParams, SyncWizard } from '../../../shared/sam/sync' -import { WizardPrompter } from '../../../shared/ui/wizardPrompter' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -const localize = nls.loadMessageBundle() - -export class DeployTypeWizard extends Wizard<{ - choice: string - syncParam: SyncParams - deployParam: DeployParams -}> { - public constructor(syncWizard: SyncWizard, deployWizard: DeployWizard) { - super({ exitPrompterProvider: createExitPrompter }) - const form = this.form - - const items: DataQuickPickItem[] = [ - { - label: 'Sync', - data: 'sync', - detail: 'Speed up your development and testing experience in the AWS Cloud. With the --watch parameter, sync will build, deploy and watch for local changes', - description: 'Development environments', - }, - { - label: 'Deploy', - data: 'deploy', - detail: 'Deploys your template through CloudFormation', - description: 'Production environments', - }, - ] - form.choice.bindPrompter(() => { - return createQuickPick(items, { - title: localize('AWS.appBuilder.deployType.title', 'Select deployment command'), - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samDeployUrl), - }) - }) - form.deployParam.bindPrompter((state) => new WizardPrompter(deployWizard), { - showWhen: (state) => state.choice === 'deploy', - }) - form.syncParam.bindPrompter((state) => new WizardPrompter(syncWizard), { - showWhen: (state) => state.choice === 'sync', - }) - } -} diff --git a/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts b/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts deleted file mode 100644 index 9641562d6f..0000000000 --- a/packages/core/src/awsService/appBuilder/wizards/templateParametersWizard.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { Wizard } from '../../../shared/wizards/wizard' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { getRecentResponse, updateRecentResponse } from '../../../shared/sam/utils' -import { getParameters } from '../../../lambda/config/parameterUtils' - -export interface TemplateParametersForm { - [key: string]: any -} - -export class TemplateParametersWizard extends Wizard { - template: vscode.Uri - preloadedTemplate: CloudFormation.Template | undefined - samTemplateParameters: Map | undefined - samCommandUrl: vscode.Uri - commandMementoRootKey: string - - public constructor(template: vscode.Uri, samCommandUrl: vscode.Uri, commandMementoRootKey: string) { - super({ exitPrompterProvider: createExitPrompter }) - this.template = template - this.samCommandUrl = samCommandUrl - this.commandMementoRootKey = commandMementoRootKey - } - - public override async init(): Promise { - this.samTemplateParameters = await getParameters(this.template) - this.preloadedTemplate = await CloudFormation.load(this.template.fsPath) - const samTemplateNames = new Set(this.samTemplateParameters?.keys() ?? []) - - for (const name of samTemplateNames) { - if (this.preloadedTemplate) { - const defaultValue = this.preloadedTemplate.Parameters - ? (this.preloadedTemplate.Parameters[name]?.Default as string) - : undefined - this.form[name].bindPrompter(() => - this.createParamPromptProvider(name, defaultValue).transform(async (item) => { - await updateRecentResponse(this.commandMementoRootKey, this.template.fsPath, name, item) - return item - }) - ) - } - } - - return this - } - - createParamPromptProvider(name: string, defaultValue: string | undefined) { - return createInputBox({ - title: `Specify SAM Template parameter value for ${name}`, - buttons: createCommonButtons(this.samCommandUrl), - value: getRecentResponse(this.commandMementoRootKey, this.template.fsPath, name) ?? defaultValue, - }) - } -} diff --git a/packages/core/src/awsService/apprunner/activation.ts b/packages/core/src/awsService/apprunner/activation.ts deleted file mode 100644 index 5b907d2b96..0000000000 --- a/packages/core/src/awsService/apprunner/activation.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { AppRunnerServiceNode } from './explorer/apprunnerServiceNode' -import { getLogger } from '../../shared/logger/logger' -import { createAppRunnerService } from './commands/createService' -import { pauseService } from './commands/pauseService' -import { deleteService } from './commands/deleteService' -import { createFromEcr } from './commands/createServiceFromEcr' -import { ExtContext } from '../../shared/extensions' -import { copyToClipboard } from '../../shared/utilities/messages' -import { Commands } from '../../shared/vscode/commands2' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Result } from '../../shared/telemetry/telemetry' - -const localize = nls.loadMessageBundle() - -const commandMap = new Map<[command: string, errorMessage: string], (...args: any) => Promise>() - -const createServiceFailed = localize('aws.apprunner.createService.failed', 'Failed to create App Runner service') -const createServiceEcrFailed = localize( - 'aws.apprunner.createServiceFromEcr.failed', - 'Failed to create App Runner service from ECR' -) -const pauseServiceFailed = localize('aws.apprunner.pauseService.failed', 'Failed to pause App Runner service') -const resumeServiceFailed = localize('aws.apprunner.resumeService.failed', 'Failed to resume App Runner service') -const copyServiceUrlFailed = localize('aws.apprunner.copyServiceUrl.failed', 'Failed to copy App Runner service URL') -const openServiceFailed = localize('aws.apprunner.open.failed', 'Failed to open App Runner service') -const deployServiceFailed = localize( - 'aws.apprunner.startDeployment.failed', - 'Failed to start deployment of App Runner service' -) -const deleteServiceFailed = localize('aws.apprunner.deleteService.failed', 'Failed to delete App Runner service') - -const copyUrl = async (node: AppRunnerServiceNode) => { - await copyToClipboard(node.url, 'URL') - telemetry.apprunner_copyServiceUrl.emit({ passive: false }) -} -const openUrl = async (node: AppRunnerServiceNode) => { - await vscode.env.openExternal(vscode.Uri.parse(node.url)) - telemetry.apprunner_openServiceUrl.emit({ passive: false }) -} - -const resumeService = async (node: AppRunnerServiceNode) => { - let telemetryResult: Result = 'Failed' - try { - await node.resume() - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_resumeService.emit({ result: telemetryResult, passive: false }) - } -} - -const deployService = async (node: AppRunnerServiceNode) => { - let telemetryResult: Result = 'Failed' - try { - await node.deploy() - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_startDeployment.emit({ result: telemetryResult, passive: false }) - } -} - -commandMap.set(['aws.apprunner.createService', createServiceFailed], createAppRunnerService) -commandMap.set(['aws.apprunner.createServiceFromEcr', createServiceEcrFailed], createFromEcr) -commandMap.set(['aws.apprunner.pauseService', pauseServiceFailed], pauseService) -commandMap.set(['aws.apprunner.resumeService', resumeServiceFailed], resumeService) -commandMap.set(['aws.apprunner.copyServiceUrl', copyServiceUrlFailed], copyUrl) -commandMap.set(['aws.apprunner.open', openServiceFailed], openUrl) -commandMap.set(['aws.apprunner.startDeployment', deployServiceFailed], deployService) -commandMap.set(['aws.apprunner.deleteService', deleteServiceFailed], deleteService) - -/** - * Activates App Runner - */ -export async function activate(context: ExtContext): Promise { - for (const [tuple, command] of commandMap.entries()) { - context.extensionContext.subscriptions.push( - Commands.register(tuple[0], async (...args: any) => { - try { - await command(...args) - } catch (err) { - getLogger().error(`${tuple[1]}: %s`, err) - await showViewLogsMessage(tuple[1]) - } - }) - ) - } -} diff --git a/packages/core/src/awsService/apprunner/commands/createService.ts b/packages/core/src/awsService/apprunner/commands/createService.ts deleted file mode 100644 index cafe328ffc..0000000000 --- a/packages/core/src/awsService/apprunner/commands/createService.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { telemetry } from '../../../shared/telemetry/telemetry' -import { AppRunnerServiceSource, Result } from '../../../shared/telemetry/telemetry' -import { AppRunnerNode } from '../explorer/apprunnerNode' -import { CreateAppRunnerServiceWizard } from '../wizards/apprunnerCreateServiceWizard' - -export async function createAppRunnerService(node: AppRunnerNode): Promise { - let telemetryResult: Result = 'Failed' - let source: AppRunnerServiceSource | undefined = undefined - - try { - const wizard = new CreateAppRunnerServiceWizard(node.regionCode) - const result = await wizard.run() - if (result === undefined) { - telemetryResult = 'Cancelled' - return - } - - await node.createService(result) - source = - result.SourceConfiguration.CodeRepository !== undefined - ? 'repository' - : result.SourceConfiguration.ImageRepository !== undefined - ? result.SourceConfiguration.ImageRepository.ImageRepositoryType === 'ECR_PUBLIC' - ? 'ecrPublic' - : 'ecr' - : undefined - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_createService.emit({ - result: telemetryResult, - // If we cancel there is no source type (so this should be optional) - appRunnerServiceSource: source as any, - passive: false, - }) - } -} diff --git a/packages/core/src/awsService/apprunner/commands/createServiceFromEcr.ts b/packages/core/src/awsService/apprunner/commands/createServiceFromEcr.ts deleted file mode 100644 index dfd2c3df09..0000000000 --- a/packages/core/src/awsService/apprunner/commands/createServiceFromEcr.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { EcrRepositoryNode } from '../../../awsService/ecr/explorer/ecrRepositoryNode' -import { EcrTagNode } from '../../../awsService/ecr/explorer/ecrTagNode' - -import { CreateAppRunnerServiceWizard } from '../wizards/apprunnerCreateServiceWizard' -import { AppRunnerClient } from '../../../shared/clients/apprunner' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { Result } from '../../../shared/telemetry/telemetry' - -export async function createFromEcr( - node: EcrTagNode | EcrRepositoryNode, - client = new AppRunnerClient(node.regionCode) -): Promise { - let telemetryResult: Result = 'Failed' - - try { - const ecrNode = (node as any).tag === undefined ? (node as EcrRepositoryNode) : (node as EcrTagNode).parent - const wizard = new CreateAppRunnerServiceWizard(node.regionCode, { - SourceConfiguration: { - ImageRepository: { - ImageIdentifier: `${ecrNode.repository.repositoryUri}:${(node as any).tag ?? 'latest'}`, - ImageRepositoryType: 'ECR', - ImageConfiguration: {}, - }, - AuthenticationConfiguration: {}, - }, - }) - const result = await wizard.run() - - if (result === undefined) { - telemetryResult = 'Cancelled' - return - } - - await client.createService(result) - await vscode.commands.executeCommand('aws.refreshAwsExplorer', true) - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_createService.emit({ - result: telemetryResult, - appRunnerServiceSource: 'ecr', - passive: false, - }) - } - - return undefined -} diff --git a/packages/core/src/awsService/apprunner/commands/deleteService.ts b/packages/core/src/awsService/apprunner/commands/deleteService.ts deleted file mode 100644 index 51209d1dd7..0000000000 --- a/packages/core/src/awsService/apprunner/commands/deleteService.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createHelpButton } from '../../../shared/ui/buttons' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { AppRunnerServiceNode } from '../explorer/apprunnerServiceNode' -import * as nls from 'vscode-nls' -import { isValidResponse } from '../../../shared/wizards/wizard' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { AppRunnerServiceStatus, Result } from '../../../shared/telemetry/telemetry' -const localize = nls.loadMessageBundle() - -function validateName(name: string) { - if (name !== 'delete') { - return localize('AWS.apprunner.deleteService.name.invalid', `Type 'delete' to confirm`) - } - - return undefined -} - -export async function deleteService(node: AppRunnerServiceNode): Promise { - let telemetryResult: Result = 'Failed' - const appRunnerServiceStatus = node.info.Status as AppRunnerServiceStatus - - try { - const inputBox = createInputBox({ - title: localize('AWS.apprunner.deleteService.title', 'Delete App Runner service'), - placeholder: localize('AWS.apprunner.deleteService.placeholder', 'delete'), - buttons: [createHelpButton('https://docs.aws.amazon.com/apprunner/latest/dg/manage-delete.html')], - validateInput: validateName, - }) - - const userInput = await inputBox.prompt() - - if (!isValidResponse(userInput)) { - telemetryResult = 'Cancelled' - return - } - - await node.delete() - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_deleteService.emit({ - result: telemetryResult, - appRunnerServiceStatus, - passive: false, - }) - } -} diff --git a/packages/core/src/awsService/apprunner/commands/pauseService.ts b/packages/core/src/awsService/apprunner/commands/pauseService.ts deleted file mode 100644 index b2cbdee1ca..0000000000 --- a/packages/core/src/awsService/apprunner/commands/pauseService.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as localizedText from '../../../shared/localizedText' -import { showConfirmationMessage } from '../../../shared/utilities/messages' -import { AppRunnerServiceNode } from '../explorer/apprunnerServiceNode' -import { ToolkitPromptSettings } from '../../../shared/settings' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { Result } from '../../../shared/telemetry/telemetry' - -export async function pauseService(node: AppRunnerServiceNode): Promise { - let telemetryResult: Result = 'Failed' - - try { - const prompts = ToolkitPromptSettings.instance - const shouldNotify = prompts.isPromptEnabled('apprunnerNotifyPause') - const notifyPrompt = localize( - 'aws.apprunner.pauseService.notify', - 'Your service will be unavailable while paused. ' + - 'You can resume the service once the pause operation is complete.' - ) - const confirmationOptions = { prompt: notifyPrompt, confirm: localizedText.ok, cancel: localizedText.cancel } - - if (shouldNotify && !(await showConfirmationMessage(confirmationOptions))) { - telemetryResult = 'Cancelled' - return - } - - await node.pause() - telemetryResult = 'Succeeded' - } finally { - telemetry.apprunner_pauseService.emit({ - result: telemetryResult, - passive: false, - }) - } -} diff --git a/packages/core/src/awsService/apprunner/explorer/apprunnerNode.ts b/packages/core/src/awsService/apprunner/explorer/apprunnerNode.ts deleted file mode 100644 index 6e8bc49647..0000000000 --- a/packages/core/src/awsService/apprunner/explorer/apprunnerNode.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { compareTreeItems, makeChildrenNodes } from '../../../shared/treeview/utils' -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { AppRunnerServiceNode } from './apprunnerServiceNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import * as nls from 'vscode-nls' -import { AppRunnerClient, CreateServiceRequest, ServiceSummary } from '../../../shared/clients/apprunner' -import { PollingSet } from '../../../shared/utilities/pollingSet' - -const localize = nls.loadMessageBundle() - -export class AppRunnerNode extends AWSTreeNodeBase { - private readonly serviceNodes: Map = new Map() - private readonly pollingSet: PollingSet = new PollingSet(20000, this.refresh.bind(this)) - - public constructor( - public override readonly regionCode: string, - public readonly client: AppRunnerClient - ) { - super('App Runner', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsAppRunnerNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.serviceNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.apprunner.noServices', '[No App Runner services found]') - ), - sort: (nodeA, nodeB) => compareTreeItems(nodeA, nodeB), - }) - } - - private async getServiceSummaries(): Promise { - // TODO: avoid resolving all services at once. - const serviceCollection = this.client.paginateServices({}) - return await serviceCollection.flatten().promise() - } - - public async updateChildren(): Promise { - const serviceSummaries = await this.getServiceSummaries() - const deletedNodeArns = new Set(this.serviceNodes.keys()) - - await Promise.all( - serviceSummaries.map(async (summary) => { - if (this.serviceNodes.has(summary.ServiceArn)) { - this.serviceNodes.get(summary.ServiceArn)!.update(summary) - if (summary.Status !== 'OPERATION_IN_PROGRESS') { - this.pollingSet.delete(summary.ServiceArn) - this.pollingSet.clearTimer() - } - } else { - this.serviceNodes.set(summary.ServiceArn, new AppRunnerServiceNode(this, this.client, summary)) - } - deletedNodeArns.delete(summary.ServiceArn) - }) - ) - - // eslint-disable-next-line unicorn/no-array-for-each - deletedNodeArns.forEach(this.deleteNode.bind(this)) - } - - public startPollingNode(id: string): void { - this.pollingSet.add(id) - } - - public stopPollingNode(id: string): void { - this.pollingSet.delete(id) - this.serviceNodes.get(id)?.refresh() - this.pollingSet.clearTimer() - } - - public deleteNode(id: string): void { - this.serviceNodes.delete(id) - this.pollingSet.delete(id) - } - - public async createService(request: CreateServiceRequest): Promise { - await this.client.createService(request) - this.refresh() - } -} diff --git a/packages/core/src/awsService/apprunner/explorer/apprunnerServiceNode.ts b/packages/core/src/awsService/apprunner/explorer/apprunnerServiceNode.ts deleted file mode 100644 index 7a50f6f53f..0000000000 --- a/packages/core/src/awsService/apprunner/explorer/apprunnerServiceNode.ts +++ /dev/null @@ -1,186 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import AsyncLock from 'async-lock' -import { AppRunnerClient, ServiceSummary } from '../../../shared/clients/apprunner' -import { AppRunnerNode } from './apprunnerNode' - -import { toArrayAsync, toMap } from '../../../shared/utilities/collectionUtils' -import { CloudWatchLogsBase } from '../../../awsService/cloudWatchLogs/explorer/cloudWatchLogsNode' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' - -import * as nls from 'vscode-nls' -import { getLogger } from '../../../shared/logger/logger' -import { getIcon } from '../../../shared/icons' -import * as AppRunner from '@aws-sdk/client-apprunner' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { LogGroup } from '@aws-sdk/client-cloudwatch-logs' -const localize = nls.loadMessageBundle() - -const contextBase = 'awsAppRunnerServiceNode' - -const operationStatus = { - START_DEPLOYMENT: localize('AWS.apprunner.operationStatus.deploy', 'Deploying...'), // eslint-disable-line @typescript-eslint/naming-convention - CREATE_SERVICE: localize('AWS.apprunner.operationStatus.create', 'Creating...'), // eslint-disable-line @typescript-eslint/naming-convention - PAUSE_SERVICE: localize('AWS.apprunner.operationStatus.pause', 'Pausing...'), // eslint-disable-line @typescript-eslint/naming-convention - RESUME_SERVICE: localize('AWS.apprunner.operationStatus.resume', 'Resuming...'), // eslint-disable-line @typescript-eslint/naming-convention - DELETE_SERVICE: localize('AWS.apprunner.operationStatus.delete', 'Deleting...'), // eslint-disable-line @typescript-eslint/naming-convention - UPDATE_SERVICE: localize('AWS.apprunner.operationStatus.update', 'Updating...'), // eslint-disable-line @typescript-eslint/naming-convention -} - -type ServiceOperation = keyof typeof operationStatus - -export class AppRunnerServiceNode extends CloudWatchLogsBase implements AWSResourceNode { - public readonly name: string - public readonly arn: string - private readonly lock: AsyncLock = new AsyncLock() - protected readonly placeholderMessage = localize('AWS.explorerNode.apprunner.nologs', '[No App Runner logs found]') - - constructor( - public readonly parent: AppRunnerNode, - private readonly client: AppRunnerClient, - private _info: ServiceSummary, - private currentOperation: AppRunner.OperationSummary & { Type?: ServiceOperation } = {}, - cloudwatchClient = new CloudWatchLogsClient(client.regionCode) - ) { - super('App Runner Service', parent.regionCode, cloudwatchClient) - - this.iconPath = getIcon('aws-apprunner-service') - this.id = `AppRunnerService-${_info.ServiceArn}` - this.name = _info.ServiceName - this.arn = _info.ServiceArn - - this.update(_info) - } - - public get info(): Readonly { - return this._info - } - - public get url(): string { - return `https://${this._info.ServiceUrl}` - } - - protected async getLogGroups(): Promise> { - return toMap( - await toArrayAsync( - this.cloudwatchClient.describeLogGroups({ - logGroupNamePrefix: `/aws/apprunner/${this._info.ServiceName}/${this._info.ServiceId}`, - }) - ), - (configuration) => configuration.logGroupName - ) - } - - private setLabel(): void { - const displayStatus = this.currentOperation.Type - ? operationStatus[this.currentOperation.Type] - : `${this._info.Status.charAt(0)}${this._info.Status.slice(1).toLowerCase().replace(/\_/g, ' ')}` - this.label = `${this._info.ServiceName} [${displayStatus}]` - } - - public update(info: ServiceSummary): void { - // update can be called multiple times during an event loop - // this would rarely cause the node's status to appear as 'Operation in progress' - this.lock - .acquire(this._info.ServiceId, (done) => { - const lastLabel = this.label - this.updateInfo(info) - this.updateStatus(typeof lastLabel === 'string' ? lastLabel : lastLabel?.label) - done() - }) - .catch((e) => { - getLogger().error('AsyncLock.acquire failed: %s', e.message) - }) - } - - private updateStatus(lastLabel?: string): void { - if (this.label !== lastLabel && lastLabel !== 'App Runner Service') { - this.refresh() - } - - if (this._info.Status === 'DELETED') { - this.parent.deleteNode(this._info.ServiceArn) - this.parent.refresh() - } - - if (this._info.Status === 'OPERATION_IN_PROGRESS') { - this.parent.startPollingNode(this._info.ServiceArn) - } else if (this.currentOperation.Type !== undefined) { - this.currentOperation.Id = undefined - this.currentOperation.Type = undefined - this.setLabel() - this.parent.stopPollingNode(this._info.ServiceArn) - } - } - - private async updateOperation(): Promise { - return this.client - .listOperations({ MaxResults: 1, ServiceArn: this._info.ServiceArn }) - .then((resp) => { - const operations = resp.OperationSummaryList - const operation = operations && operations[0]?.EndedAt === undefined ? operations[0] : undefined - if (operation !== undefined) { - this.setOperation(this._info, operation.Id!, operation.Type as any) - } - }) - .catch((err) => { - // Apparently App Runner can rarely list deleted services with the wrong status - getLogger().warn( - `Failed to list operations for service "${this._info.ServiceName}", service may be in an unstable state.` - ) - getLogger().debug(`Failed to list operations for service "${this.arn}": %s`, err) - }) - } - - private updateInfo(info: ServiceSummary): void { - if (info.Status === 'OPERATION_IN_PROGRESS' && this.currentOperation.Type === undefined) { - // Asynchronous since it is not currently possible for race-conditions to occur with updating operations - void this.updateOperation() - } - - this._info = Object.assign(this._info, info) - this.contextValue = `${contextBase}.${this._info.Status}` - this.setLabel() - } - - public async pause(): Promise { - const resp = await this.client.pauseService({ ServiceArn: this._info.ServiceArn }) - this.setOperation(resp.Service, resp.OperationId, 'PAUSE_SERVICE') - } - - public async resume(): Promise { - const resp = await this.client.resumeService({ ServiceArn: this._info.ServiceArn }) - this.setOperation(resp.Service, resp.OperationId, 'RESUME_SERVICE') - } - - public async delete(): Promise { - const resp = await this.client.deleteService({ ServiceArn: this._info.ServiceArn }) - this.setOperation(resp.Service, resp.OperationId, 'DELETE_SERVICE') - } - - public async updateService(request: AppRunner.UpdateServiceRequest): Promise { - const resp = await this.client.updateService({ ...request, ServiceArn: this._info.ServiceArn }) - this.setOperation(resp.Service, resp.OperationId, 'UPDATE_SERVICE') - } - - public async deploy(): Promise { - const resp = await this.client.startDeployment({ ServiceArn: this._info.ServiceArn }) - this.setOperation(this._info, resp.OperationId, 'START_DEPLOYMENT') - } - - // eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents - public setOperation(info: ServiceSummary, id?: string, type?: ServiceOperation): void { - this.currentOperation.Id = id - this.currentOperation.Type = type - this.update(info) - } - - public async describe(): Promise { - const resp = await this.client.describeService({ ServiceArn: this.arn }) - this.update(resp.Service) - return this._info - } -} diff --git a/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts b/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts deleted file mode 100644 index 913fdf93be..0000000000 --- a/packages/core/src/awsService/apprunner/wizards/apprunnerCreateServiceWizard.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import { createCommonButtons, QuickInputToggleButton } from '../../../shared/ui/buttons' -import * as input from '../../../shared/ui/inputPrompter' -import * as picker from '../../../shared/ui/pickerPrompter' -import { Prompter } from '../../../shared/ui/prompter' -import { Wizard, WizardState } from '../../../shared/wizards/wizard' -import { AppRunnerImageRepositoryWizard } from './imageRepositoryWizard' -import { AppRunnerCodeRepositoryWizard } from './codeRepositoryWizard' -import { GitExtension } from '../../../shared/extensions/git' -import { makeDeploymentButton } from './deploymentButton' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -import { IamClient } from '../../../shared/clients/iam' -import { DefaultEcrClient } from '../../../shared/clients/ecrClient' -import { AppRunnerClient, CreateServiceRequest, SourceConfiguration } from '../../../shared/clients/apprunner' -import { getAppRunnerCreateServiceDocUrl } from '../../../shared/extensionUtilities' -import * as AppRunner from '@aws-sdk/client-apprunner' - -const localize = nls.loadMessageBundle() - -// I'm sure this code could be reused in many places -const validateName = (name: string) => { - const badNameRegExp = /[^A-Za-z0-9-_]/g - if (!name || name.length < 4) { - return localize('AWS.apprunner.createService.name.validation', 'Service names must be at least 4 characters') - } else if (name.length > 40) { - return localize( - 'AWS.apprunner.createService.name.validationExceeds', - 'Service names cannot be more than 40 characters' - ) - } else if (name.match(/\s/)) { - return localize( - 'AWS.apprunner.createService.name.validationWhitespace', - 'Service names cannot contain whitespace' - ) - } - - let matches: string[] | undefined = name.match(badNameRegExp) ?? undefined - if (name[0] === '_' || name[0] === '-') { - matches = matches ? [name[0]].concat(matches) : [name[0]] - } - if (matches && matches.length > 0) { - return localize( - 'AWS.apprunner.createService.name.validationBadChar', - 'Invalid character(s): {0}', - Array.from(new Set(matches)).join('') - ) - } - - return undefined -} - -function createInstanceStep(): Prompter { - const enumerations = [ - [1, 2], - [1, 3], - [2, 4], - ] - - const items: picker.DataQuickPickItem[] = enumerations.map((e) => ({ - label: `${e[0]} vCPUs, ${e[1]} GBs Memory`, - data: { Cpu: `${e[0]} vCPU`, Memory: `${e[1]} GB` }, - })) - - return picker.createQuickPick(items, { - title: localize('AWS.apprunner.createService.selectInstanceConfig.title', 'Select instance configuration'), - buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), - }) -} - -function createSourcePrompter( - autoDeployButton: QuickInputToggleButton -): Prompter { - const ecrPath = { - label: 'ECR', - data: { ImageRepository: {} } as SourceConfiguration, - detail: localize( - 'AWS.apprunner.createService.ecr.detail', - 'Create a service from a public or private Elastic Container Registry repository' - ), - } - - const repositoryPath = { - label: 'Repository', - data: { CodeRepository: {} } as SourceConfiguration, - detail: localize('AWS.apprunner.createService.repository.detail', 'Create a service from a GitHub repository'), - } - - return picker.createQuickPick([ecrPath, repositoryPath], { - title: localize('AWS.apprunner.createService.sourceType.title', 'Select a source code location type'), - buttons: [autoDeployButton, ...createCommonButtons(getAppRunnerCreateServiceDocUrl())], - }) -} - -export class CreateAppRunnerServiceWizard extends Wizard { - public constructor( - region: string, - initState: WizardState = {}, - implicitState: WizardState = {}, - clients = { - iam: new IamClient(region), - ecr: new DefaultEcrClient(region), - apprunner: new AppRunnerClient(region), - } - ) { - super({ - initState, - implicitState, - exitPrompterProvider: createExitPrompter, - }) - - const autoDeployButton = makeDeploymentButton() - const gitExtension = GitExtension.instance - const codeRepositoryWizard = new AppRunnerCodeRepositoryWizard( - clients.apprunner, - gitExtension, - autoDeployButton - ) - const imageRepositoryWizard = new AppRunnerImageRepositoryWizard(clients.ecr, clients.iam, autoDeployButton) - - const form = this.form - - form.SourceConfiguration.bindPrompter(() => createSourcePrompter(autoDeployButton)) - - form.SourceConfiguration.applyBoundForm(imageRepositoryWizard.boundForm, { - showWhen: (state) => state.SourceConfiguration?.ImageRepository !== undefined, - }) - form.SourceConfiguration.applyBoundForm(codeRepositoryWizard.boundForm, { - showWhen: (state) => state.SourceConfiguration?.CodeRepository !== undefined, - }) - - form.ServiceName.bindPrompter(() => - input.createInputBox({ - title: localize('AWS.apprunner.createService.name.title', 'Name your service'), - validateInput: validateName, // TODO: we can check if names match any already made services - buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), - }) - ) - - form.InstanceConfiguration.bindPrompter(() => createInstanceStep()) - form.SourceConfiguration.AutoDeploymentsEnabled.setDefault(() => autoDeployButton.state === 'on') - } -} diff --git a/packages/core/src/awsService/apprunner/wizards/codeRepositoryWizard.ts b/packages/core/src/awsService/apprunner/wizards/codeRepositoryWizard.ts deleted file mode 100644 index 0489664865..0000000000 --- a/packages/core/src/awsService/apprunner/wizards/codeRepositoryWizard.ts +++ /dev/null @@ -1,260 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import { createCommonButtons, createRefreshButton, QuickInputToggleButton } from '../../../shared/ui/buttons' -import { Remote } from '../../../../types/git.d' -import { GitExtension } from '../../../shared/extensions/git' -import * as vscode from 'vscode' -import { WizardForm } from '../../../shared/wizards/wizardForm' -import { createVariablesPrompter } from '../../../shared/ui/common/variablesPrompter' -import { - AppRunnerClient, - SourceConfiguration, - CodeRepository, - CodeConfigurationValues, -} from '../../../shared/clients/apprunner' -import { makeDeploymentButton } from './deploymentButton' -import { - createLabelQuickPick, - createQuickPick, - DataQuickPickItem, - QuickPickPrompter, -} from '../../../shared/ui/pickerPrompter' -import { createInputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter' -import { apprunnerConfigHelpUrl, apprunnerConnectionHelpUrl, apprunnerRuntimeHelpUrl } from '../../../shared/constants' -import { Wizard, WIZARD_BACK } from '../../../shared/wizards/wizard' -import { openUrl } from '../../../shared/utilities/vsCodeUtils' -import { getAppRunnerCreateServiceDocUrl } from '../../../shared/extensionUtilities' -import * as AppRunner from '@aws-sdk/client-apprunner' - -const localize = nls.loadMessageBundle() - -function validateCommand(command: string): string | undefined { - if (command === '') { - return localize('AWS.apprunner.command.invalid', 'Command cannot be empty') - } - - return undefined -} - -function createRepoPrompter(git: GitExtension): QuickPickPrompter { - const mapRemote = (remote: Remote) => ({ label: remote.name, detail: remote.fetchUrl, data: remote }) - const remotes = git.getRemotes().then((r) => r.map(mapRemote)) - const userInputString = localize('AWS.apprunner.createService.customRepo', 'Enter GitHub URL') - - return createQuickPick(remotes, { - title: localize('AWS.apprunner.createService.selectRepository.title', 'Select a remote GitHub repository'), - placeholder: localize( - 'AWS.apprunner.createService.selectRepository.placeholder', - 'Select a remote repository or enter a URL' - ), - filterBoxInputSettings: { - label: userInputString, - transform: (resp) => ({ name: 'UserRemote', isReadOnly: true, fetchUrl: resp }), - }, - buttons: createCommonButtons(), - }) -} - -function createBranchPrompter( - git: GitExtension, - cache: { [key: string]: any }, - repo: string = '' -): QuickPickPrompter { - const last = cache[repo] - const branchItems = - last ?? - git.getBranchesForRemote({ name: '', fetchUrl: repo } as any).then((branches) => { - const branchItems = branches - .filter((b) => b.name !== undefined && b.name !== '') - .map((branch) => ({ - label: branch.name!.split('/').slice(1).join('/'), - })) - cache[repo] = branchItems - return branchItems - }) - const userInputString = localize('AWS.apprunner.createService.customRepo', 'Enter branch name') - return createLabelQuickPick(branchItems, { - title: localize('AWS.apprunner.createService.selectBranch.title', 'Select a branch'), - filterBoxInputSettings: { - label: userInputString, - transform: (resp) => resp, - }, - buttons: createCommonButtons(), - placeholder: localize( - 'AWS.apprunner.createService.selectBranch.placeholder', - 'Select a branch or enter a branch name' - ), - }) -} - -function createRuntimePrompter(): QuickPickPrompter { - const items: DataQuickPickItem[] = [ - { label: 'python3', data: AppRunner.Runtime.PYTHON_3 }, - { label: 'nodejs16', data: AppRunner.Runtime.NODEJS_16 }, - ] - - return createQuickPick(items, { - title: localize('AWS.apprunner.createService.selectRuntime.title', 'Select a runtime'), - buttons: createCommonButtons(apprunnerRuntimeHelpUrl), - }) -} - -function createBuildCommandPrompter(runtime: AppRunner.Runtime): InputBoxPrompter { - const buildCommandMap = { - python: 'pip install -r requirements.txt', - node: 'npm install', - } as { [key: string]: string } - - return createInputBox({ - title: localize('AWS.apprunner.createService.buildCommand.title', 'Enter a build command'), - buttons: createCommonButtons(apprunnerRuntimeHelpUrl), - placeholder: - buildCommandMap[Object.keys(buildCommandMap).filter((key) => runtime.toLowerCase().includes(key))[0]], - validateInput: validateCommand, - }) -} - -function createStartCommandPrompter(runtime: AppRunner.Runtime): InputBoxPrompter { - const startCommandMap = { - python: 'python runapp.py', - node: 'node app.js', - } as { [key: string]: string } - - return createInputBox({ - title: localize('AWS.apprunner.createService.startCommand.title', 'Enter a start command'), - buttons: createCommonButtons(apprunnerRuntimeHelpUrl), - placeholder: - startCommandMap[Object.keys(startCommandMap).filter((key) => runtime.toLowerCase().includes(key))[0]], - validateInput: validateCommand, - }) -} - -function createPortPrompter(): InputBoxPrompter { - const validatePort = (port: string) => { - if (isNaN(Number(port)) || port === '') { - return localize('AWS.apprunner.createService.selectPort.invalidPort', 'Port must be a number') - } - - return undefined - } - - return createInputBox({ - validateInput: validatePort, - title: localize('AWS.apprunner.createService.selectPort.title', 'Enter a port for the new service'), - placeholder: 'Enter a port', - buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), - }) -} - -export function createConnectionPrompter(client: AppRunnerClient) { - const noItemsFoundItem = { - label: 'No connections found', - detail: 'Click for documentation on creating a new GitHub connection for App Runner', - data: {} as any, - invalidSelection: true, - onClick: () => openUrl(vscode.Uri.parse(apprunnerConnectionHelpUrl)), - } - - const errorItem = { - label: localize('AWS.apprunner.createService.selectConnection.failed', 'Failed to list GitHub connections'), - description: localize('AWS.generic.goBack', 'Click to go back'), - data: WIZARD_BACK, - } - - const getItems = async () => { - const resp = await client.listConnections() - - return resp - .filter((conn) => conn.Status === 'AVAILABLE') - .map((conn) => ({ - label: conn.ConnectionName!, - data: conn, - })) - } - - const refreshButton = createRefreshButton() - refreshButton.onClick = () => void prompter.clearAndLoadItems(getItems()) - - const prompter = createQuickPick(getItems(), { - errorItem, - noItemsFoundItem, - buttons: [refreshButton, ...createCommonButtons(apprunnerConnectionHelpUrl)], - title: localize('AWS.apprunner.createService.selectConnection.title', 'Select a connection'), - }) - - return prompter -} - -function createSourcePrompter(): QuickPickPrompter { - const configDetail = localize( - 'AWS.apprunner.createService.configSource.detail', - 'App Runner will read "apprunner.yaml" in the root of your repository for configuration details' - ) - const apiLabel = localize('AWS.apprunner.createService.configSource.apiLabel', 'Configure all settings here') - const repoLabel = localize('AWS.apprunner.createService.configSource.repoLabel', 'Use configuration file') - const apiItem: DataQuickPickItem = { - label: apiLabel, - data: AppRunner.ConfigurationSource.API, - } - const repoItem: DataQuickPickItem = { - label: repoLabel, - data: AppRunner.ConfigurationSource.REPOSITORY, - detail: configDetail, - } - - return createQuickPick([apiItem, repoItem], { - title: localize('AWS.apprunner.createService.configSource.title', 'Choose configuration source'), - buttons: createCommonButtons(apprunnerConfigHelpUrl), - }) -} - -function createCodeRepositorySubForm(git: GitExtension): WizardForm { - const subform = new WizardForm() - const form = subform.body - - form.RepositoryUrl.bindPrompter(() => createRepoPrompter(git).transform((r) => r.fetchUrl!)) - form.SourceCodeVersion.Value.bindPrompter((state) => - createBranchPrompter(git, state.stepCache, state.RepositoryUrl).transform((resp) => - resp.replace(`${state.RepositoryUrl}/`, '') - ) - ) - form.CodeConfiguration.ConfigurationSource.bindPrompter(createSourcePrompter) - form.SourceCodeVersion.Type.setDefault(() => 'BRANCH') - - const codeConfigForm = new WizardForm() - codeConfigForm.body.Runtime.bindPrompter(createRuntimePrompter) - codeConfigForm.body.BuildCommand.bindPrompter((state) => createBuildCommandPrompter(state.Runtime!)) - codeConfigForm.body.StartCommand.bindPrompter((state) => createStartCommandPrompter(state.Runtime!)) - codeConfigForm.body.Port.bindPrompter(createPortPrompter) - codeConfigForm.body.RuntimeEnvironmentVariables.bindPrompter(() => - createVariablesPrompter(createCommonButtons(getAppRunnerCreateServiceDocUrl())) - ) - // TODO: ask user if they would like to save their parameters into an App Runner config file - - form.CodeConfiguration.CodeConfigurationValues.applyBoundForm(codeConfigForm, { - showWhen: (state) => state.CodeConfiguration?.ConfigurationSource === 'API', - }) - - return subform -} - -export class AppRunnerCodeRepositoryWizard extends Wizard { - constructor( - client: AppRunnerClient, - git: GitExtension, - autoDeployButton: QuickInputToggleButton = makeDeploymentButton() - ) { - super() - const form = this.form - - form.AuthenticationConfiguration.ConnectionArn.bindPrompter(() => - createConnectionPrompter(client).transform((conn) => conn.ConnectionArn!) - ) - form.CodeRepository.applyBoundForm(createCodeRepositorySubForm(git)) - form.AutoDeploymentsEnabled.setDefault(() => autoDeployButton.state === 'on') - } -} diff --git a/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts b/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts deleted file mode 100644 index dee77c05e9..0000000000 --- a/packages/core/src/awsService/apprunner/wizards/deploymentButton.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import * as vscode from 'vscode' - -import { QuickInputButton, QuickInputToggleButton } from '../../../shared/ui/buttons' -import { apprunnerPricingUrl } from '../../../shared/constants' -import { ToolkitPromptSettings } from '../../../shared/settings' -import { getIcon } from '../../../shared/icons' -import { dontShow } from '../../../shared/localizedText' -import { openUrl } from '../../../shared/utilities/vsCodeUtils' - -const localize = nls.loadMessageBundle() - -function makeDeployButtons() { - const autoDeploymentsEnable: QuickInputButton = { - iconPath: getIcon('vscode-sync-ignore'), - tooltip: localize('AWS.apprunner.buttons.enableAutoDeploy', 'Turn on automatic deployment'), - } - - const autoDeploymentsDisable: QuickInputButton = { - iconPath: getIcon('vscode-sync'), - tooltip: localize('AWS.apprunner.buttons.disableAutoDeploy', 'Turn off automatic deployment'), - } - - return [autoDeploymentsDisable, autoDeploymentsEnable] -} - -async function showDeploymentCostNotification(): Promise { - const settings = ToolkitPromptSettings.instance - - if (settings.isPromptEnabled('apprunnerNotifyPricing')) { - const notice = localize( - 'aws.apprunner.createService.priceNotice.message', - 'App Runner automatic deployments incur an additional cost.' - ) - const viewPricing = localize('aws.apprunner.createService.priceNotice.view', 'View Pricing') - const pricingUri = vscode.Uri.parse(apprunnerPricingUrl) - - await vscode.window.showInformationMessage(notice, viewPricing, dontShow).then(async (button) => { - if (button === viewPricing) { - await openUrl(pricingUri) - await showDeploymentCostNotification() - } else if (button === dontShow) { - await settings.disablePrompt('apprunnerNotifyPricing') - } - }) - } -} - -export function makeDeploymentButton() { - const [autoDeploymentsDisable, autoDeploymentsEnable] = makeDeployButtons() - - return new QuickInputToggleButton(autoDeploymentsDisable, autoDeploymentsEnable, { - onCallback: showDeploymentCostNotification, - }) -} diff --git a/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts b/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts deleted file mode 100644 index e906e249e7..0000000000 --- a/packages/core/src/awsService/apprunner/wizards/imageRepositoryWizard.ts +++ /dev/null @@ -1,274 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createCommonButtons, QuickInputButton, QuickInputToggleButton } from '../../../shared/ui/buttons' -import { toArrayAsync } from '../../../shared/utilities/collectionUtils' -import { EcrClient, EcrRepository } from '../../../shared/clients/ecrClient' -import * as input from '../../../shared/ui/inputPrompter' -import * as picker from '../../../shared/ui/pickerPrompter' -import { Prompter } from '../../../shared/ui/prompter' -import { Wizard, WizardControl, WIZARD_BACK } from '../../../shared/wizards/wizard' -import { WizardPrompter } from '../../../shared/ui/wizardPrompter' - -import * as nls from 'vscode-nls' -import { WizardForm } from '../../../shared/wizards/wizardForm' -import { createVariablesPrompter } from '../../../shared/ui/common/variablesPrompter' -import { makeDeploymentButton } from './deploymentButton' -import { IamClient, IamRole } from '../../../shared/clients/iam' -import { createRolePrompter } from '../../../shared/ui/common/roles' -import { getLogger } from '../../../shared/logger/logger' -import { getAppRunnerCreateServiceDocUrl, isCloud9 } from '../../../shared/extensionUtilities' -import { createExitPrompter } from '../../../shared/ui/common/exitPrompter' -import { ImageRepository, SourceConfiguration } from '../../../shared/clients/apprunner' - -const localize = nls.loadMessageBundle() - -const publicEcr = 'public.ecr.aws' -const appRunnerEcrEntity = 'build.apprunner.amazonaws' - -export type TaggedEcrRepository = EcrRepository & { tag?: string } - -interface ImagePrompterOptions { - noPublicMessage?: string - extraButtons?: QuickInputButton -} - -function createEcrRole(client: IamClient): Promise { - const policy = { - Version: '2008-10-17', - Statement: [ - { - Sid: '', - Effect: 'Allow', - Principal: { - Service: ['build.apprunner.amazonaws.com'], - }, - Action: 'sts:AssumeRole', - }, - ], - } - const ecrPolicy = 'arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess' - - return client - .createRole({ - RoleName: `${isCloud9() ? 'Cloud9-' : ''}AppRunnerECRAccessRole${ - Math.floor(Math.random() * 1000000000) + 1000 - }`, - AssumeRolePolicyDocument: JSON.stringify(policy), - }) - .then((resp) => { - const role = resp.Role - return client.attachRolePolicy({ RoleName: role.RoleName, PolicyArn: ecrPolicy }).then(() => role) - }) -} - -function createImagePrompter( - ecrClient: EcrClient, - cache: { [key: string]: any }, - options: ImagePrompterOptions = {} -): picker.QuickPickPrompter { - const last = cache['repos'] - const imageRepos = - last ?? - toArrayAsync(ecrClient.describeRepositories()) - .then((resp) => { - const repos = resp.map((repo) => ({ - label: repo.repositoryName, - detail: repo.repositoryUri, - data: repo, - })) - cache['repos'] = repos - return repos - }) - .catch((err) => { - getLogger().error(`Unabled to list repositories: %s`, err) - return [ - { - label: localize( - 'AWS.apprunner.createService.selectImageRepo.failed', - 'Failed to list repositories' - ), - description: localize('AWS.generic.goBack', 'Click to go back'), - data: WIZARD_BACK, - }, - ] - }) - - const customUserInputLabel = localize('AWS.apprunner.createService.selectImageRepo.input', 'Custom ECR URL') - const customUserInputTransform = (resp: string) => { - const userInputParts = resp.split(':') - - return { - repositoryArn: '', - repositoryName: 'UserDefined', - repositoryUri: userInputParts[0], - tag: userInputParts[1]?.trim() ?? 'latest', - } - } - - const ecrUriValidator = (input: string) => { - const userInputParts = input.split(':') - - if (userInputParts.length > 2) { - return 'colon should be used to delimit tag' - } - - if (userInputParts.length === 2 && userInputParts[1].trim() === '') { - return 'tag cannot be empty' - } - - const privateRegExp = /[0-9]+\.dkr\.ecr\.[a-zA-z0-9\-]+\.amazonaws\.com/ - - if (options.noPublicMessage && userInputParts[0].startsWith(publicEcr)) { - return options.noPublicMessage - } - - if (!userInputParts[0].startsWith(publicEcr) && !userInputParts[0].match(privateRegExp)) { - return 'not a valid ECR URL' - } - } - - const customUserInputValidator = (input: string) => { - const message = ecrUriValidator(input) - return message !== undefined ? `$(close) Invalid input: ${message}` : undefined - } - - return picker.createQuickPick(imageRepos, { - title: localize('AWS.apprunner.createService.selectImageRepo.title', 'Select or enter an image repository'), - placeholder: '123456789012.dkr.ecr.us-east-1.amazonaws.com/myrepo:latest', - filterBoxInputSettings: { - label: customUserInputLabel, - transform: customUserInputTransform, - validator: customUserInputValidator, - }, - buttons: createCommonButtons(), - }) -} - -function createPortPrompter(): Prompter { - const validatePort = (port: string) => { - if (isNaN(Number(port)) || port === '') { - return localize('AWS.apprunner.createService.selectPort.invalidPort', 'Port must be a number') - } - - return undefined - } - - return input.createInputBox({ - validateInput: validatePort, - title: localize('AWS.apprunner.createService.selectPort.title', 'Enter a port for the new service'), - placeholder: 'Enter a port', - buttons: createCommonButtons(getAppRunnerCreateServiceDocUrl()), - }) -} - -function createTagPrompter( - ecrClient: EcrClient, - imageRepo: EcrRepository, - cache: { [key: string]: any } -): picker.QuickPickPrompter { - const last: picker.DataQuickPickItem[] = cache[imageRepo.repositoryName] - const tagItems = - last ?? - toArrayAsync(ecrClient.describeTags(imageRepo.repositoryName)) - .then((tags) => { - if (tags.length === 0) { - return [ - { - label: localize('AWS.apprunner.createService.selectTags.noFound', 'No tags found'), - description: localize('AWS.generic.goBack', 'Click to go back'), - data: WIZARD_BACK, - }, - ] - } - - const tagT = tags.map((tag) => ({ label: tag })) - cache[imageRepo.repositoryName] = tagT - return tagT - }) - .catch((err) => { - getLogger().error(`Unabled to list tags for repository "${imageRepo.repositoryName}": %s`, err) - return [ - { - label: localize( - 'AWS.apprunner.createService.selectTag.failed', - 'Failed to get tags for repository' - ), - description: localize('AWS.generic.goBack', 'Click to go back'), - data: WIZARD_BACK, - }, - ] - }) - - return picker.createLabelQuickPick(tagItems, { - title: localize('AWS.apprunner.createService.selectTag.title', 'Select an ECR tag'), - placeholder: 'latest', - buttons: createCommonButtons(), - }) -} - -export class ImageIdentifierForm extends WizardForm<{ repo: TaggedEcrRepository }> { - constructor(ecrClient: EcrClient, options: ImagePrompterOptions = {}) { - super() - - this.body.repo.bindPrompter((state) => createImagePrompter(ecrClient, state.stepCache, options)) - this.body.repo.tag.bindPrompter((state) => createTagPrompter(ecrClient, state.repo!, state.stepCache), { - requireParent: true, - }) - } -} - -function createImageRepositorySubForm( - ecrClient: EcrClient, - autoDeployButton: QuickInputToggleButton -): WizardForm { - const subform = new WizardForm() - const form = subform.body - - // note: this is intentionally initialized only once to preserve caches - const imageIdentifierWizard = new Wizard({ - initForm: new ImageIdentifierForm(ecrClient), - exitPrompterProvider: createExitPrompter, - }) - - form.ImageIdentifier.bindPrompter(() => - new WizardPrompter(imageIdentifierWizard).transform((resp) => `${resp.repo.repositoryUri}:${resp.repo.tag}`) - ) - - function isPublic(imageRepo: string): boolean { - return imageRepo.search(/^public.ecr.aws/) !== -1 - } - - form.ImageRepositoryType.setDefault((state) => - state.ImageIdentifier !== undefined ? (isPublic(state.ImageIdentifier) ? 'ECR_PUBLIC' : 'ECR') : undefined - ) - - form.ImageConfiguration.Port.bindPrompter(() => createPortPrompter()) - form.ImageConfiguration.RuntimeEnvironmentVariables.bindPrompter(() => - createVariablesPrompter(createCommonButtons(getAppRunnerCreateServiceDocUrl())) - ) - - return subform -} - -export class AppRunnerImageRepositoryWizard extends Wizard { - constructor(ecrClient: EcrClient, iamClient: IamClient, autoDeployButton = makeDeploymentButton()) { - super() - const form = this.form - const createAccessRolePrompter = () => - createRolePrompter(iamClient, { - title: localize('AWS.apprunner.createService.selectRole.title', 'Select a role to pull from ECR'), - helpUrl: getAppRunnerCreateServiceDocUrl(), - roleFilter: (role) => (role.AssumeRolePolicyDocument ?? '').includes(appRunnerEcrEntity), - createRole: createEcrRole.bind(undefined, iamClient), - }).transform((resp) => resp.Arn) - - form.ImageRepository.applyBoundForm(createImageRepositorySubForm(ecrClient, autoDeployButton)) - form.AuthenticationConfiguration.AccessRoleArn.bindPrompter(createAccessRolePrompter, { - showWhen: (form) => form.ImageRepository?.ImageRepositoryType === 'ECR', - }) - form.AutoDeploymentsEnabled.setDefault(() => autoDeployButton.state === 'on') - } -} diff --git a/packages/core/src/awsService/cdk/explorer/cdkProject.ts b/packages/core/src/awsService/cdk/explorer/cdkProject.ts deleted file mode 100644 index c90fd3aa21..0000000000 --- a/packages/core/src/awsService/cdk/explorer/cdkProject.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import fs from '../../../shared/fs/fs' -import { ConstructTree } from './tree/types' - -export interface CdkApp { - location: CdkAppLocation - constructTree: ConstructTree -} - -export interface CdkAppLocation { - cdkJsonUri: vscode.Uri - treeUri: vscode.Uri -} - -export async function getApp(location: CdkAppLocation): Promise { - const constructTree = JSON.parse(await fs.readFileText(location.treeUri)) as ConstructTree - - return { location, constructTree } -} diff --git a/packages/core/src/awsService/cdk/explorer/detectCdkProjects.ts b/packages/core/src/awsService/cdk/explorer/detectCdkProjects.ts deleted file mode 100644 index acf7e28af1..0000000000 --- a/packages/core/src/awsService/cdk/explorer/detectCdkProjects.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import { getLogger } from '../../../shared/logger/logger' -import { CdkAppLocation } from './cdkProject' - -export async function detectCdkProjects( - workspaceFolders: readonly vscode.WorkspaceFolder[] | undefined = vscode.workspace.workspaceFolders -): Promise { - if (!workspaceFolders) { - return [] - } - - const results = new Map() - const projects = (await Promise.all(workspaceFolders.map(detectCdkProjectsFromWorkspaceFolder))).reduce( - (a, b) => a.concat(b), - [] - ) - - for (const p of projects) { - results.set(p.cdkJsonUri.toString(), p) - } - - return Array.from(results.values()) -} - -async function detectCdkProjectsFromWorkspaceFolder( - workspaceFolder: vscode.WorkspaceFolder -): Promise { - const result = [] - const pattern = new vscode.RelativePattern(workspaceFolder, '**/cdk.json') - const cdkJsonFiles = await vscode.workspace.findFiles(pattern, '**/node_modules/**') - - for await (const cdkJson of cdkJsonFiles) { - try { - const cdkJsonDoc = await vscode.workspace.openTextDocument(cdkJson) - const { output = 'cdk.out' } = JSON.parse(cdkJsonDoc.getText()) - const outputUri = vscode.Uri.file(path.resolve(vscode.Uri.joinPath(cdkJson, '..').fsPath, output)) - const treeJsonUri = vscode.Uri.joinPath(outputUri, 'tree.json') - const project = { cdkJsonUri: cdkJson, treeUri: treeJsonUri } - result.push(project) - } catch (err) { - getLogger().error(`Failed to parse cdk.json from %s: %O`, cdkJson.fsPath, err) - } - } - - return result -} diff --git a/packages/core/src/awsService/cdk/explorer/nodes/appNode.ts b/packages/core/src/awsService/cdk/explorer/nodes/appNode.ts deleted file mode 100644 index 3e79ebba74..0000000000 --- a/packages/core/src/awsService/cdk/explorer/nodes/appNode.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { getLogger } from '../../../../shared/logger/logger' -import { CdkAppLocation, getApp } from '../cdkProject' -import { ConstructNode, generateConstructNodes } from './constructNode' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { createPlaceholderItem } from '../../../../shared/treeview/utils' -import { getIcon } from '../../../../shared/icons' - -/** - * Represents a CDK App - * Existence of apps is determined by the presence of `cdk.json` in a workspace folder - */ -export class AppNode implements TreeNode { - public readonly id = this.location.cdkJsonUri.toString() - public readonly resource = this.location - public readonly label = vscode.workspace.asRelativePath(vscode.Uri.joinPath(this.location.cdkJsonUri, '..')) - - public constructor(private readonly location: CdkAppLocation) {} - - public async getChildren(): Promise<(ConstructNode | TreeNode)[]> { - const constructs = [] - try { - const successfulApp = await getApp(this.location) - - const constructsInTree = successfulApp.constructTree.tree.children - if (constructsInTree) { - constructs.push(...generateConstructNodes(this.location, constructsInTree)) - } - - // indicate that App exists, but it is empty - if (constructs.length === 0) { - return [ - createPlaceholderItem(localize('AWS.cdk.explorerNode.app.noStacks', '[No stacks in this CDK App]')), - ] - } - - return constructs - } catch (error) { - getLogger().error(`Could not load the construct tree located at '${this.id}': %O`, error as Error) - - return [ - createPlaceholderItem( - localize( - 'AWS.cdk.explorerNode.app.noConstructTree', - '[Unable to load construct tree for this App. Run `cdk synth`]' - ) - ), - ] - } - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.label, vscode.TreeItemCollapsibleState.Collapsed) - - item.contextValue = 'awsCdkAppNode' - item.iconPath = getIcon('aws-cdk-logo') - item.tooltip = this.location.cdkJsonUri.path - - return item - } -} diff --git a/packages/core/src/awsService/cdk/explorer/nodes/constructNode.ts b/packages/core/src/awsService/cdk/explorer/nodes/constructNode.ts deleted file mode 100644 index 8e75843ad1..0000000000 --- a/packages/core/src/awsService/cdk/explorer/nodes/constructNode.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getIcon } from '../../../../shared/icons' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' -import { CdkAppLocation } from '../cdkProject' -import * as treeInspector from '../tree/treeInspector' -import { ConstructTreeEntity } from '../tree/types' -import { generatePropertyNodes } from './propertyNode' - -export class ConstructNode implements TreeNode { - public readonly id = this.construct.id - private readonly type = treeInspector.getTypeAttributeOrDefault(this.construct, '') - private readonly properties = treeInspector.getProperties(this.construct) - - public constructor( - private readonly location: CdkAppLocation, - private readonly construct: ConstructTreeEntity - ) {} - - public get resource() { - return { - construct: this.construct, - location: this.location.treeUri.with({ fragment: this.construct.path }), - } - } - - public getChildren() { - const propertyNodes = this.properties !== undefined ? generatePropertyNodes(this.properties) : [] - const constructNodes = - this.construct.children !== undefined ? generateConstructNodes(this.location, this.construct.children) : [] - - return [...propertyNodes, ...constructNodes] - } - - public getTreeItem() { - const collapsibleState = - this.construct.children || this.construct.attributes - ? vscode.TreeItemCollapsibleState.Collapsed - : vscode.TreeItemCollapsibleState.None - - const item = new vscode.TreeItem(treeInspector.getDisplayLabel(this.construct), collapsibleState) - item.contextValue = isStateMachine(this.construct) ? 'awsCdkStateMachineNode' : 'awsCdkConstructNode' - item.tooltip = this.type || this.construct.path - item.iconPath = this.type ? getIcon('aws-cloudformation-stack') : getIcon('aws-cdk-logo') - - return item - } -} - -export function generateConstructNodes(app: CdkAppLocation, children: NonNullable) { - return Object.values(children) - .filter((c) => treeInspector.includeConstructInTree(c)) - .map((c) => new ConstructNode(app, c)) -} - -/** - * Determines if a CDK construct is of type state machine - * - * @param {ConstructTreeEntity} construct - CDK construct - */ -export function isStateMachine(construct: ConstructTreeEntity): boolean { - const resource = construct.children?.Resource - if (!resource) { - return false - } - const type: string = treeInspector.getTypeAttributeOrDefault(resource, '') - return type === 'AWS::StepFunctions::StateMachine' -} diff --git a/packages/core/src/awsService/cdk/explorer/nodes/propertyNode.ts b/packages/core/src/awsService/cdk/explorer/nodes/propertyNode.ts deleted file mode 100644 index e694821ef8..0000000000 --- a/packages/core/src/awsService/cdk/explorer/nodes/propertyNode.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getIcon } from '../../../../shared/icons' -import { TreeNode } from '../../../../shared/treeview/resourceTreeDataProvider' - -/* - * Represents a property of a CDK construct. Properties can be simple key-value pairs, Arrays, - * or objects that are nested deeply - */ -export class PropertyNode implements TreeNode { - public readonly id = this.key - public readonly resource = this.value - - public constructor( - private readonly key: string, - private readonly value: unknown - ) {} - - public async getChildren(): Promise { - if (this.value instanceof Array || this.value instanceof Object) { - return generatePropertyNodes(this.value) - } else { - return [] - } - } - - public getTreeItem() { - const item = new vscode.TreeItem(`${this.key}: ${this.value}`) - - item.contextValue = 'awsCdkPropertyNode' - item.iconPath = getIcon('vscode-gear') - - if (this.value instanceof Array || this.value instanceof Object) { - item.label = this.key - item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - } - - return item - } -} - -export function generatePropertyNodes(properties: { [key: string]: any }): TreeNode[] { - return Object.entries(properties).map(([k, v]) => new PropertyNode(k, v)) -} diff --git a/packages/core/src/awsService/cdk/explorer/rootNode.ts b/packages/core/src/awsService/cdk/explorer/rootNode.ts deleted file mode 100644 index 86b6f45aab..0000000000 --- a/packages/core/src/awsService/cdk/explorer/rootNode.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { cdkDocumentationUrl } from '../../../shared/constants' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { ResourceTreeDataProvider, TreeNode } from '../../../shared/treeview/resourceTreeDataProvider' -import { createPlaceholderItem } from '../../../shared/treeview/utils' -import { localize, openUrl } from '../../../shared/utilities/vsCodeUtils' -import { Commands } from '../../../shared/vscode/commands2' -import { detectCdkProjects } from './detectCdkProjects' -import { AppNode } from './nodes/appNode' - -export async function getAppNodes(): Promise { - const appsFound = await detectCdkProjects(vscode.workspace.workspaceFolders) - - if (appsFound.length === 0) { - return [createPlaceholderItem(localize('AWS.cdk.explorerNode.noApps', '[No CDK Apps found in Workspaces]'))] - } - - return appsFound.map((appLocation) => new AppNode(appLocation)).sort((a, b) => a.label.localeCompare(b.label) ?? 0) -} - -export class CdkRootNode implements TreeNode { - public readonly id = 'cdk' - public readonly resource = this - private readonly onDidChangeChildrenEmitter = new vscode.EventEmitter() - public readonly onDidChangeChildren = this.onDidChangeChildrenEmitter.event - private readonly _refreshCdkExplorer - - constructor() { - Commands.register('aws.cdk.viewDocs', () => { - void openUrl(vscode.Uri.parse(cdkDocumentationUrl)) - telemetry.aws_help.emit({ name: 'cdk' }) - }) - this._refreshCdkExplorer = (provider?: ResourceTreeDataProvider) => - Commands.register('aws.cdk.refresh', () => { - this.refresh() - if (provider) { - provider.refresh() - } - }) - } - - public get refreshCdkExplorer() { - return this._refreshCdkExplorer - } - - public getChildren() { - return getAppNodes() - } - - public refresh(): void { - this.onDidChangeChildrenEmitter.fire() - } - - public getTreeItem() { - const item = new vscode.TreeItem('CDK') - item.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed - item.contextValue = 'awsCdkRootNode' - - return item - } - - static #instance: CdkRootNode - - static get instance(): CdkRootNode { - return (this.#instance ??= new CdkRootNode()) - } -} diff --git a/packages/core/src/awsService/cdk/explorer/tree/treeInspector.ts b/packages/core/src/awsService/cdk/explorer/tree/treeInspector.ts deleted file mode 100644 index d60eeee591..0000000000 --- a/packages/core/src/awsService/cdk/explorer/tree/treeInspector.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CfnResourceKeys, ConstructProps, ConstructTreeEntity } from './types' - -/** - * Determines the type of the construct if it exists - * - * @param construct CDK construct - * @param defaultValue - value to return if type cannot be determined - */ -export function getTypeAttributeOrDefault(construct: ConstructTreeEntity, defaultValue: string): string { - const attributes = construct.attributes - if (attributes && attributes[CfnResourceKeys.TYPE]) { - return attributes[CfnResourceKeys.TYPE] as string - } - - return defaultValue -} - -/** - * Determines the props of a construct if they exist - * - * @param construct CDK construct - */ -export function getProperties(construct: ConstructTreeEntity): ConstructProps | undefined { - const attributes = construct.attributes - if (attributes && attributes[CfnResourceKeys.PROPS]) { - return attributes[CfnResourceKeys.PROPS] as ConstructProps - } - - return undefined -} - -/** - * Some constructs encoded in the Tree do not need to be included in the design-time view - * i.e. the `Tree` construct should not be encoded in tree.json and will be removed - * - * @param construct CDK construct - */ -export function includeConstructInTree(construct: ConstructTreeEntity): boolean { - if (construct.id === 'Tree' && construct.path === 'Tree') { - return false - } - - return true -} - -/** - * Determines the display label for a construct - * - * @param construct CDK construct - */ -export function getDisplayLabel(construct: ConstructTreeEntity): string { - const type: string = getTypeAttributeOrDefault(construct, '') - - // the 'Resource' label has been a point of confusion so also including the type - if (construct.id === 'Resource' && type) { - return `${construct.id} (${type})` - } - - return construct.id -} diff --git a/packages/core/src/awsService/cdk/explorer/tree/types.ts b/packages/core/src/awsService/cdk/explorer/tree/types.ts deleted file mode 100644 index 7a371b89c4..0000000000 --- a/packages/core/src/awsService/cdk/explorer/tree/types.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * The CDK construct tree is the hierarchy of a CDK application. Every time a CDK application - * is built and `cdk synth` is called on it from the CLI, the construct tree is written to a - * file named `tree.json`. - * - * The tree includes all of the resources that are encapsulated by the CDK. - * A motivation for sharing this tree is to provide a design-time view of the resources that are - * contained with a CDK application - * - * The intial RFC around the construct tree can be found here: - * https://github.com/aws/aws-cdk/pull/4053 - * - * @attribute version - version of the tree to leverage if the spec changes - * @attribute tree - every construct in the tree adheres to this schema - */ -export interface ConstructTree { - readonly version: string - readonly tree: ConstructTreeEntity -} - -/** - * Represents a construct in the CDK construct tree. - * The tree is produced by a construct called `Tree` which visits every construct in an application - * and adds an entry for it. - * - * @attribute id - identifier in the tree. At the top level every CDK application will have a construct called "App" - * The children of "App" will include 'Tree' - * @attribute path - path in the tree. All application start with a path of ''. The path represents the depth of a - * construct in a CDK application (i.e Apps contain Stacks which contain Resource - the path would be Stack/Resource) - * @attribute children - All of the chidren encapsulated by the current level in the tree. Children are keyed on their ids - * @attribute attributes - An Attribute bag that constructs can voluntarily contribute towards. All CloudFormation - * resources contribute their type and properties. Attributes will be keyed on convention. i.e. CloudFormation properties - * are prefixed `aws:cdk:cloudformation` - */ -export interface ConstructTreeEntity { - readonly id: string - readonly path: string - readonly children?: { [key: string]: ConstructTreeEntity } - readonly attributes?: { [key: string]: any } -} - -/** - * CloudFormation attributes in the construct tree will contain these keys - * The keys are defined by convention and the prefix `aws:cdk:cloudformation` - * indicates that the attribute is a property of a CloudFormation resource - * - */ -export enum CfnResourceKeys { - TYPE = 'aws:cdk:cloudformation:type', - PROPS = 'aws:cdk:cloudformation:props', -} - -/** - * Attributes of a CDK construct - */ -export interface ConstructAttributes { - readonly type: string - readonly props: ConstructProps -} - -/** - * Represents a bag of properties - * Construct properties can be arrays, objects, strings, or boolean - */ -export interface ConstructProps { - readonly props: { [key: string]: any } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/activation.ts b/packages/core/src/awsService/cloudWatchLogs/activation.ts deleted file mode 100644 index 03cf23c235..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/activation.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { cloudwatchLogsLiveTailScheme, CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' -import { Settings } from '../../shared/settings' -import { addLogEvents } from './commands/addLogEvents' -import { copyLogResource } from './commands/copyLogResource' -import { saveCurrentLogDataContent } from './commands/saveCurrentLogDataContent' -import { viewLogStream } from './commands/viewLogStream' -import { LogDataCodeLensProvider } from './document/logDataCodeLensProvider' -import { LogDataDocumentProvider } from './document/logDataDocumentProvider' -import { LogGroupNode } from './explorer/logGroupNode' -import { LogDataRegistry } from './registry/logDataRegistry' -import { Commands } from '../../shared/vscode/commands2' -import { searchLogGroup } from './commands/searchLogGroup' -import { changeLogSearchParams } from './changeLogSearch' -import { CloudWatchLogsNode } from './explorer/cloudWatchLogsNode' -import { loadAndOpenInitialLogStreamFile, LogStreamCodeLensProvider } from './document/logStreamsCodeLensProvider' -import { clearDocument, closeSession, tailLogGroup } from './commands/tailLogGroup' -import { LiveTailDocumentProvider } from './document/liveTailDocumentProvider' -import { LiveTailSessionRegistry } from './registry/liveTailSessionRegistry' -import { DeployedResourceNode } from '../appBuilder/explorer/nodes/deployedNode' -import { isTreeNode, TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import { getLogger } from '../../shared/logger/logger' -import { ToolkitError } from '../../shared/errors' -import { LiveTailCodeLensProvider } from './document/liveTailCodeLensProvider' -import { generateLambdaNodeFromResource } from '../appBuilder/explorer/nodes/resourceNode' -import { LambdaFunctionNode } from '../../lambda/explorer/lambdaFunctionNode' -import { getSourceNode } from '../../shared/utilities/treeNodeUtils' - -export const liveTailRegistry = LiveTailSessionRegistry.instance -export const liveTailCodeLensProvider = new LiveTailCodeLensProvider(liveTailRegistry) -export async function activate(context: vscode.ExtensionContext, configuration: Settings): Promise { - const registry = LogDataRegistry.instance - - const documentProvider = new LogDataDocumentProvider(registry) - const liveTailDocumentProvider = new LiveTailDocumentProvider() - context.subscriptions.push( - vscode.languages.registerCodeLensProvider( - { - language: 'log', - scheme: CLOUDWATCH_LOGS_SCHEME, - }, - new LogStreamCodeLensProvider(registry, documentProvider) - ) - ) - - context.subscriptions.push( - vscode.workspace.registerTextDocumentContentProvider(CLOUDWATCH_LOGS_SCHEME, documentProvider) - ) - - context.subscriptions.push( - vscode.languages.registerCodeLensProvider( - { - language: 'log', - scheme: cloudwatchLogsLiveTailScheme, - }, - liveTailCodeLensProvider - ) - ) - - context.subscriptions.push( - vscode.workspace.registerTextDocumentContentProvider(cloudwatchLogsLiveTailScheme, liveTailDocumentProvider) - ) - - context.subscriptions.push( - vscode.workspace.onDidCloseTextDocument((doc) => { - if (doc.isClosed && doc.uri.scheme === CLOUDWATCH_LOGS_SCHEME) { - registry.disposeRegistryData(doc.uri) - } - }) - ) - - context.subscriptions.push( - vscode.languages.registerCodeLensProvider( - { - language: 'log', - scheme: CLOUDWATCH_LOGS_SCHEME, - }, - new LogDataCodeLensProvider(registry) - ) - ) - - context.subscriptions.push( - Commands.register('aws.loadLogStreamFile', async (uri: vscode.Uri, registry: LogDataRegistry) => - loadAndOpenInitialLogStreamFile(uri, registry) - ) - ) - context.subscriptions.push(Commands.register('aws.copyLogResource', copyLogResource)) - context.subscriptions.push( - Commands.register( - 'aws.addLogEvents', - async ( - document: vscode.TextDocument, - registry: LogDataRegistry, - headOrTail: 'head' | 'tail', - onDidChangeCodeLensEvent: vscode.EventEmitter - ) => addLogEvents(document, registry, headOrTail, onDidChangeCodeLensEvent) - ), - Commands.register('aws.saveCurrentLogDataContent', async () => await saveCurrentLogDataContent()), - // AWS Explorer right-click action - // Here instead of in ../awsexplorer/activation due to dependence on the registry. - Commands.register('aws.cwl.viewLogStream', async (node: LogGroupNode) => await viewLogStream(node, registry)), - - Commands.register('aws.cwl.searchLogGroup', async (node: LogGroupNode | CloudWatchLogsNode) => { - const logGroupInfo = - node instanceof LogGroupNode - ? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! } - : undefined - const source = node ? (logGroupInfo ? 'ExplorerLogGroupNode' : 'ExplorerServiceNode') : 'Command' - await searchLogGroup(registry, source, logGroupInfo) - }), - - Commands.register('aws.cwl.changeFilterPattern', async () => changeLogSearchParams(registry, 'filterPattern')), - - Commands.register('aws.cwl.changeTimeFilter', async () => changeLogSearchParams(registry, 'timeFilter')), - - Commands.register('aws.cwl.tailLogGroup', async (node: LogGroupNode | CloudWatchLogsNode) => { - const logGroupInfo = - node instanceof LogGroupNode - ? { regionName: node.regionCode, groupName: node.logGroup.logGroupName! } - : undefined - const source = node ? (logGroupInfo ? 'ExplorerLogGroupNode' : 'ExplorerServiceNode') : 'Command' - await tailLogGroup(liveTailRegistry, source, liveTailCodeLensProvider, logGroupInfo) - }), - - Commands.register('aws.cwl.stopTailingLogGroup', async (document: vscode.TextDocument, source: string) => { - closeSession(document.uri, liveTailRegistry, source, liveTailCodeLensProvider) - }), - - Commands.register('aws.cwl.clearDocument', async (document: vscode.TextDocument) => { - await clearDocument(document) - }), - - Commands.register('aws.appBuilder.searchLogs', async (node: DeployedResourceNode | TreeNode) => { - try { - let tmpNode: LambdaFunctionNode | undefined = getSourceNode(node) - if (!tmpNode && isTreeNode(node)) { - // failed to extract, meaning this is appbuilder function node - tmpNode = await generateLambdaNodeFromResource(node.resource as any) - } - const logGroupInfo = { - regionName: tmpNode.regionCode, - groupName: getFunctionLogGroupName(tmpNode.configuration), - } - - const source: string = logGroupInfo ? 'AppBuilderSearchLogs' : 'CommandPaletteSearchLogs' - await searchLogGroup(registry, source, logGroupInfo) - } catch (err) { - getLogger().error('Failed to search logs: %s', err) - throw ToolkitError.chain(err, 'Failed to search logs') - } - }) - ) -} - -export function getFunctionLogGroupName(configuration: any) { - const logGroupPrefix = '/aws/lambda/' - return configuration.LoggingConfig?.LogGroup || logGroupPrefix + configuration.FunctionName -} diff --git a/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts b/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts deleted file mode 100644 index 349f7339e8..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/changeLogSearch.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { CancellationError } from '../../shared/utilities/timeoutUtils' -import { telemetry } from '../../shared/telemetry/telemetry' -import { showInputBox } from '../../shared/ui/inputPrompter' -import { cwlUriSchema, isLogStreamUri, recordTelemetryFilter } from './cloudWatchLogsUtils' -import { prepareDocument } from './commands/searchLogGroup' -import { getActiveDocumentUri } from './document/logDataDocumentProvider' -import { CloudWatchLogsData, filterLogEventsFromUri, LogDataRegistry } from './registry/logDataRegistry' -import { isViewAllEvents, TimeFilterResponse, TimeFilterSubmenu } from './timeFilterSubmenu' - -/** - * Prompts the user for new value for param in logSearch. - * @param registry - * @param param - * @param oldUri - * @returns Undefined if cancelled and the newData otherwise. - */ - -export async function getNewData( - param: 'filterPattern' | 'timeFilter', - oldData: CloudWatchLogsData -): Promise { - // We must deepcopy the parameters so that we don't change their original value in oldData - const newData: CloudWatchLogsData = { - ...oldData, - parameters: { ...oldData.parameters }, - events: [], - next: undefined, - previous: undefined, - } - - let newPattern: string | undefined - let newTimeRange: TimeFilterResponse | undefined - - switch (param) { - case 'filterPattern': - newPattern = await showInputBox({ - title: oldData.logGroupInfo.streamName ? 'Filter Log Stream' : 'Search Log Group', - placeholder: 'Enter Text Here', - value: oldData.parameters.filterPattern ?? undefined, - }) - if (newPattern === undefined) { - return - } - newData.parameters.filterPattern = newPattern - break - - case 'timeFilter': - newTimeRange = (await new TimeFilterSubmenu(oldData.parameters).prompt()) as TimeFilterResponse - if (newTimeRange === undefined) { - return - } - newData.parameters.startTime = isViewAllEvents(newTimeRange) ? undefined : newTimeRange.start - newData.parameters.endTime = isViewAllEvents(newTimeRange) ? undefined : newTimeRange.end - break - } - - if (newData.logGroupInfo.streamName) { - newData.retrieveLogsFunction = filterLogEventsFromUri - newData.parameters.streamNameOptions = [newData.logGroupInfo.streamName] - newData.logGroupInfo.streamName = undefined - } - - recordTelemetryFilter(newData) - - return newData -} - -export async function changeLogSearchParams( - registry: LogDataRegistry, - param: 'filterPattern' | 'timeFilter' -): Promise { - await telemetry.cloudwatchlogs_open.run(async (span) => { - const oldUri = getActiveDocumentUri(registry) - span.record({ - source: 'Editor', - cloudWatchResourceType: isLogStreamUri(oldUri) ? 'logStream' : 'logGroup', - hasTimeFilter: param === 'timeFilter', - hasTextFilter: param === 'filterPattern', - }) - - if (!registry.isRegistered(oldUri)) { - throw new Error(`cwl: Failed to get data for URI: ${oldUri}`) - } - - const oldData = registry.getRegisteredLog(oldUri) - const newData = await getNewData(param, oldData) - span.record({ - cloudWatchResourceType: isLogStreamUri(oldUri) ? 'logStream' : 'logGroup', - hasTimeFilter: !!oldData.parameters.startTime || param === 'timeFilter', - hasTextFilter: !!oldData.parameters.filterPattern || param === 'filterPattern', - }) - - if (!newData) { - throw new CancellationError('user') - } - - const newUri = cwlUriSchema.form({ logGroupInfo: newData.logGroupInfo, parameters: newData.parameters }) - await prepareDocument(newUri, newData, registry) - }) -} diff --git a/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts b/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts deleted file mode 100644 index e235659515..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/cloudWatchLogsUtils.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { telemetry } from '../../shared/telemetry/telemetry' -import * as vscode from 'vscode' -import { CLOUDWATCH_LOGS_SCHEME } from '../../shared/constants' -import { fromExtensionManifest } from '../../shared/settings' -import { CloudWatchLogsArgs, CloudWatchLogsData, CloudWatchLogsGroupInfo } from './registry/logDataRegistry' -import { CloudWatchLogsParameters } from './registry/logDataRegistry' -import { UriSchema } from '../../shared/utilities/uriUtils' - -// URIs are the only vehicle for delivering information to a TextDocumentContentProvider. -// The following functions are used to structure and destructure relevant information to/from a URI. -// Colons are not valid characters in either the group name or stream name and will be used as separators. - -/** Records "filter" metric if a filter was actually applied. */ -export function recordTelemetryFilter(logData: CloudWatchLogsData): void { - const hasTimeFilter = !!logData.parameters.startTime - const hasTextFilter = !!logData.parameters.filterPattern && logData.parameters.filterPattern !== '' - // Update current container (span) metric. - telemetry.record({ - hasTimeFilter: hasTimeFilter, - hasTextFilter: hasTextFilter, - }) -} - -/** - * This function concatenates the path and query to create a unique (enough) identifier for a URI. - * @param uri - * @returns - */ -export function uriToKey(uri: vscode.Uri): string { - if (uri.query) { - try { - const { filterPattern, startTime, endTime, limit, streamNameOptions } = cwlUriSchema.parse(uri).parameters - const parts = [uri.path, filterPattern, startTime, endTime, limit, streamNameOptions] - return parts.map((p) => p ?? '').join(':') - } catch { - throw new Error( - `Unable to parse ${uri.query} into JSON and therefore cannot key uri with path: ${uri.path}` - ) - } - } - return uri.path -} - -/** - * Creates a message key from region + loggroup + logstream. - * - * Intentionally _ignores_ query parameters, so that the validation step can use the same progress - * message as the actual log group search. That results in a more fluid UX. - */ -export function msgKey(logGroupInfo: CloudWatchLogsGroupInfo): string { - const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} }) - return uri.toString() -} - -/** - * Destructures an awsCloudWatchLogs URI into its component pieces. - * @param uri URI for a Cloudwatch Logs file - */ -function parseCloudWatchLogsUri(uri: vscode.Uri): CloudWatchLogsArgs { - const parts = uri.path.split(':') - - if (uri.scheme !== CLOUDWATCH_LOGS_SCHEME) { - throw new Error(`URI ${uri} is not parseable for CloudWatch Logs`) - } - - const logGroupInfo: CloudWatchLogsGroupInfo = { - regionName: parts[0], - groupName: parts[1], - } - - if (parts.length === 3) { - logGroupInfo.streamName = parts[2] - } - - return { - logGroupInfo, - parameters: JSON.parse(uri.query), - } -} - -export function patternFromCwlUri(uri: vscode.Uri): CloudWatchLogsParameters['filterPattern'] { - return cwlUriSchema.parse(uri).parameters.filterPattern -} - -/** - * Determines if loadOlder codelense should be visible on virtual document - * @param uri CloudWatchLogs Document URI - * @returns - */ -export function isLogStreamUri(uri: vscode.Uri): boolean { - const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo - return logGroupInfo.streamName !== undefined -} - -/** - * Converts relevant information for CloudWatch Logs Streams into an awsCloudWatchLogs URI - * @param groupName Log group name - * @param streamName Log stream name - * @param regionName AWS region - */ -function createURIFromArgs(args: CloudWatchLogsArgs): vscode.Uri { - let uriStr = `${CLOUDWATCH_LOGS_SCHEME}:${args.logGroupInfo.regionName}:${args.logGroupInfo.groupName}` - uriStr += args.logGroupInfo.streamName ? `:${args.logGroupInfo.streamName}` : '' - - uriStr += `?${encodeURIComponent(JSON.stringify(args.parameters))}` - return vscode.Uri.parse(uriStr) -} -export const cwlUriSchema = new UriSchema(parseCloudWatchLogsUri, createURIFromArgs) - -export class CloudWatchLogsSettings extends fromExtensionManifest('aws.cwl', { limit: Number }) {} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/addLogEvents.ts b/packages/core/src/awsService/cloudWatchLogs/commands/addLogEvents.ts deleted file mode 100644 index fe8b7cb533..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/addLogEvents.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import AsyncLock from 'async-lock' -import { getLogger } from '../../../shared/logger/logger' -import { LogDataRegistry } from '../registry/logDataRegistry' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { localize } from 'vscode-nls' - -// TODO: Cut a PR to the async-lock package?...as of now, maxPending = 0 is theoretically ideal, but also falsy (which sets maxPending = 1000): -// https://github.com/rogierschouten/async-lock/blob/78cb0c2441650d7bdc148548f99542ccc9c93fd7/lib/index.js#L19 -const lock = new AsyncLock({ maxPending: 1 }) - -export async function addLogEvents( - document: vscode.TextDocument, - registry: LogDataRegistry, - headOrTail: 'head' | 'tail', - onDidChangeCodeLensEvent: vscode.EventEmitter -): Promise { - const uri = document.uri - const lockName = `${headOrTail === 'head' ? 'logHeadLock' : 'logTailLock'}:${uri.path}` - - if (lock.isBusy(lockName)) { - getLogger().debug(`addLogEvents already locked for lock: ${lockName}`) - return - } - - try { - await lock.acquire(lockName, async () => { - // TODO: Find a way to force this to fire and run through codelens provider prior to updateLog call? - // Currently EXTREMELY unreliable that the event signaling that the busy status is set occurs before updateLog completes. - registry.setBusyStatus(uri, true) - if (onDidChangeCodeLensEvent) { - onDidChangeCodeLensEvent.fire() - } - await registry.fetchNextLogEvents(uri, headOrTail) - getLogger().debug('Update done, releasing lock...') - }) - } catch (e) { - if (CancellationError.isUserCancelled(e)) { - getLogger().debug('cwl: User Cancelled Search') - } else { - // contingency in case lock isn't busy but still locked out. Don't want to accidentally trigger making codelens not busy - const error = e as Error - void vscode.window.showErrorMessage( - localize( - 'AWS.cwl.searchLogGroup.errorRetrievingLogs2', - 'Failed to get logs for {0}: {1}', - uri.path, - error.message - ) - ) - } - } finally { - registry.setBusyStatus(uri, false) - if (onDidChangeCodeLensEvent) { - onDidChangeCodeLensEvent.fire() - } - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts b/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts deleted file mode 100644 index 846f87e797..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/copyLogResource.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' -import { copyToClipboard } from '../../../shared/utilities/messages' - -export async function copyLogResource(uri?: vscode.Uri): Promise { - try { - if (!uri) { - // No URI = used command palette as entrypoint, attempt to get URI from active editor - // should work correctly under any normal circumstances since the action only appears in command palette when the editor is a CloudWatch Logs editor - uri = vscode.window.activeTextEditor?.document.uri - if (!uri) { - throw new Error('no active text editor, or undefined URI') - } - } - const parsedUri = cwlUriSchema.parse(uri) - const resourceName = isLogStreamUri(uri) ? parsedUri.logGroupInfo.streamName : parsedUri.logGroupInfo.groupName - - if (!resourceName) { - throw new Error(`Failed to copy log resource name for URI (missing stream or group?): ${uri}`) - } - await copyToClipboard(resourceName) - } catch (e) { - void vscode.window.showErrorMessage( - localize( - 'AWS.cwl.invalidEditor', - 'Invalid Cloudwatch Log stream or group: {0}', - vscode.window.activeTextEditor?.document.fileName - ) - ) - return - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts b/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts deleted file mode 100644 index 9d1600fafb..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' -import { telemetry, CloudWatchResourceType, Result } from '../../../shared/telemetry/telemetry' -import fs from '../../../shared/fs/fs' - -/** Prompts the user to select a file location to save the currently visible "aws-cwl:" document to. */ -export async function saveCurrentLogDataContent(): Promise { - let result: Result = 'Succeeded' - let resourceType: CloudWatchResourceType = 'logStream' // Default to stream if it fails to find URI - - try { - // Get URI from active editor. - const uri = vscode.window.activeTextEditor?.document.uri - if (!uri) { - throw new Error() - } - - resourceType = isLogStreamUri(uri) ? 'logStream' : 'logGroup' - const content = vscode.window.activeTextEditor?.document.getText() - const workspaceDir = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri - : vscode.Uri.file(fs.getUserHomeDir()) - const uriComponents = cwlUriSchema.parse(uri) - const logGroupInfo = uriComponents.logGroupInfo - - const localizedLogFile = localize('AWS.command.saveCurrentLogDataContent.logfile', 'Log File') - const selectedUri = await vscode.window.showSaveDialog({ - defaultUri: vscode.Uri.joinPath( - workspaceDir, - logGroupInfo.streamName ? logGroupInfo.streamName : logGroupInfo.groupName - ), - filters: { - [localizedLogFile]: ['log'], - }, - }) - - if (selectedUri && content) { - try { - await fs.writeFile(selectedUri, content) - } catch (e) { - result = 'Failed' - const err = e as Error - void vscode.window.showErrorMessage( - localize( - 'AWS.command.saveCurrentLogDataContent.error', - 'Error saving current log to {0}: {1}', - selectedUri.fsPath, - err.message - ) - ) - } - } else { - result = 'Cancelled' - } - } catch (e) { - result = 'Failed' - void vscode.window.showErrorMessage( - localize( - 'AWS.cwl.invalidEditor', - 'Not a Cloudwatch Log data source: {0}', - vscode.window.activeTextEditor?.document.fileName - ) - ) - } - - telemetry.cloudwatchlogs_download.emit({ - result: result, - cloudWatchResourceType: resourceType, - }) -} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts deleted file mode 100644 index 6b45f35fb5..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/searchLogGroup.ts +++ /dev/null @@ -1,304 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { - CloudWatchLogsData, - CloudWatchLogsGroupInfo, - LogDataRegistry, - filterLogEventsFromUri, - CloudWatchLogsParameters, - initLogData, -} from '../registry/logDataRegistry' -import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { isValidResponse, isWizardControl, Wizard, WIZARD_RETRY } from '../../../shared/wizards/wizard' -import { cwlUriSchema, msgKey, recordTelemetryFilter } from '../cloudWatchLogsUtils' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { getLogger } from '../../../shared/logger/logger' -import { TimeFilterResponse, TimeFilterSubmenu } from '../timeFilterSubmenu' -import { ExtendedInputBoxOptions, InputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter' -import { RegionSubmenu, RegionSubmenuResponse } from '../../../shared/ui/common/regionSubmenu' -import { truncate } from '../../../shared/utilities/textUtilities' -import { createBackButton, createExitButton, createHelpButton } from '../../../shared/ui/buttons' -import { PromptResult } from '../../../shared/ui/prompter' -import { ToolkitError } from '../../../shared/errors' -import { Messages } from '../../../shared/utilities/messages' -import { showFile } from '../../../shared/utilities/textDocumentUtilities' -import { LogGroup } from '@aws-sdk/client-cloudwatch-logs' - -const localize = nls.loadMessageBundle() - -function handleWizardResponse(response: SearchLogGroupWizardResponse, registry: LogDataRegistry): CloudWatchLogsData { - const logGroupInfo: CloudWatchLogsGroupInfo = { - groupName: response.submenuResponse.data, - regionName: response.submenuResponse.region, - } - let parameters: CloudWatchLogsParameters - const limitParam = registry.configuration.get('limit', 10000) - - if (response.timeRange.start === response.timeRange.end) { - // this means no time filter. - parameters = { - limit: limitParam, - filterPattern: response.filterPattern, - } - } else { - parameters = { - limit: limitParam, - filterPattern: response.filterPattern, - startTime: response.timeRange.start, - endTime: response.timeRange.end, - } - } - - const logData = initLogData(logGroupInfo, parameters, filterLogEventsFromUri) - recordTelemetryFilter(logData) - - return logData -} - -export async function prepareDocument(uri: vscode.Uri, logData: CloudWatchLogsData, registry: LogDataRegistry) { - try { - // Gets the data: calls filterLogEventsFromUri(). - await registry.fetchNextLogEvents(uri) - await showFile(uri) - } catch (err) { - if (CancellationError.isUserCancelled(err)) { - throw err - } - - throw ToolkitError.chain( - err, - localize( - 'AWS.cwl.searchLogGroup.errorRetrievingLogs', - 'Failed to get logs for {0}', - cwlUriSchema.parse(uri).logGroupInfo.groupName - ) - ) - } -} - -/** - * "Search Log Group" command - * - * @param registry - * @param source Telemetry "source" name - * @param logData - */ -export async function searchLogGroup( - registry: LogDataRegistry, - source: string, - logData?: { regionName: string; groupName: string } -): Promise { - await telemetry.cloudwatchlogs_open.run(async (span) => { - const wizard = new SearchLogGroupWizard(logData) - span.record({ cloudWatchResourceType: 'logGroup', source: source }) - const response = await wizard.run() - if (!response) { - throw new CancellationError('user') - } - - const userResponse = handleWizardResponse(response, registry) - const uri = cwlUriSchema.form({ logGroupInfo: userResponse.logGroupInfo, parameters: userResponse.parameters }) - await prepareDocument(uri, userResponse, registry) - }) -} - -async function getLogGroupsFromRegion(regionCode: string): Promise[]> { - const client = new CloudWatchLogsClient(regionCode) - const logGroups = await logGroupsToArray(client.describeLogGroups()) - const options = logGroups.map>((logGroupString) => ({ - label: logGroupString, - data: logGroupString, - })) - return options -} - -async function logGroupsToArray(logGroups: AsyncIterableIterator): Promise { - const logGroupsArray = [] - for await (const logGroupObject of logGroups) { - logGroupObject.logGroupName && logGroupsArray.push(logGroupObject.logGroupName) - } - return logGroupsArray -} - -/** - * HACK: this subclass overrides promptUser() so that we can validate the - * search pattern against the service and if it fails, keep the prompt displayed. - * - * This is necessary until vscode's inputbox.onDidAccept() awaits async callbacks: - * - https://github.com/aws/aws-toolkit-vscode/pull/3114#discussion_r1085484630 - * - https://github.com/microsoft/vscode/blob/78947444843f4ebb094e5ab4288360010a293463/extensions/git-base/src/remoteSource.ts#L13 - * - https://github.com/microsoft/vscode/blob/78947444843f4ebb094e5ab4288360010a293463/src/vs/base/browser/ui/inputbox/inputBox.ts#L511 - */ -export class SearchPatternPrompter extends InputBoxPrompter { - constructor( - public logGroup: CloudWatchLogsGroupInfo, - public logParams: CloudWatchLogsParameters, - /** HACK: also maintain ad-hoc state because `wizardState` is not mutable. */ - public readonly retryState: any, - public override readonly inputBox: InputBox, - protected override readonly options: ExtendedInputBoxOptions = {}, - private noValidate: boolean - ) { - super(inputBox, options) - this.inputBox.validationMessage = retryState.validationMessage ? retryState.validationMessage : undefined - if (this.retryState.searchPattern) { - this.inputBox.value = this.retryState.searchPattern - } - this.inputBox.onDidChangeValue((val) => { - this.inputBox.validationMessage = undefined - }) - } - - protected override async promptUser(): Promise> { - const rv = await super.promptUser() - this.inputBox.busy = true - try { - if (isWizardControl(rv)) { - return rv - } - - const validationResult = await this.validateSearchPattern(this.inputBox.value, this.noValidate) - // HACK: maintain our own state and restore it. - this.retryState.searchPattern = isValidResponse(rv) ? rv : undefined - this.retryState.validationMessage = validationResult - - if (validationResult !== undefined) { - return WIZARD_RETRY - } - return this.inputBox.value - } finally { - this.inputBox.busy = false - } - } - - async validateSearchPattern(searchPattern: string, noValidate: boolean): Promise { - if (noValidate) { - return undefined // Skip validation (service call) in tests. - } - getLogger().debug('cwl: validateSearchPattern: %O', searchPattern) - try { - await filterLogEventsFromUri( - this.logGroup, - { - ...this.logParams, - filterPattern: searchPattern, - limit: 1, - }, - undefined, - false - ) - } catch (e) { - // Validation error. Get the progress message from the global map and cancel it. - const msgTimeout = await Messages.putMessage(msgKey(this.logGroup), '') - msgTimeout.cancel() - - return (e as Error).message - } - return undefined - } -} - -/** - * Prompts the user for a search query, and validates it. - * - * @param noValidate For testing only: disable validation (which does a service call). - */ -export function createSearchPatternPrompter( - logGroup: CloudWatchLogsGroupInfo, - logParams: CloudWatchLogsParameters, - retryState: any, - isFirst: boolean, - noValidate: boolean -): SearchPatternPrompter { - const helpUri = - 'https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html#matching-terms-events' - const titleText = localize( - 'AWS.cwl.searchLogGroup.filterPatternTitle', - `Search Log Group {0}`, - truncate(logGroup.groupName, -50) - ) - const placeHolderText = localize( - 'AWS.cwl.searchLogGroup.filterPatternPlaceholder', - 'search pattern (case sensitive; empty matches all)' - ) - - const options = { - title: titleText, - placeholder: placeHolderText, - buttons: [createHelpButton(helpUri), createExitButton()], - } - - if (!isFirst) { - options.buttons = [...options.buttons, createBackButton()] - } - - const inputBox = vscode.window.createInputBox() as InputBox - // assign({ ...defaultInputboxOptions, ...options }, inputBox) - inputBox.title = titleText - inputBox.placeholder = placeHolderText - inputBox.buttons = options.buttons - const prompter = new SearchPatternPrompter(logGroup, logParams, retryState, inputBox, {}, noValidate) - return prompter -} - -export function createRegionSubmenu() { - return new RegionSubmenu( - getLogGroupsFromRegion, - { title: localize('AWS.cwl.searchLogGroup.logGroupPromptTitle', 'Select Log Group') }, - { title: localize('AWS.cwl.searchLogGroup.regionPromptTitle', 'Select Region for Log Group') }, - 'Log Groups' - ) -} - -export interface SearchLogGroupWizardResponse { - submenuResponse: RegionSubmenuResponse - filterPattern: string - timeRange: TimeFilterResponse -} - -export class SearchLogGroupWizard extends Wizard { - /** HACK: maintain our own state and restore it because WizardState is not mutable. */ - private retryState: any = {} - - public constructor(logGroupInfo?: CloudWatchLogsGroupInfo) { - super({ - initState: { - submenuResponse: logGroupInfo - ? { - data: logGroupInfo.groupName, - region: logGroupInfo.regionName, - } - : undefined, - }, - }) - - this.form.submenuResponse.bindPrompter(createRegionSubmenu) - this.form.timeRange.bindPrompter(() => new TimeFilterSubmenu()) - this.form.filterPattern.bindPrompter((state) => { - if (!state.submenuResponse) { - throw Error('state.submenuResponse is null') - } - return createSearchPatternPrompter( - { - groupName: state.submenuResponse.data, - regionName: state.submenuResponse.region, - }, - { - startTime: state.timeRange?.start, - endTime: state.timeRange?.end, - filterPattern: undefined, - }, - this.retryState, - logGroupInfo ? true : false, - false - ) - }) - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts b/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts deleted file mode 100644 index a86ed4d338..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/tailLogGroup.ts +++ /dev/null @@ -1,292 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { TailLogGroupWizard } from '../wizard/tailLogGroupWizard' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { LiveTailSession, LiveTailSessionConfiguration } from '../registry/liveTailSession' -import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' -import { - LiveTailSessionLogEvent, - LiveTailSessionUpdate, - StartLiveTailResponseStream, -} from '@aws-sdk/client-cloudwatch-logs' -import { ToolkitError } from '../../../shared/errors' -import { getLogger } from '../../../shared/logger/logger' -import globals from '../../../shared/extensionGlobals' -import { uriToKey } from '../cloudWatchLogsUtils' -import { LiveTailCodeLensProvider } from '../document/liveTailCodeLensProvider' -import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu' - -export async function tailLogGroup( - registry: LiveTailSessionRegistry, - source: string, - codeLensProvider: LiveTailCodeLensProvider, - logData?: { regionName: string; groupName: string }, - logStreamFilterData?: LogStreamFilterResponse -): Promise { - await telemetry.cloudwatchlogs_startLiveTail.run(async (span) => { - const wizard = new TailLogGroupWizard(logData, logStreamFilterData) - const wizardResponse = await wizard.run() - if (!wizardResponse) { - throw new CancellationError('user') - } - if (wizardResponse.logStreamFilter.type === 'menu' || wizardResponse.logStreamFilter.type === undefined) { - // logstream filter wizard uses type to determine which submenu to show. 'menu' is set when no type is selected - // and to show the 'menu' of selecting a type. This should not be reachable due to the picker logic, but validating in case. - throw new ToolkitError(`Invalid Log Stream filter type: ${wizardResponse.logStreamFilter.type}`) - } - const awsCredentials = await globals.awsContext.getCredentials() - if (awsCredentials === undefined) { - throw new ToolkitError('Failed to start LiveTail session: credentials are undefined.') - } - const liveTailSessionConfig: LiveTailSessionConfiguration = { - logGroupArn: wizardResponse.regionLogGroupSubmenuResponse.data, - logStreamFilter: wizardResponse.logStreamFilter, - logEventFilterPattern: wizardResponse.filterPattern, - region: wizardResponse.regionLogGroupSubmenuResponse.region, - awsCredentials: awsCredentials, - } - const session = new LiveTailSession(liveTailSessionConfig) - if (registry.has(uriToKey(session.uri))) { - await vscode.window.showTextDocument(session.uri, { preview: false }) - void vscode.window.showInformationMessage(`Switching editor to an existing session that matches request.`) - span.record({ - result: 'Succeeded', - sessionAlreadyStarted: true, - source: source, - }) - return - } - const document = await prepareDocument(session) - - const disposables: vscode.Disposable[] = [] - disposables.push(hideShowStatusBarItemsOnActiveEditor(session, document)) - disposables.push(closeSessionWhenAllEditorsClosed(session, registry, document, codeLensProvider)) - - try { - const stream = await session.startLiveTailSession() - registry.set(uriToKey(session.uri), session) - codeLensProvider.refresh() - getLogger().info(`LiveTail session started: ${uriToKey(session.uri)}`) - span.record({ - source: source, - result: 'Succeeded', - sessionAlreadyStarted: false, - hasTextFilter: Boolean(wizardResponse.filterPattern), - filterType: wizardResponse.logStreamFilter.type, - }) - await handleSessionStream(stream, document, session) - } finally { - for (const disposable of disposables) { - disposable.dispose() - } - } - }) -} - -export function closeSession( - sessionUri: vscode.Uri, - registry: LiveTailSessionRegistry, - source: string, - codeLensProvider: LiveTailCodeLensProvider -) { - telemetry.cloudwatchlogs_stopLiveTail.run((span) => { - const session = registry.get(uriToKey(sessionUri)) - if (session === undefined) { - throw new ToolkitError(`No LiveTail session found for URI: ${uriToKey(sessionUri)}`) - } - session.stopLiveTailSession() - registry.delete(uriToKey(sessionUri)) - void vscode.window.showInformationMessage(`Stopped LiveTail session: ${uriToKey(sessionUri)}`) - codeLensProvider.refresh() - span.record({ - result: 'Succeeded', - source: source, - duration: session.getLiveTailSessionDuration(), - }) - }) -} - -export async function clearDocument(textDocument: vscode.TextDocument) { - const edit = new vscode.WorkspaceEdit() - const startPosition = new vscode.Position(0, 0) - const endPosition = new vscode.Position(textDocument.lineCount, 0) - edit.delete(textDocument.uri, new vscode.Range(startPosition, endPosition)) - await vscode.workspace.applyEdit(edit) -} - -async function prepareDocument(session: LiveTailSession): Promise { - const textDocument = await vscode.workspace.openTextDocument(session.uri) - await clearDocument(textDocument) - await vscode.window.showTextDocument(textDocument, { preview: false }) - await vscode.languages.setTextDocumentLanguage(textDocument, 'log') - session.showStatusBarItem(true) - return textDocument -} - -async function handleSessionStream( - stream: AsyncIterable, - document: vscode.TextDocument, - session: LiveTailSession -) { - try { - for await (const event of stream) { - if (event.sessionUpdate !== undefined && event.sessionUpdate.sessionResults !== undefined) { - const formattedLogEvents = event.sessionUpdate.sessionResults.map((logEvent) => - formatLogEvent(logEvent) - ) - if (formattedLogEvents.length !== 0) { - // Determine should scroll before adding new lines to doc because adding large - // amount of new lines can push bottom of file out of view before scrolling. - const editorsToScroll = getTextEditorsToScroll(document) - await updateTextDocumentWithNewLogEvents(formattedLogEvents, document, session.maxLines) - // eslint-disable-next-line unicorn/no-array-for-each - editorsToScroll.forEach(scrollTextEditorToBottom) - } - session.eventRate = eventRate(event.sessionUpdate) - session.isSampled = isSampled(event.sessionUpdate) - } - } - } catch (e) { - if (session.isAborted) { - // Expected case. User action cancelled stream (CodeLens, Close Editor, etc.). - // AbortSignal interrupts the LiveTail stream, causing error to be thrown here. - // Can assume that stopLiveTailSession() has already been called - AbortSignal is only - // exposed through that method. - getLogger().info(`LiveTail session stopped: ${uriToKey(session.uri)}`) - } else { - // Unexpected exception. - session.stopLiveTailSession() - throw ToolkitError.chain( - e, - `Unexpected on-stream exception while tailing session: ${session.uri.toString()}` - ) - } - } -} - -function formatLogEvent(logEvent: LiveTailSessionLogEvent): string { - if (!logEvent.timestamp || !logEvent.message) { - return '' - } - const timestamp = new Date(logEvent.timestamp).toLocaleTimeString('en', { - timeStyle: 'medium', - hour12: false, - timeZone: 'UTC', - }) - let line = timestamp.concat('\t', logEvent.message) - if (!line.endsWith('\n')) { - line = line.concat('\n') - } - return line -} - -// Auto scroll visible LiveTail session editors if the end-of-file is in view. -// This allows for newly added log events to stay in view. -function getTextEditorsToScroll(document: vscode.TextDocument): vscode.TextEditor[] { - return vscode.window.visibleTextEditors.filter((editor) => { - if (editor.document !== document) { - return false - } - return editor.visibleRanges[0].contains(new vscode.Position(document.lineCount - 1, 0)) - }) -} - -function scrollTextEditorToBottom(editor: vscode.TextEditor) { - const position = new vscode.Position(Math.max(editor.document.lineCount - 2, 0), 0) - editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.Default) -} - -async function updateTextDocumentWithNewLogEvents( - formattedLogEvents: string[], - document: vscode.TextDocument, - maxLines: number -) { - const edit = new vscode.WorkspaceEdit() - for (const formattedLogEvent of formattedLogEvents) { - edit.insert(document.uri, new vscode.Position(document.lineCount, 0), formattedLogEvent) - } - - if (document.lineCount + formattedLogEvents.length > maxLines) { - trimOldestLines(formattedLogEvents.length, maxLines, document, edit) - } - await vscode.workspace.applyEdit(edit) -} - -function trimOldestLines( - numNewLines: number, - maxLines: number, - document: vscode.TextDocument, - edit: vscode.WorkspaceEdit -) { - const numLinesToTrim = document.lineCount + numNewLines - maxLines - const startPosition = new vscode.Position(0, 0) - const endPosition = new vscode.Position(numLinesToTrim, 0) - const range = new vscode.Range(startPosition, endPosition) - edit.delete(document.uri, range) -} - -function isSampled(event: LiveTailSessionUpdate): boolean { - return event.sessionMetadata === undefined || event.sessionMetadata.sampled === undefined - ? false - : event.sessionMetadata.sampled -} - -function eventRate(event: LiveTailSessionUpdate): number { - return event.sessionResults === undefined ? 0 : event.sessionResults.length -} - -function hideShowStatusBarItemsOnActiveEditor( - session: LiveTailSession, - document: vscode.TextDocument -): vscode.Disposable { - return vscode.window.onDidChangeActiveTextEditor((editor) => { - session.showStatusBarItem(editor?.document === document) - }) -} - -/** - * The LiveTail session should be automatically closed if the user does not have the session's - * document in any Tab in their editor. - * - * `onDidCloseTextDocument` doesn't work for our case because the tailLogGroup command will keep the stream - * writing to the doc even when all its tabs/editors are closed, seemingly keeping the doc 'open'. - * Also there is no guarantee that this event fires when an editor tab is closed - * - * `onDidChangeVisibleTextEditors` returns editors that the user can see its contents. An editor that is open, but hidden - * from view, will not be returned. Meaning a Tab that is created (shown in top bar), but not open, will not be returned. Even if - * the tab isn't visible, we want to continue writing to the doc, and keep the session alive. - */ -function closeSessionWhenAllEditorsClosed( - session: LiveTailSession, - registry: LiveTailSessionRegistry, - document: vscode.TextDocument, - codeLensProvider: LiveTailCodeLensProvider -): vscode.Disposable { - return vscode.window.tabGroups.onDidChangeTabs((tabEvent) => { - const isOpen = isLiveTailSessionOpenInAnyTab(session) - if (!isOpen) { - closeSession(session.uri, registry, 'ClosedEditors', codeLensProvider) - void clearDocument(document) - } - }) -} - -function isLiveTailSessionOpenInAnyTab(liveTailSession: LiveTailSession) { - let isOpen = false - // eslint-disable-next-line unicorn/no-array-for-each - vscode.window.tabGroups.all.forEach(async (tabGroup) => { - for (const tab of tabGroup.tabs) { - if (tab.input instanceof vscode.TabInputText) { - if (liveTailSession.uri.toString() === tab.input.uri.toString()) { - isOpen = true - } - } - } - }) - return isOpen -} diff --git a/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts b/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts deleted file mode 100644 index 87b6261110..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/commands/viewLogStream.ts +++ /dev/null @@ -1,222 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import * as picker from '../../../shared/ui/picker' -import { MultiStepWizard, WIZARD_RETRY, WIZARD_TERMINATE, WizardStep } from '../../../shared/wizards/multiStepWizard' -import { LogGroupNode } from '../explorer/logGroupNode' -import * as CloudWatchLogs from '@aws-sdk/client-cloudwatch-logs' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { getPaginatedAwsCallIter, IteratorTransformer } from '../../../shared/utilities/collectionUtils' -import { - CloudWatchLogsGroupInfo, - CloudWatchLogsParameters, - LogDataRegistry, - initLogData as initLogData, - filterLogEventsFromUri, -} from '../registry/logDataRegistry' -import { prepareDocument, searchLogGroup } from './searchLogGroup' -import { telemetry, Result } from '../../../shared/telemetry/telemetry' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { cwlUriSchema } from '../cloudWatchLogsUtils' -import { formatLocalized } from '../../../shared/datetime' - -export async function viewLogStream(node: LogGroupNode, registry: LogDataRegistry): Promise { - await telemetry.cloudwatchlogs_open.run(async (span) => { - span.record({ cloudWatchResourceType: 'logStream', source: 'Explorer' }) - const r = await new SelectLogStreamWizard(node).run() - if (r === undefined || r.kind === 'cancelled' || r.kind === 'failed') { - throw new CancellationError('user') - } - - if (r.kind === 'doSearchLogGroup') { - return searchLogGroup(registry, 'LogStreamMenu', { - regionName: node.regionCode, - groupName: node.logGroup.logGroupName!, - }) - } - - const logGroupInfo: CloudWatchLogsGroupInfo = { - groupName: r.logGroupName, - regionName: r.region, - streamName: r.logStreamName, - } - - const parameters: CloudWatchLogsParameters = { - limit: registry.configuration.get('limit', 10000), - } - - const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: parameters }) - const logData = initLogData(logGroupInfo, parameters, filterLogEventsFromUri) - await prepareDocument(uri, logData, registry) - }) -} - -/** - * This represents the final user choice from the select log stream wizard. - * - * Why? This wizard has different kinds of results, and this is a simple way to - * represent them. - */ -export type LogSearchChoice = - | { kind: 'doSearchLogGroup' } - | { kind: 'selectedLogStream'; logStreamName: string; logGroupName: string; region: string } - | { kind: 'cancelled' } - | { kind: 'failed' } - -export interface SelectLogStreamWizardContext { - pickLogStream(): Promise -} - -export class DefaultSelectLogStreamWizardContext implements SelectLogStreamWizardContext { - private readonly totalSteps = 1 - public constructor( - private readonly regionCode: string, - private readonly logGroupName: string - ) {} - - public async pickLogStream(): Promise { - const client = new CloudWatchLogsClient(this.regionCode) - const request: CloudWatchLogs.DescribeLogStreamsRequest = { - logGroupName: this.logGroupName, - orderBy: 'LastEventTime', - descending: true, - } - const qp = picker.createQuickPick({ - options: { - title: localize('AWS.cwl.viewLogStream.workflow.prompt', 'Select a log stream'), - step: 1, - totalSteps: this.totalSteps, - }, - }) - - // Add item to top of quick pick which indicates user instead wants to do Search Log Group quickpick flow - const searchLogGroupItems: vscode.QuickPickItem[] = [ - { label: 'Actions', kind: vscode.QuickPickItemKind.Separator }, - { label: 'Search Log Group', detail: 'Search all Log Streams in this Log Group' }, - { label: 'Log Streams', kind: vscode.QuickPickItemKind.Separator }, - ] - - qp.items = searchLogGroupItems.concat(qp.items) - - const populator = new IteratorTransformer( - () => - getPaginatedAwsCallIter({ - awsCall: (request) => client.describeLogStreams(request), - nextTokenNames: { - request: 'nextToken', - response: 'nextToken', - }, - request, - }), - (response) => convertDescribeLogToQuickPickItems(response) - ) - - const controller = new picker.IteratingQuickPickController(qp, populator) - controller.startRequests() - const choices = await picker.promptUser({ - picker: qp, - onDidTriggerButton: (button, resolve, reject) => - controller.iteratingOnDidTriggerButton(button, resolve, reject), - }) - - const val = picker.verifySinglePickerOutput(choices) - - const result = val?.label - - // The possible results of this wizard are not binary, the following - // returns an object representing the final result (along with any necessary data) - return this.shouldSearchLogGroup(result) ?? this.getSelectedLogStream(result) - } - - /** - * Returns an object indicating the user wants to search the log groups, - * otherwise undefined - */ - shouldSearchLogGroup(quickPickResult?: string): (LogSearchChoice & { kind: 'doSearchLogGroup' }) | undefined { - if (quickPickResult !== 'Search Log Group') { - return undefined - } - - return { kind: 'doSearchLogGroup' } - } - - /** - * Returns the result of the log stream selection process. - * - * It is possible nothing was selected, this will be indicated - * by the return value. - */ - getSelectedLogStream(result?: string): LogSearchChoice { - let telemetryResult: Result = 'Succeeded' - let choice: LogSearchChoice - // handle no items for a group as a cancel - if (!result || result === picker.IteratingQuickPickController.NO_ITEMS_ITEM.label) { - choice = { kind: 'cancelled' } - telemetryResult = 'Cancelled' - } - // retry handled by caller -- should this be a "Failed"? - // of note: we don't track if an error pops up, we just track if the error is selected. - else if (result === picker.IteratingQuickPickController.ERROR_ITEM.label) { - choice = { kind: 'failed' } - telemetryResult = 'Failed' - } else { - choice = { - kind: 'selectedLogStream', - logStreamName: result, - region: this.regionCode, - logGroupName: this.logGroupName, - } - } - - telemetry.cloudwatchlogs_openGroup.emit({ result: telemetryResult }) - return choice - } -} - -export function convertDescribeLogToQuickPickItems( - response: CloudWatchLogs.DescribeLogStreamsResponse -): vscode.QuickPickItem[] { - return (response.logStreams ?? []).map((stream) => ({ - label: stream.logStreamName!, - detail: stream.lastEventTimestamp - ? formatLocalized(new Date(stream.lastEventTimestamp)) - : localize('AWS.cwl.viewLogStream.workflow.noStreams', '[No Log Events found]'), - })) -} - -export class SelectLogStreamWizard extends MultiStepWizard { - private response: LogSearchChoice = { kind: 'cancelled' } - - public constructor( - node: LogGroupNode, - private readonly context: SelectLogStreamWizardContext = new DefaultSelectLogStreamWizardContext( - node.regionCode, - node.logGroup.logGroupName! - ) - ) { - super() - } - - protected get startStep(): WizardStep { - return this.selectStream - } - - protected getResult(): LogSearchChoice { - return this.response - } - - private readonly selectStream: WizardStep = async () => { - this.response = await this.context.pickLogStream() - if (this.response.kind === 'failed') { - // retry on error - return WIZARD_RETRY - } - return WIZARD_TERMINATE - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts deleted file mode 100644 index dde0ddbbe2..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/liveTailCodeLensProvider.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' -import { LiveTailSessionRegistry } from '../registry/liveTailSessionRegistry' -import { uriToKey } from '../cloudWatchLogsUtils' - -export class LiveTailCodeLensProvider implements vscode.CodeLensProvider { - private _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter() - public readonly onDidChangeCodeLenses: vscode.Event = this._onDidChangeCodeLenses.event - - public constructor(private readonly registry: LiveTailSessionRegistry) {} - - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.ProviderResult { - const uri = document.uri - // if registry does not contain session, it is assumed to have been stopped, thus, hide lenses. - if (uri.scheme !== cloudwatchLogsLiveTailScheme || !this.registry.has(uriToKey(uri))) { - return [] - } - const codeLenses: vscode.CodeLens[] = [] - codeLenses.push(this.buildClearDocumentCodeLens(document)) - codeLenses.push(this.buildStopTailingCodeLens(document)) - return codeLenses - } - - public refresh() { - this._onDidChangeCodeLenses.fire() - } - - private buildClearDocumentCodeLens(document: vscode.TextDocument): vscode.CodeLens { - const range = this.getBottomOfDocumentRange(document) - const command: vscode.Command = { - title: 'Clear document', - command: 'aws.cwl.clearDocument', - arguments: [document], - } - return new vscode.CodeLens(range, command) - } - - private buildStopTailingCodeLens(document: vscode.TextDocument): vscode.CodeLens { - const range = this.getBottomOfDocumentRange(document) - const command: vscode.Command = { - title: 'Stop tailing', - command: 'aws.cwl.stopTailingLogGroup', - arguments: [document, 'codeLens'], - } - return new vscode.CodeLens(range, command) - } - - private getBottomOfDocumentRange(document: vscode.TextDocument): vscode.Range { - return new vscode.Range( - new vscode.Position(document.lineCount - 1, 0), - new vscode.Position(document.lineCount - 1, 0) - ) - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts deleted file mode 100644 index 7d662890dc..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/liveTailDocumentProvider.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' - -export class LiveTailDocumentProvider implements vscode.TextDocumentContentProvider { - provideTextDocumentContent(uri: vscode.Uri, token: vscode.CancellationToken): vscode.ProviderResult { - // Content will be written to the document via handling a LiveTail response stream in the TailLogGroup command. - return '' - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/logDataCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/logDataCodeLensProvider.ts deleted file mode 100644 index 52245896c1..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/logDataCodeLensProvider.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { CLOUDWATCH_LOGS_SCHEME } from '../../../shared/constants' -import { LogDataRegistry } from '../registry/logDataRegistry' -import { isLogStreamUri } from '../cloudWatchLogsUtils' - -export class LogDataCodeLensProvider implements vscode.CodeLensProvider { - public constructor(private readonly registry: LogDataRegistry) {} - - private _onDidChangeCodeLenses = new vscode.EventEmitter() - public get onDidChangeCodeLenses(): vscode.Event { - return this._onDidChangeCodeLenses.event - } - - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.ProviderResult { - const uri = document.uri - const isBusy = this.registry.getBusyStatus(uri) - - const busyCommand: vscode.Command = { - title: localize('AWS.message.loading', 'Loading...'), - command: 'aws.doNothingCommand', - } - const codelenses = [] - const newerCodelense = { - range: new vscode.Range( - new vscode.Position(document.lineCount - 1, 0), - new vscode.Position(document.lineCount - 1, 0) - ), - isResolved: true, - command: isBusy - ? busyCommand - : { - title: localize('AWS.cwl.codeLens.loadNewer', 'Load newer events...'), - command: 'aws.addLogEvents', - arguments: [document, this.registry, 'tail', this._onDidChangeCodeLenses], - }, - } - - const oldCodeLense = { - range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)), - isResolved: true, - command: isBusy - ? busyCommand - : { - title: localize('AWS.cwl.codeLens.loadOlder', 'Load older events...'), - command: 'aws.addLogEvents', - arguments: [document, this.registry, 'head', this._onDidChangeCodeLenses], - }, - } - - // log stream documents always start at the oldest log event, so no need to get older events - if (!isLogStreamUri(uri)) { - codelenses.push(oldCodeLense) - } - codelenses.push(newerCodelense) - return uri.scheme === CLOUDWATCH_LOGS_SCHEME ? codelenses : [] - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts deleted file mode 100644 index f54a8b1239..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/logDataDocumentProvider.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { CloudWatchLogsGroupInfo, LogDataRegistry, UriString } from '../registry/logDataRegistry' -import { getLogger } from '../../../shared/logger/logger' -import { generateTextFromLogEvents, LineToLogStreamMap } from './textContent' -import { cwlUriSchema } from '../cloudWatchLogsUtils' - -export class LogDataDocumentProvider implements vscode.TextDocumentContentProvider { - /** Resolves the correct {@link LineToLogStreamMap} instance for a given URI */ - readonly lineToLogStreamMapResolver: Map = new Map() - // Expose an event to signal changes of _virtual_ documents - // to the editor - private _onDidChange = new vscode.EventEmitter() - public get onDidChange(): vscode.Event { - return this._onDidChange.event - } - - public constructor(private readonly registry: LogDataRegistry) { - this.registry.onDidChange((uri) => { - getLogger().debug(`Registry item changed: ${uri.path}`) - this._onDidChange.fire(uri) - }) - } - - public provideTextDocumentContent(uri: vscode.Uri): string { - if (!cwlUriSchema.isValid(uri)) { - throw new Error(`Uri is not a CWL Uri, so no text can be provided: ${uri.toString()}`) - } - const events = this.registry.fetchCachedLogEvents(uri) - const { text, streamIdMap } = generateTextFromLogEvents(events, { timestamps: true }) - this.lineToLogStreamMapResolver.set(uri.toString(), streamIdMap) - return text - } - - /** - * Gives the Log Stream ID at a given line of a given document. - * - * The given document uri must have already been processed by this - * class, otherwise an error will be thrown. - * - * * @param lineNumber The line number of the document. Starts at 0, NOT 1! - */ - public getLogStreamNameAtLine( - uri: vscode.Uri, - lineNumber: number - ): NonNullable { - let lineToLogStreamMap = this.lineToLogStreamMapResolver.get(uri.toString()) - if (lineToLogStreamMap === undefined) { - // Document was not initialized yet, we will initialize and try again. - this.provideTextDocumentContent(uri) - lineToLogStreamMap = this.lineToLogStreamMapResolver.get(uri.toString()) - if (lineToLogStreamMap === undefined) { - throw new Error(`${this.provideTextDocumentContent.name}() should have prevented this error.`) - } - } - - const logStreamName = lineToLogStreamMap.get(lineNumber) - if (logStreamName === undefined) { - throw new Error(`Line number was not set in LineToLogStreamMap: ${lineNumber}`) - } - return logStreamName - } -} - -export function getActiveDocumentUri(registry: LogDataRegistry) { - const currentEditor = vscode.window.activeTextEditor - if (!currentEditor) { - throw new Error('cwl: Failed to identify active editor.') - } - - const activeUri = currentEditor.document.uri - if (!registry.isRegistered(activeUri)) { - throw new Error('cwl: Document open has unregistered uri.') - } - - return activeUri -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts b/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts deleted file mode 100644 index 9c6409d731..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.ts +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { CLOUDWATCH_LOGS_SCHEME } from '../../../shared/constants' -import { CloudWatchLogsGroupInfo, LogDataRegistry } from '../registry/logDataRegistry' -import { CloudWatchLogsSettings, cwlUriSchema, isLogStreamUri } from '../cloudWatchLogsUtils' -import { LogDataDocumentProvider } from './logDataDocumentProvider' - -type IdWithLine = { streamId: string; lineNum: number } - -export class LogStreamCodeLensProvider implements vscode.CodeLensProvider { - /** - * Constructor - * - * @param documentProvider We specifically want {@link LogDataDocumentProvider} since it manages the stream - * id map which this class will read data from. - */ - public constructor( - private readonly registry: LogDataRegistry, - private readonly documentProvider: LogDataDocumentProvider - ) {} - - private _onDidChangeCodeLenses = new vscode.EventEmitter() - public get onDidChangeCodeLenses(): vscode.Event { - return this._onDidChangeCodeLenses.event - } - - public async provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): Promise { - const uri = document.uri - - if (uri.scheme !== CLOUDWATCH_LOGS_SCHEME || isLogStreamUri(uri)) { - return [] - } - - const logGroupInfo = cwlUriSchema.parse(uri).logGroupInfo - - if (logGroupInfo.streamName) { - // This means we have a stream file not a log search. - return - } - - const codelenses: vscode.CodeLens[] = [] - - const linesToGenerateCodeLens = await this.getStartingLineOfEachStreamId(document) - - // Create a code lens at the start of each Log Stream in the document - for (const idWithLine of linesToGenerateCodeLens) { - codelenses.push(this.createLogStreamCodeLens(logGroupInfo, idWithLine)) - } - return codelenses - } - - createLogStreamCodeLens(logGroupInfo: CloudWatchLogsGroupInfo, idWithLine: IdWithLine): vscode.CodeLens { - const settings = new CloudWatchLogsSettings() - const limit = settings.get('limit', 1000) - const cwlArgs = { - logGroupInfo: { ...logGroupInfo, streamName: idWithLine.streamId }, - parameters: { limit: limit }, - } - const streamUri = cwlUriSchema.form(cwlArgs) - const cmd: vscode.Command = { - command: 'aws.loadLogStreamFile', - arguments: [streamUri, this.registry], - title: 'View Full Log Stream...', - tooltip: 'Open the full Log Stream associated with these search results', - } - const codeLensLocation = new vscode.Range(idWithLine.lineNum, 0, idWithLine.lineNum, 0) - return new vscode.CodeLens(codeLensLocation, cmd) - } - - /** - * Gets the Log Stream Id + Line number it starts at for each Log Stream - * in the given document. - */ - private async getStartingLineOfEachStreamId(document: vscode.TextDocument): Promise { - const result: IdWithLine[] = [] - - let currLine = 0 - let lastStreamId = '' - while (currLine < document.lineCount - 1) { - const currStreamId = this.documentProvider.getLogStreamNameAtLine(document.uri, currLine) - - if (currStreamId === undefined) { - throw new Error(`uriToStreamIdMap does not reflect the latest content of: ${document.uri.toString()}`) - } - - if (currStreamId === lastStreamId) { - currLine++ - continue - } - result.push({ streamId: currStreamId, lineNum: currLine }) - currLine++ - lastStreamId = currStreamId - } - return result - } -} - -export async function loadAndOpenInitialLogStreamFile(uri: vscode.Uri, registry: LogDataRegistry): Promise { - const td = await vscode.workspace.openTextDocument(uri) - await Promise.all([ - vscode.window.showTextDocument(td), - vscode.languages.setTextDocumentLanguage(td, 'log'), - registry.fetchNextLogEvents(uri), - ]) -} diff --git a/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts b/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts deleted file mode 100644 index 7b415c586b..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/document/textContent.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { formatDateTimestamp } from '../../../shared/datetime' -import { CloudWatchLogsEvent, CloudWatchLogsGroupInfo } from '../registry/logDataRegistry' - -export const timestampSpaceEquivalent = ' ' - -/** - * CWL output text can consist of Log Events from multiple different - * Log Streams. This map type is intended to map a specific line to - * its respective Log Stream - */ -export type LineToLogStreamMap = Map> - -export function generateTextFromLogEvents( - events: CloudWatchLogsEvent[], - formatting?: { timestamps?: boolean } -): { text: string; streamIdMap: LineToLogStreamMap } { - const inlineNewLineRegex = /((\r\n)|\n|\r)(?!$)/g - // if no timestamp for some reason, entering a blank of equal length (29 characters long) - - const streamIdMap: LineToLogStreamMap = new Map() - let text: string = '' - let lineNumber = 0 - for (const event of events) { - let line: string = event.message ?? '' - if (formatting?.timestamps) { - // TODO: Handle different timezones and unix timestamps? - const timestamp = event.timestamp - ? formatDateTimestamp(true, new Date(event.timestamp)) - : timestampSpaceEquivalent - line = timestamp.concat('\t', line) - // log entries containing newlines are indented to the same length as the timestamp. - line = line.replace(inlineNewLineRegex, `\n${timestampSpaceEquivalent}\t`) - } - - if (!line.endsWith('\n')) { - line = line.concat('\n') - } - - const lineBreaks = (line.match(/\n/g) || []).length - if (event.logStreamName) { - for (let currentLine = lineNumber; currentLine <= lineNumber + lineBreaks - 1; currentLine++) { - streamIdMap.set(currentLine, event.logStreamName) - } - } - lineNumber += lineBreaks - - text = text.concat(line) - } - return { text, streamIdMap } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.ts b/packages/core/src/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.ts deleted file mode 100644 index 5d3891bd07..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' - -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' - -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { toMap, updateInPlace, toArrayAsync } from '../../../shared/utilities/collectionUtils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { LogGroupNode } from './logGroupNode' -import { LogGroup } from '@aws-sdk/client-cloudwatch-logs' - -export abstract class CloudWatchLogsBase extends AWSTreeNodeBase { - protected readonly logGroupNodes: Map - protected abstract readonly placeholderMessage: string - - public constructor( - label: string, - public override readonly regionCode: string, - protected readonly cloudwatchClient: CloudWatchLogsClient - ) { - super(label, vscode.TreeItemCollapsibleState.Collapsed) - this.logGroupNodes = new Map() - } - - protected abstract getLogGroups(client: CloudWatchLogsClient): Promise> - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.logGroupNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => new PlaceholderNode(this, this.placeholderMessage), - sort: (nodeA, nodeB) => nodeA.name.localeCompare(nodeB.name), - }) - } - - public async updateChildren(): Promise { - const logGroups = await this.getLogGroups(this.cloudwatchClient) - - updateInPlace( - this.logGroupNodes, - logGroups.keys(), - (key) => this.logGroupNodes.get(key)!.update(logGroups.get(key)!), - (key) => new LogGroupNode(this.regionCode, logGroups.get(key)!) - ) - } -} -export class CloudWatchLogsNode extends CloudWatchLogsBase { - protected readonly placeholderMessage = localize('AWS.explorerNode.cloudWatchLogs.nologs', '[No log groups found]') - - public constructor(regionCode: string, client = new CloudWatchLogsClient(regionCode)) { - super('CloudWatch Logs', regionCode, client) - this.contextValue = 'awsCloudWatchLogParentNode' - } - - protected async getLogGroups(client: CloudWatchLogsClient): Promise> { - return toMap(await toArrayAsync(client.describeLogGroups()), (configuration) => configuration.logGroupName) - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/explorer/logGroupNode.ts b/packages/core/src/awsService/cloudWatchLogs/explorer/logGroupNode.ts deleted file mode 100644 index 12c2e9dd0a..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/explorer/logGroupNode.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as os from 'os' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { getIcon } from '../../../shared/icons' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { LogGroup } from '@aws-sdk/client-cloudwatch-logs' - -export const contextValueCloudwatchLog = 'awsCloudWatchLogNode' - -export class LogGroupNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public override readonly regionCode: string, - public logGroup: LogGroup - ) { - super('') - this.update(logGroup) - this.iconPath = getIcon('aws-cloudwatch-log-group') - this.contextValue = contextValueCloudwatchLog - this.command = { - command: 'aws.cwl.viewLogStream', - title: localize('AWS.command.cloudWatchLogs.viewLogStream', 'View Log Stream'), - arguments: [this], - } - } - - public update(logGroup: LogGroup): void { - this.logGroup = logGroup - this.label = this.logGroup.logGroupName || '' - this.tooltip = `${this.logGroup.logGroupName}${os.EOL}${this.logGroup.arn}` - } - - public get name(): string { - return this.logGroup.logGroupName! - } - - public get arn(): string { - if (this.logGroup.arn === undefined) { - throw new Error('Log Group Arn expected but not found') - } - - return this.logGroup.arn - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts deleted file mode 100644 index a2364f0746..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSession.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import * as AWS from '@aws-sdk/types' -import { - CloudWatchLogsClient, - type CloudWatchLogsClientConfig, - StartLiveTailCommand, - StartLiveTailResponseStream, -} from '@aws-sdk/client-cloudwatch-logs' -import { LogStreamFilterResponse } from '../wizard/liveTailLogStreamSubmenu' -import { CloudWatchLogsSettings } from '../cloudWatchLogsUtils' -import globals from '../../../shared/extensionGlobals' -import { Settings } from '../../../shared/settings' -import { ToolkitError } from '../../../shared/errors' -import { createLiveTailURIFromArgs } from './liveTailSessionRegistry' -import { getUserAgent } from '../../../shared/telemetry/util' -import { convertToTimeString } from '../../../shared/datetime' - -export type LiveTailSessionConfiguration = { - logGroupArn: string - logStreamFilter?: LogStreamFilterResponse - logEventFilterPattern?: string - region: string - awsCredentials: AWS.Credentials -} - -export type LiveTailSessionClient = { - cwlClient: CloudWatchLogsClient - abortController: AbortController -} - -export class LiveTailSession { - private liveTailClient: LiveTailSessionClient - private _logGroupArn: string - private logStreamFilter?: LogStreamFilterResponse - private logEventFilterPattern?: string - private _maxLines: number - private _uri: vscode.Uri - private statusBarItem: vscode.StatusBarItem - private startTime: number | undefined - private endTime: number | undefined - private _eventRate: number - private _isSampled: boolean - - // While session is running, used to update the StatusBar each half second. - private statusBarUpdateTimer: NodeJS.Timer | undefined - - static settings = new CloudWatchLogsSettings(Settings.instance) - - public constructor(configuration: LiveTailSessionConfiguration) { - this._logGroupArn = configuration.logGroupArn - this.logStreamFilter = configuration.logStreamFilter - this.logEventFilterPattern = configuration.logEventFilterPattern - const cwlClientProps: CloudWatchLogsClientConfig = { - credentials: configuration.awsCredentials, - region: configuration.region, - customUserAgent: getUserAgent(), - } - const endpointUrl = globals.awsContext.getCredentialEndpointUrl() - if (endpointUrl !== undefined) { - cwlClientProps.endpoint = endpointUrl - } - this.liveTailClient = { - cwlClient: new CloudWatchLogsClient(cwlClientProps), - abortController: new AbortController(), - } - this._maxLines = LiveTailSession.settings.get('limit', 10000) - this._uri = createLiveTailURIFromArgs(configuration) - this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0) - this._eventRate = 0 - this._isSampled = false - } - - public get maxLines() { - return this._maxLines - } - - public get uri() { - return this._uri - } - - public get logGroupArn() { - return this._logGroupArn - } - - public set eventRate(rate: number) { - this._eventRate = rate - } - - public set isSampled(isSampled: boolean) { - this._isSampled = isSampled - } - - public async startLiveTailSession(): Promise> { - const commandOutput = await this.liveTailClient.cwlClient.send(this.buildStartLiveTailCommand(), { - abortSignal: this.liveTailClient.abortController.signal, - }) - if (!commandOutput.responseStream) { - throw new ToolkitError('LiveTail session response stream is undefined.') - } - this.startTime = globals.clock.Date.now() - this.endTime = undefined - this.statusBarUpdateTimer = globals.clock.setInterval(() => { - this.updateStatusBarItemText() - }, 500) - return commandOutput.responseStream - } - - public stopLiveTailSession() { - this.endTime = globals.clock.Date.now() - this.statusBarItem.dispose() - globals.clock.clearInterval(this.statusBarUpdateTimer) - this.liveTailClient.abortController.abort() - this.liveTailClient.cwlClient.destroy() - } - - public getLiveTailSessionDuration(): number { - // Never started - if (this.startTime === undefined) { - return 0 - } - // Currently running - if (this.endTime === undefined) { - return globals.clock.Date.now() - this.startTime - } - return this.endTime - this.startTime - } - - public buildStartLiveTailCommand(): StartLiveTailCommand { - let logStreamNamePrefix = undefined - let logStreamName = undefined - if (this.logStreamFilter) { - if (this.logStreamFilter.type === 'prefix') { - logStreamNamePrefix = this.logStreamFilter.filter - logStreamName = undefined - } else if (this.logStreamFilter.type === 'specific') { - logStreamName = this.logStreamFilter.filter - logStreamNamePrefix = undefined - } - } - - return new StartLiveTailCommand({ - logGroupIdentifiers: [this.logGroupArn], - logStreamNamePrefixes: logStreamNamePrefix ? [logStreamNamePrefix] : undefined, - logStreamNames: logStreamName ? [logStreamName] : undefined, - logEventFilterPattern: this.logEventFilterPattern ? this.logEventFilterPattern : undefined, - }) - } - - public showStatusBarItem(shouldShow: boolean) { - shouldShow ? this.statusBarItem.show() : this.statusBarItem.hide() - } - - public updateStatusBarItemText() { - const elapsedTime = this.getLiveTailSessionDuration() - const timeString = convertToTimeString(elapsedTime) - const sampledString = this._isSampled ? 'Yes' : 'No' - this.statusBarItem.text = `Tailing: ${timeString}, ${this._eventRate} events/sec, Sampled: ${sampledString}` - } - - public get isAborted() { - return this.liveTailClient.abortController.signal.aborted - } -} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts deleted file mode 100644 index 988725edc8..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/registry/liveTailSessionRegistry.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { cloudwatchLogsLiveTailScheme } from '../../../shared/constants' -import { LiveTailSession, LiveTailSessionConfiguration } from './liveTailSession' - -export class LiveTailSessionRegistry extends Map { - static #instance: LiveTailSessionRegistry - - public static get instance() { - return (this.#instance ??= new this()) - } - - public constructor() { - super() - } -} - -export function createLiveTailURIFromArgs(sessionData: LiveTailSessionConfiguration): vscode.Uri { - let uriStr = `${cloudwatchLogsLiveTailScheme}:${sessionData.region}:${sessionData.logGroupArn}` - - if (sessionData.logStreamFilter) { - if (sessionData.logStreamFilter.type !== 'all') { - uriStr += `:${sessionData.logStreamFilter.type}:${sessionData.logStreamFilter.filter}` - } else { - uriStr += `:${sessionData.logStreamFilter.type}` - } - } - uriStr += sessionData.logEventFilterPattern ? `:${sessionData.logEventFilterPattern}` : '' - - return vscode.Uri.parse(uriStr) -} diff --git a/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts b/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts deleted file mode 100644 index 1bbe0ce06f..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/registry/logDataRegistry.ts +++ /dev/null @@ -1,332 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { CloudWatchLogsSettings, uriToKey, msgKey, cwlUriSchema } from '../cloudWatchLogsUtils' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { waitTimeout } from '../../../shared/utilities/timeoutUtils' -import { Messages } from '../../../shared/utilities/messages' -import { pageableToCollection } from '../../../shared/utilities/collectionUtils' -import { Settings } from '../../../shared/settings' -import * as CloudWatchLogs from '@aws-sdk/client-cloudwatch-logs' -// TODO: Add debug logging statements - -/** Uri as a string */ -export type UriString = string -/** - * Operations and persistence for CloudWatch Log Data (events from a single logstream or events from log group search) - */ -export class LogDataRegistry { - private readonly _onDidChange: vscode.EventEmitter = new vscode.EventEmitter() - - static #instance: LogDataRegistry - - public static get instance() { - return (this.#instance ??= new this()) - } - - public constructor( - public readonly configuration: CloudWatchLogsSettings = new CloudWatchLogsSettings(Settings.instance), - private readonly registry: Map = new Map() - ) {} - - /** - * Event fired on log content change - */ - public get onDidChange(): vscode.Event { - return this._onDidChange.event - } - - /** Disposes registry structures associated with the given document. Does not dispose the document itself. */ - public disposeRegistryData(uri: vscode.Uri): void { - this.registry.delete(uriToKey(uri)) - } - - /** - * Returns whether or not the log is registered. - * @param uri Document URI - */ - public isRegistered(uri: vscode.Uri): boolean { - return this.registry.has(uriToKey(uri)) - } - - public fetchCachedLogEvents(uri: vscode.Uri): CloudWatchLogsEvent[] { - if (!this.isRegistered(uri)) { - this.registerInitialLog(uri) - } - return this.getRegisteredLog(uri).events - } - - /** - * Retrieves the next set of data for a log and adds it to the registry. Data can either be added to the front of the log (`'head'`) or end (`'tail'`) - * @param uri Document URI - * @param headOrTail Determines update behavior: `'head'` retrieves the most recent previous token and appends data to the top of the log, `'tail'` does the opposite. Default: `'tail'` - * @param getLogEventsFromUriComponentsFn Override for testing purposes. - */ - public async fetchNextLogEvents( - uri: vscode.Uri, - headOrTail: 'head' | 'tail' = 'tail' - ): Promise { - if (!this.isRegistered(uri)) { - this.registerInitialLog(uri) - } - - const logData = this.getRegisteredLog(uri) - const request: CloudWatchLogsResponse = { - events: [], - nextForwardToken: logData.next?.token, - nextBackwardToken: logData.previous?.token, - } - - const isHead = headOrTail === 'head' - - // We are at the earliest data and trying to go back in time, there is nothing to see. - // If we don't return now, redundant data will be retrieved. - if (isHead && logData.previous?.token === undefined) { - // show something so the user doesn't think nothing happened. - await Messages.putMessage( - msgKey(logData.logGroupInfo), - `Loading from: '${logData.logGroupInfo.groupName}'`, - undefined, - 500 - ) - return [] - } - - const stream = pageableToCollection( - (r: typeof request) => - logData.retrieveLogsFunction( - logData.logGroupInfo, - logData.parameters, - isHead ? r.nextBackwardToken : r.nextForwardToken - ), - request, - isHead ? 'nextBackwardToken' : 'nextForwardToken' - ) - - async function firstOrLast( - iterable: AsyncIterable, - predicate: (item: T) => boolean - ): Promise { - let last: T | undefined - for await (const item of iterable) { - if (predicate((last = item))) { - return item - } - } - return last - } - - const msgTimeout = await Messages.putMessage( - msgKey(logData.logGroupInfo), - `Loading from: '${logData.logGroupInfo.groupName}'` - ) - const responseData = await firstOrLast(stream, (resp) => resp.events.length > 0).finally(() => { - msgTimeout.dispose() - }) - - if (!responseData) { - return [] - } - - const newData = - headOrTail === 'head' - ? (responseData.events ?? []).concat(logData.events) - : logData.events.concat(responseData.events ?? []) - - const tokens: Pick = {} - // update if no token exists or if the token is updated in the correct direction. - if (!logData.previous || headOrTail === 'head') { - const token = responseData.nextBackwardToken - if (token) { - tokens.previous = { token } - } - } - if (!logData.next || headOrTail === 'tail') { - const token = responseData.nextForwardToken ?? request.nextForwardToken - if (token) { - tokens.next = { token } - } - } - this.setLogData(uri, { - ...logData, - ...tokens, - events: newData, - }) - - this._onDidChange.fire(uri) - return newData - } - - public setBusyStatus(uri: vscode.Uri, isBusy: boolean): void { - const log = this.getRegisteredLog(uri) - if (log) { - this.setLogData(uri, { - ...log, - busy: isBusy, - }) - } - } - - public getBusyStatus(uri: vscode.Uri): boolean { - const log = this.getRegisteredLog(uri) - return (log && log.busy) ?? false - } - - protected setLogData(uri: vscode.Uri, newData: CloudWatchLogsData): void { - this.registry.set(uriToKey(uri), newData) - } - - protected getLogData(uri: vscode.Uri): CloudWatchLogsData | undefined { - return this.registry.get(uriToKey(uri)) - } - - public registerInitialLog( - uri: vscode.Uri, - retrieveLogsFunction: CloudWatchLogsAction = filterLogEventsFromUri - ): void { - if (this.isRegistered(uri)) { - throw new Error(`Already registered: ${uri.toString()}`) - } - const data = cwlUriSchema.parse(uri) - this.setLogData(uri, initLogData(data.logGroupInfo, data.parameters, retrieveLogsFunction)) - } - - public getRegisteredLog(uri: vscode.Uri): CloudWatchLogsData { - const logData = this.getLogData(uri) - if (!logData) { - throw Error(`Cannot get data for unregistered uri: ${uri.toString()}`) - } - return logData - } -} - -/** - * @param completeTimeout True to close the vscode cancel window when request is completed. - */ -export async function filterLogEventsFromUri( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters, - nextToken?: string, - completeTimeout = false -): Promise { - const client = new CloudWatchLogsClient(logGroupInfo.regionName) - - const cwlParameters: CloudWatchLogs.FilterLogEventsRequest = { - logGroupName: logGroupInfo.groupName, - filterPattern: parameters.filterPattern, - nextToken, - limit: parameters.limit, - } - - if (logGroupInfo.streamName !== undefined) { - cwlParameters.logStreamNames = [logGroupInfo.streamName] - } - - if (parameters.startTime && parameters.endTime) { - cwlParameters.startTime = parameters.startTime - cwlParameters.endTime = parameters.endTime - } - - cwlParameters.logStreamNames = [] - - if (parameters.streamNameOptions) { - cwlParameters.logStreamNames.concat(parameters.streamNameOptions) - } - - if (logGroupInfo.streamName) { - cwlParameters.logStreamNames.push(logGroupInfo.streamName) - } - - if (cwlParameters.logStreamNames.length === 0) { - // API fails on empty array - delete cwlParameters.logStreamNames - } - - const msgTimeout = await Messages.putMessage(msgKey(logGroupInfo), `Loading from: '${logGroupInfo.groupName}'`, { - message: logGroupInfo.streamName ?? '', - }) - - const responsePromise = client.filterLogEvents(cwlParameters) - const response = await waitTimeout(responsePromise, msgTimeout, { allowUndefined: false, completeTimeout }) - - // Use heuristic of last token as backward token and next token as forward to generalize token form. - // Note that this may become inconsistent if the contents of the calls are changing as they are being made. - // However, this fail wouldn't really impact customers. - if (response) { - return { - events: response.events ? response.events : [], - nextForwardToken: response.nextToken, - nextBackwardToken: nextToken, - } - } else { - throw new Error('cwl: filterLogEvents returned null') - } -} - -/** Creates a log data container including a log fetcher which will populate the data if called. */ -export function initLogData( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters, - retrieveLogsFunction: CloudWatchLogsAction -): CloudWatchLogsData { - return { - events: [], - parameters: parameters, - logGroupInfo: logGroupInfo, - retrieveLogsFunction: retrieveLogsFunction, - busy: false, - } -} - -export type CloudWatchLogsArgs = { - logGroupInfo: CloudWatchLogsGroupInfo - parameters: CloudWatchLogsParameters -} - -export type CloudWatchLogsGroupInfo = { - groupName: string - regionName: string - streamName?: string -} - -export type CloudWatchLogsParameters = { - filterPattern?: string - startTime?: number - endTime?: number - limit?: number - streamNameOptions?: string[] -} - -export type CloudWatchLogsResponse = { - events: CloudWatchLogs.FilteredLogEvent[] - nextForwardToken?: string - nextBackwardToken?: string -} - -export type CloudWatchLogsAction = ( - logGroupInfo: CloudWatchLogsGroupInfo, - apiParameters: CloudWatchLogsParameters, - nextToken?: string -) => Promise - -export type CloudWatchLogsEvent = CloudWatchLogs.OutputLogEvent & { - logStreamName?: string - eventId?: string -} - -export class CloudWatchLogsData { - events: CloudWatchLogsEvent[] = [] - parameters: CloudWatchLogsParameters = {} - logGroupInfo!: CloudWatchLogsGroupInfo - retrieveLogsFunction!: CloudWatchLogsAction - next?: { - token: string - } - previous?: { - token: string - } - busy: boolean = false -} diff --git a/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts b/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts deleted file mode 100644 index 0bb3cd9670..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/timeFilterSubmenu.ts +++ /dev/null @@ -1,223 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Prompter, PromptResult } from '../../shared/ui/prompter' -import { InputBoxPrompter } from '../../shared/ui/inputPrompter' -import { QuickPickPrompter, DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter' -import { createInputBox } from '../../shared/ui/inputPrompter' -import { isValidResponse, StepEstimator } from '../../shared/wizards/wizard' -import { createCommonButtons } from '../../shared/ui/buttons' -import * as nls from 'vscode-nls' -import { CloudWatchLogsParameters } from './registry/logDataRegistry' - -const localize = nls.loadMessageBundle() - -export function isViewAllEvents(response: TimeFilterResponse) { - return response.start === response.end -} - -export interface TimeFilterResponse { - // # of miliseconds since january 1, 1970 since thats what API expects. - readonly start: number - readonly end: number -} - -const customRange = Symbol('customRange') - -export class TimeFilterSubmenu extends Prompter { - // TODO: Generalize submenu code between this and the region Submenu. - private currentState: 'custom-range' | 'recent-range' = 'recent-range' - private steps?: [current: number, total: number] - public defaultPrompter: QuickPickPrompter = this.createMenuPrompter() - - public constructor(private readonly oldData?: Pick) { - super() - } - - private get recentTimeItems(): DataQuickPickItem[] { - const options: DataQuickPickItem[] = [] - // appromixate 31 days as month length (better to overshoot) - options.push({ - label: 'All time', - data: 0, - }) - options.push({ - label: 'Last 15 minutes', - data: 15, - }) - options.push({ - label: 'Last hour', - data: 60, - }) - options.push({ - label: 'Last 3 Hours', - data: 60 * 3, - }) - options.push({ - label: 'Last 24 Hours', - data: 60 * 24, - }) - options.push({ - label: 'Last 3 Days', - data: 60 * 24 * 3, - }) - options.push({ - label: 'Last week', - data: 60 * 24 * 7, - }) - options.push({ - label: 'Last month', - data: 60 * 24 * 31, - }) - options.push({ - label: 'Last 3 months', - data: 60 * 24 * 31 * 3, - }) - options.push({ - label: 'Last year', - data: 60 * 24 * 365, - }) - return options - } - - public createMenuPrompter() { - const prompter = createQuickPick(this.recentTimeItems, { - title: 'Select Time Filter', - buttons: createCommonButtons(), - }) - - prompter.quickPick.items = [ - ...prompter.quickPick.items, - { - label: 'Custom time range', - data: customRange, - detail: `YYYY/MM/DD-YYYY/MM/DD`, - }, - ] - - return prompter - } - - private switchState(newState: 'custom-range' | 'recent-range') { - this.currentState = newState - } - - public createDateBox(): InputBoxPrompter { - return createInputBox({ - title: 'Enter custom date range', - placeholder: 'YYYY/MM/DD-YYYY/MM/DD', - value: this.formatTimesToDateRange(this.oldData?.startTime, this.oldData?.endTime), - validateInput: (input) => this.validateDate(input), - buttons: createCommonButtons(), - }) - } - - protected async promptUser(): Promise> { - while (true) { - switch (this.currentState) { - case 'recent-range': { - const prompter = this.createMenuPrompter() - this.steps && prompter.setSteps(this.steps[0], this.steps[1]) - - const resp = await prompter.prompt() - if (resp === customRange) { - this.switchState('custom-range') - } else if (isValidResponse(resp)) { - const [endTime, startTime] = [new Date(), new Date()] - startTime.setMinutes(endTime.getMinutes() - resp) - - return { start: startTime.valueOf(), end: endTime.valueOf() } - } else { - return undefined - } - - break - } - case 'custom-range': { - const resp = await this.createDateBox().prompt() - if (isValidResponse(resp)) { - const [startTime, endTime] = this.parseDateRange(resp) - - return { start: startTime.valueOf(), end: endTime.valueOf() } - } - - this.switchState('recent-range') - - break - } - } - } - } - - public validateDate(input: string) { - const parts = input.split('-') - - if (parts.length !== 2) { - return localize('AWS.cwl.validateDate.notTwoDates', 'String must include two dates seperated by `-`') - } - const [startTime, endTime] = parts - - if (!Date.parse(startTime)) { - return localize('AWS.cwl.validateDate.startTimeInvalid', 'starting time format is invalid, use YYYY/MM/DD') - } - if (!Date.parse(endTime)) { - return localize('AWS.cwl.validateDate.endTimeInvalid', 'ending time format is valid, use YYYY/MM/DD') - } - const regEx = /^\d{4}\/\d{2}\/\d{2}$/ - if (!startTime.match(regEx) || !endTime.match(regEx)) { - return localize('AWS.cwl.validateDate.dateFormat', 'enter date in format YYYY/MM/DD-YYYY/MM/DD') - } - if (startTime === endTime) { - return localize('AWS.cwl.validateDate.sameDateError', 'must enter two different dates for valid range') - } - if (Date.parse(startTime) > Date.parse(endTime)) { - return localize('AWS.cwl.validateDate.startBeforeEndDate', 'first date must occur before second date') - } - } - - public setSteps(current: number, total: number): void { - this.steps = [current, total] - } - - private parseDateRange(resp: string) { - const parts = resp.split('-') - - return [this.parseDate(parts[0]), this.parseDate(parts[1])] - } - - private parseDate(date: string) { - const [year, month, day] = date.split('/').map(Number) - return new Date(Date.UTC(year, month - 1, day)) - } - - /** - * Formats a given start and end time in milliseconds to - * a string in the format 'YYYY/MM/DD-YYYY/MM/DD' - * - * Returns undefined if any of the times are not provided. - */ - public formatTimesToDateRange(startTimeMillis?: number, endTimeMillis?: number): string | undefined { - if (startTimeMillis === undefined || endTimeMillis === undefined) { - return undefined - } - const startDate = new Date(startTimeMillis) - const endDate = new Date(endTimeMillis) - - // Convert to string with format: YYYY/MM/DD - const allDashes = new RegExp('-', 'g') - const formattedStartDate = startDate.toISOString().split('T')[0].replace(allDashes, '/') - const formattedEndDate = endDate.toISOString().split('T')[0].replace(allDashes, '/') - - return `${formattedStartDate}-${formattedEndDate}` - } - - // Unused - public get recentItem(): any { - return - } - - public set recentItem(response: any) {} - public setStepEstimator(estimator: StepEstimator): void {} -} diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts deleted file mode 100644 index 6a9d24a5c3..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { Prompter, PromptResult } from '../../../shared/ui/prompter' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { createInputBox, InputBoxPrompter } from '../../../shared/ui/inputPrompter' -import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../../shared/ui/pickerPrompter' -import { pageableToCollection } from '../../../shared/utilities/collectionUtils' -import * as CloudWatchLogs from '@aws-sdk/client-cloudwatch-logs' -import { isValidResponse, StepEstimator } from '../../../shared/wizards/wizard' -import { isNonNullable } from '../../../shared/utilities/tsUtils' -import { - startLiveTailHelpUrl, - startLiveTailLogStreamNamesHelpUrl, - startLiveTailLogStreamPrefixHelpUrl, -} from '../../../shared/constants' - -export type LogStreamFilterType = 'menu' | 'prefix' | 'specific' | 'all' - -export interface LogStreamFilterResponse { - readonly filter?: string - readonly type: LogStreamFilterType -} - -export class LogStreamFilterSubmenu extends Prompter { - private logStreamPrefixRegEx = /^[^:*]*$/ - private currentState: LogStreamFilterType = 'menu' - private steps?: [current: number, total: number] - private region: string - private logGroupArn: string - public defaultPrompter: QuickPickPrompter = this.createMenuPrompter() - - public constructor(logGroupArn: string, region: string) { - super() - this.region = region - this.logGroupArn = logGroupArn - } - - public createMenuPrompter() { - const helpUri = startLiveTailHelpUrl - const prompter = createQuickPick(this.menuOptions, { - title: 'Include log events from...', - buttons: createCommonButtons(helpUri), - }) - return prompter - } - - private get menuOptions(): DataQuickPickItem[] { - const options: DataQuickPickItem[] = [] - options.push({ - label: 'All Log Streams', - data: 'all', - }) - options.push({ - label: 'Specific Log Stream', - data: 'specific', - }) - options.push({ - label: 'Log Streams matching prefix', - data: 'prefix', - }) - return options - } - - public createLogStreamPrefixBox(): InputBoxPrompter { - const helpUri = startLiveTailLogStreamPrefixHelpUrl - return createInputBox({ - title: 'Enter Log Stream prefix', - placeholder: 'log stream prefix (case sensitive; empty matches all)', - prompt: 'Only log events in Log Streams whose name starts with the supplied prefix will be included.', - validateInput: (input) => this.validateLogStreamPrefix(input), - buttons: createCommonButtons(helpUri), - }) - } - - public validateLogStreamPrefix(prefix: string) { - if (prefix.length > 512) { - return 'Log Stream prefix cannot be longer than 512 characters' - } - - if (!this.logStreamPrefixRegEx.test(prefix)) { - return 'Log Stream prefix must match pattern: [^:*]*' - } - } - - public createLogStreamSelector(): QuickPickPrompter { - const helpUri = startLiveTailLogStreamNamesHelpUrl - const client = new CloudWatchLogsClient(this.region) - const request: CloudWatchLogs.DescribeLogStreamsRequest = { - logGroupIdentifier: this.logGroupArn, - orderBy: 'LastEventTime', - descending: true, - } - const requester = (request: CloudWatchLogs.DescribeLogStreamsRequest) => client.describeLogStreams(request) - const collection = pageableToCollection(requester, request, 'nextToken', 'logStreams') - - const items = collection - .filter(isNonNullable) - .map((streams) => streams!.map((stream) => ({ data: stream.logStreamName!, label: stream.logStreamName! }))) - - return createQuickPick(items, { - title: 'Select Log Stream', - buttons: createCommonButtons(helpUri), - }) - } - - private switchState(newState: LogStreamFilterType) { - this.currentState = newState - } - - protected async promptUser(): Promise> { - while (true) { - switch (this.currentState) { - case 'menu': { - const prompter = this.createMenuPrompter() - this.steps && prompter.setSteps(this.steps[0], this.steps[1]) - - const resp = await prompter.prompt() - if (resp === 'prefix') { - this.switchState('prefix') - } else if (resp === 'specific') { - this.switchState('specific') - } else if (resp === 'all') { - return { filter: undefined, type: resp } - } else { - return undefined - } - - break - } - case 'prefix': { - const resp = await this.createLogStreamPrefixBox().prompt() - if (isValidResponse(resp)) { - return { filter: resp, type: 'prefix' } - } - this.switchState('menu') - break - } - case 'specific': { - const resp = await this.createLogStreamSelector().prompt() - if (isValidResponse(resp)) { - return { filter: resp, type: 'specific' } - } - this.switchState('menu') - break - } - } - } - } - - public setSteps(current: number, total: number): void { - this.steps = [current, total] - } - - // Unused - public get recentItem(): any { - return - } - public set recentItem(response: any) {} - public setStepEstimator(estimator: StepEstimator): void {} -} diff --git a/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts b/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts deleted file mode 100644 index 02166e3a85..0000000000 --- a/packages/core/src/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import globals from '../../../shared/extensionGlobals' -import { ToolkitError } from '../../../shared/errors' -import { CloudWatchLogsClient } from '../../../shared/clients/cloudWatchLogs' -import { cwlFilterPatternHelpUrl } from '../../../shared/constants' -import { createBackButton, createExitButton, createHelpButton } from '../../../shared/ui/buttons' -import { RegionSubmenu, RegionSubmenuResponse } from '../../../shared/ui/common/regionSubmenu' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { Wizard } from '../../../shared/wizards/wizard' -import { CloudWatchLogsGroupInfo } from '../registry/logDataRegistry' -import { LogStreamFilterResponse, LogStreamFilterSubmenu } from './liveTailLogStreamSubmenu' - -const localize = nls.loadMessageBundle() - -export interface TailLogGroupWizardResponse { - regionLogGroupSubmenuResponse: RegionSubmenuResponse - logStreamFilter: LogStreamFilterResponse - filterPattern: string -} - -export class TailLogGroupWizard extends Wizard { - public constructor(logGroupInfo?: CloudWatchLogsGroupInfo, logStreamInfo?: LogStreamFilterResponse) { - super({ - initState: { - regionLogGroupSubmenuResponse: logGroupInfo - ? { - data: buildLogGroupArn(logGroupInfo.groupName, logGroupInfo.regionName), - region: logGroupInfo.regionName, - } - : undefined, - logStreamFilter: logStreamInfo ?? undefined, - }, - }) - this.form.regionLogGroupSubmenuResponse.bindPrompter(createRegionLogGroupSubmenu) - this.form.logStreamFilter.bindPrompter((state) => { - if (!state.regionLogGroupSubmenuResponse?.data) { - throw new ToolkitError('Log Group name is null') - } - return new LogStreamFilterSubmenu( - state.regionLogGroupSubmenuResponse.data, - state.regionLogGroupSubmenuResponse.region - ) - }) - this.form.filterPattern.bindPrompter((state) => createFilterPatternPrompter()) - } -} - -export function createRegionLogGroupSubmenu(): RegionSubmenu { - return new RegionSubmenu( - getLogGroupQuickPickOptions, - { - title: localize('AWS.cwl.tailLogGroup.logGroupPromptTitle', 'Select Log Group to tail'), - buttons: [createExitButton()], - }, - { title: localize('AWS.cwl.tailLogGroup.regionPromptTitle', 'Select Region for Log Group') }, - 'Log Groups' - ) -} - -async function getLogGroupQuickPickOptions(regionCode: string): Promise[]> { - const client = new CloudWatchLogsClient(regionCode) - const logGroups = client.describeLogGroups() - - const logGroupsOptions: DataQuickPickItem[] = [] - - for await (const logGroupObject of logGroups) { - if (!logGroupObject.arn || !logGroupObject.logGroupName) { - throw new ToolkitError('Log Group name or arn is undefined') - } - - logGroupsOptions.push({ - label: logGroupObject.logGroupName, - data: formatLogGroupArn(logGroupObject.arn), - }) - } - - return logGroupsOptions -} - -export function buildLogGroupArn(logGroupName: string, region: string): string { - if (logGroupName.startsWith('arn:')) { - return logGroupName - } - const awsAccountId = globals.awsContext.getCredentialAccountId() - if (awsAccountId === undefined) { - throw new ToolkitError( - `Failed to construct Arn for Log Group because awsAccountId is undefined. Log Group: ${logGroupName}` - ) - } - return `arn:aws:logs:${region}:${awsAccountId}:log-group:${logGroupName}` -} - -function formatLogGroupArn(logGroupArn: string): string { - return logGroupArn.endsWith(':*') ? logGroupArn.substring(0, logGroupArn.length - 2) : logGroupArn -} - -export function createFilterPatternPrompter() { - const helpUri = cwlFilterPatternHelpUrl - return createInputBox({ - title: 'Provide log event filter pattern', - placeholder: 'filter pattern (case sensitive; empty matches all)', - prompt: 'Optional filter to include only log events that match the supplied pattern.', - buttons: [createHelpButton(helpUri), createBackButton(), createExitButton()], - }) -} diff --git a/packages/core/src/awsService/cloudformation/artifacts/awsDocumentationLinks.ts b/packages/core/src/awsService/cloudformation/artifacts/awsDocumentationLinks.ts deleted file mode 100644 index 9da617f518..0000000000 --- a/packages/core/src/awsService/cloudformation/artifacts/awsDocumentationLinks.ts +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ResourceIdentifierDocumentationUrl = - 'https://docs.aws.amazon.com/cloudcontrolapi/latest/userguide/resource-identifier.html' diff --git a/packages/core/src/awsService/cloudformation/auth/credentials.ts b/packages/core/src/awsService/cloudformation/auth/credentials.ts deleted file mode 100644 index 95da665c47..0000000000 --- a/packages/core/src/awsService/cloudformation/auth/credentials.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Disposable } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { StacksManager } from '../stacks/stacksManager' -import { ResourcesManager } from '../resources/resourcesManager' -import { CloudFormationRegionManager } from '../explorer/regionManager' -import globals from '../../../shared/extensionGlobals' -import * as jose from 'jose' -import * as crypto from 'crypto' - -export const encryptionKey = crypto.randomBytes(32) - -export class AwsCredentialsService implements Disposable { - private authChangeListener: Disposable - private client: LanguageClient | undefined - - constructor( - private readonly stacksManager: StacksManager, - private readonly resourcesManager: ResourcesManager, - private readonly regionManager: CloudFormationRegionManager - ) { - this.authChangeListener = globals.awsContext.onDidChangeContext(() => { - void this.updateCredentialsFromActiveConnection() - }) - } - - async initialize(client: LanguageClient): Promise { - this.client = client - await this.updateCredentialsFromActiveConnection() - } - - private async updateCredentialsFromActiveConnection(): Promise { - if (!this.client) { - return - } - - const credentials = await globals.awsContext.getCredentials() - const profileName = globals.awsContext.getCredentialProfileName() - - if (credentials && profileName) { - const encryptedRequest = await this.createEncryptedCredentialsRequest({ - profile: profileName.replaceAll('profile:', ''), - region: this.regionManager.getSelectedRegion(), - accessKeyId: credentials.accessKeyId, - secretAccessKey: credentials.secretAccessKey, - sessionToken: credentials.sessionToken, - }) - - await this.client.sendRequest('aws/credentials/iam/update', encryptedRequest) - } - - this.stacksManager.clear() - void this.resourcesManager.reload() - } - - async updateRegion(): Promise { - await this.updateCredentialsFromActiveConnection() - } - - private async createEncryptedCredentialsRequest(data: any): Promise { - const payload = new TextEncoder().encode(JSON.stringify({ data })) - - const jwt = await new jose.CompactEncrypt(payload) - .setProtectedHeader({ alg: 'dir', enc: 'A256GCM' }) - .encrypt(encryptionKey) - - return { - data: jwt, - encrypted: true, - } - } - - dispose(): void { - this.authChangeListener.dispose() - } -} diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentApi.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentApi.ts deleted file mode 100644 index 1df272c175..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentApi.ts +++ /dev/null @@ -1,19 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LanguageClient } from 'vscode-languageclient/node' -import { - ParsedCfnEnvironmentFile, - ParseCfnEnvironmentFilesParams, - ParseCfnEnvironmentFilesRequest, -} from './cfnEnvironmentRequestType' - -export async function parseCfnEnvironmentFiles( - client: LanguageClient, - params: ParseCfnEnvironmentFilesParams -): Promise { - const result = await client.sendRequest(ParseCfnEnvironmentFilesRequest, params) - return result.parsedFiles -} diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentManager.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentManager.ts deleted file mode 100644 index ba38b00460..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentManager.ts +++ /dev/null @@ -1,267 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Disposable, Uri, window, workspace, commands } from 'vscode' -import { Auth } from '../../../auth/auth' -import { commandKey, extractErrorMessage, formatMessage, toString } from '../utils' -import { - CfnConfig, - CfnEnvironmentConfig, - CfnEnvironmentLookup, - DeploymentConfig, - CfnEnvironmentFileSelectorItem as DeploymentFileDetail, - CfnEnvironmentFileSelectorItem, - unselectedValue, -} from './cfnProjectTypes' -import path from 'path' -import fs from '../../../shared/fs/fs' -import { CfnEnvironmentSelector } from '../ui/cfnEnvironmentSelector' -import { CfnEnvironmentFileSelector } from '../ui/cfnEnvironmentFileSelector' -import globals from '../../../shared/extensionGlobals' -import { TemplateParameter } from '../stacks/actions/stackActionRequestType' -import { validateParameterValue } from '../stacks/actions/stackActionInputValidation' -import { getLogger } from '../../../shared/logger/logger' -import { DocumentInfo } from './cfnEnvironmentRequestType' -import { parseCfnEnvironmentFiles } from './cfnEnvironmentApi' -import { LanguageClient } from 'vscode-languageclient/node' -import { Parameter } from '@aws-sdk/client-cloudformation' -import { - convertRecordToParameters, - convertRecordToTags, - getConfigPath, - getEnvironmentDir, - getProjectDir, -} from './utils' - -export class CfnEnvironmentManager implements Disposable { - private readonly selectedEnvironmentKey = 'aws.cloudformation.selectedEnvironment' - private readonly auth = Auth.instance - private listeners: (() => void)[] = [] - - private readonly initializeOption = 'Initialize Project' - - constructor( - private readonly client: LanguageClient, - private readonly environmentSelector: CfnEnvironmentSelector, - private readonly environmentFileSelector: CfnEnvironmentFileSelector - ) {} - - public addListener(listener: () => void): void { - this.listeners.push(listener) - } - - public getSelectedEnvironmentName(): string | undefined { - return globals.context.workspaceState.get(this.selectedEnvironmentKey) - } - - private notifyListeners(): void { - for (const listener of this.listeners) { - listener() - } - } - - public async promptInitializeIfNeeded(operation: string): Promise { - if (!(await this.isProjectInitialized())) { - const choice = await window.showWarningMessage( - `You must initialize your CFN Project to perform ${operation}`, - this.initializeOption - ) - - if (choice === this.initializeOption) { - void commands.executeCommand(commandKey('init.initializeProject')) - } - return true - } - - return false - } - - public async selectEnvironment(): Promise { - if (await this.promptInitializeIfNeeded('Environment Selection')) { - return - } - - let environmentLookup: CfnEnvironmentLookup - - try { - environmentLookup = await this.fetchAvailableEnvironments() - } catch (error) { - void window.showErrorMessage( - formatMessage(`Failed to retrieve environments from configuration: ${toString(error)}`) - ) - return - } - - const environmentName = await this.environmentSelector.selectEnvironment(environmentLookup) - - if (environmentName) { - await this.setSelectedEnvironment(environmentName, environmentLookup) - } - } - - private async isProjectInitialized(): Promise { - const configPath = await getConfigPath() - const projectDirectory = await getProjectDir() - - return (await fs.existsFile(configPath)) && (await fs.existsDir(projectDirectory)) - } - - private async setSelectedEnvironment( - environmentName: string, - environmentLookup: CfnEnvironmentLookup - ): Promise { - const environment = environmentLookup[environmentName] - - if (environment) { - await globals.context.workspaceState.update(this.selectedEnvironmentKey, environmentName) - - await this.syncEnvironmentWithProfile(environment) - } else { - await globals.context.workspaceState.update(this.selectedEnvironmentKey, undefined) - } - - this.notifyListeners() - } - - private async syncEnvironmentWithProfile(environment: CfnEnvironmentConfig) { - const profileName = environment.profile - - const currentConnection = await this.auth.getConnection({ id: `profile:${profileName}` }) - - if (!currentConnection) { - void window.showErrorMessage(formatMessage(`No connection found for profile: ${profileName}`)) - return - } - - await this.auth.useConnection(currentConnection) - } - - public async fetchAvailableEnvironments(): Promise { - const configPath = await getConfigPath() - const config = JSON.parse(await fs.readFileText(configPath)) as CfnConfig - - return config.environments - } - - public async selectEnvironmentFile( - templateUri: string, - requiredParameters: TemplateParameter[] - ): Promise { - const environmentName = this.getSelectedEnvironmentName() - const selectorItems: CfnEnvironmentFileSelectorItem[] = [] - - if (!environmentName) { - return undefined - } - - try { - const environmentDir = await getEnvironmentDir(environmentName) - const files = await fs.readdir(environmentDir) - - const filesToParse: DocumentInfo[] = await Promise.all( - files - .filter( - ([fileName]) => - fileName.endsWith('.json') || fileName.endsWith('.yaml') || fileName.endsWith('.yml') - ) - .map(async ([fileName]) => { - const filePath = path.join(environmentDir, fileName) - const content = await fs.readFileText(filePath) - const type = fileName.endsWith('.json') ? 'JSON' : 'YAML' - - return { - type, - content, - fileName, - } - }) - ) - - const environmentFiles = await parseCfnEnvironmentFiles(this.client, { documents: filesToParse }) - - for (const deploymentFile of environmentFiles) { - const item = await this.createEnvironmentFileSelectorItem( - deploymentFile.fileName, - deploymentFile.deploymentConfig, - requiredParameters, - templateUri - ) - if (item) { - selectorItems.push(item) - } - } - } catch (error) { - void window.showErrorMessage(`Error loading deployment files: ${extractErrorMessage(error)}`) - return undefined - } - - return await this.environmentFileSelector.selectEnvironmentFile(selectorItems, requiredParameters.length) - } - - public async refreshSelectedEnvironment() { - const environmentName = this.getSelectedEnvironmentName() - const availableEnvironments = await this.fetchAvailableEnvironments() - - // unselect environment if an environment was manually deleted - if (environmentName && !availableEnvironments[environmentName]) { - await this.setSelectedEnvironment(unselectedValue, availableEnvironments) - - return undefined - } - } - - private async createEnvironmentFileSelectorItem( - fileName: string, - deploymentConfig: DeploymentConfig, - requiredParameters: TemplateParameter[], - templateUri: string - ): Promise { - try { - return { - fileName: fileName, - hasMatchingTemplatePath: - workspace.asRelativePath(Uri.parse(templateUri)) === deploymentConfig.templateFilePath, - compatibleParameters: this.getCompatibleParams(deploymentConfig, requiredParameters), - optionalFlags: { - tags: deploymentConfig.tags ? convertRecordToTags(deploymentConfig.tags) : undefined, - includeNestedStacks: deploymentConfig.includeNestedStacks, - importExistingResources: deploymentConfig.importExistingResources, - onStackFailure: deploymentConfig.onStackFailure, - }, - } - } catch (error) { - getLogger().warn(`Failed to create selector item ${fileName}:`, error) - } - } - - private getCompatibleParams( - deploymentConfig: DeploymentConfig, - requiredParameters: TemplateParameter[] - ): Parameter[] | undefined { - if (deploymentConfig.parameters && requiredParameters.length > 0) { - const parameters = deploymentConfig.parameters - - // Filter only parameters that are in template and are valid - const validParams = requiredParameters.filter((templateParam) => { - if (!(templateParam.name in parameters)) { - return false - } - const value = parameters[templateParam.name] - return validateParameterValue(value, templateParam) === undefined - }) - - const validParameterNames = validParams.map((p) => p.name) - const filteredParameters = Object.fromEntries( - Object.entries(parameters).filter(([key]) => validParameterNames.includes(key)) - ) - - return convertRecordToParameters(filteredParameters) - } - } - - dispose(): void { - // No resources to dispose - } -} diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentRequestType.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentRequestType.ts deleted file mode 100644 index cb1e930109..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnEnvironmentRequestType.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RequestType } from 'vscode-languageserver-protocol' -import { DeploymentConfig } from './cfnProjectTypes' - -export type DocumentInfo = { - type: 'JSON' | 'YAML' - content: string - fileName: string -} - -export type ParsedCfnEnvironmentFile = { - deploymentConfig: DeploymentConfig - fileName: string -} - -export type ParseCfnEnvironmentFilesParams = { - documents: DocumentInfo[] -} - -export type ParseCfnEnvironmentFilesResult = { - parsedFiles: ParsedCfnEnvironmentFile[] -} - -export const ParseCfnEnvironmentFilesRequest = new RequestType< - ParseCfnEnvironmentFilesParams, - ParseCfnEnvironmentFilesResult, - void ->('aws/cfn/environment/files/parse') diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnInitCliCaller.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnInitCliCaller.ts deleted file mode 100644 index 21ab40a5c3..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnInitCliCaller.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { ChildProcess } from '../../../shared/utilities/processUtils' -import { extractErrorMessage } from '../utils' - -export interface EnvironmentOption { - name: string - awsProfile: string - parametersFiles?: string[] -} - -export class CfnInitCliCaller { - private binaryPath: string - - constructor(serverRootDir: string) { - this.binaryPath = path.join(serverRootDir, 'bin', 'cfn-init') - } - - async createProject( - projectName: string, - options?: { - projectPath?: string - environments?: EnvironmentOption[] - } - ) { - const args = ['create', projectName] - - if (options?.projectPath) { - args.push('--project-path', options.projectPath) - } - - if (options?.environments && options.environments.length > 0) { - const environmentConfig = { - environments: options.environments, - } - args.push('--environments', JSON.stringify(environmentConfig)) - } - - return this.executeCommand(args) - } - - async addEnvironments(environments: EnvironmentOption[]) { - const args = ['environment', 'add', '--environments', JSON.stringify({ environments })] - return this.executeCommand(args) - } - - async removeEnvironment(envName: string) { - const args = ['environment', 'remove', envName] - return this.executeCommand(args) - } - - private async executeCommand(args: string[]) { - const cwd = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd() - - try { - const result = await ChildProcess.run(this.binaryPath, args, { - spawnOptions: { - cwd, - }, - }) - - if (result.exitCode === 0) { - return { success: true, output: result.stdout || undefined } - } else { - void vscode.window.showWarningMessage( - `cfn init command returned exit code ${result.exitCode}: ${result.stderr} - ${result.stdout} - ${extractErrorMessage(result.error)}` - ) - return { success: false, error: result.stderr || `Process exited with code ${result.exitCode}` } - } - } catch (error) { - void vscode.window.showErrorMessage(`Error executing cfn init command: ${extractErrorMessage(error)}`) - return { success: false, error: error instanceof Error ? error.message : String(error) } - } - } -} diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnInitUiInterface.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnInitUiInterface.ts deleted file mode 100644 index 92447d84e4..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnInitUiInterface.ts +++ /dev/null @@ -1,290 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { CfnInitCliCaller, EnvironmentOption } from './cfnInitCliCaller' -import { Auth } from '../../../auth/auth' -import { promptForConnection } from '../../../auth/utils' -import { getEnvironmentName, getProjectName, getProjectPath } from '../ui/inputBox' -import fs from '../../../shared/fs/fs' -import path from 'path' -import { unselectedValue } from './cfnProjectTypes' - -interface FormState { - projectName?: string - projectPath?: string - environments: EnvironmentOption[] -} - -export class CfnInitUiInterface { - private state: FormState = { environments: [] } - - constructor(private cfnInitService: CfnInitCliCaller) {} - - async promptForCreate() { - try { - // Set default project path with validation - const defaultPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd() - - // Validate default path - try { - await fs.checkPerms(defaultPath, '*w*') - const cfnProjectPath = path.join(defaultPath, 'cfn-project') - const cfnProjectExists = await fs.existsDir(cfnProjectPath) - - // Only use default if it's valid and doesn't have cfn-project - this.state.projectPath = cfnProjectExists ? undefined : defaultPath - } catch { - // Default path is invalid, leave undefined to force user selection - this.state.projectPath = undefined - } - - await this.showForm() - } catch (error) { - void vscode.window.showErrorMessage(`CFN Init failed: ${error}`) - } - } - - private async showForm(): Promise { - const quickPick = vscode.window.createQuickPick() - quickPick.title = 'CFN Init: Initialize Project' - quickPick.placeholder = 'Configure your CloudFormation project' - quickPick.buttons = [{ iconPath: new vscode.ThemeIcon('check'), tooltip: 'Create Project' }] - - return new Promise((resolve) => { - const updateItems = () => { - const items = [ - { - label: `Project Name`, - detail: this.state.projectName || unselectedValue, - }, - { - label: `Project Path`, - detail: this.state.projectPath || unselectedValue, - }, - ] - - // Add environment items - for (const [_index, env] of this.state.environments.entries()) { - items.push({ - label: `Adding Environment: ${env.name}`, - detail: `AWS Profile: ${env.awsProfile}`, - }) - } - - const addEnvItem = { - label: '$(plus) Add Environment (At least one required)', - detail: 'Configure a new deployment environment', - } - items.push(addEnvItem) - - if (this.state.environments.length > 0) { - items.push({ - label: '$(trash) Delete Environment', - detail: 'Remove an existing environment', - }) - } - - const createProjectItem = { - label: '$(check) Create Project', - detail: 'Create the CloudFormation project with current configuration', - } - items.push(createProjectItem) - - quickPick.items = items - - // Highlight first undefined state property - if (!this.state.projectName) { - quickPick.activeItems = [items[0]] - } else if (!this.state.projectPath) { - quickPick.activeItems = [items[1]] - } else if (this.state.environments.length === 0) { - quickPick.activeItems = [addEnvItem] - } else { - quickPick.activeItems = [createProjectItem] - } - } - - updateItems() - - quickPick.onDidAccept(async () => { - const selected = quickPick.selectedItems[0] - if (!selected) { - return - } - - if (selected.label.includes('Project Name')) { - const name = await getProjectName(this.state.projectName) - - if (name) { - this.state.projectName = name - } - } else if (selected.label.includes('Project Path')) { - const currentPath = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '.' - - const pathInput = await getProjectPath(this.state.projectPath || currentPath) - - if (pathInput !== undefined) { - this.state.projectPath = pathInput.trim() || currentPath - } - } else if (selected.label.includes('Add Environment')) { - await this.addEnvironment() - } else if (selected.label.includes('Delete Environment')) { - await this.deleteEnvironment() - } else if (selected.label.includes('Create Project')) { - if (await this.isFormStateValid()) { - quickPick.hide() - resolve(true) - await this.executeProject() - } - return - } - - updateItems() - quickPick.show() - }) - - quickPick.onDidTriggerButton(async () => { - if (!(await this.isFormStateValid())) { - return - } - quickPick.hide() - resolve(true) - await this.executeProject() - }) - - quickPick.onDidHide(() => resolve(false)) - quickPick.show() - }) - } - - private async isFormStateValid(): Promise { - if (!this.state.projectName) { - void vscode.window.showWarningMessage('Project name is required') - return false - } - if (!this.state.projectPath) { - void vscode.window.showWarningMessage('Project path is required') - return false - } - if (this.state.environments.length === 0) { - void vscode.window.showWarningMessage('At least one environment is required') - return false - } - - return true - } - - async collectEnvironmentConfig(): Promise { - const envName = await getEnvironmentName() - - if (!envName) { - return undefined - } - - const connection = await promptForConnection(Auth.instance, 'iam-only') - if (!connection) { - return undefined - } - - if (connection.type !== 'iam') { - void vscode.window.showErrorMessage('Must select a valid IAM Profile for environment setup') - return undefined - } - - const selectedProfile = connection.id.replace('profile:', '') - - const addParamsFile = await vscode.window.showQuickPick(['Yes', 'No'], { - placeHolder: 'Import parameters files?', - }) - - const environment: EnvironmentOption = { - name: envName, - awsProfile: selectedProfile, - } - - if (addParamsFile === 'Yes') { - const result = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectMany: true, - filters: { 'Parameters Files': ['json', 'yaml', 'yml'] }, - }) - if (result && result.length > 0) { - environment.parametersFiles = result.map((uri) => uri.fsPath) - } - } - - return environment - } - - private async addEnvironment() { - const environment = await this.collectEnvironmentConfig() - if (!environment) { - return - } - - // Check for duplicate names - if (this.state.environments.some((e) => e.name === environment.name)) { - void vscode.window.showErrorMessage('Environment name already exists') - return - } - - this.state.environments.push(environment) - } - - private async deleteEnvironment() { - if (this.state.environments.length === 0) { - return - } - - const envNames = this.state.environments.map((env) => env.name) - const selected = await vscode.window.showQuickPick(envNames, { - placeHolder: 'Select environment to delete', - }) - - if (selected) { - this.state.environments = this.state.environments.filter((env) => env.name !== selected) - } - } - - private async executeProject() { - const progress = vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: 'Creating CFN project...', - cancellable: false, - }, - async (progress) => { - progress.report({ increment: 25, message: 'Creating project...' }) - - const projectPath = this.state.projectPath || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '.' - - const result = await this.cfnInitService.createProject(this.state.projectName!, { - projectPath, - environments: this.state.environments, - }) - - if (!result.success) { - throw new Error(result.error) - } - - progress.report({ increment: 100, message: 'Complete!' }) - } - ) - - await progress - void vscode.window.showInformationMessage(`CFN project '${this.state.projectName}' created!`) - - const openProject = await vscode.window.showQuickPick(['Yes', 'No'], { - placeHolder: 'Open project folder in new window?', - }) - - if (openProject === 'Yes') { - const finalPath = this.state.projectPath || vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || '.' - const uri = vscode.Uri.file(finalPath) - await vscode.commands.executeCommand('vscode.openFolder', uri, true) - } - } -} diff --git a/packages/core/src/awsService/cloudformation/cfn-init/cfnProjectTypes.ts b/packages/core/src/awsService/cloudformation/cfn-init/cfnProjectTypes.ts deleted file mode 100644 index 59aac49bf8..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/cfnProjectTypes.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { OnStackFailure, Parameter } from '@aws-sdk/client-cloudformation' -import { ChangeSetOptionalFlags } from '../stacks/actions/stackActionRequestType' - -export type CfnEnvironmentConfig = { - name: string - profile: string -} - -export type CfnEnvironmentLookup = Record - -export type CfnConfig = { - version: string - project: { - name: string - created: string - } - environments: CfnEnvironmentLookup -} - -export type DeploymentConfig = { - templateFilePath?: string - parameters?: Record - tags?: Record - includeNestedStacks?: boolean - importExistingResources?: boolean - onStackFailure?: OnStackFailure -} - -export type CfnEnvironmentFileSelectorItem = { - fileName: string - hasMatchingTemplatePath?: boolean - compatibleParameters?: Parameter[] - optionalFlags?: ChangeSetOptionalFlags -} - -export const unselectedValue = '-' diff --git a/packages/core/src/awsService/cloudformation/cfn-init/utils.ts b/packages/core/src/awsService/cloudformation/cfn-init/utils.ts deleted file mode 100644 index 91b482a11e..0000000000 --- a/packages/core/src/awsService/cloudformation/cfn-init/utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Parameter, Tag } from '@aws-sdk/client-cloudformation' -import path from 'path' -import { workspace } from 'vscode' - -const cfnProjectPath = 'cfn-project' -const configFile = 'cfn-config.json' -const environmentsDirectory = 'environments' - -export function convertRecordToParameters(parameters: Record): Parameter[] { - return Object.entries(parameters).map(([key, value]) => ({ - ParameterKey: key, - ParameterValue: value, - })) -} - -export function convertRecordToTags(tags: Record): Tag[] { - return Object.entries(tags).map(([key, value]) => ({ - Key: key, - Value: value, - })) -} - -export function convertParametersToRecord(parameters: Parameter[]): Record { - return Object.fromEntries( - parameters - .filter((param) => param.ParameterKey && param.ParameterValue) - .map((param) => [param.ParameterKey!, param.ParameterValue!]) - ) -} - -export function convertTagsToRecord(tags: Tag[]): Record { - return Object.fromEntries(tags.filter((tag) => tag.Key && tag.Value).map((tag) => [tag.Key!, tag.Value!])) -} - -export async function getEnvironmentDir(environmentName: string): Promise { - const workspaceRoot = getWorkspaceRoot() - return path.join(workspaceRoot, cfnProjectPath, environmentsDirectory, environmentName) -} - -export async function getConfigPath(): Promise { - const workspaceRoot = getWorkspaceRoot() - return path.join(workspaceRoot, cfnProjectPath, configFile) -} - -export async function getProjectDir(): Promise { - const workspaceRoot = getWorkspaceRoot() - return path.join(workspaceRoot, cfnProjectPath) -} - -export function getWorkspaceRoot(): string { - const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath - if (!workspaceRoot) { - throw new Error('You must open a workspace to use CFN environment commands') - } - - return workspaceRoot -} diff --git a/packages/core/src/awsService/cloudformation/codelens/stackActionCodeLensProvider.ts b/packages/core/src/awsService/cloudformation/codelens/stackActionCodeLensProvider.ts deleted file mode 100644 index bcf366890c..0000000000 --- a/packages/core/src/awsService/cloudformation/codelens/stackActionCodeLensProvider.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CancellationToken, CodeLens, CodeLensProvider, Event, EventEmitter, TextDocument } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' - -const codeLensRequest = 'textDocument/codeLens' - -export class StackActionCodeLensProvider implements CodeLensProvider { - private readonly _onDidChangeCodeLenses = new EventEmitter() - public readonly onDidChangeCodeLenses: Event = this._onDidChangeCodeLenses.event - - constructor(private readonly client: LanguageClient) {} - - async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { - if (token.isCancellationRequested) { - return [] - } - - const result = await this.client.sendRequest( - codeLensRequest, - { textDocument: { uri: document.uri.toString() } }, - token - ) - - return result || [] - } - - refresh(): void { - this._onDidChangeCodeLenses.fire() - } -} diff --git a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts b/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts deleted file mode 100644 index be5b2d7f60..0000000000 --- a/packages/core/src/awsService/cloudformation/commands/cfnCommands.ts +++ /dev/null @@ -1,996 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - commands, - env, - Uri, - window, - workspace, - Range, - Selection, - TextEditorRevealType, - ProgressLocation, - Disposable, -} from 'vscode' -import { commandKey, extractErrorMessage, findParameterDescriptionPosition, isStackInTransientState } from '../utils' -import { handleLspError } from '../utils/onlineErrorHandler' -import { LanguageClient } from 'vscode-languageclient/node' -import { Command } from 'vscode-languageclient/node' -import * as yaml from 'js-yaml' - -import { Deployment } from '../stacks/actions/deploymentWorkflow' -import { Parameter, Capability, OnStackFailure, Stack, StackStatus } from '@aws-sdk/client-cloudformation' -import { - getParameterValues, - getStackName, - getTemplatePath, - confirmCapabilities, - shouldImportResources, - getResourcesToImport, - getEnvironmentName, - chooseOptionalFlagSuggestion as chooseOptionalFlagMode, - getTags, - getOnStackFailure, - getIncludeNestedStacks, - getImportExistingResources, - getDeploymentMode, - shouldUploadToS3, - getS3Bucket, - getS3Key, - shouldSaveFlagsToFile, - getFilePath, -} from '../ui/inputBox' -import { DiffWebviewProvider } from '../ui/diffWebviewProvider' -import { showErrorMessage } from '../ui/message' -import { getLastValidation, setLastValidation, Validation } from '../stacks/actions/validationWorkflow' -import { - getParameters, - getCapabilities, - getTemplateResources, - getTemplateArtifacts, - describeChangeSet, -} from '../stacks/actions/stackActionApi' -import { - ChangeSetOptionalFlags, - OptionalFlagMode, - TemplateParameter, - ResourceToImport, - ChangeSetReference, - DeploymentMode, -} from '../stacks/actions/stackActionRequestType' -import { ResourceNode } from '../explorer/nodes/resourceNode' -import { ResourcesManager } from '../resources/resourcesManager' -import { RelatedResourcesManager } from '../relatedResources/relatedResourcesManager' -import { DocumentManager } from '../documents/documentManager' -import { CfnEnvironmentManager } from '../cfn-init/cfnEnvironmentManager' - -import { StackOverviewWebviewProvider } from '../ui/stackOverviewWebviewProvider' -import { StackOutputsWebviewProvider } from '../ui/stackOutputsWebviewProvider' -import { StackResourcesWebviewProvider } from '../ui/stackResourcesWebviewProvider' -import { StackViewCoordinator } from '../ui/stackViewCoordinator' -import { ResourceContextValue } from '../explorer/contextValue' -import { getLogger } from '../../../shared/logger/logger' -import { CloudFormationExplorer } from '../explorer/explorer' -import { StacksNode } from '../explorer/nodes/stacksNode' -import { StackNode } from '../explorer/nodes/stackNode' -import { ResourcesNode } from '../explorer/nodes/resourcesNode' -import { ResourceTypeNode } from '../explorer/nodes/resourceTypeNode' -import { StackChangeSetsNode } from '../explorer/nodes/stackChangeSetsNode' -import { CfnInitCliCaller } from '../cfn-init/cfnInitCliCaller' -import { CfnInitUiInterface } from '../cfn-init/cfnInitUiInterface' -import { ChangeSetDeletion } from '../stacks/actions/changeSetDeletionWorkflow' -import { fs } from '../../../shared/fs/fs' -import { convertParametersToRecord, convertTagsToRecord, getEnvironmentDir } from '../cfn-init/utils' -import { DescribeStackRequest } from '../stacks/actions/stackActionProtocol' -import { ResourceIdentifierDocumentationUrl } from '../artifacts/awsDocumentationLinks' -import { CfnEnvironmentFileSelectorItem } from '../cfn-init/cfnProjectTypes' - -export function deployTemplateFromStacksMenuCommand() { - return commands.registerCommand(commandKey('api.deployTemplateFromStacksMenu'), async () => { - return commands.executeCommand(commandKey('api.deployTemplate')) - }) -} - -export function executeChangeSetCommand(client: LanguageClient, coordinator: StackViewCoordinator) { - return commands.registerCommand( - commandKey('api.executeChangeSet'), - async (stackName: string, changeSetName: string) => { - try { - const deployment = new Deployment(stackName, changeSetName, client, coordinator) - - await deployment.deploy() - } catch (error) { - await handleLspError(error, 'Error executing change set') - } - } - ) -} - -export function deleteChangeSetCommand(client: LanguageClient) { - return commands.registerCommand(commandKey('stacks.deleteChangeSet'), async (params: ChangeSetReference) => { - try { - const changeSetDeletion = new ChangeSetDeletion(params.stackName, params.changeSetName, client) - - await changeSetDeletion.delete() - } catch (error) { - await handleLspError(error, 'Error deleting change set') - } - }) -} - -export function viewChangeSetCommand(client: LanguageClient, diffProvider: DiffWebviewProvider) { - return commands.registerCommand(commandKey('stacks.viewChangeSet'), async (params: ChangeSetReference) => { - try { - const describeChangeSetResult = await describeChangeSet(client, { - changeSetName: params.changeSetName, - stackName: params.stackName, - }) - - void diffProvider.updateData( - params.stackName, - describeChangeSetResult.changes, - params.changeSetName, - true, - [], - describeChangeSetResult.deploymentMode, - describeChangeSetResult.status - ) - void commands.executeCommand(commandKey('diff.focus')) - } catch (error) { - await handleLspError(error, 'Error viewing change set') - } - }) -} - -export function deployTemplateCommand( - client: LanguageClient, - diffProvider: DiffWebviewProvider, - documentManager: DocumentManager, - environmentManager: CfnEnvironmentManager -) { - return commands.registerCommand(commandKey('api.deployTemplate'), async (changeSetParams?: string | StackNode) => { - try { - const result = await changeSetSteps( - client, - documentManager, - environmentManager, - false, - typeof changeSetParams === 'string' ? changeSetParams : undefined, - typeof changeSetParams === 'object' ? changeSetParams?.stack.StackName : undefined - ) - if (!result) { - return - } - - const validation = new Validation( - result.templateUri, - result.stackName, - client, - diffProvider, - result.parameters, - result.capabilities, - result.resourcesToImport, - true, // Confirm deployment following successful validation - result.optionalFlags, - result.s3Bucket, - result.s3Key - ) - - setLastValidation(validation) - - await validation.validate() - } catch (error) { - await handleLspError(error, 'Error deploying template') - } - }) -} - -async function promptForResourceImport(client: LanguageClient, templateUri: string) { - const importMode = await shouldImportResources() - let resourcesToImport - if (importMode) { - const templateResources = await getTemplateResources(client, templateUri) - if (!templateResources || templateResources.length === 0) { - showErrorMessage('No resources found in template to import') - return - } - - resourcesToImport = await getResourcesToImport(templateResources) - if (!resourcesToImport || resourcesToImport.length === 0) { - return - } - } - return resourcesToImport -} - -type OptionalFlagSelection = ChangeSetOptionalFlags & { - shouldSaveOptions?: boolean -} - -function isRevertDriftEligible( - stackDetails: Stack | undefined, - importExistingResources: boolean | undefined, - includeNestedStacks: boolean | undefined, - onStackFailure: OnStackFailure | undefined -): boolean { - const isCreate = !stackDetails || stackDetails.StackStatus === StackStatus.REVIEW_IN_PROGRESS - const hasDisableRollback = onStackFailure === OnStackFailure.DO_NOTHING - - return !isCreate && !importExistingResources && !includeNestedStacks && !hasDisableRollback -} - -export async function promptForOptionalFlags( - fileFlags?: ChangeSetOptionalFlags, - stackDetails?: Stack -): Promise { - if (fileFlags && Object.values(fileFlags).every((v) => v !== undefined)) { - return { - ...fileFlags, - shouldSaveOptions: false, - } - } - - let optionalFlags: OptionalFlagSelection | undefined - - const optionSelection = await chooseOptionalFlagMode() - - switch (optionSelection) { - case OptionalFlagMode.Skip: - optionalFlags = { - onStackFailure: fileFlags?.onStackFailure, - includeNestedStacks: fileFlags?.includeNestedStacks, - tags: fileFlags?.tags, - importExistingResources: fileFlags?.importExistingResources, - // default to REVERT_DRIFT if possible because it's generally useful - deploymentMode: - fileFlags?.deploymentMode ?? - (isRevertDriftEligible( - stackDetails, - fileFlags?.importExistingResources, - fileFlags?.includeNestedStacks, - fileFlags?.onStackFailure - ) - ? DeploymentMode.REVERT_DRIFT - : undefined), - shouldSaveOptions: false, - } - - break - case OptionalFlagMode.Input: { - // Only available for UPDATE stack and is incompatible with the other options - const deploymentMode = - fileFlags?.deploymentMode ?? - (isRevertDriftEligible(stackDetails, undefined, undefined, undefined) - ? await getDeploymentMode() - : undefined) - - let onStackFailure: OnStackFailure | undefined - let includeNestedStacks: boolean | undefined - let importExistingResources: boolean | undefined - - if (deploymentMode !== DeploymentMode.REVERT_DRIFT) { - onStackFailure = fileFlags?.onStackFailure ?? (await getOnStackFailure(stackDetails)) - includeNestedStacks = fileFlags?.includeNestedStacks ?? (await getIncludeNestedStacks()) - importExistingResources = fileFlags?.importExistingResources ?? (await getImportExistingResources()) - } - - optionalFlags = { - onStackFailure, - includeNestedStacks, - tags: fileFlags?.tags ?? (await getTags(stackDetails?.Tags)), - importExistingResources, - deploymentMode, - } - - if (!fileFlags && Object.values(optionalFlags).some((val) => val !== undefined)) { - optionalFlags.shouldSaveOptions = true - } - - break - } - case OptionalFlagMode.DevFriendly: - optionalFlags = { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: fileFlags?.tags ?? (await getTags(stackDetails?.Tags)), - importExistingResources: true, - deploymentMode: undefined, - } - - if (!fileFlags && optionalFlags.tags) { - optionalFlags.shouldSaveOptions = true - } - - break - default: - optionalFlags = undefined - } - - return optionalFlags -} - -export async function promptToSaveToFile( - environmentDir: string, - optionalFlags?: ChangeSetOptionalFlags, - parameters?: Parameter[] -): Promise { - const shouldSave = await shouldSaveFlagsToFile() - - if (!shouldSave) { - return - } - - const filePath = await getFilePath(environmentDir) - - if (!filePath) { - return - } - - const data = { - parameters: parameters ? convertParametersToRecord(parameters) : undefined, - tags: optionalFlags?.tags ? convertTagsToRecord(optionalFlags?.tags) : undefined, - 'on-stack-failure': optionalFlags?.onStackFailure, - 'include-nested-stacks': optionalFlags?.includeNestedStacks, - 'import-existing-resources': optionalFlags?.importExistingResources, - 'deployment-mode': optionalFlags?.deploymentMode, - } - - // Determine file type and format accordingly - const isJsonFile = filePath.endsWith('.json') - const config = workspace.getConfiguration('editor') - const tabSize = config.get('tabSize', 2) - const insertSpaces = config.get('insertSpaces', true) - let content: string - - try { - if (isJsonFile) { - // JSON allows both tabs and spaces - respect user preference - const indent = insertSpaces ? tabSize : '\t' - content = JSON.stringify(data, undefined, indent) - } else { - // YAML spec requires spaces for indentation - always use spaces - content = yaml.dump(data, { indent: tabSize, noRefs: true, sortKeys: true }) - } - } catch (error) { - showErrorMessage(`Failed to format deployment options: ${extractErrorMessage(error)}`) - return - } - - try { - await fs.writeFile(filePath, content) - void window.showInformationMessage(`options saved to: ${filePath}`) - } catch (error) { - showErrorMessage(`Failed to save deployment options file: ${extractErrorMessage(error)}`) - } -} - -async function validateArtifactPaths(client: LanguageClient, templateUri: string): Promise { - try { - const artifactsResult = await getTemplateArtifacts(client, templateUri) - if (artifactsResult.artifacts.length === 0) { - return false - } - - for (const artifact of artifactsResult.artifacts) { - const artifactPath = artifact.filePath.startsWith('/') - ? artifact.filePath - : Uri.joinPath(Uri.parse(templateUri), '..', artifact.filePath).fsPath - - if (!(await fs.exists(artifactPath))) { - showErrorMessage(`Artifact path does not exist: ${artifact.filePath}`) - return undefined - } - } - return true - } catch (error) { - getLogger().warn(`Failed to check for artifacts: ${error}`) - return false - } -} - -type UserInputtedTemplateParameters = { - templateUri: string - stackName: string - parameters: Parameter[] | undefined - capabilities: Capability[] - resourcesToImport: ResourceToImport[] | undefined - optionalFlags: ChangeSetOptionalFlags | undefined - s3Bucket?: string - s3Key?: string -} - -async function changeSetSteps( - client: LanguageClient, - documentManager: DocumentManager, - environmentManager: CfnEnvironmentManager, - isValidation: boolean, - templateUri: string | undefined, - stackName: string | undefined -): Promise { - try { - await environmentManager.refreshSelectedEnvironment() - } catch (error) { - getLogger().warn(`Failed to refresh selected environment: ${extractErrorMessage(error)}`) - } - - templateUri ??= await getTemplatePath(documentManager) - if (!templateUri) { - return - } - - await ensureFileIsOpen(templateUri) - - // Check for artifacts first - const hasArtifacts = await validateArtifactPaths(client, templateUri) - if (hasArtifacts === undefined) { - return // Error occurred during validation - } - - // Ask user if they want to upload to S3 - let s3Bucket: string | undefined - let s3Key: string | undefined - const uploadChoice = await shouldUploadToS3() - if (uploadChoice === undefined) { - return // User chose to configure settings, exit command - } - if (uploadChoice) { - s3Bucket = await getS3Bucket() - if (!s3Bucket) { - return - } - - const fileName = templateUri.split('/').pop() - const timestamp = Date.now() - const fileNameWithTimestamp = fileName - ? `${fileName.split('.')[0]}-${timestamp}.${fileName.split('.').pop()}` - : `template-${timestamp}.yaml` - s3Key = await getS3Key(fileNameWithTimestamp) - if (!s3Key) { - return - } - } else if (hasArtifacts) { - s3Bucket = await getS3Bucket( - 'S3 bucket is required because template contains artifacts that need to be uploaded to S3' - ) - if (!s3Bucket) { - return - } - } - - if (!stackName) { - if (isValidation) { - stackName = await getStackName(getLastValidation()?.stackName) - } else { - stackName = await getStackName() - } - // User cancelled - if (!stackName) { - return - } - } - - const stackDetails = await getStackDetails(client, stackName) - - const resourcesToImport = await promptForResourceImport(client, templateUri) - - const paramDefinition = await getTemplateParameters(client, templateUri) - let parameters: Parameter[] | undefined - - let environmentFile: CfnEnvironmentFileSelectorItem | undefined - - try { - environmentFile = await environmentManager.selectEnvironmentFile(templateUri, paramDefinition) - } catch (error) { - getLogger().warn(`Failed to select environment file: ${extractErrorMessage(error)}`) - } - - if (paramDefinition.length > 0) { - parameters = environmentFile?.compatibleParameters - - // Prompt for any remaining parameters not provided by file - const providedParamNames = parameters?.map((p) => p.ParameterKey) ?? [] - const remainingParams = paramDefinition.filter((p) => !providedParamNames.includes(p.name)) - - if (remainingParams.length > 0) { - let prefilledParams: Parameter[] | undefined - - if (stackDetails) { - prefilledParams = stackDetails.Parameters - } else if (isValidation) { - prefilledParams = getLastValidation()?.parameters - } - - const additionalParams = await getParameterValues(remainingParams, prefilledParams) - - if (!additionalParams) { - return - } - - parameters = [...(parameters ?? []), ...additionalParams] - } - } - if (paramDefinition.length > 0 && !parameters) { - return - } - - const optionalFlags = await promptForOptionalFlags(environmentFile?.optionalFlags, stackDetails) - const shouldSaveParameters = parameters && parameters.length > 0 && !environmentFile - - let selectedEnvironment: string | undefined - - try { - selectedEnvironment = environmentManager.getSelectedEnvironmentName() - } catch (error) { - getLogger().warn(`Failed to get selected environment: ${extractErrorMessage(error)}`) - } - - if (selectedEnvironment && (shouldSaveParameters || optionalFlags?.shouldSaveOptions)) { - await promptToSaveToFile(await getEnvironmentDir(selectedEnvironment), optionalFlags, parameters) - } - - const capabilitiesResult = await getCapabilities(client, templateUri) - const capabilities = await confirmCapabilities(capabilitiesResult.capabilities) - if (capabilities === undefined) { - return - } // User cancelled - return { templateUri, stackName, parameters, capabilities, resourcesToImport, optionalFlags, s3Bucket, s3Key } -} - -export function rerunValidateAndDeployCommand() { - return commands.registerCommand(commandKey('api.rerunValidateAndDeploy'), async () => { - try { - const lastValidation = getLastValidation() - if (!lastValidation) { - showErrorMessage('No previous validation to rerun') - return - } - await lastValidation.validate() - } catch (error) { - await handleLspError(error, 'Error rerunning validation') - } - }) -} - -async function ensureFileIsOpen(templateUri: string): Promise { - const uri = Uri.parse(templateUri) - const openEditors = window.visibleTextEditors - const isFileOpen = openEditors.some((editor) => editor.document.uri.toString() === uri.toString()) - - if (!isFileOpen) { - try { - const document = await workspace.openTextDocument(uri) - await window.showTextDocument(document) - } catch (error) { - getLogger().warn(`Could not open file: ${error}`) - throw error - } - } -} - -async function getStackDetails(client: LanguageClient, stackName: string) { - let stackDetails: Stack | undefined - - try { - stackDetails = ( - await client.sendRequest(DescribeStackRequest, { - stackName: stackName, - }) - ).stack - } catch (error) { - const errorMessage = extractErrorMessage(error) - - if (!errorMessage.toLowerCase().includes('does not exist')) { - showErrorMessage(`Encountered error while extracting stack details: ${errorMessage}`) - } - } - - return stackDetails -} - -async function getTemplateParameters(client: LanguageClient, templateUri: string): Promise { - try { - const result = await getParameters(client, templateUri) - return result.parameters - } catch (error) { - showErrorMessage(`Error getting template parameters: ${error instanceof Error ? error.message : String(error)}`) - return [] - } -} - -export function addResourceTypesCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand( - commandKey('api.addResourceTypes'), - async () => await resourcesManager.selectResourceTypes() - ) -} - -export function removeResourceTypeCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand( - commandKey('removeResourceType'), - async (node: ResourceTypeNode) => await resourcesManager.removeResourceType(node.typeName) - ) -} - -export function importResourceStateCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand( - commandKey('api.importResourceState'), - async (node?: ResourceNode, selectedNodes?: ResourceNode[]) => { - try { - const nodes = selectedNodes ?? (node ? [node] : []) - const resourceNodes = nodes.filter((n) => n.contextValue === ResourceContextValue) - await resourcesManager.importResourceStates(resourceNodes) - } catch (error) { - await handleLspError(error, 'Error importing resource state') - } - } - ) -} - -export function cloneResourceStateCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand( - commandKey('api.cloneResourceState'), - async (node?: ResourceNode, selectedNodes?: ResourceNode[]) => { - try { - const nodes = selectedNodes ?? (node ? [node] : []) - const resourceNodes = nodes.filter((n) => n.contextValue === ResourceContextValue) - await resourcesManager.cloneResourceStates(resourceNodes) - } catch (error) { - await handleLspError(error, 'Error cloning resource state') - } - } - ) -} - -export const RefreshResourceListCommand: Command = { - title: 'Refresh Resource List', - command: commandKey('api.refreshResourceList'), - arguments: [], -} - -export function copyResourceIdentifierCommand() { - return commands.registerCommand(commandKey('api.copyResourceIdentifier'), async (resourceNode?: ResourceNode) => { - if (resourceNode?.resourceIdentifier) { - await env.clipboard.writeText(resourceNode.resourceIdentifier) - window.setStatusBarMessage(`Resource identifier copied to clipboard`, 3000) - } - }) -} - -export function refreshAllResourcesCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand(commandKey('api.refreshAllResources'), async () => { - try { - await resourcesManager.refreshAllResources() - } catch (error) { - await handleLspError(error, 'Error refreshing resources') - } - }) -} - -export function refreshResourceListCommand(resourcesManager: ResourcesManager, explorer: CloudFormationExplorer) { - return commands.registerCommand(RefreshResourceListCommand.command, async (resourceTypeNode?: ResourceTypeNode) => { - if (!resourceTypeNode) { - const children = await explorer.getChildren() - const resourcesNode = children.find((child) => child instanceof ResourcesNode) as ResourcesNode | undefined - if (!resourcesNode) { - return - } - - const resourceTypeNodes = (await resourcesNode.getChildren()) as ResourceTypeNode[] - if (resourceTypeNodes.length === 0) { - void window.showInformationMessage('No resource types selected') - return - } - - const selected = await window.showQuickPick( - resourceTypeNodes.map((n) => ({ label: n.typeName, node: n })), - { placeHolder: 'Select resource type to refresh' } - ) - - if (!selected) { - return - } - - resourceTypeNode = selected.node - } - - try { - await resourcesManager.refreshResourceList(resourceTypeNode.typeName) - } catch (error) { - await handleLspError(error, 'Error refreshing resource list') - } - }) -} - -export function focusDiffCommand() { - return commands.registerCommand(commandKey('diff.focus'), () => { - void commands.executeCommand('workbench.view.extension.cfn-diff') - }) -} - -export function getStackManagementInfoCommand(resourcesManager: ResourcesManager) { - return commands.registerCommand(commandKey('api.getStackManagementInfo'), async (resourceNode?: ResourceNode) => { - try { - await resourcesManager.getStackManagementInfo(resourceNode) - } catch (error) { - await handleLspError(error, 'Error getting stack management info') - } - }) -} - -export function extractToParameterPositionCursorCommand(client: LanguageClient) { - return commands.registerCommand( - 'aws.cloudformation.extractToParameter.positionCursor', - async ( - documentUri: string, - parameterName: string, - documentType: string, - trackingCommand?: string, - actionType?: string - ) => { - try { - // Track code action acceptance on the server if tracking parameters provided - if (trackingCommand && actionType) { - await client.sendRequest('workspace/executeCommand', { - command: trackingCommand, - arguments: [actionType], - }) - } - - const uri = Uri.parse(documentUri) - const document = await workspace.openTextDocument(uri) - const editor = await window.showTextDocument(document) - - const text = document.getText() - const position = findParameterDescriptionPosition(text, parameterName, documentType) - - if (position) { - editor.selection = new Selection(position, position) - editor.revealRange(new Range(position, position), TextEditorRevealType.InCenter) - } - } catch (error) { - getLogger().error(`Error positioning cursor in parameter description: ${error}`) - } - } - ) -} - -export function loadMoreResourcesCommand(explorer: CloudFormationExplorer) { - return commands.registerCommand(commandKey('api.loadMoreResources'), async (node?: ResourceTypeNode) => { - if (!node) { - const children = await explorer.getChildren() - const resourcesNode = children.find((child) => child instanceof ResourcesNode) as ResourcesNode | undefined - if (!resourcesNode) { - return - } - - const resourceTypeNodes = (await resourcesNode.getChildren()) as ResourceTypeNode[] - const nodesWithMore = resourceTypeNodes.filter((n) => n.contextValue === 'resourceTypeWithMore') - - if (nodesWithMore.length === 0) { - void window.showInformationMessage('No resource types have more resources to load') - return - } - - const selected = await window.showQuickPick( - nodesWithMore.map((n) => ({ label: n.typeName, node: n })), - { placeHolder: 'Select resource type to load more' } - ) - - if (!selected) { - return - } - - node = selected.node - } - - try { - await node.loadMoreResources() - explorer.refresh(node) - } catch (error) { - await handleLspError(error, 'Error loading more resources') - } - }) -} - -export function loadMoreStacksCommand(explorer: CloudFormationExplorer) { - return commands.registerCommand(commandKey('api.loadMoreStacks'), async (node?: StacksNode) => { - if (!node) { - const children = await explorer.getChildren() - node = children.find((child) => child instanceof StacksNode) as StacksNode | undefined - if (!node) { - return - } - } - - if (node.contextValue !== 'stackSectionWithMore') { - void window.showInformationMessage('No more stacks to load') - return - } - - const stacksNode = node - await window.withProgress( - { - location: ProgressLocation.Notification, - title: 'Loading More Stacks', - }, - async () => { - try { - await stacksNode.loadMoreStacks() - explorer.refresh(stacksNode) - } catch (error) { - await handleLspError(error, 'Error loading more stacks') - } - } - ) - }) -} - -export function searchResourceCommand(explorer: CloudFormationExplorer, resourcesManager: ResourcesManager) { - return commands.registerCommand(commandKey('api.searchResource'), async (node: ResourceTypeNode) => { - try { - const identifier = await window.showInputBox({ - prompt: `Enter ${node.label} identifier to add to list`, - placeHolder: 'Resource identifier must match exactly', - }) - - if (!identifier) { - return - } - - const result = await resourcesManager.searchResource(node.label as string, identifier) - - if (result.found) { - void window.showInformationMessage(`${identifier} (${node.label}) has been added to the list`) - explorer.refresh(node) - } else { - const action = await window.showErrorMessage( - `${node.label} with identifier '${identifier}' was not found. The identifier must match exactly.`, - 'See Documentation' - ) - if (action === 'See Documentation') { - void env.openExternal(Uri.parse(ResourceIdentifierDocumentationUrl)) - } - } - } catch (error) { - await handleLspError(error, 'Error searching for resource') - } - }) -} - -export function refreshChangeSetsCommand(explorer: CloudFormationExplorer) { - return commands.registerCommand(commandKey('stacks.refreshChangeSets'), async (node: StackChangeSetsNode) => { - explorer.refresh(node) - }) -} - -export function loadMoreChangeSetsCommand(explorer: CloudFormationExplorer) { - return commands.registerCommand(commandKey('api.loadMoreChangeSets'), async (node: StackChangeSetsNode) => { - try { - await node.loadMoreChangeSets() - explorer.refresh(node) - } catch (error) { - await handleLspError(error, 'Error loading more change sets') - } - }) -} - -export function viewStackCommand( - coordinator: StackViewCoordinator, - overviewProvider: StackOverviewWebviewProvider, - outputsProvider: StackOutputsWebviewProvider, - resourcesProvider: StackResourcesWebviewProvider -) { - return commands.registerCommand(commandKey('stack.view'), async (node?: StackNode) => { - try { - let stackName: string | undefined - - if (node?.stack.StackName) { - stackName = node.stack.StackName - } else { - stackName = await getStackName() - if (!stackName) { - return - } - } - - await coordinator.setStack(stackName) - - await overviewProvider.showStackOverview(stackName) - - const stackStatus = coordinator.currentStackStatus - - await resourcesProvider.updateData(stackName) - - if (stackStatus && !isStackInTransientState(stackStatus)) { - await outputsProvider.showOutputs(stackName) - } - - await commands.executeCommand(commandKey('stack.overview.focus')) - } catch (error) { - await handleLspError(error, 'Error viewing stack') - } - }) -} - -export function createProjectCommand(uiInterface: CfnInitUiInterface) { - return commands.registerCommand(commandKey('init.initializeProject'), async () => { - await uiInterface.promptForCreate() - }) -} - -export function addEnvironmentCommand( - uiInterface: CfnInitUiInterface, - cfnInit: CfnInitCliCaller, - environmentManager: CfnEnvironmentManager -) { - return commands.registerCommand(commandKey('init.addEnvironment'), async () => { - try { - if (await environmentManager.promptInitializeIfNeeded('Environment Addition')) { - return - } - - const environment = await uiInterface.collectEnvironmentConfig() - if (!environment) { - return - } - - const result = await cfnInit.addEnvironments([environment]) - - if (result.success) { - void window.showInformationMessage(`Environment '${environment.name}' added successfully`) - } else { - showErrorMessage(`Failed to add environment: ${result.error}`) - } - } catch (error) { - showErrorMessage(`Error adding environment: ${error}`) - } - }) -} - -export function removeEnvironmentCommand(cfnInit: CfnInitCliCaller, environmentManager: CfnEnvironmentManager) { - return commands.registerCommand(commandKey('init.removeEnvironment'), async () => { - try { - if (await environmentManager.promptInitializeIfNeeded('Environment Deletion')) { - return - } - - // TODO: Show quickpick of environments instead of inputting it - const envName = await getEnvironmentName() - if (!envName) { - return - } - - const confirm = await window.showWarningMessage(`Remove environment '${envName}'?`, 'Remove', 'Cancel') - if (confirm !== 'Remove') { - return - } - - const result = await cfnInit.removeEnvironment(envName) - - await environmentManager.refreshSelectedEnvironment() - if (result.success) { - void window.showInformationMessage(`Environment '${envName}' removed successfully`) - } else { - showErrorMessage(`Failed to remove environment: ${result.error}`) - } - } catch (error) { - showErrorMessage(`Error removing environment: ${error}`) - } - }) -} - -export function addRelatedResourcesCommand(relatedResourcesManager: RelatedResourcesManager) { - return commands.registerCommand(commandKey('api.addRelatedResources'), async (node?: ResourceTypeNode) => { - const selectedResourceType = node?.typeName - await relatedResourcesManager.addRelatedResources(selectedResourceType) - }) -} - -export function selectEnvironmentCommand(explorer: CloudFormationExplorer): Disposable { - return commands.registerCommand(commandKey('environment.select'), async () => { - await explorer.environmentManager.selectEnvironment() - }) -} diff --git a/packages/core/src/awsService/cloudformation/commands/openStackTemplate.ts b/packages/core/src/awsService/cloudformation/commands/openStackTemplate.ts deleted file mode 100644 index 02c7b8ac70..0000000000 --- a/packages/core/src/awsService/cloudformation/commands/openStackTemplate.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*! -import { getLogger } from '../../../shared/logger' - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { commands, window, workspace, ViewColumn, Position, Range, Selection, ProgressLocation } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { RequestType } from 'vscode-languageserver-protocol' -import { commandKey, formatMessage } from '../utils' -import { handleLspError } from '../utils/onlineErrorHandler' -import { getLogger } from '../../../shared/logger/logger' - -interface GetStackTemplateParams { - stackName: string - primaryIdentifier?: string -} - -interface GetStackTemplateResponse { - templateBody: string - lineNumber?: number -} - -const GetStackTemplateRequest = new RequestType( - 'aws/cfn/stack/template' -) - -function isValidStackName(stackName: string): boolean { - // CloudFormation stack names: 1-128 chars, alphanumeric and hyphens, start with letter - const stackNameRegex = /^[a-zA-Z][a-zA-Z0-9-]{0,127}$/ - return stackNameRegex.test(stackName) -} - -export function openStackTemplateCommand(client: LanguageClient) { - return commands.registerCommand( - commandKey('api.openStackTemplate'), - async (stackName: string, primaryIdentifier?: string) => { - if (!stackName) { - void window.showErrorMessage(formatMessage('No stack name provided')) - return - } - - if (!isValidStackName(stackName)) { - void window.showErrorMessage(formatMessage('Invalid stack name format')) - return - } - - await window - .withProgress( - { - location: ProgressLocation.Notification, - title: `Opening template for stack: ${stackName}`, - cancellable: false, - }, - async () => { - try { - const response = await client.sendRequest(GetStackTemplateRequest, { - stackName, - primaryIdentifier, - }) - - if (!response?.templateBody) { - void window.showWarningMessage( - formatMessage(`No template found for stack: ${stackName}`) - ) - return - } - - const doc = await workspace.openTextDocument({ - content: response.templateBody, - language: response.templateBody.trim().startsWith('{') ? 'json' : 'yaml', - }) - - const editor = await window.showTextDocument(doc, ViewColumn.Active) - - if (response.lineNumber !== undefined) { - const line = doc.lineAt(response.lineNumber) - const position = new Position(response.lineNumber, line.text.length) - editor.selection = new Selection(position, position) - editor.revealRange(new Range(position, position)) - } - - return response - } catch (error) { - getLogger().error('Failed to get stack template: %O', { - stackName, - primaryIdentifier, - error: error instanceof Error ? error.message : String(error), - }) - - await handleLspError(error, `Failed to open template for stack: ${stackName}`) - } - } - ) - .then(async (response) => { - if (!response) { - return - } - - const action = await window.showInformationMessage( - 'Template opened. Would you like to save it locally?', - 'Save As...', - 'No Thanks' - ) - - if (action === 'Save As...') { - const extension = response.templateBody.trim().startsWith('{') ? 'json' : 'yaml' - const saveUri = await window.showSaveDialog({ - defaultUri: workspace.workspaceFolders?.[0]?.uri.with({ - path: `${workspace.workspaceFolders[0].uri.path}/${stackName}-template.${extension}`, - }), - filters: { - 'CloudFormation Templates': [extension], - 'All Files': ['*'], - }, - }) - - if (saveUri) { - await workspace.fs.writeFile(saveUri, Buffer.from(response.templateBody, 'utf8')) - void window.showInformationMessage(`Template saved to ${saveUri.fsPath}`) - } - } - }) - } - ) -} diff --git a/packages/core/src/awsService/cloudformation/commands/regionCommands.ts b/packages/core/src/awsService/cloudformation/commands/regionCommands.ts deleted file mode 100644 index 0869b40437..0000000000 --- a/packages/core/src/awsService/cloudformation/commands/regionCommands.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { CloudFormationExplorer } from '../explorer/explorer' -import { commandKey } from '../utils' - -export function selectRegionCommand(explorer: CloudFormationExplorer): vscode.Disposable { - return vscode.commands.registerCommand(commandKey('selectRegion'), async () => { - await explorer.selectRegion() - }) -} diff --git a/packages/core/src/awsService/cloudformation/consoleLinksUtils.ts b/packages/core/src/awsService/cloudformation/consoleLinksUtils.ts deleted file mode 100644 index 390f1f3804..0000000000 --- a/packages/core/src/awsService/cloudformation/consoleLinksUtils.ts +++ /dev/null @@ -1,45 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { parse } from '@aws-sdk/util-arn-parser' - -export function arnToConsoleUrl(arn: string): string { - return `https://console.aws.amazon.com/go/view?arn=${encodeURIComponent(arn)}` -} - -export function arnToConsoleTabUrl(arn: string, tab: 'resources' | 'events' | 'outputs'): string { - const region = arn.split(':')[3] - return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/${tab}?stackId=${encodeURIComponent(arn)}` -} - -export function operationIdToConsoleUrl(arn: string, operationId: string): string | undefined { - try { - const region = parse(arn).region - return `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/operations/info?stackId=${encodeURIComponent(arn)}&operationId=${operationId}` - } catch { - return undefined - } -} - -// Reference link - https://cloudscape.design/foundation/visual-foundation/iconography/ - icon name: external -export function externalLinkSvg(): string { - return `` -} - -export const consoleLinkStyles = ` -.console-link { - display: inline-flex; - align-items: center; - opacity: 0.8; - transition: opacity 0.2s; - line-height: 1; -} -.console-link:hover { - opacity: 1; -} -.console-link svg path { - fill: #007ACC; -} -` diff --git a/packages/core/src/awsService/cloudformation/documents/documentManager.ts b/packages/core/src/awsService/cloudformation/documents/documentManager.ts deleted file mode 100644 index 5232d6c00c..0000000000 --- a/packages/core/src/awsService/cloudformation/documents/documentManager.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { NotificationType } from 'vscode-languageserver-protocol' -import { LanguageClient } from 'vscode-languageclient/node' - -export type DocumentMetadata = { - uri: string - fileName: string - ext: string - type: string - cfnType: string - languageId: string - version: number - lineCount: number -} - -const DocumentsMetadataNotification = new NotificationType('aws/documents/metadata') - -type DocumentsChangeListener = (documents: DocumentMetadata[]) => void - -export class DocumentManager { - private documents: DocumentMetadata[] = [] - private readonly listeners: DocumentsChangeListener[] = [] - - constructor(private readonly client: LanguageClient) { - this.client.onNotification(DocumentsMetadataNotification, (documents: DocumentMetadata[]) => { - this.documents = documents - for (const listener of this.listeners) { - listener(this.documents) - } - }) - } - - addListener(listener: DocumentsChangeListener) { - this.listeners.push(listener) - } - - get() { - return [...this.documents] - } -} diff --git a/packages/core/src/awsService/cloudformation/documents/documentPreview.ts b/packages/core/src/awsService/cloudformation/documents/documentPreview.ts deleted file mode 100644 index 208ba7867c..0000000000 --- a/packages/core/src/awsService/cloudformation/documents/documentPreview.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { NotificationType } from 'vscode-languageserver-protocol' -import { LanguageClient } from 'vscode-languageclient/node' -import { ViewColumn, window, workspace } from 'vscode' - -type DocumentPreviewType = { - content: string - language: string - viewColumn?: number - preserveFocus?: boolean -} - -const DocumentPreviewNotification = new NotificationType('aws/document/preview') - -export class DocumentPreview { - constructor(private readonly client: LanguageClient) { - this.client.onNotification(DocumentPreviewNotification, (preview: DocumentPreviewType) => { - if (preview) { - void docPreview(preview) - } - }) - } -} - -export async function docPreview(preview: DocumentPreviewType) { - const { content, language, viewColumn = ViewColumn.Beside, preserveFocus = true } = preview - - await window.showTextDocument( - await workspace.openTextDocument({ - content, - language, - }), - { - viewColumn, - preserveFocus, - } - ) -} diff --git a/packages/core/src/awsService/cloudformation/explorer/contextValue.ts b/packages/core/src/awsService/cloudformation/explorer/contextValue.ts deleted file mode 100644 index 0122fa2ffc..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/contextValue.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ResourceSectionContextValue = 'resourceSection' -export const ResourceTypeContextValue = 'resourceType' -export const ResourceTypeWithMoreContextValue = 'resourceTypeWithMore' -export const LoadMoreResourcesContextValue = 'loadMoreResources' -export const ResourceContextValue = 'resource' -export const RegionSelectorContextValue = 'regionSelector' diff --git a/packages/core/src/awsService/cloudformation/explorer/explorer.ts b/packages/core/src/awsService/cloudformation/explorer/explorer.ts deleted file mode 100644 index 1afa1256b7..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/explorer.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { RegionProvider } from '../../../shared/regions/regionProvider' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { RefreshableAwsTreeProvider } from '../../../shared/treeview/awsTreeProvider' -import { CloudFormationRegionManager } from './regionManager' -import { StacksNode } from './nodes/stacksNode' -import { ResourcesNode } from './nodes/resourcesNode' -import { RegionSelectorNode } from './nodes/regionSelectorNode' -import { AwsCredentialsService } from '../auth/credentials' -import { getLogger } from '../../../shared/logger/logger' -import { getIcon } from '../../../shared/icons' -import globals from '../../../shared/extensionGlobals' - -import { StacksManager } from '../stacks/stacksManager' -import { ResourcesManager } from '../resources/resourcesManager' - -import { DocumentManager } from '../documents/documentManager' -import { ChangeSetsManager } from '../stacks/changeSetsManager' -import { CfnEnvironmentManager } from '../cfn-init/cfnEnvironmentManager' -import { CfnEnvironmentsNode } from './nodes/cfnEnvironmentsNode' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { cloudFormationUiClickMetric } from '../utils' - -export class CloudFormationExplorer implements vscode.TreeDataProvider, RefreshableAwsTreeProvider { - public viewProviderId: string = 'aws.cloudformation' - public readonly onDidChangeTreeData: vscode.Event - private readonly _onDidChangeTreeData: vscode.EventEmitter - public readonly regionManager: CloudFormationRegionManager - public readonly environmentManager: CfnEnvironmentManager - private credentialsService: AwsCredentialsService | undefined - - public constructor( - private readonly stacksManager: StacksManager, - private readonly resourcesManager: ResourcesManager, - private readonly changeSetsManager: ChangeSetsManager, - documentManager: DocumentManager, - regionProvider: RegionProvider, - environmentManager: CfnEnvironmentManager - ) { - this._onDidChangeTreeData = new vscode.EventEmitter() - this.onDidChangeTreeData = this._onDidChangeTreeData.event - this.regionManager = new CloudFormationRegionManager(regionProvider) - this.environmentManager = environmentManager - } - - public setCredentialsService(credentialsService: AwsCredentialsService): void { - this.credentialsService = credentialsService - } - - public async selectRegion(): Promise { - telemetry.ui_click.emit({ elementId: cloudFormationUiClickMetric }) - const changed = await this.regionManager.showRegionSelector() - if (changed) { - this.refresh() - if (this.credentialsService) { - await this.credentialsService.updateRegion() - } - } - } - - public getTreeItem(element: AWSTreeNodeBase): vscode.TreeItem { - return element - } - - public async getChildren(element?: AWSTreeNodeBase): Promise { - if (!element) { - return this.getRootChildren() - } - telemetry.ui_click.emit({ elementId: cloudFormationUiClickMetric }) - return element.getChildren() - } - - private getRootChildren(): AWSTreeNodeBase[] { - try { - // Show sign-in message when not authenticated - if (!globals.awsContext.getCredentialProfileName()) { - const signInNode = new PlaceholderNode(this as any, 'Sign in to get started') - signInNode.iconPath = getIcon('vscode-account') - signInNode.command = { - command: 'aws.toolkit.login', - title: 'Sign in', - } - return [signInNode] - } - - return [ - new RegionSelectorNode(this.regionManager), - new CfnEnvironmentsNode(this.environmentManager), - new StacksNode(this.stacksManager, this.changeSetsManager), - new ResourcesNode(this.resourcesManager), - ] - } catch (error) { - getLogger().error('CloudFormation explorer error: %O', error) - return [] - } - } - - public refresh(node?: AWSTreeNodeBase): void { - this._onDidChangeTreeData.fire(node) - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/cfnEnvironmentsNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/cfnEnvironmentsNode.ts deleted file mode 100644 index 4e287158c6..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/cfnEnvironmentsNode.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState, ThemeIcon } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { CfnEnvironmentManager } from '../../cfn-init/cfnEnvironmentManager' -import { commandKey } from '../../utils' - -export class CfnEnvironmentsNode extends AWSTreeNodeBase { - public constructor(readonly environmentManager: CfnEnvironmentManager) { - const selectedEnv = environmentManager.getSelectedEnvironmentName() - const label = selectedEnv ? `Environment: ${selectedEnv}` : 'Environment: not selected' - - super(label, TreeItemCollapsibleState.None) - this.contextValue = 'environmentsSection' - this.iconPath = new ThemeIcon('settings-gear') - this.tooltip = selectedEnv - ? `Current environment: ${selectedEnv}. Click to select a different environment.` - : 'No environment selected. Click to select an environment.' - this.command = { - command: commandKey('environment.select'), - title: 'Select Environment', - } - } - - public override async getChildren(): Promise { - return [] - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/regionSelectorNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/regionSelectorNode.ts deleted file mode 100644 index 9766a6a3c4..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/regionSelectorNode.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState, ThemeIcon } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { CloudFormationRegionManager } from '../regionManager' -import { RegionSelectorContextValue } from '../contextValue' -import { commandKey } from '../../utils' - -export class RegionSelectorNode extends AWSTreeNodeBase { - public constructor(regionManager: CloudFormationRegionManager) { - const currentRegion = regionManager.getSelectedRegion() - super(currentRegion, TreeItemCollapsibleState.None) - this.contextValue = RegionSelectorContextValue - this.iconPath = new ThemeIcon('globe') - this.tooltip = `Current region: ${currentRegion}. Click to select a different region.` - this.command = { - command: commandKey('selectRegion'), - title: 'Select Region', - } - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/resourceNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/resourceNode.ts deleted file mode 100644 index d92c52aa3f..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/resourceNode.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' - -export class ResourceNode extends AWSTreeNodeBase { - public constructor( - public readonly resourceIdentifier: string, - public readonly resourceType: string - ) { - super(resourceIdentifier, TreeItemCollapsibleState.None) - this.contextValue = 'resource' - } - - public override async getChildren(): Promise { - return [] - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/resourceTypeNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/resourceTypeNode.ts deleted file mode 100644 index 0968d72719..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/resourceTypeNode.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState, ThemeIcon } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { ResourceList } from '../../resources/resourceRequestTypes' -import { ResourceNode } from './resourceNode' -import { commandKey } from '../../utils' -import { ResourcesManager } from '../../resources/resourcesManager' -import { - LoadMoreResourcesContextValue, - ResourceTypeContextValue, - ResourceTypeWithMoreContextValue, -} from '../contextValue' - -class LoadMoreResourcesNode extends AWSTreeNodeBase { - public constructor(private readonly parent: ResourceTypeNode) { - super('[Load More...]', TreeItemCollapsibleState.None) - this.contextValue = LoadMoreResourcesContextValue - this.command = { - title: 'Load More', - command: commandKey('api.loadMoreResources'), - arguments: [this.parent], - } - } -} - -class NoResourcesNode extends AWSTreeNodeBase { - public constructor() { - super('No resources found', TreeItemCollapsibleState.None) - this.contextValue = 'noResources' - this.iconPath = new ThemeIcon('info') - } -} - -export class ResourceTypeNode extends AWSTreeNodeBase { - private loaded = false - - public constructor( - public readonly typeName: string, - private readonly resourcesManager: ResourcesManager, - private resourceList?: ResourceList - ) { - super(typeName, TreeItemCollapsibleState.Collapsed) - this.loaded = resourceList !== undefined - this.updateNode() - } - - private updateNode(): void { - if (!this.resourceList) { - this.description = undefined - this.contextValue = ResourceTypeContextValue - return - } - const count = this.resourceList.resourceIdentifiers.length - const hasMore = this.resourceList.nextToken !== undefined - this.description = hasMore ? `(${count}+)` : `(${count})` - this.contextValue = hasMore ? ResourceTypeWithMoreContextValue : ResourceTypeContextValue - } - - public override async getChildren(): Promise { - if (!this.loaded) { - await this.resourcesManager.loadResourceType(this.typeName) - this.resourceList = this.resourcesManager.get().find((r) => r.typeName === this.typeName) - this.loaded = true - this.updateNode() - } - - if (!this.resourceList || this.resourceList.resourceIdentifiers.length === 0) { - return [new NoResourcesNode()] - } - - const nodes = this.resourceList.resourceIdentifiers.map( - (identifier) => new ResourceNode(identifier, this.typeName) - ) - - return this.resourceList.nextToken ? [...nodes, new LoadMoreResourcesNode(this)] : nodes - } - - public async loadMoreResources(): Promise { - if (!this.resourceList?.nextToken) { - return - } - - await this.resourcesManager.loadMoreResources(this.typeName, this.resourceList.nextToken) - - this.resourceList = this.resourcesManager.get().find((r) => r.typeName === this.typeName) - this.updateNode() - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/resourcesNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/resourcesNode.ts deleted file mode 100644 index 69292f12bb..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/resourcesNode.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { ResourcesManager } from '../../resources/resourcesManager' -import { ResourceTypeNode } from './resourceTypeNode' - -export class ResourcesNode extends AWSTreeNodeBase { - public constructor(private readonly resourcesManager: ResourcesManager) { - super('Resources', TreeItemCollapsibleState.Collapsed) - this.contextValue = 'resourceSection' - } - - public override async getChildren(): Promise { - const selectedTypes = this.resourcesManager.getSelectedResourceTypes() - const loadedResources = this.resourcesManager.get() - - return selectedTypes.map((typeName) => { - const resourceList = loadedResources.find((r) => r.typeName === typeName) - return new ResourceTypeNode(typeName, this.resourcesManager, resourceList) - }) - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/stackChangeSetsNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/stackChangeSetsNode.ts deleted file mode 100644 index 343bf97c88..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/stackChangeSetsNode.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState, ThemeIcon, ThemeColor } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { ChangeSetsManager } from '../../stacks/changeSetsManager' -import { ChangeSetInfo } from '../../stacks/actions/stackActionRequestType' -import { commandKey } from '../../utils' - -class LoadMoreChangeSetsNode extends AWSTreeNodeBase { - public constructor(private readonly parent: StackChangeSetsNode) { - super('[Load More...]', TreeItemCollapsibleState.None) - this.contextValue = 'loadMoreChangeSets' - this.command = { - title: 'Load More', - command: commandKey('api.loadMoreChangeSets'), - arguments: [this.parent], - } - } -} - -class NoChangeSetsNode extends AWSTreeNodeBase { - public constructor() { - super('No change sets found', TreeItemCollapsibleState.None) - this.contextValue = 'noChangeSets' - this.iconPath = new ThemeIcon('info') - } -} - -export class StackChangeSetsNode extends AWSTreeNodeBase { - public constructor( - private readonly stackName: string, - private readonly changeSetsManager: ChangeSetsManager - ) { - super('Change Sets', TreeItemCollapsibleState.Collapsed) - this.contextValue = 'stackChangeSets' - this.iconPath = new ThemeIcon('diff') - this.updateNode() - } - - private updateNode(): void { - const count = this.changeSetsManager.get(this.stackName).length - const hasMore = this.changeSetsManager.hasMore(this.stackName) - this.description = hasMore ? `(${count}+)` : `(${count})` - this.contextValue = hasMore ? 'stackChangeSetsWithMore' : 'stackChangeSets' - } - - public override async getChildren(): Promise { - const changeSets = await this.changeSetsManager.getChangeSets(this.stackName) - this.updateNode() - - if (changeSets.length === 0) { - return [new NoChangeSetsNode()] - } - - const nodes = changeSets.map((changeSet) => new ChangeSetNode(changeSet, this.stackName)) - return this.changeSetsManager.hasMore(this.stackName) ? [...nodes, new LoadMoreChangeSetsNode(this)] : nodes - } - - public async loadMoreChangeSets(): Promise { - await this.changeSetsManager.loadMoreChangeSets(this.stackName) - this.updateNode() - } -} - -export class ChangeSetNode extends AWSTreeNodeBase { - public readonly stackName: string - public readonly changeSetName: string - - public constructor( - public readonly changeSet: ChangeSetInfo, - stackName: string - ) { - super(changeSet.changeSetName, TreeItemCollapsibleState.None) - this.stackName = stackName - this.changeSetName = changeSet.changeSetName - this.contextValue = 'changeSet' - this.tooltip = `${changeSet.changeSetName} [${changeSet.status}]` - this.iconPath = this.getIconForStatus(changeSet.status) - this.stackName = stackName - this.changeSetName = changeSet.changeSetName - } - - private getIconForStatus(status: string): ThemeIcon { - switch (status) { - case 'CREATE_PENDING': - case 'DELETE_PENDING': - return new ThemeIcon('clock') - case 'CREATE_IN_PROGRESS': - case 'DELETE_IN_PROGRESS': - return new ThemeIcon('sync~spin', new ThemeColor('charts.yellow')) - case 'CREATE_COMPLETE': - return new ThemeIcon('check', new ThemeColor('charts.green')) - case 'DELETE_COMPLETE': - return new ThemeIcon('trash') - case 'DELETE_FAILED': - case 'FAILED': - return new ThemeIcon('error', new ThemeColor('charts.red')) - default: - return new ThemeIcon('git-commit') - } - } - - public override async getChildren(): Promise { - return [] - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/stackNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/stackNode.ts deleted file mode 100644 index 531f864b3d..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/stackNode.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState, ThemeIcon, ThemeColor } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { StackSummary } from '@aws-sdk/client-cloudformation' -import { StackChangeSetsNode } from './stackChangeSetsNode' -import { ChangeSetsManager } from '../../stacks/changeSetsManager' - -export class StackNode extends AWSTreeNodeBase { - public constructor( - public readonly stack: StackSummary, - private readonly changeSetsManager: ChangeSetsManager - ) { - super(stack.StackName ?? 'Unknown Stack', TreeItemCollapsibleState.Collapsed) - this.contextValue = 'stack' - this.tooltip = `${stack.StackName} [${stack.StackStatus}]` - this.iconPath = this.getStackIcon(stack.StackStatus) - } - - private getStackIcon(status?: string): ThemeIcon { - if (!status) { - return new ThemeIcon('layers') - } - - if (status.includes('COMPLETE') && !status.includes('ROLLBACK')) { - return new ThemeIcon('check', new ThemeColor('charts.green')) - } else if (status.includes('FAILED') || status.includes('ROLLBACK')) { - return new ThemeIcon('error', new ThemeColor('charts.red')) - } else if (status.includes('PROGRESS')) { - return new ThemeIcon('sync~spin', new ThemeColor('charts.yellow')) - } else { - return new ThemeIcon('layers') - } - } - - public override async getChildren(): Promise { - const stackName = this.stack.StackName ?? '' - - await this.changeSetsManager.getChangeSets(stackName) - - return [new StackChangeSetsNode(stackName, this.changeSetsManager)] - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/nodes/stacksNode.ts b/packages/core/src/awsService/cloudformation/explorer/nodes/stacksNode.ts deleted file mode 100644 index b601efcc12..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/nodes/stacksNode.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState } from 'vscode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { commandKey } from '../../utils' -import { StacksManager } from '../../stacks/stacksManager' -import { StackSummary } from '@aws-sdk/client-cloudformation' -import { ChangeSetsManager } from '../../stacks/changeSetsManager' -import { StackNode } from './stackNode' - -class LoadMoreStacksNode extends AWSTreeNodeBase { - public constructor(private readonly parent: StacksNode) { - super('[Load More...]', TreeItemCollapsibleState.None) - this.contextValue = 'loadMoreStacks' - this.command = { - title: 'Load More', - command: commandKey('api.loadMoreStacks'), - arguments: [this.parent], - } - } -} - -export class StacksNode extends AWSTreeNodeBase { - public constructor( - private readonly stacksManager: StacksManager, - private readonly changeSetsManager: ChangeSetsManager - ) { - super('Stacks', TreeItemCollapsibleState.Collapsed) - this.updateNode() - } - - public override async getChildren(): Promise { - await this.stacksManager.ensureLoaded() - this.updateNode() - const stacks = this.stacksManager.get() - const nodes = stacks.map((stack: StackSummary) => new StackNode(stack, this.changeSetsManager)) - return this.stacksManager.hasMore() ? [...nodes, new LoadMoreStacksNode(this)] : nodes - } - - private updateNode(): void { - if (this.stacksManager.isLoaded()) { - const count = this.stacksManager.get().length - const hasMore = this.stacksManager.hasMore() - this.description = hasMore ? `(${count}+)` : `(${count})` - this.contextValue = hasMore ? 'stackSectionWithMore' : 'stackSection' - } else { - this.description = undefined - this.contextValue = 'stackSection' - } - } - - public async loadMoreStacks(): Promise { - await this.stacksManager.loadMoreStacks() - this.updateNode() - } -} diff --git a/packages/core/src/awsService/cloudformation/explorer/regionManager.ts b/packages/core/src/awsService/cloudformation/explorer/regionManager.ts deleted file mode 100644 index 6dfdaf9291..0000000000 --- a/packages/core/src/awsService/cloudformation/explorer/regionManager.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { RegionProvider } from '../../../shared/regions/regionProvider' -import globals from '../../../shared/extensionGlobals' - -const localize = nls.loadMessageBundle() - -export class CloudFormationRegionManager { - private static readonly storageKey = 'aws.cloudformation.region' - - constructor(private readonly regionProvider: RegionProvider) {} - - public getSelectedRegion(): string { - const cfnRegion = globals.globalState.tryGet(CloudFormationRegionManager.storageKey, String) - - // If no CloudFormation region selected, use credential default region, then AWS explorer region as fallback - if (!cfnRegion) { - const credentialDefaultRegion = globals.awsContext.getCredentialDefaultRegion() - if (credentialDefaultRegion) { - return credentialDefaultRegion - } - - const awsExplorerRegions = globals.globalState.tryGet('region', Object, []) - return awsExplorerRegions.length > 0 ? awsExplorerRegions[0] : 'us-east-1' - } - - return cfnRegion - } - - public async updateSelectedRegion(region: string): Promise { - await globals.globalState.update(CloudFormationRegionManager.storageKey, region) - } - - public async showRegionSelector(): Promise { - const currentRegion = this.getSelectedRegion() - const allRegions = this.regionProvider.getRegions() - - const items: vscode.QuickPickItem[] = allRegions.map((r) => ({ - label: r.name, - detail: r.id, - })) - - const placeholder = localize( - 'cloudformation.showHideRegionPlaceholder', - 'Select region for CloudFormation panel' - ) - - const result = await vscode.window.showQuickPick(items, { - placeHolder: placeholder, - canPickMany: false, - matchOnDetail: true, - }) - - if (!result || !result.detail) { - return false - } - - if (result.detail !== currentRegion) { - await this.updateSelectedRegion(result.detail) - return true - } - - return false - } -} diff --git a/packages/core/src/awsService/cloudformation/extension.ts b/packages/core/src/awsService/cloudformation/extension.ts deleted file mode 100644 index 22016000d8..0000000000 --- a/packages/core/src/awsService/cloudformation/extension.ts +++ /dev/null @@ -1,345 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ExtensionContext, window, languages, commands, Disposable } from 'vscode' -import { - LanguageClient, - LanguageClientOptions, - ServerOptions, - TransportKind, - ErrorHandlerResult, - CloseHandlerResult, -} from 'vscode-languageclient/node' -import { CloseAction, ErrorAction, Message } from 'vscode-languageclient/node' -import { formatMessage, toString } from './utils' -import globals from '../../shared/extensionGlobals' -import { getServiceEnvVarConfig } from '../../shared/vscode/env' -import { DevSettings } from '../../shared/settings' -import { - deployTemplateCommand, - rerunValidateAndDeployCommand, - importResourceStateCommand, - cloneResourceStateCommand, - addResourceTypesCommand, - removeResourceTypeCommand, - refreshAllResourcesCommand, - refreshResourceListCommand, - copyResourceIdentifierCommand, - focusDiffCommand, - getStackManagementInfoCommand, - extractToParameterPositionCursorCommand, - loadMoreResourcesCommand, - loadMoreStacksCommand, - searchResourceCommand, - executeChangeSetCommand, - addRelatedResourcesCommand, - refreshChangeSetsCommand, - loadMoreChangeSetsCommand, - viewStackCommand, - createProjectCommand, - addEnvironmentCommand, - removeEnvironmentCommand, - deleteChangeSetCommand, - viewChangeSetCommand, - deployTemplateFromStacksMenuCommand, - selectEnvironmentCommand, -} from './commands/cfnCommands' -import { openStackTemplateCommand } from './commands/openStackTemplate' -import { selectRegionCommand } from './commands/regionCommands' -import { AwsCredentialsService, encryptionKey } from './auth/credentials' -import { ExtensionId, ExtensionName, Version, CloudFormationTelemetrySettings } from './extensionConfig' -import { commandKey } from './utils' -import { CloudFormationExplorer } from './explorer/explorer' -import { handleTelemetryOptIn } from './telemetryOptIn' - -import { refreshCommand, StacksManager } from './stacks/stacksManager' -import { StackOverviewWebviewProvider } from './ui/stackOverviewWebviewProvider' -import { StackEventsWebviewProvider } from './ui/stackEventsWebviewProvider' -import { StackOutputsWebviewProvider } from './ui/stackOutputsWebviewProvider' -import { DiffWebviewProvider } from './ui/diffWebviewProvider' -import { StackResourcesWebviewProvider } from './ui/stackResourcesWebviewProvider' -import { StackViewCoordinator } from './ui/stackViewCoordinator' -import { DocumentManager } from './documents/documentManager' - -import { ResourcesManager } from './resources/resourcesManager' -import { ResourceSelector } from './ui/resourceSelector' -import { RelatedResourcesManager } from './relatedResources/relatedResourcesManager' -import { RelatedResourceSelector } from './ui/relatedResourceSelector' - -import { StackActionCodeLensProvider } from './codelens/stackActionCodeLensProvider' -import { registerStatusBarCommand } from './ui/statusBar' -import { getClientId } from '../../shared/telemetry/util' -import { SettingsLspServerProvider } from './lsp-server/settingsLspServerProvider' -import { DevLspServerProvider } from './lsp-server/devLspServerProvider' -import { RemoteLspServerProvider } from './lsp-server/remoteLspServerProvider' -import { LspServerProvider } from './lsp-server/lspServerProvider' -import { getLogger } from '../../shared/logger/logger' -import { ChangeSetsManager } from './stacks/changeSetsManager' -import { CfnEnvironmentManager } from './cfn-init/cfnEnvironmentManager' -import { CfnEnvironmentSelector } from './ui/cfnEnvironmentSelector' -import { CfnInitUiInterface } from './cfn-init/cfnInitUiInterface' -import { CfnInitCliCaller } from './cfn-init/cfnInitCliCaller' -import { CfnEnvironmentFileSelector } from './ui/cfnEnvironmentFileSelector' -import { fs } from '../../shared/fs/fs' -import { ToolkitError } from '../../shared/errors' - -let client: LanguageClient -let clientDisposables: Disposable[] = [] - -async function startClient(context: ExtensionContext) { - const cfnTelemetrySettings = new CloudFormationTelemetrySettings() - const telemetryEnabled = await handleTelemetryOptIn(context, cfnTelemetrySettings) - - const cfnLspConfig = { - ...DevSettings.instance.getServiceConfig('cloudformationLsp', {}), - ...getServiceEnvVarConfig('cloudformationLsp', ['path', 'cloudformationEndpoint']), - } - - const serverProvider = new LspServerProvider([ - new DevLspServerProvider(context), - new SettingsLspServerProvider(cfnLspConfig), - new RemoteLspServerProvider(), - ]) - const serverFile = await serverProvider.serverExecutable() - if (!(await fs.existsFile(serverFile))) { - throw new Error(`CloudFormation LSP ${serverFile} not found`) - } - getLogger('awsCfnLsp').info(`Found CloudFormation LSP executable: ${serverFile}`) - const serverRootDir = await serverProvider.serverRootDir() - - const envOptions = { - NODE_OPTIONS: '--enable-source-maps', - } - - const serverOptions: ServerOptions = { - run: { - module: serverFile, - transport: TransportKind.ipc, - options: { - env: envOptions, - }, - }, - debug: { - module: serverFile, - transport: TransportKind.ipc, - options: { - execArgv: ['--no-lazy'], - env: envOptions, - }, - }, - } - - const clientOptions: LanguageClientOptions = { - documentSelector: [ - { scheme: 'file', language: 'plaintext' }, - { scheme: 'file', language: 'cloudformation' }, - { scheme: 'file', language: 'template' }, - { scheme: 'file', language: 'json' }, - { scheme: 'file', language: 'yaml' }, - { scheme: 'file', pattern: '**/*.txt' }, - { scheme: 'file', pattern: '**/*.template' }, - { scheme: 'file', pattern: '**/*.cfn' }, - { scheme: 'file', pattern: '**/*.json' }, - { scheme: 'file', pattern: '**/*.yaml' }, - ], - initializationOptions: { - handledSchemaProtocols: ['file'], - aws: { - clientInfo: { - extension: { - name: ExtensionId, - version: Version, - }, - clientId: getClientId(globals.globalState, telemetryEnabled), - }, - telemetryEnabled: telemetryEnabled, - ...(cfnLspConfig.cloudformationEndpoint && { - cloudformation: { - endpoint: cfnLspConfig.cloudformationEndpoint, - }, - }), - encryption: { - key: encryptionKey.toString('base64'), - mode: 'JWT', - }, - }, - }, - errorHandler: { - error: (error: Error, message: Message | undefined, count: number | undefined): ErrorHandlerResult => { - void window.showErrorMessage(formatMessage(`${toString(message)} - ${toString(error)}`)) - return { action: ErrorAction.Continue } - }, - closed: (): CloseHandlerResult => { - return { action: CloseAction.DoNotRestart } - }, - }, - } - - client = new LanguageClient(ExtensionId, ExtensionName, serverOptions, clientOptions) - - const stacksManager = new StacksManager(client) - - await client.start() - - const documentManager = new DocumentManager(client) - const resourceSelector = new ResourceSelector(client) - const resourcesManager = new ResourcesManager(client, resourceSelector) - const relatedResourceSelector = new RelatedResourceSelector(client) - const relatedResourcesManager = new RelatedResourcesManager( - client, - relatedResourceSelector, - resourceSelector, - resourcesManager - ) - const changeSetManager = new ChangeSetsManager(client) - const environmentSelector = new CfnEnvironmentSelector() - const environmentFileSelector = new CfnEnvironmentFileSelector() - const environmentManager = new CfnEnvironmentManager(client, environmentSelector, environmentFileSelector) - - const cfnInitCliCaller = new CfnInitCliCaller(serverRootDir) - const cfnInitUiInterface = new CfnInitUiInterface(cfnInitCliCaller) - - const cfnExplorer = new CloudFormationExplorer( - stacksManager, - resourcesManager, - changeSetManager, - documentManager, - globals.regionProvider, - environmentManager - ) - - resourceSelector.setRefreshCallback(() => cfnExplorer.refresh()) - - resourcesManager.addListener(() => { - cfnExplorer.refresh() - }) - stacksManager.addListener(() => { - cfnExplorer.refresh() - }) - documentManager.addListener(() => { - cfnExplorer.refresh() - }) - environmentManager.addListener(() => { - cfnExplorer.refresh() - }) - - const credentialsService = new AwsCredentialsService(stacksManager, resourcesManager, cfnExplorer.regionManager) - cfnExplorer.setCredentialsService(credentialsService) - - const stackViewCoordinator = new StackViewCoordinator() - stackViewCoordinator.setStackStatusUpdateCallback((stackName, stackStatus) => { - stacksManager.updateStackStatus(stackName, stackStatus) - cfnExplorer.refresh() - }) - - const diffProvider = new DiffWebviewProvider(stackViewCoordinator) - const resourcesProvider = new StackResourcesWebviewProvider(client, stackViewCoordinator) - const overviewProvider = new StackOverviewWebviewProvider(client, stackViewCoordinator) - const eventsProvider = new StackEventsWebviewProvider(client, stackViewCoordinator) - const outputsProvider = new StackOutputsWebviewProvider(client, stackViewCoordinator) - - const documentSelector = [ - { scheme: 'file', language: 'cloudformation' }, - { scheme: 'file', language: 'yaml' }, - { scheme: 'file', language: 'json' }, - ] - - const codeLensProvider = languages.registerCodeLensProvider( - documentSelector, - new StackActionCodeLensProvider(client) - ) - - clientDisposables = [ - codeLensProvider, - stacksManager, - window.createTreeView('aws.cloudformation', { - treeDataProvider: cfnExplorer, - showCollapseAll: true, - canSelectMany: true, - }), - loadMoreResourcesCommand(cfnExplorer), - loadMoreStacksCommand(cfnExplorer), - searchResourceCommand(cfnExplorer, resourcesManager), - refreshChangeSetsCommand(cfnExplorer), - loadMoreChangeSetsCommand(cfnExplorer), - viewStackCommand(stackViewCoordinator, overviewProvider, outputsProvider, resourcesProvider), - addResourceTypesCommand(resourcesManager), - removeResourceTypeCommand(resourcesManager), - refreshAllResourcesCommand(resourcesManager), - refreshResourceListCommand(resourcesManager, cfnExplorer), - copyResourceIdentifierCommand(), - importResourceStateCommand(resourcesManager), - cloneResourceStateCommand(resourcesManager), - getStackManagementInfoCommand(resourcesManager), - window.registerWebviewViewProvider(commandKey('stack.overview'), overviewProvider), - window.registerWebviewViewProvider(commandKey('diff'), diffProvider), - window.registerWebviewViewProvider(commandKey('stack.events'), eventsProvider), - window.registerWebviewViewProvider(commandKey('stack.resources'), resourcesProvider), - window.registerWebviewViewProvider(commandKey('stack.outputs'), outputsProvider), - focusDiffCommand(), - deployTemplateCommand(client, diffProvider, documentManager, environmentManager), - deployTemplateFromStacksMenuCommand(), - executeChangeSetCommand(client, stackViewCoordinator), - deleteChangeSetCommand(client), - viewChangeSetCommand(client, diffProvider), - refreshCommand(stacksManager), - openStackTemplateCommand(client), - selectRegionCommand(cfnExplorer), - selectEnvironmentCommand(cfnExplorer), - rerunValidateAndDeployCommand(), - extractToParameterPositionCursorCommand(client), - createProjectCommand(cfnInitUiInterface), - addEnvironmentCommand(cfnInitUiInterface, cfnInitCliCaller, environmentManager), - removeEnvironmentCommand(cfnInitCliCaller, environmentManager), - addRelatedResourcesCommand(relatedResourcesManager), - credentialsService, - serverProvider, - { dispose: () => client?.stop() }, - ] - - registerStatusBarCommand() - - context.subscriptions.push(...clientDisposables) - await credentialsService.initialize(client) -} - -async function restartClient(context: ExtensionContext) { - // Dispose all client-related resources - for (const disposable of clientDisposables) { - disposable.dispose() - } - clientDisposables = [] - - // Start new client - await startClient(context) -} - -export async function activate(context: ExtensionContext) { - context.subscriptions.push( - commands.registerCommand(commandKey('server.restartServer'), async () => { - try { - await restartClient(context) - } catch (error) { - void window.showErrorMessage( - formatMessage(`Failed to restart CloudFormation language server: ${toString(error)}`) - ) - } - }) - ) - - try { - await startClient(context) - } catch (err) { - getLogger('awsCfnLsp').error(ToolkitError.chain(err, 'CloudFormation language server failed to start')) - } -} - -export function deactivate(): Thenable | undefined { - if (!client) { - return undefined - } - - return client.stop() -} diff --git a/packages/core/src/awsService/cloudformation/extensionConfig.ts b/packages/core/src/awsService/cloudformation/extensionConfig.ts deleted file mode 100644 index c3577d9c19..0000000000 --- a/packages/core/src/awsService/cloudformation/extensionConfig.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fromExtensionManifest } from '../../shared/settings' - -export const ExtensionId = 'amazonwebservices.cloudformation' -export const ExtensionName = 'AWS CloudFormation' -export const Version = '1.0.0' -export const ExtensionConfigKey = 'aws.cloudformation' - -export class CloudFormationTelemetrySettings extends fromExtensionManifest(`${ExtensionConfigKey}.telemetry`, { - enabled: Boolean, -}) {} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/CLibCheck.ts b/packages/core/src/awsService/cloudformation/lsp-server/CLibCheck.ts deleted file mode 100644 index 5a21ca9b79..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/CLibCheck.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { execSync } from 'child_process' // eslint-disable-line no-restricted-imports, aws-toolkits/no-string-exec-for-child-process -import * as fs from 'fs' // eslint-disable-line no-restricted-imports -import * as semver from 'semver' -import { getLogger } from '../../../shared/logger/logger' - -interface VersionResult { - maxFound: string | undefined - allAvailable: string[] -} - -export class CLibCheck { - /** - * Checks the GNU C Library (glibc) version. - * Uses `ldd --version` to parse the version number. - */ - public static getGLibCVersion(): string | undefined { - try { - const output = execSync('ldd --version', { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'ignore'], - timeout: 5000, - }) - // Output usually looks like: "ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35" - // We look for the first version number pattern on the first line. - const firstLine = output.split('\n')[0] - const match = firstLine.match(/(\d+\.\d+)/) - return match ? semver.coerce(match[0])?.version || match[0] : undefined - } catch (error) { - getLogger('awsCfnLsp').warn('Could not run ldd. Is this a glibc-based distro?') - return undefined - } - } - - /** - * Checks available GLIBCXX versions in libstdc++. - * 1. Finds libstdc++.so.6 location. - * 2. Scans the binary for "GLIBCXX_*" strings. - * 3. Sorts them to find the maximum version supported. - */ - public static getGLibCXXVersions(): VersionResult { - const libPath = this.findLibStdCpp() - - if (!libPath) { - return { maxFound: undefined, allAvailable: [] } - } - - try { - // Method 1: Try using the `strings` command (fastest, but requires binutils) - const output = execSync(`strings "${libPath}" | grep GLIBCXX`, { - encoding: 'utf8', - stdio: ['ignore', 'pipe', 'ignore'], - timeout: 10000, - }) - return this.parseGLibCXXOutput(output) - } catch (e) { - // Method 2: Fallback to Node.js FS reading (works in minimal containers w/o strings) - try { - const content = fs.readFileSync(libPath, 'binary') // Read as binary string - // Regex to find all GLIBCXX_x.x.x occurrences - const matches = content.match(/GLIBCXX_\d+\.\d+(\.\d+)?/g) - if (matches) { - return this.parseGLibCXXOutput(matches.join('\n')) - } - } catch (readError) { - getLogger('awsCfnLsp').error(`Failed to read library at ${libPath}`) - } - } - - return { maxFound: undefined, allAvailable: [] } - } - - private static parseGLibCXXOutput(rawOutput: string): VersionResult { - const rawVersions = rawOutput - .trim() - .split('\n') - .map((line) => line.trim()) - // 1. Strict Filter: Must be GLIBCXX_ followed immediately by a digit - .filter((line) => /^GLIBCXX_\d/.test(line)) - // 2. Extraction: Capture strictly the numeric part - .map((line) => { - const match = line.match(/^GLIBCXX_(\d+\.\d+(?:\.\d+)?)/) - return match ? match[1] : undefined - }) - .filter((v): v is string => v !== undefined) - - // 3. Deduplicate - const uniqueVersions = [...new Set(rawVersions)] - - // 4. Sort using Semver - // We use coerce() because "3.4" is not valid strict semver, but "3.4.0" is. - const sorted = uniqueVersions.sort((a, b) => { - const verA = semver.coerce(a) - const verB = semver.coerce(b) - // Handle unlikely case where coerce fails (returns null) by pushing it to the bottom - if (!verA || !verB) { - return 0 - } - return semver.compare(verA, verB) - }) - - return { - maxFound: sorted.length > 0 ? sorted[sorted.length - 1] : undefined, - allAvailable: sorted, - } - } - - private static findLibStdCpp(): string | undefined { - // 1. Try ldconfig cache (most reliable on standard linux) - try { - const ldconfig = execSync('/sbin/ldconfig -p | grep libstdc++.so.6', { encoding: 'utf8', timeout: 5000 }) - // Output: "libstdc++.so.6 (libc6,x86-64) => /lib/x86_64-linux-gnu/libstdc++.so.6" - const match = ldconfig.match(/=>\s+(.+)$/m) - if (match && match[1]) { - return match[1].trim() - } - } catch (e) { - /* ignore */ - } - - // 2. Search common paths (fallback for containers/weird setups) - const commonPaths = [ - '/usr/lib/x86_64-linux-gnu/libstdc++.so.6', - '/usr/lib64/libstdc++.so.6', - '/usr/lib/libstdc++.so.6', - '/lib/x86_64-linux-gnu/libstdc++.so.6', - ] - - for (const p of commonPaths) { - if (fs.existsSync(p)) { - return p - } - } - - return undefined - } -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/devLspServerProvider.ts b/packages/core/src/awsService/cloudformation/lsp-server/devLspServerProvider.ts deleted file mode 100644 index 604f4ac132..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/devLspServerProvider.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { dirname, join } from 'path' -import { ExtensionContext } from 'vscode' -import { LspServerProviderI } from './lspServerProvider' -import { CfnLspServerFile } from './lspServerConfig' -import { existsSync, readdirSync } from 'fs' // eslint-disable-line no-restricted-imports -import { isDebugInstance } from '../../../shared/vscode/env' -import { getLogger } from '../../../shared/logger/logger' - -export class DevLspServerProvider implements LspServerProviderI { - private readonly devServerLocation?: string - - constructor(context: ExtensionContext) { - this.devServerLocation = findServerInDevelopment(context.extensionPath) - } - - name(): string { - return 'DevLspServerProvider' - } - - canProvide(): boolean { - return isDebugInstance() && this.devServerLocation !== undefined - } - - async serverExecutable(): Promise { - return Promise.resolve(this.devServerLocation!) - } - - async serverRootDir(): Promise { - return Promise.resolve(dirname(this.devServerLocation!)) - } -} - -function findServerInDevelopment(path: string): string | undefined { - const parentDir = dirname(dirname(dirname(path))) - const possibleLocations = [] - - // Get all directories in parent directory - const siblingDirs = readdirSync(parentDir, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name) - - // Check each sibling directory for bundle/development structure - for (const siblingDir of siblingDirs) { - const serverPath = join(parentDir, siblingDir, 'bundle', 'development', CfnLspServerFile) - if (existsSync(serverPath)) { - possibleLocations.push(serverPath) - } - } - - const validLocations = possibleLocations.filter((path) => { - return existsSync(path) - }) - - if (validLocations.length < 1) { - return undefined - } - - if (validLocations.length === 1) { - getLogger().debug(`Found CloudFormation LSP dev server ${possibleLocations[0]}`) - return possibleLocations[0] - } - - throw Error( - `Found ${validLocations.length} locations with server executable file: ${JSON.stringify(possibleLocations)}` - ) -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/githubManifestAdapter.ts b/packages/core/src/awsService/cloudformation/lsp-server/githubManifestAdapter.ts deleted file mode 100644 index 88dfeb441c..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/githubManifestAdapter.ts +++ /dev/null @@ -1,201 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CfnLspName, CfnLspServerEnvType } from './lspServerConfig' -import { - addWindows, - CfnManifest, - CfnTarget, - CfnLspVersion, - dedupeAndGetLatestVersions, - extractPlatformAndArch, - useOldLinuxVersion, - mapLegacyLinux, -} from './utils' -import { getLogger } from '../../../shared/logger/logger' -import { ToolkitError } from '../../../shared/errors' - -export class GitHubManifestAdapter { - constructor( - private readonly repoOwner: string, - private readonly repoName: string, - readonly environment: CfnLspServerEnvType - ) {} - - async getManifest(): Promise { - let manifest: CfnManifest - try { - manifest = await this.getManifestJson() - } catch (err) { - getLogger('awsCfnLsp').error(ToolkitError.chain(err, 'Failed to get CloudFormation manifest')) - manifest = await this.getFromReleases() - } - - getLogger('awsCfnLsp').info( - 'Candidate versions: %s', - manifest.versions - .map( - (v) => - `${v.serverVersion}[${v.targets - .sort() - .map((t) => `${t.platform}-${t.arch}-${t.nodejs}`) - .join(',')}]` - ) - .join(', ') - ) - - if (process.platform !== 'linux') { - return manifest - } - - const useFallbackLinux = useOldLinuxVersion() - if (!useFallbackLinux) { - return manifest - } - - getLogger('awsCfnLsp').info('In a legacy or sandbox Linux environment') - manifest.versions = mapLegacyLinux(manifest.versions) - - getLogger('awsCfnLsp').info( - 'Remapped candidate versions: %s', - manifest.versions - .map( - (v) => - `${v.serverVersion}[${v.targets - .sort() - .map((t) => `${t.platform}-${t.arch}-${t.nodejs}`) - .join(',')}]` - ) - .join(', ') - ) - return manifest - } - - private async getFromReleases(): Promise { - const releases = await this.fetchGitHubReleases() - const envReleases = this.filterByEnvironment(releases) - const sortedReleases = envReleases.sort((a, b) => { - return b.tag_name.localeCompare(a.tag_name) - }) - const versions = dedupeAndGetLatestVersions(sortedReleases.map((release) => this.convertRelease(release))) - getLogger('awsCfnLsp').info( - 'Candidate versions: %s', - versions - .map((v) => `${v.serverVersion}[${v.targets.map((t) => `${t.platform}-${t.arch}`).join(',')}]`) - .join(', ') - ) - return { - manifestSchemaVersion: '1.0', - artifactId: CfnLspName, - artifactDescription: 'GitHub CloudFormation Language Server', - isManifestDeprecated: false, - versions: versions, - } - } - - private filterByEnvironment(releases: GitHubRelease[]): GitHubRelease[] { - return releases.filter((release) => { - const tag = release.tag_name - if (this.environment === 'alpha') { - return release.prerelease && tag.endsWith('-alpha') - } else if (this.environment === 'beta') { - return release.prerelease && tag.endsWith('-beta') - } else { - return !release.prerelease - } - }) - } - - private async fetchGitHubReleases(): Promise { - const response = await fetch(`https://api.github.com/repos/${this.repoOwner}/${this.repoName}/releases`) - if (!response.ok) { - throw new Error(`GitHub API error: ${response.status}`) - } - return response.json() - } - - private convertRelease(release: GitHubRelease): CfnLspVersion { - return { - serverVersion: release.tag_name, - isDelisted: false, - targets: addWindows(this.extractTargets(release.assets)), - } - } - - private extractTargets(assets: GitHubAsset[]): CfnTarget[] { - return assets.map((asset) => { - const { arch, platform, nodejs } = extractPlatformAndArch(asset.name) - - return { - platform, - arch, - nodejs, - contents: [ - { - filename: asset.name, - url: asset.browser_download_url, - hashes: [], - bytes: asset.size, - }, - ], - } - }) - } - - private async getManifestJson(): Promise { - const response = await fetch( - `https://raw.githubusercontent.com/${this.repoOwner}/${this.repoName}/refs/heads/main/assets/release-manifest.json` - ) - if (!response.ok) { - throw new Error(`GitHub API error: ${response.status}`) - } - - const json = (await response.json()) as Record - - return { - manifestSchemaVersion: json.manifestSchemaVersion as string, - artifactId: json.artifactId as string, - artifactDescription: json.artifactDescription as string, - isManifestDeprecated: json.isManifestDeprecated as boolean, - versions: json[this.environment] as CfnLspVersion[], - } - } -} - -/* eslint-disable @typescript-eslint/naming-convention */ -interface GitHubAsset { - url: string - browser_download_url: string - id: number - node_id: string - name: string - label: string | null - state: string - content_type: string - size: number - download_count: number - created_at: string - updated_at: string -} - -interface GitHubRelease { - url: string - html_url: string - assets_url: string - upload_url: string - tarball_url: string | null - zipball_url: string | null - id: number - node_id: string - tag_name: string - target_commitish: string - name: string | null - body: string | null - draft: boolean - prerelease: boolean - created_at: string // ISO 8601 date string - published_at: string | null // ISO 8601 date string - assets: GitHubAsset[] -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/lspInstaller.ts b/packages/core/src/awsService/cloudformation/lsp-server/lspInstaller.ts deleted file mode 100644 index fa54beba44..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/lspInstaller.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BaseLspInstaller } from '../../../shared/lsp/baseLspInstaller' -import { GitHubManifestAdapter } from './githubManifestAdapter' -import { fs } from '../../../shared/fs/fs' -import { CfnLspName, CfnLspServerEnvType, CfnLspServerFile } from './lspServerConfig' -import { isAutomation, isBeta, isDebugInstance } from '../../../shared/vscode/env' -import { dirname, join } from 'path' -import { getLogger } from '../../../shared/logger/logger' -import { ResourcePaths } from '../../../shared/lsp/types' -import * as nodeFs from 'fs' // eslint-disable-line no-restricted-imports -import globals from '../../../shared/extensionGlobals' -import { toString } from '../utils' - -function determineEnvironment(): CfnLspServerEnvType { - if (isDebugInstance()) { - return 'alpha' - } else if (isBeta() || isAutomation()) { - return 'beta' - } - return 'prod' -} - -export class CfnLspInstaller extends BaseLspInstaller { - private readonly githubManifest = new GitHubManifestAdapter( - 'aws-cloudformation', - 'cloudformation-languageserver', - determineEnvironment() - ) - - constructor() { - super( - { - manifestUrl: 'github', - supportedVersions: '<2.0.0', - id: CfnLspName, - suppressPromptPrefix: 'cfnLsp', - }, - 'awsCfnLsp', - { - resolve: async () => { - const log = getLogger('awsCfnLsp') - const cfnManifestStorageKey = 'aws.cloudformation.lsp.manifest' - - try { - const manifest = await this.githubManifest.getManifest() - log.info( - `Creating CloudFormation LSP manifest for ${this.githubManifest.environment}`, - manifest.versions.map((v) => v.serverVersion) - ) - - // Cache in CloudFormation-specific global state storage - globals.globalState.tryUpdate(cfnManifestStorageKey, { - content: JSON.stringify(manifest), - }) - - return manifest - } catch (error) { - log.warn(`GitHub fetch failed, trying cached manifest: ${error}`) - - // Try cached manifest from CloudFormation-specific storage - const manifestData = globals.globalState.tryGet(cfnManifestStorageKey, Object, {}) - - if (manifestData?.content) { - log.debug('Using cached manifest for offline mode') - return JSON.parse(manifestData.content) - } - - log.error('No cached manifest found') - throw error - } - }, - } as any, - 'sha256' - ) - } - - protected async postInstall(assetDirectory: string): Promise { - const resourcePaths = this.resourcePaths(assetDirectory) - const rootDir = dirname(resourcePaths.lsp) - await fs.chmod(join(rootDir, 'bin', process.platform === 'win32' ? 'cfn-init.exe' : 'cfn-init'), 0o755) - } - - protected resourcePaths(assetDirectory?: string): ResourcePaths { - if (!assetDirectory) { - return { - lsp: this.config.path ?? CfnLspServerFile, - node: process.execPath, - } - } - - // Find the single extracted directory - const entries = nodeFs.readdirSync(assetDirectory, { withFileTypes: true }) - const folders = entries.filter((entry) => entry.isDirectory()) - - if (folders.length !== 1) { - throw new Error(`${folders.length} CloudFormation LSP folders found ${toString(folders)}`) - } - - return { - lsp: join(assetDirectory, folders[0].name, CfnLspServerFile), - node: process.execPath, - } - } -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/lspServerConfig.ts b/packages/core/src/awsService/cloudformation/lsp-server/lspServerConfig.ts deleted file mode 100644 index afc8cfd7ed..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/lspServerConfig.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const CfnLspName = 'cloudformation-languageserver' -export const CfnLspServerFile = 'cfn-lsp-server-standalone.js' -export const CfnLspServerStorageName = '.aws-cfn-storage' -export const RequiredFiles = [ - 'node_modules', - 'cfn-lsp-server-standalone.js', - 'package.json', - 'pyodide-worker.js', - 'assets', -] - -export type CfnLspServerEnvType = 'alpha' | 'beta' | 'prod' diff --git a/packages/core/src/awsService/cloudformation/lsp-server/lspServerProvider.ts b/packages/core/src/awsService/cloudformation/lsp-server/lspServerProvider.ts deleted file mode 100644 index 3ecd11208a..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/lspServerProvider.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Disposable } from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { ToolkitError } from '../../../shared/errors' - -export interface LspServerResolverI { - serverExecutable(): Promise - serverRootDir(): Promise -} - -export interface LspServerProviderI extends LspServerResolverI { - canProvide(): boolean - name(): string -} - -export class LspServerProvider implements LspServerResolverI, Disposable { - private readonly matchedProviders: LspServerProviderI[] - private _serverExecutable?: string - private _serverRootDir?: string - - constructor(providers: LspServerProviderI[]) { - const matches = providers.filter((provider) => provider.canProvide()) - - if (matches.length < 1) { - throw new Error(`Matched with 0 CloudFormation LSP providers`) - } - - this.matchedProviders = matches - getLogger('awsCfnLsp').info( - `Found CloudFormation LSP provider: ${this.matchedProviders.map((provider) => provider.name())}` - ) - } - - async serverExecutable(): Promise { - await this.evaluateProviders() - return this._serverExecutable! - } - - async serverRootDir(): Promise { - await this.evaluateProviders() - return this._serverRootDir! - } - - private async evaluateProviders() { - if (this._serverExecutable && this._serverRootDir) { - return - } - - for (const provider of this.matchedProviders) { - try { - const executable = await provider.serverExecutable() - const dir = await provider.serverRootDir() - - this._serverExecutable = executable - this._serverRootDir = dir - return - } catch (err) { - getLogger('awsCfnLsp').error( - ToolkitError.chain(err, `Failed to resolve CloudFormation LSP provider ${provider.name()}`) - ) - } - } - } - - dispose() {} -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/remoteLspServerProvider.ts b/packages/core/src/awsService/cloudformation/lsp-server/remoteLspServerProvider.ts deleted file mode 100644 index 19ef80724d..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/remoteLspServerProvider.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { dirname } from 'path' -import { LspServerProviderI } from './lspServerProvider' -import { CfnLspInstaller } from './lspInstaller' - -export class RemoteLspServerProvider implements LspServerProviderI { - private installer = new CfnLspInstaller() - private serverPath?: string - - name(): string { - return 'RemoteLspServerProvider' - } - - canProvide(): boolean { - return true - } - - async serverExecutable(): Promise { - if (this.serverPath) { - return this.serverPath - } - - const result = await this.installer.resolve() - this.serverPath = result.resourcePaths.lsp - return this.serverPath - } - - async serverRootDir(): Promise { - return dirname(await this.serverExecutable()) - } -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/settingsLspServerProvider.ts b/packages/core/src/awsService/cloudformation/lsp-server/settingsLspServerProvider.ts deleted file mode 100644 index 8d08bb9bcc..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/settingsLspServerProvider.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { dirname, join } from 'path' -import { LspServerProviderI } from './lspServerProvider' -import { CfnLspServerFile } from './lspServerConfig' - -export class SettingsLspServerProvider implements LspServerProviderI { - private readonly path?: string - - constructor(config?: { path?: string }) { - this.path = config?.path - } - - name(): string { - return 'SettingsLspServerProvider' - } - - canProvide(): boolean { - return this.path !== undefined - } - - async serverExecutable(): Promise { - const serverFile = join(this.path!, CfnLspServerFile) - return Promise.resolve(serverFile) - } - - async serverRootDir(): Promise { - return Promise.resolve(dirname(await this.serverExecutable())) - } -} diff --git a/packages/core/src/awsService/cloudformation/lsp-server/utils.ts b/packages/core/src/awsService/cloudformation/lsp-server/utils.ts deleted file mode 100644 index 9775fb0036..0000000000 --- a/packages/core/src/awsService/cloudformation/lsp-server/utils.ts +++ /dev/null @@ -1,175 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LspVersion, Target, Manifest } from '../../../shared/lsp/types' -import * as semver from 'semver' -import { CLibCheck } from './CLibCheck' -import { toString } from '../utils' -import { getLogger } from '../../../shared/logger/logger' - -export interface CfnTarget extends Target { - nodejs?: string -} -export interface CfnLspVersion extends LspVersion { - targets: CfnTarget[] -} -export interface CfnManifest extends Manifest { - versions: CfnLspVersion[] -} - -export function addWindows(targets: CfnTarget[]): CfnTarget[] { - const win32Targets = targets.filter((target) => { - return target.platform === 'win32' - }) - - const windowsTargets = targets.filter((target) => { - return target.platform === 'windows' - }) - - if (win32Targets.length < 1 || windowsTargets.length > 0) { - return targets - } - - return [ - ...targets, - ...win32Targets.map((target) => { - return { - ...target, - platform: 'windows', - } - }), - ] -} - -export function dedupeAndGetLatestVersions(versions: CfnLspVersion[]): CfnLspVersion[] { - const grouped: Record = {} - - // Group by normalized version - for (const version of versions) { - const normalizedV = getMajorMinorPatchVersion(version.serverVersion) - if (!grouped[normalizedV]) { - grouped[normalizedV] = [] - } - grouped[normalizedV].push(version) - } - - const groupedAndSorted: Record = Object.fromEntries( - Object.entries(grouped).sort(([v1], [v2]) => { - return compareVersionsDesc(v1, v2) - }) - ) - - // Sort each group by version descending and pick the first (latest) - return Object.values(groupedAndSorted).map((group) => { - group.sort((a, b) => compareVersionsDesc(a.serverVersion, b.serverVersion)) - const latest = group[0] - latest.serverVersion = `${latest.serverVersion.replace('v', '')}` - - return latest // take the highest version - }) -} - -function compareVersionsDesc(v1: string, v2: string) { - const a = convertVersionToNumbers(v1) - const b = convertVersionToNumbers(v2) - - for (let i = 0; i < Math.max(a.length, b.length); i++) { - const partA = a[i] || 0 - const partB = b[i] || 0 - - if (partA > partB) { - return -1 - } - if (partA < partB) { - return 1 - } - } - return 0 -} - -function removeWordsFromVersion(version: string): string { - return version.replaceAll('-beta', '').replaceAll('-alpha', '').replaceAll('-prod', '').replaceAll('v', '') -} - -function convertVersionToNumbers(version: string): number[] { - return removeWordsFromVersion(version).replaceAll('-', '.').split('.').map(Number) -} - -function getMajorMinorPatchVersion(version: string): string { - return removeWordsFromVersion(version).split('-')[0] -} - -export function extractPlatformAndArch(filename: string): { platform: string; arch: string; nodejs?: string } { - const match = filename.match(/^cloudformation-languageserver-(.*)-(.*)-(x64|arm64)(?:-node(\d+))?\.zip$/) - if (match === null) { - throw new Error(`Could not extract platform from ${filename}`) - } - - const platform = match[2] - const arch = match[3] - const nodejs = match[4] - - if (!platform || !arch) { - throw new Error(`Unknown arch and platform ${arch} ${platform}`) - } - - return { arch, platform, nodejs } -} - -export function useOldLinuxVersion(): boolean { - if (process.platform !== 'linux') { - return false - } - - if (process.env.SNAP !== undefined) { - return true - } - - const glibcxx = CLibCheck.getGLibCXXVersions() - const maxAvailGLibCXX = glibcxx.maxFound - if (!maxAvailGLibCXX) { - return false - } - - getLogger('awsCfnLsp').info(`Found GLIBCXX ${toString(glibcxx)}`) - return semver.lt(maxAvailGLibCXX, '3.4.29') -} - -const LegacyLinuxGLibPlatform = 'linuxglib2.28' - -export function mapLegacyLinux(versions: CfnLspVersion[]): CfnLspVersion[] { - const remappedVersions: CfnLspVersion[] = [] - - for (const version of versions) { - const hasLegacyLinux = version.targets.some((t) => t.platform === LegacyLinuxGLibPlatform) - - if (!hasLegacyLinux) { - getLogger('awsCfnLsp').warn(`Found no compatible legacy linux builds for ${version.serverVersion}`) - remappedVersions.push(version) - } else { - const newTargets = version.targets - .filter((target) => { - return target.platform !== 'linux' - }) - .map((target) => { - if (target.platform !== LegacyLinuxGLibPlatform) { - return target - } - - return { - ...target, - platform: 'linux', - } - }) - - remappedVersions.push({ - ...version, - targets: newTargets, - }) - } - } - - return remappedVersions -} diff --git a/packages/core/src/awsService/cloudformation/lspTypes.ts b/packages/core/src/awsService/cloudformation/lspTypes.ts deleted file mode 100644 index 602587203c..0000000000 --- a/packages/core/src/awsService/cloudformation/lspTypes.ts +++ /dev/null @@ -1,8 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export type Identifiable = { - id: string -} diff --git a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesApi.ts b/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesApi.ts deleted file mode 100644 index 95592e0a9c..0000000000 --- a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesApi.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LanguageClient } from 'vscode-languageclient/node' -import { - GetAuthoredResourceTypesRequest, - GetRelatedResourceTypesParams, - GetRelatedResourceTypesRequest, - InsertRelatedResourcesParams, - InsertRelatedResourcesRequest, - RelatedResourcesCodeAction, - TemplateUri, -} from './relatedResourcesProtocol' - -export async function getAuthoredResourceTypes(client: LanguageClient, templateUri: TemplateUri): Promise { - return client.sendRequest(GetAuthoredResourceTypesRequest, templateUri) -} - -export async function getRelatedResourceTypes( - client: LanguageClient, - params: GetRelatedResourceTypesParams -): Promise { - return client.sendRequest(GetRelatedResourceTypesRequest, params) -} - -export async function insertRelatedResources( - client: LanguageClient, - params: InsertRelatedResourcesParams -): Promise { - return client.sendRequest(InsertRelatedResourcesRequest, params) -} diff --git a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesManager.ts b/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesManager.ts deleted file mode 100644 index 78b151de33..0000000000 --- a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesManager.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Position, Range, TextEdit, TextEditorRevealType, Uri, window, workspace, WorkspaceEdit } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { RelatedResourceSelector } from '../ui/relatedResourceSelector' -import { ResourceSelector } from '../ui/resourceSelector' -import { insertRelatedResources } from './relatedResourcesApi' -import { RelatedResourcesCodeAction } from './relatedResourcesProtocol' -import { showErrorMessage } from '../ui/message' -import { ResourceNode } from '../explorer/nodes/resourceNode' -import { ResourcesManager } from '../resources/resourcesManager' - -export class RelatedResourcesManager { - constructor( - private client: LanguageClient, - private selector: RelatedResourceSelector, - private resourceSelector: ResourceSelector, - private resourcesManager: ResourcesManager - ) {} - - async addRelatedResources(preSelectedResourceType?: string): Promise { - const activeEditor = window.activeTextEditor - if (!activeEditor) { - void window.showErrorMessage('No template file opened') - return - } - - try { - const templateUri = activeEditor.document.uri.toString() - - const selectedParentResourceType = - preSelectedResourceType || (await this.selector.selectAuthoredResourceType(templateUri)) - if (!selectedParentResourceType) { - return - } - - const selectedRelatedTypes = await this.selector.selectRelatedResourceTypes(selectedParentResourceType) - if (!selectedRelatedTypes || selectedRelatedTypes.length === 0) { - return - } - - const action = await this.selector.promptCreateOrImport() - if (!action) { - return - } - - if (action === 'create') { - await this.createRelatedResources(templateUri, selectedParentResourceType, selectedRelatedTypes) - } else { - await this.importRelatedResources(selectedRelatedTypes, selectedParentResourceType) - } - } catch (error) { - showErrorMessage( - `Error adding related resources: ${error instanceof Error ? error.message : String(error)}` - ) - } - } - - private async createRelatedResources( - templateUri: string, - parentResourceType: string, - relatedResourceTypes: string[] - ): Promise { - const result = await insertRelatedResources(this.client, { - templateUri, - relatedResourceTypes, - parentResourceType, - }) - - await this.applyCodeAction(result) - - const activeEditor = window.activeTextEditor - if (activeEditor && result.data?.scrollToPosition) { - const position = new Position(result.data.scrollToPosition.line, result.data.scrollToPosition.character) - const revealRange = new Range( - new Position(Math.max(0, position.line - 2), 0), - new Position(position.line + 8, 0) - ) - activeEditor.revealRange(revealRange, TextEditorRevealType.InCenter) - } - - void window.showInformationMessage(`Added ${relatedResourceTypes.length} related resources`) - } - - private async applyCodeAction(codeAction: RelatedResourcesCodeAction): Promise { - if (codeAction.edit?.changes) { - const workspaceEdit = new WorkspaceEdit() - - for (const [uri, textEdits] of Object.entries(codeAction.edit.changes)) { - const docUri = Uri.parse(uri) - const docEdits = textEdits.map((edit) => { - const range = new Range( - new Position(edit.range.start.line, edit.range.start.character), - new Position(edit.range.end.line, edit.range.end.character) - ) - return new TextEdit(range, edit.newText) - }) - workspaceEdit.set(docUri, docEdits) - } - - await workspace.applyEdit(workspaceEdit) - } - } - - private async importRelatedResources( - relatedResourceTypes: string[], - selectedParentResourceType: string - ): Promise { - const selections = await this.resourceSelector.selectResources(true, relatedResourceTypes, { - getCached: (type) => this.resourcesManager.get().find((r) => r.typeName === type), - loadMore: (type, token) => this.resourcesManager.loadMoreResources(type, token), - search: (type, id) => this.resourcesManager.searchResource(type, id), - }) - if (selections.length === 0) { - return - } - - const resourceNodes = selections.map((selection) => ({ - resourceType: selection.resourceType, - resourceIdentifier: selection.resourceIdentifier, - })) as ResourceNode[] - - await this.resourcesManager.importResourceStates(resourceNodes, selectedParentResourceType) - } -} diff --git a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesProtocol.ts b/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesProtocol.ts deleted file mode 100644 index d5ded1a5bf..0000000000 --- a/packages/core/src/awsService/cloudformation/relatedResources/relatedResourcesProtocol.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RequestType, CodeAction, Position } from 'vscode-languageserver-protocol' - -export type TemplateUri = string - -export type GetRelatedResourceTypesParams = { - parentResourceType: string -} - -export type InsertRelatedResourcesParams = { - templateUri: string - relatedResourceTypes: string[] - parentResourceType: string -} - -export interface RelatedResourcesCodeAction extends CodeAction { - data?: { - scrollToPosition?: Position - firstLogicalId?: string - } -} - -export const GetAuthoredResourceTypesRequest = new RequestType( - 'aws/cfn/template/resources/authored' -) - -export const GetRelatedResourceTypesRequest = new RequestType( - 'aws/cfn/template/resources/related' -) - -export const InsertRelatedResourcesRequest = new RequestType< - InsertRelatedResourcesParams, - RelatedResourcesCodeAction, - void ->('aws/cfn/template/resources/insert') diff --git a/packages/core/src/awsService/cloudformation/resources/resourceRequestTypes.ts b/packages/core/src/awsService/cloudformation/resources/resourceRequestTypes.ts deleted file mode 100644 index 9b64782526..0000000000 --- a/packages/core/src/awsService/cloudformation/resources/resourceRequestTypes.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RequestType, CompletionItem, TextDocumentIdentifier } from 'vscode-languageserver-protocol' - -export interface ResourceRequest { - resourceType: string - nextToken?: string -} - -export interface ListResourcesParams { - resources?: ResourceRequest[] -} - -export interface ResourceTypesParams {} - -export interface ResourceTypesResult { - resourceTypes: string[] -} - -export interface ResourceList { - typeName: string - resourceIdentifiers: string[] - nextToken?: string -} - -export interface ListResourcesResult { - resources: ResourceList[] -} - -export const ListResourcesRequest = new RequestType( - 'aws/cfn/resources/list' -) - -export const RefreshResourcesRequest = new RequestType( - 'aws/cfn/resources/refresh' -) - -export const ResourceTypesRequest = new RequestType( - 'aws/cfn/resources/types' -) - -export const RemoveResourceTypeRequest = new RequestType('aws/cfn/resources/list/remove') - -export type ResourceSelection = { - resourceType: string - resourceIdentifiers: string[] -} - -export enum ResourceStatePurpose { - Import = 'Import', - Clone = 'Clone', -} - -export interface ResourceStateParams { - textDocument: TextDocumentIdentifier - resourceSelections?: ResourceSelection[] - purpose: ResourceStatePurpose - parentResourceType?: string -} - -export type ResourceType = string -export type ResourceIdentifier = string - -export interface ResourceStateResult { - completionItem?: CompletionItem - successfulImports: Map - failedImports: Map - warning?: string -} - -export const ResourceStateRequest = new RequestType( - 'aws/cfn/resources/state' -) - -export type ResourceStackManagementResult = { - physicalResourceId: string - managedByStack: boolean | undefined - stackName?: string - stackId?: string - error?: string -} - -export const StackMgmtInfoRequest = new RequestType( - 'aws/cfn/resources/stackMgmtInfo' -) - -export type SearchResourceParams = { - resourceType: string - identifier: string -} - -export type SearchResourceResult = { - found: boolean - resource?: ResourceList -} - -export const SearchResourceRequest = new RequestType( - 'aws/cfn/resources/search' -) diff --git a/packages/core/src/awsService/cloudformation/resources/resourcesManager.ts b/packages/core/src/awsService/cloudformation/resources/resourcesManager.ts deleted file mode 100644 index bdba551c7c..0000000000 --- a/packages/core/src/awsService/cloudformation/resources/resourcesManager.ts +++ /dev/null @@ -1,484 +0,0 @@ -/*! -import { getLogger } from '../../../shared/logger' - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ResourceSelectionResult, ResourceSelector } from '../ui/resourceSelector' -import { ResourceNode } from '../explorer/nodes/resourceNode' -import { LanguageClient } from 'vscode-languageclient/node' -import { - ListResourcesRequest, - RefreshResourcesRequest, - ResourceList, - ResourceSelection, - ResourceStackManagementResult, - ResourceStateParams, - ResourceStatePurpose, - ResourceStateRequest, - ResourceStateResult, - StackMgmtInfoRequest, - SearchResourceRequest, - SearchResourceResult, - RemoveResourceTypeRequest, -} from './resourceRequestTypes' - -import { handleLspError } from '../utils/onlineErrorHandler' -import { showErrorMessage } from '../ui/message' -import { ProgressLocation, SnippetString, window, env, Position, Range } from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import globals from '../../../shared/extensionGlobals' -import { setContext } from '../../../shared/vscode/setContext' - -type ResourcesChangeListener = (resources: ResourceList[]) => void - -export class ResourcesManager { - private resources: Map = new Map() - private readonly listeners: ResourcesChangeListener[] = [] - private static readonly resourceTypesKey = 'aws.cloudformation.selectedResourceTypes' - - private readonly CopyStackName = 'Copy Stack Name' - private readonly CopyStackArn = 'Copy Stack Arn' - - constructor( - private readonly client: LanguageClient, - private readonly resourceSelector: ResourceSelector - ) {} - - private get selectedResourceTypes(): string[] { - return globals.globalState.tryGet(ResourcesManager.resourceTypesKey, Object, []) - } - - private async setSelectedResourceTypes(types: string[]): Promise { - await globals.globalState.update(ResourcesManager.resourceTypesKey, types) - } - - getSelectedResourceTypes(): string[] { - return this.selectedResourceTypes - } - - async removeResourceType(typeToRemove: string): Promise { - await globals.globalState.update( - ResourcesManager.resourceTypesKey, - this.selectedResourceTypes.filter((type) => type !== typeToRemove) - ) - await this.client.sendRequest(RemoveResourceTypeRequest, typeToRemove) - this.notifyAllListeners() - } - - get(): ResourceList[] { - return Array.from(this.resources.values()) - } - - addListener(listener: ResourcesChangeListener) { - this.listeners.push(listener) - } - - async loadResources(): Promise { - try { - if (this.selectedResourceTypes.length === 0) { - this.resources.clear() - return - } - - this.resources.clear() - - const response = await this.client.sendRequest(ListResourcesRequest, { - resources: this.selectedResourceTypes.map((resourceType) => ({ resourceType })), - }) - - for (const resource of response.resources) { - this.resources.set(resource.typeName, resource) - } - } catch (error) { - await handleLspError(error, 'Error loading resources') - this.resources.clear() - } finally { - this.notifyAllListeners() - } - } - - async loadResourceType(resourceType: string): Promise { - const response = await this.client.sendRequest(ListResourcesRequest, { - resources: [{ resourceType }], - }) - - if (response.resources.length > 0) { - this.resources.set(resourceType, response.resources[0]) - this.notifyAllListeners() - } - } - - async loadMoreResources(resourceType: string, nextToken: string): Promise { - await setContext('aws.cloudformation.loadingResources', true) - try { - const response = await this.client.sendRequest(ListResourcesRequest, { - resources: [{ resourceType, nextToken }], - }) - - if (response.resources.length > 0) { - this.resources.set(resourceType, response.resources[0]) - } - - this.notifyAllListeners() - } catch (error) { - await handleLspError(error, 'Error loading more resources') - } finally { - await setContext('aws.cloudformation.loadingResources', false) - } - } - - async refreshAllResources(): Promise { - await window.withProgress( - { - location: ProgressLocation.Notification, - title: 'Refreshing All Resources List', - }, - async () => { - await setContext('aws.cloudformation.refreshingAllResources', true) - try { - if (this.selectedResourceTypes.length === 0) { - return - } - - const response = await this.client.sendRequest(RefreshResourcesRequest, { - resources: this.selectedResourceTypes.map((resourceType) => ({ resourceType })), - }) - this.resources.clear() - for (const resource of response.resources) { - this.resources.set(resource.typeName, resource) - } - } finally { - await setContext('aws.cloudformation.refreshingAllResources', false) - this.notifyAllListeners() - } - } - ) - } - - async refreshResourceList(resourceType: string): Promise { - await window.withProgress( - { - location: ProgressLocation.Notification, - title: `Refreshing ${resourceType} Resources List`, - }, - async () => { - await setContext('aws.cloudformation.refreshingResourceList', true) - try { - const response = await this.client.sendRequest(RefreshResourcesRequest, { - resources: [{ resourceType }], - }) - - const updatedResource = response.resources.find( - (r: { typeName: string }) => r.typeName === resourceType - ) - if (updatedResource) { - this.resources.set(resourceType, updatedResource) - } - } finally { - await setContext('aws.cloudformation.refreshingResourceList', false) - this.notifyAllListeners() - } - } - ) - } - - async searchResource(resourceType: string, identifier: string): Promise { - try { - const response = await this.client.sendRequest(SearchResourceRequest, { - resourceType, - identifier, - }) - - if (response.found && response.resource) { - this.resources.set(resourceType, response.resource) - this.notifyAllListeners() - } - - return response - } catch (error) { - getLogger().error(`Failed to search resource: ${error}`) - return { found: false } - } - } - - async selectResourceTypes(): Promise { - const selectedTypes = await this.resourceSelector.selectResourceTypes(this.selectedResourceTypes) - if (selectedTypes !== undefined) { - await this.setSelectedResourceTypes(selectedTypes) - - // Remove resources that are no longer selected - const selectedSet = new Set(selectedTypes) - for (const typeName of this.resources.keys()) { - if (!selectedSet.has(typeName)) { - this.resources.delete(typeName) - } - } - - this.notifyAllListeners() - } - } - - private async executeResourceStateOperation( - resourceNodes: ResourceNode[] | undefined, - purpose: ResourceStatePurpose, - parentResourceType?: string - ): Promise { - const editor = window.activeTextEditor - if (!editor) { - showErrorMessage('Open a CloudFormation template to author resource state') - return - } - - const contextKey = - purpose === ResourceStatePurpose.Import - ? 'aws.cloudformation.importingResource' - : 'aws.cloudformation.cloningResource' - await setContext(contextKey, true) - - try { - const resourceSelectionsArray = await this.getResourceSelectionArray(resourceNodes) - if (resourceSelectionsArray.length === 0) { - return - } - - const params: ResourceStateParams = { - textDocument: { uri: editor.document.uri.toString() }, - resourceSelections: resourceSelectionsArray, - purpose, - parentResourceType, - } - - const title = - purpose === ResourceStatePurpose.Import ? 'Importing Resource State' : 'Cloning Resource State' - await window.withProgress( - { - location: ProgressLocation.Notification, - title, - cancellable: false, - }, - async () => { - const result = (await this.client.sendRequest( - ResourceStateRequest.method, - params - )) as ResourceStateResult - if (result.warning) { - void window.showWarningMessage(result.warning) - } - await this.applyCompletionSnippet(result) - const [successCount, failureCount] = this.getSuccessAndFailureCount(result) - this.renderResultMessage(successCount, failureCount, purpose) - } - ) - } catch (error) { - const action = purpose === ResourceStatePurpose.Import ? 'importing' : 'cloning' - showErrorMessage( - `Error ${action} resource state: ${error instanceof Error ? error.message : String(error)}` - ) - } finally { - await setContext(contextKey, false) - } - } - - async importResourceStates(resourceNodes?: ResourceNode[], parentResourceType?: string): Promise { - await this.executeResourceStateOperation(resourceNodes, ResourceStatePurpose.Import, parentResourceType) - } - - private getResourcesToImportInput(selections: ResourceSelectionResult[]): ResourceSelection[] { - // Group selections by resource type - const resourceSelections = new Map() - for (const selection of selections) { - const identifiers = resourceSelections.get(selection.resourceType) ?? [] - identifiers.push(selection.resourceIdentifier) - resourceSelections.set(selection.resourceType, identifiers) - } - - // Convert to ResourceSelection[] format expected by server - return Array.from(resourceSelections.entries()).map(([resourceType, resourceIdentifiers]) => ({ - resourceType, - resourceIdentifiers, - })) - } - - private async applyCompletionSnippet(result: ResourceStateResult): Promise { - const { completionItem } = result - - if (!completionItem?.textEdit) { - getLogger().warn('No completionItem or textEdit in result') - return - } - - const editor = window.activeTextEditor - if (!editor) { - getLogger().warn('No active editor for snippet insertion') - return - } - - try { - const textEdit = completionItem.textEdit - if (!textEdit || !('range' in textEdit)) { - getLogger().warn('No valid textEdit range found') - return - } - - const targetLine = textEdit.range.start.line - await this.ensureLineExists(editor, targetLine) - - const range = new Range( - new Position(textEdit.range.start.line, textEdit.range.start.character), - new Position(textEdit.range.end.line, textEdit.range.end.character) - ) - - getLogger().info( - `Inserting snippet at server-provided position: line ${range.start.line}, char ${range.start.character}` - ) - await editor.insertSnippet(new SnippetString(textEdit.newText), range) - getLogger().info('Snippet insertion successful') - } catch (error) { - getLogger().error(`Failed to insert snippet: ${error instanceof Error ? error.message : String(error)}`) - showErrorMessage(`Failed to insert resource: ${error instanceof Error ? error.message : String(error)}`) - } - } - - private async ensureLineExists(editor: any, targetLine: number): Promise { - const document = editor.document - if (targetLine >= document.lineCount) { - const linesToAdd = targetLine - document.lineCount + 1 - const lastLine = document.lineAt(document.lineCount - 1) - const endPosition = lastLine.range.end - - await editor.edit((editBuilder: any) => { - editBuilder.insert(endPosition, '\n'.repeat(linesToAdd)) - }) - } - } - - private getSuccessAndFailureCount(result: ResourceStateResult): [number, number] { - const successCount = Object.values(result.successfulImports ?? {}).reduce( - (sum: number, ids: string[]) => sum + ids.length, - 0 - ) as number - const failureCount = Object.values(result.failedImports ?? {}).reduce( - (sum: number, ids: string[]) => sum + ids.length, - 0 - ) as number - return [successCount, failureCount] - } - - async cloneResourceStates(resourceNodes?: ResourceNode[]): Promise { - await this.executeResourceStateOperation(resourceNodes, ResourceStatePurpose.Clone) - } - - private async getResourceSelectionArray(resourceNodes?: ResourceNode[]): Promise { - let selections: ResourceSelectionResult[] - - if (resourceNodes?.length) { - selections = resourceNodes.map((node) => ({ - resourceType: node.resourceType, - resourceIdentifier: node.resourceIdentifier, - })) - } else { - selections = await this.resourceSelector.selectResources(true, undefined, { - getCached: (type) => this.resources.get(type), - loadMore: (type, token) => this.loadMoreResources(type, token), - search: (type, id) => this.searchResource(type, id), - }) - } - - if (selections.length === 0) { - return [] - } - - return this.getResourcesToImportInput(selections) - } - - private renderResultMessage(successCount: number, failureCount: number, purpose: ResourceStatePurpose) { - const action = purpose === ResourceStatePurpose.Import ? 'imported' : 'cloned' - - if (successCount > 0 && failureCount === 0) { - void window.showInformationMessage(`Successfully ${action} ${successCount} resource(s)`) - } else if (successCount > 0 && failureCount > 0) { - void window.showWarningMessage( - `${action.charAt(0).toUpperCase() + action.slice(1)} ${successCount} resource(s), ${failureCount} failed` - ) - } else if (failureCount > 0) { - showErrorMessage(`Failed to ${action.replace('ed', '')} ${failureCount} resource(s)`) - } else { - void window.showInformationMessage(`No resources were ${action}`) - } - } - - private getResourcesArray(): ResourceList[] { - return Array.from(this.resources.values()) - } - - private notifyAllListeners(): void { - for (const listener of this.listeners) { - listener(this.getResourcesArray()) - } - } - - reload() { - this.resources.clear() - this.notifyAllListeners() - } - - async getStackManagementInfo(resourceNode?: ResourceNode): Promise { - let resourceIdentifier: string | undefined - - if (resourceNode?.resourceIdentifier) { - resourceIdentifier = resourceNode.resourceIdentifier - } else { - const selection = await this.resourceSelector.selectSingleResource({ - getCached: (type) => this.resources.get(type), - loadMore: (type, token) => this.loadMoreResources(type, token), - search: (type, id) => this.searchResource(type, id), - }) - if (!selection) { - return - } - resourceIdentifier = selection.resourceIdentifier - } - - await setContext('aws.cloudformation.gettingStackMgmtInfo', true) - try { - const result = (await window.withProgress( - { - location: ProgressLocation.SourceControl, - title: 'Getting Stack Management Info', - cancellable: false, - }, - async () => { - return await this.client.sendRequest(StackMgmtInfoRequest.method, resourceIdentifier) - } - )) as ResourceStackManagementResult - - await setContext('aws.cloudformation.gettingStackMgmtInfo', false) - - if (result.managedByStack === true && result.stackName && result.stackId) { - const action = await window.showInformationMessage( - `${result.physicalResourceId} is managed by stack: ${result.stackName}`, - this.CopyStackName, - this.CopyStackArn - ) - - if (action === this.CopyStackName) { - await env.clipboard.writeText(result.stackName) - window.setStatusBarMessage('Stack name copied to clipboard', 3000) - } else if (action === this.CopyStackArn) { - await env.clipboard.writeText(result.stackId) - window.setStatusBarMessage('Stack ARN copied to clipboard', 3000) - } - } else if (result.managedByStack === false) { - void window.showInformationMessage(`${result.physicalResourceId} is not managed by any stack`) - } else { - showErrorMessage(`Failed to determine stack management status: ${result.error ?? 'Unknown error'}`) - } - } catch (error) { - showErrorMessage( - `Error getting stack management info: ${error instanceof Error ? error.message : String(error)}` - ) - await setContext('aws.cloudformation.gettingStackMgmtInfo', false) - } - } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts deleted file mode 100644 index a1fa6bdd65..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.ts +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { v4 as uuidv4 } from 'uuid' -import { StackActionPhase, StackActionState } from './stackActionRequestType' -import { LanguageClient } from 'vscode-languageclient/node' -import { - showErrorMessage, - showChangeSetDeletionStarted, - showChangeSetDeletionSuccess, - showChangeSetDeletionFailure, -} from '../../ui/message' -import { deleteChangeSet, describeChangeSetDeletionStatus, getChangeSetDeletionStatus } from './stackActionApi' -import { createChangeSetDeletionParams } from './stackActionUtil' -import { getLogger } from '../../../../shared/logger/logger' -import { commandKey, extractErrorMessage } from '../../utils' -import { commands } from 'vscode' -import globals from '../../../../shared/extensionGlobals' - -export class ChangeSetDeletion { - private readonly id: string - private status: StackActionPhase | undefined - - constructor( - private readonly stackName: string, - private readonly changeSetName: string, - private readonly client: LanguageClient - ) { - this.id = uuidv4() - } - - async delete() { - await deleteChangeSet(this.client, createChangeSetDeletionParams(this.id, this.stackName, this.changeSetName)) - showChangeSetDeletionStarted(this.changeSetName, this.stackName) - this.pollForProgress() - } - - private pollForProgress() { - const interval = globals.clock.setInterval(() => { - getChangeSetDeletionStatus(this.client, { id: this.id }) - .then(async (deletionResult) => { - if (deletionResult.phase === this.status) { - return - } - - this.status = deletionResult.phase - - switch (deletionResult.phase) { - case StackActionPhase.DELETION_IN_PROGRESS: - break - case StackActionPhase.DELETION_COMPLETE: - if (deletionResult.state === StackActionState.SUCCESSFUL) { - showChangeSetDeletionSuccess(this.changeSetName, this.stackName) - } else { - const describeDeplomentStatusResult = await describeChangeSetDeletionStatus( - this.client, - { - id: this.id, - } - ) - showChangeSetDeletionFailure( - this.changeSetName, - this.stackName, - describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' - ) - } - void commands.executeCommand(commandKey('stacks.refresh')) - globals.clock.clearInterval(interval) - break - case StackActionPhase.DELETION_FAILED: { - const describeDeplomentStatusResult = await describeChangeSetDeletionStatus(this.client, { - id: this.id, - }) - showChangeSetDeletionFailure( - this.changeSetName, - this.stackName, - describeDeplomentStatusResult.FailureReason ?? 'No failure reason provided' - ) - void commands.executeCommand(commandKey('stacks.refresh')) - globals.clock.clearInterval(interval) - break - } - } - }) - .catch(async (error) => { - getLogger().error(`Error polling for deletion status: ${error}`) - showErrorMessage(`Error polling for deletion status: ${extractErrorMessage(error)}`) - void commands.executeCommand(commandKey('stacks.refresh')) - globals.clock.clearInterval(interval) - }) - }, 1000) - } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/deploymentWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/deploymentWorkflow.ts deleted file mode 100644 index e4e790a56d..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/deploymentWorkflow.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { v4 as uuidv4 } from 'uuid' -import { StackActionPhase, StackActionState } from './stackActionRequestType' -import { LanguageClient } from 'vscode-languageclient/node' -import { showDeploymentStarted, showDeploymentSuccess, showDeploymentFailure, showErrorMessage } from '../../ui/message' -import { createDeploymentStatusBar, updateWorkflowStatus } from '../../ui/statusBar' -import { commands } from 'vscode' -import { deploy, describeDeploymentStatus, getDeploymentStatus } from './stackActionApi' -import { createDeploymentParams } from './stackActionUtil' -import { getLogger } from '../../../../shared/logger/logger' -import { extractErrorMessage, commandKey } from '../../utils' -import { StackViewCoordinator } from '../../ui/stackViewCoordinator' - -export class Deployment { - private readonly id: string - private status: StackActionPhase | undefined - private statusBarHandle?: { update(phase: StackActionPhase): void; release(): void } - - constructor( - private readonly stackName: string, - private readonly changeSetName: string, - private readonly client: LanguageClient, - private readonly coordinator: StackViewCoordinator - ) { - this.id = uuidv4() - } - - async deploy() { - await deploy(this.client, createDeploymentParams(this.id, this.stackName, this.changeSetName)) - showDeploymentStarted(this.stackName) - - await this.coordinator.setStack(this.stackName) - await commands.executeCommand(commandKey('stack.events.focus')) - - this.statusBarHandle = createDeploymentStatusBar(this.stackName, 'Deployment', this.changeSetName) - this.pollForProgress() - } - - private pollForProgress() { - const interval = setInterval(() => { - getDeploymentStatus(this.client, { id: this.id }) - .then(async (deploymentResult) => { - if (deploymentResult.phase === this.status) { - return - } - - this.status = deploymentResult.phase - if (this.statusBarHandle) { - updateWorkflowStatus(this.statusBarHandle, deploymentResult.phase) - } - - switch (deploymentResult.phase) { - case StackActionPhase.DEPLOYMENT_IN_PROGRESS: - break - case StackActionPhase.DEPLOYMENT_COMPLETE: - if (deploymentResult.state === StackActionState.SUCCESSFUL) { - showDeploymentSuccess(this.stackName) - } else { - const describeDeploymentStatusResult = await describeDeploymentStatus(this.client, { - id: this.id, - }) - showDeploymentFailure( - this.stackName, - describeDeploymentStatusResult.FailureReason ?? 'UNKNOWN' - ) - } - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - break - case StackActionPhase.DEPLOYMENT_FAILED: - case StackActionPhase.VALIDATION_FAILED: { - const describeDeplomentStatusResult = await describeDeploymentStatus(this.client, { - id: this.id, - }) - showDeploymentFailure( - this.stackName, - describeDeplomentStatusResult.FailureReason ?? 'UNKNOWN' - ) - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - break - } - } - }) - .catch(async (error) => { - getLogger().error(`Error polling for deployment status: ${error}`) - showErrorMessage(`Error polling for deployment status: ${extractErrorMessage(error)}`) - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - }) - }, 1000) - } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionApi.ts b/packages/core/src/awsService/cloudformation/stacks/actions/stackActionApi.ts deleted file mode 100644 index 0ca569de95..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionApi.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LanguageClient } from 'vscode-languageclient/node' -import { - TemplateUri, - GetParametersResult, - GetCapabilitiesResult, - CreateStackActionResult, - GetStackActionStatusResult, - TemplateResource, - CreateValidationParams, - CreateDeploymentParams, - DescribeValidationStatusResult, - DescribeDeploymentStatusResult, - DeleteChangeSetParams, - DescribeDeletionStatusResult, - DescribeChangeSetParams, - DescribeChangeSetResult, - GetTemplateArtifactsResult, -} from './stackActionRequestType' -import { - GetParametersRequest, - GetCapabilitiesRequest, - CreateValidationRequest, - CreateDeploymentRequest, - GetValidationStatusRequest, - GetDeploymentStatusRequest, - GetTemplateResourcesRequest, - GetTemplateArtifactsRequest, - DescribeValidationStatusRequest, - DescribeDeploymentStatusRequest, - DeleteChangeSetRequest, - GetChangeSetDeletionStatusRequest, - DescribeChangeSetDeletionStatusRequest, - DescribeChangeSetRequest, -} from './stackActionProtocol' -import { Identifiable } from '../../lspTypes' - -export async function validate( - client: LanguageClient, - params: CreateValidationParams -): Promise { - return await client.sendRequest(CreateValidationRequest, params) -} - -export async function deploy(client: LanguageClient, params: CreateDeploymentParams): Promise { - return await client.sendRequest(CreateDeploymentRequest, params) -} - -export async function deleteChangeSet( - client: LanguageClient, - params: DeleteChangeSetParams -): Promise { - return await client.sendRequest(DeleteChangeSetRequest, params) -} - -export async function getValidationStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(GetValidationStatusRequest, params) -} - -export async function getDeploymentStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(GetDeploymentStatusRequest, params) -} - -export async function describeValidationStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(DescribeValidationStatusRequest, params) -} - -export async function describeDeploymentStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(DescribeDeploymentStatusRequest, params) -} - -export async function getChangeSetDeletionStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(GetChangeSetDeletionStatusRequest, params) -} - -export async function describeChangeSetDeletionStatus( - client: LanguageClient, - params: Identifiable -): Promise { - return await client.sendRequest(DescribeChangeSetDeletionStatusRequest, params) -} - -export async function getParameters(client: LanguageClient, params: TemplateUri): Promise { - return await client.sendRequest(GetParametersRequest, params) -} - -export async function getCapabilities(client: LanguageClient, params: TemplateUri): Promise { - return await client.sendRequest(GetCapabilitiesRequest, params) -} - -export async function getTemplateResources(client: LanguageClient, params: TemplateUri): Promise { - const result = await client.sendRequest(GetTemplateResourcesRequest, params) - return result.resources -} - -export async function getTemplateArtifacts( - client: LanguageClient, - params: TemplateUri -): Promise { - return await client.sendRequest(GetTemplateArtifactsRequest, params) -} - -export async function describeChangeSet( - client: LanguageClient, - params: DescribeChangeSetParams -): Promise { - return await client.sendRequest(DescribeChangeSetRequest, params) -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionInputValidation.ts b/packages/core/src/awsService/cloudformation/stacks/actions/stackActionInputValidation.ts deleted file mode 100644 index a03e2eaa12..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionInputValidation.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fs } from '../../../../shared/fs/fs' -import { TemplateParameter } from './stackActionRequestType' - -export function validateTemplatePath(value: string): string | undefined { - if (!value) { - return 'Template path is required' - } - - const filePath = value.startsWith('file://') ? value.slice(7) : value - if (!fs.exists(filePath)) { - return 'Template file does not exist' - } - - const validExtensions = ['.yaml', '.json', '.yml', '.txt', '.cfn', '.template'] - if (!validExtensions.some((ext) => filePath.endsWith(ext))) { - return 'Invalid template file extension' - } - - return undefined -} - -export function validateStackName(value: string): string | undefined { - if (!value) { - return 'Stack name is required' - } - - if (value.length > 128) { - return 'Stack name must be 128 characters or less' - } - - if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(value)) { - return 'Stack name must start with a letter and contain only alphanumeric characters and hyphens' - } - - return undefined -} - -export function validateChangeSetName(value: string): string | undefined { - if (!value) { - return 'Change Set name is required' - } - - if (value.length > 128) { - return 'Change Set name must be 128 characters or less' - } - - if (!/^[a-zA-Z][-a-zA-Z0-9]*$/.test(value)) { - return 'Change Set name must start with a letter and contain only alphanumeric characters and hyphens' - } - - return undefined -} - -export function validateParameterValue(input: string, param: TemplateParameter): string | undefined { - const actualValue = input ?? param.Default?.toString() ?? '' - - // Handle CommaDelimitedList validation - if (param.Type === 'CommaDelimitedList') { - const items = actualValue.split(',').map((s) => s.trim()) - - if (param.AllowedValues) { - const allowedStrings = param.AllowedValues.map(String) - const invalidItems = items.filter((item) => !allowedStrings.includes(item)) - if (invalidItems.length > 0) { - return `Invalid values: ${invalidItems.join(', ')}. Must be one of: ${param.AllowedValues.join(', ')}` - } - } - - if (param.AllowedPattern) { - const pattern = new RegExp(param.AllowedPattern) - const invalidItems = items.filter((item) => !pattern.test(item)) - if (invalidItems.length > 0) { - return `Values must match pattern: ${param.AllowedPattern}` - } - } - - return undefined - } - - // Handle other types - if (param.AllowedValues && !param.AllowedValues.map(String).includes(actualValue)) { - return `Value must be one of: ${param.AllowedValues.join(', ')}` - } - - if (param.AllowedPattern && !new RegExp(param.AllowedPattern).test(actualValue)) { - return `Value must match pattern: ${param.AllowedPattern}` - } - - if (param.MinLength && actualValue.length < param.MinLength) { - return `Value must be at least ${param.MinLength} characters` - } - - if (param.MaxLength && actualValue.length > param.MaxLength) { - return `Value must be at most ${param.MaxLength} characters` - } - - if (param.Type === 'Number') { - const numValue = Number(actualValue) - if (isNaN(numValue)) { - return 'Value must be a number' - } - if (param.MinValue && numValue < param.MinValue) { - return `Value must be at least ${param.MinValue}` - } - if (param.MaxValue && numValue > param.MaxValue) { - return `Value must be at most ${param.MaxValue}` - } - } - - return undefined -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionProtocol.ts b/packages/core/src/awsService/cloudformation/stacks/actions/stackActionProtocol.ts deleted file mode 100644 index e01a0423f8..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionProtocol.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RequestType } from 'vscode-languageserver-protocol' -import { Identifiable } from '../../lspTypes' -import { - TemplateUri, - GetParametersResult, - CreateStackActionResult, - GetStackActionStatusResult, - GetCapabilitiesResult, - GetTemplateResourcesResult, - GetTemplateArtifactsResult, - ListChangeSetsParams, - ListChangeSetsResult, - CreateValidationParams, - CreateDeploymentParams, - DescribeValidationStatusResult, - DescribeDeploymentStatusResult, - DeleteChangeSetParams, - DescribeDeletionStatusResult, - GetStackEventsParams, - GetStackEventsResult, - ClearStackEventsParams, - DescribeChangeSetParams, - DescribeChangeSetResult, - GetStackResourcesParams, - ListStackResourcesResult, - DescribeStackParams, - DescribeStackResult, -} from './stackActionRequestType' - -export const CreateValidationRequest = new RequestType( - 'aws/cfn/stack/validation/create' -) - -export const CreateDeploymentRequest = new RequestType( - 'aws/cfn/stack/deployment/create' -) - -export const GetValidationStatusRequest = new RequestType( - 'aws/cfn/stack/validation/status' -) - -export const GetDeploymentStatusRequest = new RequestType( - 'aws/cfn/stack/deployment/status' -) - -export const DescribeValidationStatusRequest = new RequestType( - 'aws/cfn/stack/validation/status/describe' -) - -export const DescribeDeploymentStatusRequest = new RequestType( - 'aws/cfn/stack/deployment/status/describe' -) - -export const DeleteChangeSetRequest = new RequestType( - 'aws/cfn/stack/changeSet/delete' -) - -export const GetChangeSetDeletionStatusRequest = new RequestType( - 'aws/cfn/stack/changeSet/deletion/status' -) - -export const DescribeChangeSetDeletionStatusRequest = new RequestType( - 'aws/cfn/stack/changeSet/deletion/status/describe' -) - -export const GetParametersRequest = new RequestType('aws/cfn/stack/parameters') - -export const GetCapabilitiesRequest = new RequestType( - 'aws/cfn/stack/capabilities' -) - -export const GetTemplateResourcesRequest = new RequestType( - 'aws/cfn/stack/import/resources' -) - -export const GetTemplateArtifactsRequest = new RequestType( - 'aws/cfn/stack/template/artifacts' -) - -export const ListChangeSetsRequest = new RequestType( - 'aws/cfn/stack/changeSet/list' -) - -export const GetStackEventsRequest = new RequestType( - 'aws/cfn/stack/events' -) - -export const ClearStackEventsRequest = new RequestType('aws/cfn/stack/events/clear') - -export const DescribeStackRequest = new RequestType( - 'aws/cfn/stack/describe' -) - -export const DescribeChangeSetRequest = new RequestType( - 'aws/cfn/stack/changeSet/describe' -) - -export const GetStackResourcesRequest = new RequestType( - 'aws/cfn/stack/resources' -) diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionRequestType.ts b/packages/core/src/awsService/cloudformation/stacks/actions/stackActionRequestType.ts deleted file mode 100644 index 4fb6d5e2c4..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionRequestType.ts +++ /dev/null @@ -1,297 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - Parameter, - Capability, - ResourceChangeDetail, - ResourceStatus, - DetailedStatus, - ResourceTargetDefinition, - StackEvent, - OnStackFailure, - Tag, - Stack, -} from '@aws-sdk/client-cloudformation' -import { Identifiable } from '../../lspTypes' - -export type ResourceToImport = { - ResourceType: string - LogicalResourceId: string - ResourceIdentifier: Record -} - -export enum DeploymentMode { - REVERT_DRIFT = 'REVERT_DRIFT', -} - -export type ChangeSetOptionalFlags = { - onStackFailure?: OnStackFailure - includeNestedStacks?: boolean - tags?: Tag[] - importExistingResources?: boolean - deploymentMode?: DeploymentMode -} - -export type CreateValidationParams = Identifiable & { - uri: string - stackName: string - parameters?: Parameter[] - capabilities?: Capability[] - resourcesToImport?: ResourceToImport[] - keepChangeSet?: boolean - onStackFailure?: OnStackFailure - includeNestedStacks?: boolean - tags?: Tag[] - importExistingResources?: boolean - deploymentMode?: DeploymentMode - s3Bucket?: string - s3Key?: string -} - -export type ChangeSetReference = { - changeSetName: string - stackName: string -} - -export type CreateDeploymentParams = Identifiable & ChangeSetReference - -export type DeleteChangeSetParams = Identifiable & ChangeSetReference - -export type CreateStackActionResult = Identifiable & ChangeSetReference - -export type ValidationResult = { - level: 'FAIL' | 'WARN' | 'INFO' - type: string - validationName: string - status: 'COMPLETE' | 'FAILED' | 'SKIPPED' - details: string - propertyPath?: string - remediationAction?: string - detailedStatus?: string -} - -export type StackChange = { - type?: string - resourceChange?: { - action?: string - logicalResourceId?: string - physicalResourceId?: string - resourceType?: string - replacement?: string - scope?: string[] - beforeContext?: string - afterContext?: string - resourceDriftStatus?: string - details?: ResourceChangeDetailV2[] - } - validationResults?: ValidationResult[] -} - -export type ResourceTargetDefinitionV2 = ResourceTargetDefinition & { - Drift?: { - PreviousValue: string - ActualValue?: string - } - LiveResourceDrift?: { - PreviousValue: string - ActualValue?: string - } -} - -export type ResourceChangeDetailV2 = Omit & { - Target?: ResourceTargetDefinitionV2 -} - -export enum StackActionPhase { - VALIDATION_STARTED = 'VALIDATION_STARTED', - VALIDATION_IN_PROGRESS = 'VALIDATION_IN_PROGRESS', - VALIDATION_COMPLETE = 'VALIDATION_COMPLETE', - VALIDATION_FAILED = 'VALIDATION_FAILED', - DEPLOYMENT_STARTED = 'DEPLOYMENT_STARTED', - DEPLOYMENT_IN_PROGRESS = 'DEPLOYMENT_IN_PROGRESS', - DEPLOYMENT_COMPLETE = 'DEPLOYMENT_COMPLETE', - DEPLOYMENT_FAILED = 'DEPLOYMENT_FAILED', - DELETION_STARTED = 'DELETION_STARTED', - DELETION_IN_PROGRESS = 'DELETION_IN_PROGRESS', - DELETION_COMPLETE = 'DELETION_COMPLETE', - DELETION_FAILED = 'DELETION_FAILED', -} - -export enum StackActionState { - IN_PROGRESS = 'IN_PROGRESS', - SUCCESSFUL = 'SUCCESSFUL', - FAILED = 'FAILED', -} - -export type GetStackActionStatusResult = Identifiable & { - phase: StackActionPhase - state: StackActionState - changes?: StackChange[] -} - -export type ValidationDetail = { - ValidationName: string - LogicalId?: string - ResourcePropertyPath?: string - Severity: 'INFO' | 'ERROR' - Message: string -} - -export type DeploymentEvent = { - LogicalResourceId?: string - ResourceType?: string - ResourceStatus?: ResourceStatus - ResourceStatusReason?: string - DetailedStatus?: DetailedStatus -} - -export type Failable = { - FailureReason?: string -} - -export type DescribeValidationStatusResult = GetStackActionStatusResult & - Failable & { - ValidationDetails?: ValidationDetail[] - deploymentMode?: DeploymentMode - } - -export type DescribeDeploymentStatusResult = GetStackActionStatusResult & - Failable & { - DeploymentEvents?: DeploymentEvent[] - } - -export type DescribeDeletionStatusResult = GetStackActionStatusResult & Failable - -export type GetParametersResult = { - parameters: TemplateParameter[] -} - -export type GetCapabilitiesResult = { - capabilities: Capability[] -} - -export type TemplateResource = { - logicalId: string - type: string - primaryIdentifierKeys?: string[] - primaryIdentifier?: Record -} - -export type GetTemplateResourcesResult = { - resources: TemplateResource[] -} - -export type Artifact = { - resourceType: string - filePath: string -} - -export type GetTemplateArtifactsResult = { - artifacts: Artifact[] -} - -export enum OptionalFlagMode { - Skip = 'Skip for now', - Input = 'Input optional flags', - DevFriendly = 'Use default developer friendly flags', -} - -export type TemplateParameter = { - name: string - Type?: string - Default?: string | number | boolean - Description?: string - AllowedValues?: (string | number | boolean)[] - AllowedPattern?: string - MinLength?: number - MaxLength?: number - MinValue?: number - MaxValue?: number -} - -export type TemplateUri = string - -export type ChangeSetInfo = { - changeSetName: string - status: string - creationTime?: string - description?: string -} - -export type ListChangeSetsParams = { - stackName: string - nextToken?: string -} - -export type ListChangeSetsResult = { - changeSets: ChangeSetInfo[] - nextToken?: string -} - -export type DescribeChangeSetParams = ChangeSetReference - -export type DescribeChangeSetResult = ChangeSetInfo & { - stackName: string - changes?: StackChange[] - deploymentMode?: DeploymentMode -} - -export type StackInfo = { - StackName: string - StackId?: string - StackStatus?: string - StackStatusReason?: string - TemplateDescription?: string - CreationTime?: string - LastUpdatedTime?: string - RootId?: string - ParentId?: string - DisableRollback?: boolean - EnableTerminationProtection?: boolean - TimeoutInMinutes?: number -} - -export type GetStackEventsParams = { - stackName: string - nextToken?: string - refresh?: boolean -} - -export type GetStackEventsResult = { - events: StackEvent[] - nextToken?: string - gapDetected?: boolean -} - -export type ClearStackEventsParams = { - stackName: string -} - -export type DescribeStackParams = { - stackName: string -} - -export type DescribeStackResult = { - stack?: Stack -} - -export interface StackResourceSummary { - LogicalResourceId: string - PhysicalResourceId?: string - ResourceType: string - ResourceStatus: string - Timestamp?: string -} - -export type ListStackResourcesResult = { - resources: StackResourceSummary[] - nextToken?: string -} - -export interface GetStackResourcesParams { - stackName: string - nextToken?: string -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionUtil.ts b/packages/core/src/awsService/cloudformation/stacks/actions/stackActionUtil.ts deleted file mode 100644 index 9a88bb71aa..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/stackActionUtil.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - ChangeSetOptionalFlags, - CreateDeploymentParams, - CreateValidationParams, - DeleteChangeSetParams, - ResourceToImport, -} from './stackActionRequestType' -import { Capability, Parameter } from '@aws-sdk/client-cloudformation' - -export function createValidationParams( - id: string, - uri: string, - stackName: string, - parameters?: Parameter[], - capabilities?: Capability[], - resourcesToImport?: ResourceToImport[], - keepChangeSet?: boolean, - optionalFlags?: ChangeSetOptionalFlags, - s3Bucket?: string, - s3Key?: string -): CreateValidationParams { - return { - id, - uri, - stackName, - parameters, - capabilities, - resourcesToImport, - keepChangeSet, - onStackFailure: optionalFlags?.onStackFailure, - includeNestedStacks: optionalFlags?.includeNestedStacks, - tags: optionalFlags?.tags, - importExistingResources: optionalFlags?.importExistingResources, - deploymentMode: optionalFlags?.deploymentMode, - s3Bucket, - s3Key, - } -} - -export function createDeploymentParams(id: string, stackName: string, changeSetName: string): CreateDeploymentParams { - return { id, stackName, changeSetName } -} - -export function createChangeSetDeletionParams( - id: string, - stackName: string, - changeSetName: string -): DeleteChangeSetParams { - return { id, stackName, changeSetName } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/actions/validationWorkflow.ts b/packages/core/src/awsService/cloudformation/stacks/actions/validationWorkflow.ts deleted file mode 100644 index 70f0565678..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/actions/validationWorkflow.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { v4 as uuidv4 } from 'uuid' -import { Parameter, Capability, ChangeSetStatus } from '@aws-sdk/client-cloudformation' -import { - StackActionPhase, - StackChange, - StackActionState, - ResourceToImport, - ChangeSetOptionalFlags, - ValidationDetail, - DeploymentMode, -} from './stackActionRequestType' -import { LanguageClient } from 'vscode-languageclient/node' -import { showErrorMessage, showValidationStarted, showValidationSuccess, showValidationFailure } from '../../ui/message' -import { describeValidationStatus, getValidationStatus, validate } from './stackActionApi' -import { createDeploymentStatusBar, updateWorkflowStatus } from '../../ui/statusBar' -import { commands } from 'vscode' -import { DiffWebviewProvider } from '../../ui/diffWebviewProvider' -import { createValidationParams } from './stackActionUtil' -import { extractErrorMessage } from '../../utils' -import { getLogger } from '../../../../shared/logger/logger' -import { commandKey } from '../../utils' - -// TODO move this to server side, we should let server handle last validation -let lastValidation: Validation | undefined = undefined - -export function getLastValidation(): Validation | undefined { - return lastValidation -} - -export function setLastValidation(validation: Validation | undefined): void { - lastValidation = validation -} - -export class Validation { - private readonly id: string - private status: StackActionPhase | undefined - private changes: StackChange[] | undefined - private statusBarHandle?: { update(phase: StackActionPhase): void; release(): void } - private changeSetName?: string - - constructor( - public readonly uri: string, - public readonly stackName: string, - private readonly client: LanguageClient, - private readonly diffProvider: DiffWebviewProvider, - public readonly parameters?: Parameter[], - private readonly capabilities?: Capability[], - private readonly resourcesToImport?: ResourceToImport[], - private readonly shouldEnableDeployment: boolean = false, - private readonly optionalFlags?: ChangeSetOptionalFlags, - private readonly s3Bucket?: string, - private readonly s3Key?: string - ) { - this.id = uuidv4() - } - - async validate() { - try { - showValidationStarted(this.stackName) - this.statusBarHandle = createDeploymentStatusBar(this.stackName, 'Validation') - // Capture the result to get changeSetName - const result = await validate( - this.client, - createValidationParams( - this.id, - this.uri, - this.stackName, - this.parameters, - this.capabilities, - this.resourcesToImport, - this.shouldEnableDeployment, - this.optionalFlags, - this.s3Bucket, - this.s3Key - ) - ) - - void commands.executeCommand(commandKey('stacks.refresh')) - this.changeSetName = result.changeSetName - - this.pollForProgress() - } catch (error) { - showErrorMessage(`Error validating template: ${error instanceof Error ? error.message : String(error)}`) - } - } - - private pollForProgress() { - const interval = setInterval(() => { - getValidationStatus(this.client, { id: this.id }) - .then(async (validationResult) => { - if (validationResult.phase === this.status) { - return - } - - this.status = validationResult.phase - this.changes = validationResult.changes - - if (this.statusBarHandle) { - updateWorkflowStatus(this.statusBarHandle, validationResult.phase) - } - - switch (validationResult.phase) { - case StackActionPhase.VALIDATION_IN_PROGRESS: - // Status bar updated above - break - case StackActionPhase.VALIDATION_COMPLETE: { - const describeValidationStatusResult = await describeValidationStatus(this.client, { - id: this.id, - }) - if (validationResult.state === StackActionState.SUCCESSFUL) { - showValidationSuccess(this.stackName) - - this.showDiffView( - describeValidationStatusResult.ValidationDetails, - describeValidationStatusResult.deploymentMode - ) - } else { - showValidationFailure( - this.stackName, - describeValidationStatusResult.FailureReason ?? 'UNKNOWN' - ) - } - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - break - } - case StackActionPhase.VALIDATION_FAILED: { - const describeValidationStatusResult = await describeValidationStatus(this.client, { - id: this.id, - }) - showValidationFailure( - this.stackName, - describeValidationStatusResult.FailureReason ?? 'UNKNOWN' - ) - void commands.executeCommand('workbench.panel.markers.view.focus') - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - break - } - } - }) - .catch((error) => { - getLogger().error(`Error polling for deployment status: ${error}`) - showErrorMessage(`Error polling for validation status: ${extractErrorMessage(error)}`) - void commands.executeCommand(commandKey('stacks.refresh')) - this.statusBarHandle?.release() - clearInterval(interval) - }) - }, 1000) - } - - private showDiffView(validationDetail?: ValidationDetail[], deploymentMode?: DeploymentMode) { - void this.diffProvider.updateData( - this.stackName, - this.changes, - this.changeSetName, - this.shouldEnableDeployment, - validationDetail, - deploymentMode, - ChangeSetStatus.CREATE_COMPLETE - ) - void commands.executeCommand(commandKey('diff.focus')) - } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/changeSetsManager.ts b/packages/core/src/awsService/cloudformation/stacks/changeSetsManager.ts deleted file mode 100644 index 7aaf75bf48..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/changeSetsManager.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LanguageClient } from 'vscode-languageclient/node' -import { ListChangeSetsRequest } from './actions/stackActionProtocol' -import { ChangeSetInfo } from './actions/stackActionRequestType' - -type StackChangeSets = { - changeSets: ChangeSetInfo[] - nextToken?: string -} - -export class ChangeSetsManager { - private stackChangeSets = new Map() - - constructor(private readonly client: LanguageClient) {} - - async getChangeSets(stackName: string): Promise { - const response = await this.client.sendRequest(ListChangeSetsRequest, { - stackName, - }) - - this.stackChangeSets.set(stackName, { - changeSets: response.changeSets, - nextToken: response.nextToken, - }) - - return response.changeSets - } - - async loadMoreChangeSets(stackName: string): Promise { - const current = this.stackChangeSets.get(stackName) - if (!current?.nextToken) { - return - } - - const response = await this.client.sendRequest(ListChangeSetsRequest, { - stackName, - nextToken: current.nextToken, - }) - - this.stackChangeSets.set(stackName, { - changeSets: [...current.changeSets, ...response.changeSets], - nextToken: response.nextToken, - }) - } - - get(stackName: string): ChangeSetInfo[] { - return this.stackChangeSets.get(stackName)?.changeSets ?? [] - } - - hasMore(stackName: string): boolean { - return this.stackChangeSets.get(stackName)?.nextToken !== undefined - } -} diff --git a/packages/core/src/awsService/cloudformation/stacks/stacksManager.ts b/packages/core/src/awsService/cloudformation/stacks/stacksManager.ts deleted file mode 100644 index 360e799f80..0000000000 --- a/packages/core/src/awsService/cloudformation/stacks/stacksManager.ts +++ /dev/null @@ -1,134 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { commands, Disposable } from 'vscode' -import { StackStatus, StackSummary } from '@aws-sdk/client-cloudformation' -import { RequestType } from 'vscode-languageserver-protocol' -import { LanguageClient } from 'vscode-languageclient/node' -import { handleLspError } from '../utils/onlineErrorHandler' -import { commandKey } from '../utils' -import { setContext } from '../../../shared/vscode/setContext' - -type ListStacksParams = { - statusToInclude?: StackStatus[] - statusToExclude?: StackStatus[] - loadMore?: boolean -} - -type ListStacksResult = { - stacks: StackSummary[] - nextToken?: string -} - -const ListStacksRequest = new RequestType('aws/cfn/stacks') - -type StacksChangeListener = (stacks: StackSummary[]) => void - -export class StacksManager implements Disposable { - private stacks: StackSummary[] = [] - private nextToken?: string - private readonly listeners: StacksChangeListener[] = [] - private loaded = false - - constructor(private readonly client: LanguageClient) {} - - addListener(listener: StacksChangeListener) { - this.listeners.push(listener) - } - - get() { - return [...this.stacks] - } - - hasMore(): boolean { - return this.nextToken !== undefined - } - - reload() { - void this.loadStacks() - } - - isLoaded() { - return this.loaded - } - - async ensureLoaded() { - if (!this.loaded) { - await this.loadStacks() - } - } - - clear() { - this.stacks = [] - this.nextToken = undefined - this.loaded = false - this.notifyListeners() - } - - updateStackStatus(stackName: string, stackStatus: string) { - const stack = this.stacks.find((s) => s.StackName === stackName) - if (stack) { - stack.StackStatus = stackStatus as any - this.notifyListeners() - } - } - - async loadMoreStacks() { - if (!this.nextToken) { - return - } - - await setContext('aws.cloudformation.loadingStacks', true) - try { - const response = await this.client.sendRequest(ListStacksRequest, { - statusToExclude: ['DELETE_COMPLETE'], - loadMore: true, - }) - this.stacks = response.stacks - this.nextToken = response.nextToken - } catch (error) { - await handleLspError(error, 'Error loading more stacks') - } finally { - await setContext('aws.cloudformation.loadingStacks', false) - this.notifyListeners() - } - } - - dispose() { - // do nothing - } - - private async loadStacks() { - await setContext('aws.cloudformation.refreshingStacks', true) - try { - const response = await this.client.sendRequest(ListStacksRequest, { - statusToExclude: ['DELETE_COMPLETE'], - loadMore: false, - }) - this.stacks = response.stacks - this.nextToken = response.nextToken - this.loaded = true - } catch (error) { - await handleLspError(error, 'Error loading stacks') - this.stacks = [] - this.nextToken = undefined - } finally { - await setContext('aws.cloudformation.refreshingStacks', false) - this.notifyListeners() - } - } - - private notifyListeners() { - for (const listener of this.listeners) { - listener(this.stacks) - } - } -} - -export function refreshCommand(manager: StacksManager) { - return commands.registerCommand(commandKey('stacks.refresh'), () => { - manager.reload() - }) -} diff --git a/packages/core/src/awsService/cloudformation/telemetryOptIn.ts b/packages/core/src/awsService/cloudformation/telemetryOptIn.ts deleted file mode 100644 index 0b1f927e30..0000000000 --- a/packages/core/src/awsService/cloudformation/telemetryOptIn.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ExtensionContext, env, Uri, window } from 'vscode' -import { CloudFormationTelemetrySettings } from './extensionConfig' -import { commandKey } from './utils' -import { isAutomation } from '../../shared/vscode/env' -import { getLogger } from '../../shared/logger/logger' -import globals from '../../shared/extensionGlobals' - -enum TelemetryChoice { - Allow = 'Yes, Allow', - Later = 'Not Now', - Never = 'Never', - LearnMore = 'Learn More', -} - -const telemetryKeys = { - hasResponded: commandKey('telemetry.hasResponded'), - lastPromptDate: commandKey('telemetry.lastPromptDate'), - unpersistedResponse: commandKey('telemetry.unpersistedResponse'), -} as const - -const telemetrySettings = { - enabled: 'enabled', -} as const - -const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000 -const promptTimeoutMs = 2500 -const telemetryDocsUrl = 'https://github.com/aws-cloudformation/cloudformation-languageserver/tree/main/src/telemetry' - -/* eslint-disable aws-toolkits/no-banned-usages */ -export async function handleTelemetryOptIn( - context: ExtensionContext, - cfnTelemetrySettings: CloudFormationTelemetrySettings -): Promise { - // If previous choice failed to persist, persist it now and return - const unpersistedResponse = (await context.globalState.get(telemetryKeys.unpersistedResponse)) as string - const hasResponded = context.globalState.get(telemetryKeys.hasResponded) - const lastPromptDate = context.globalState.get(telemetryKeys.lastPromptDate) - if (unpersistedResponse) { - // May still raise popup if user lacks permission or file is corrupted - const didSave = await saveTelemetryResponse(unpersistedResponse, cfnTelemetrySettings) - await context.globalState.update(telemetryKeys.unpersistedResponse, undefined) - // If we still couldn't save, clear everything so they get asked again until the file/perms is fixed - if (!didSave) { - getLogger().warn( - 'CloudFormation telemetry choice was not saved successfully after restart. Clearing related globalState keys for next restart' - ) - await context.globalState.update(telemetryKeys.hasResponded, undefined) - await context.globalState.update(telemetryKeys.lastPromptDate, undefined) - } - return logAndReturnTelemetryChoice( - unpersistedResponse === TelemetryChoice.Allow.toString(), - hasResponded, - lastPromptDate - ) - } - - // Never throws because we provide a default - const telemetryEnabled = cfnTelemetrySettings.get(telemetrySettings.enabled, false) - - if (isAutomation()) { - return logAndReturnTelemetryChoice(telemetryEnabled) - } - - // If user has permanently responded, use their choice - if (hasResponded) { - return logAndReturnTelemetryChoice(telemetryEnabled, hasResponded) - } - - // Check if we should show reminder (30 days since last prompt) - const shouldPrompt = lastPromptDate === undefined || globals.clock.Date.now() - lastPromptDate >= thirtyDaysMs - if (!shouldPrompt) { - return logAndReturnTelemetryChoice(telemetryEnabled, hasResponded, lastPromptDate) - } - - // Show prompt but set false if timeout - const promptPromise = promptTelemetryOptIn(context, cfnTelemetrySettings) - const timeoutPromise = new Promise((resolve) => - globals.clock.setTimeout(() => resolve(false), promptTimeoutMs) - ) - const result = await Promise.race([promptPromise, timeoutPromise]) - - // Keep prompt alive in background - void promptPromise - - return logAndReturnTelemetryChoice(result) -} -/** - * Updates the telemetry setting. In case of error, the update calls do not throw. - * They instead raise a popup and return false. - * - * @returns boolean whether the save/update was successful - */ -/* eslint-disable aws-toolkits/no-banned-usages */ -async function saveTelemetryResponse( - response: string | undefined, - cfnTelemetrySettings: CloudFormationTelemetrySettings -): Promise { - if (response === TelemetryChoice.Allow) { - return await cfnTelemetrySettings.update(telemetrySettings.enabled, true) - } else if (response === TelemetryChoice.Never) { - return await cfnTelemetrySettings.update(telemetrySettings.enabled, false) - } else if (response === TelemetryChoice.Later) { - return await cfnTelemetrySettings.update(telemetrySettings.enabled, false) - } - return false -} - -function logAndReturnTelemetryChoice(choice: boolean, hasResponded?: boolean, lastPromptDate?: number): boolean { - getLogger().info( - 'CloudFormation telemetry: choice=%s, hasResponded=%s, lastPromptDate=%s', - choice, - hasResponded, - lastPromptDate - ) - return choice -} - -/* eslint-disable aws-toolkits/no-banned-usages */ -async function promptTelemetryOptIn( - context: ExtensionContext, - cfnTelemetrySettings: CloudFormationTelemetrySettings -): Promise { - const message = - 'Help us improve the AWS CloudFormation Language Server by sharing anonymous telemetry data with AWS. You can change this preference at any time in aws.cloudformation Settings.' - - const response = await window.showInformationMessage( - message, - TelemetryChoice.Allow, - TelemetryChoice.Later, - TelemetryChoice.Never, - TelemetryChoice.LearnMore - ) - - if (response === TelemetryChoice.LearnMore) { - await env.openExternal(Uri.parse(telemetryDocsUrl)) - return promptTelemetryOptIn(context, cfnTelemetrySettings) - } - - const now = globals.clock.Date.now() - await context.globalState.update(telemetryKeys.lastPromptDate, now) - - // There's a chance our settings aren't registered yet from package.json, so we - // see if we can persist to settings first - try { - // Throws (with no popup) if setting is not registered - cfnTelemetrySettings.get(telemetrySettings.enabled) - } catch (err) { - getLogger().warn(err as Error) - // Save the choice in globalState and save to settings next time handleTelemetryOptIn is called - await context.globalState.update(telemetryKeys.unpersistedResponse, response) - if (response === TelemetryChoice.Allow) { - await context.globalState.update(telemetryKeys.hasResponded, true) - return true - } else if (response === TelemetryChoice.Never) { - await context.globalState.update(telemetryKeys.hasResponded, true) - return false - } else if (response === TelemetryChoice.Later) { - return false - } - } - - // At this point should be able to save and get successfully - await saveTelemetryResponse(response, cfnTelemetrySettings) - await context.globalState.update(telemetryKeys.hasResponded, response !== TelemetryChoice.Later) - return cfnTelemetrySettings.get(telemetrySettings.enabled, false) -} diff --git a/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentFileSelector.ts b/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentFileSelector.ts deleted file mode 100644 index ec1c52a597..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentFileSelector.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window } from 'vscode' -import { CfnEnvironmentFileSelectorItem } from '../cfn-init/cfnProjectTypes' - -export class CfnEnvironmentFileSelector { - public async selectEnvironmentFile( - files: CfnEnvironmentFileSelectorItem[], - requiredParameterCount: number - ): Promise { - // Sort files: matching template path first, then by compatible parameter count (descending) - const sortedFiles = files.sort((a, b) => { - // First sort by hasMatchingTemplatePath (true first) - if (a.hasMatchingTemplatePath !== b.hasMatchingTemplatePath) { - return a.hasMatchingTemplatePath ? -1 : 1 - } - - // Then sort by compatible parameter count (higher first) - const aCount = a.compatibleParameters?.length ?? 0 - const bCount = b.compatibleParameters?.length ?? 0 - return bCount - aCount - }) - - const items = [ - { - label: 'Enter options manually', - detail: 'Skip environment file selection', - parameters: undefined, - }, - ...sortedFiles.map((file) => { - const compatibleCount = file.compatibleParameters?.length ?? 0 - const countText = `${compatibleCount}/${requiredParameterCount} parameters match` - - return { - label: file.hasMatchingTemplatePath ? `$(star-full) ${file.fileName}` : file.fileName, - detail: file.hasMatchingTemplatePath ? `Matching template path • ${countText}` : countText, - parameters: file, - } - }), - ] - - const selected = await window.showQuickPick(items, { - placeHolder: 'Select an environment file or enter manually', - }) - - return selected?.parameters - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentSelector.ts b/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentSelector.ts deleted file mode 100644 index 08a050b30a..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/cfnEnvironmentSelector.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { commands, window } from 'vscode' -import { CfnEnvironmentConfig, CfnEnvironmentLookup, unselectedValue } from '../cfn-init/cfnProjectTypes' -import { commandKey } from '../utils' - -export class CfnEnvironmentSelector { - public async selectEnvironment(environmentLookup: CfnEnvironmentLookup): Promise { - if (Object.keys(environmentLookup).length === 0) { - const choice = await window.showWarningMessage('No environments found in CFN Project', 'Add environment') - - if (choice === 'Add environment') { - void commands.executeCommand(commandKey('init.addEnvironment')) - } - - return - } - - const items = [ - { label: unselectedValue, description: 'Unselect environment' }, - ...Object.values(environmentLookup).map((env: CfnEnvironmentConfig) => ({ - label: env.name, - description: `AWS Profile: ${env.profile}`, - })), - ] - - const selected = await window.showQuickPick(items, { - placeHolder: 'Select an environment', - }) - - return selected?.label - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/diffViewHelper.ts b/packages/core/src/awsService/cloudformation/ui/diffViewHelper.ts deleted file mode 100644 index 9794846504..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/diffViewHelper.ts +++ /dev/null @@ -1,258 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Uri, commands, workspace, Range, Position, window, ThemeColor } from 'vscode' -import { StackChange } from '../stacks/actions/stackActionRequestType' -import * as path from 'path' -import { fs } from '../../../shared/fs/fs' -import * as os from 'os' - -export class DiffViewHelper { - static async openDiff(stackName: string, changes: StackChange[], resourceId?: string) { - const tmpDir = os.tmpdir() - const beforePath = path.join(tmpDir, `${stackName}-before.json`) - const afterPath = path.join(tmpDir, `${stackName}-after.json`) - - const beforeData: Record = {} - const afterData: Record = {} - - for (const change of changes) { - const rc = change.resourceChange - if (!rc?.logicalResourceId) { - continue - } - - const id = rc.logicalResourceId - - if (rc.action !== 'Add' || rc.resourceDriftStatus === 'DELETED') { - if (rc.beforeContext) { - try { - beforeData[id] = JSON.parse(rc.beforeContext) as Record - } catch { - beforeData[id] = {} - } - } else { - beforeData[id] = {} - } - } - - if (rc.action !== 'Remove') { - if (rc.afterContext) { - try { - afterData[id] = JSON.parse(rc.afterContext) as Record - } catch { - afterData[id] = {} - } - } else { - afterData[id] = {} - } - } - - if (!rc.beforeContext && !rc.afterContext) { - if (rc.details) { - for (const detail of rc.details) { - const target = detail.Target - if (target?.Name) { - if (rc.action !== 'Add') { - ;(beforeData[id] as Record)[target.Name] = - target.BeforeValue ?? '' - } - if (rc.action !== 'Remove') { - ;(afterData[id] as Record)[target.Name] = - target.AfterValue ?? '' - } - } - } - } - } - } - - await fs.writeFile(beforePath, JSON.stringify(beforeData, undefined, 2)) - await fs.writeFile(afterPath, JSON.stringify(afterData, undefined, 2)) - - const beforeUri = Uri.file(beforePath) - const afterUri = Uri.file(afterPath) - - await commands.executeCommand('vscode.diff', beforeUri, afterUri, `${stackName}: Before ↔ After`) - - this.addDriftDecorations(beforeUri, changes) - - if (resourceId) { - // Find the line with the resource ID in the after doc. - // In a deleted resource case this will just be the top - const editor = await workspace.openTextDocument(afterUri) - const text = editor.getText() - const lines = text.split('\n') - const lineIndex = lines.findIndex((line) => line.includes(`"${resourceId}"`)) - - if (lineIndex !== -1) { - await commands.executeCommand('vscode.diff', beforeUri, afterUri, `${stackName}: Before ↔ After`, { - selection: new Range(new Position(lineIndex, 0), new Position(lineIndex + 1, 0)), - }) - } - } - } - - private static propertyExistsInContext(context: string, path: string): boolean { - try { - const data = JSON.parse(context) - const pathParts = path.split('/').filter(Boolean) - let current: any = data - - for (const part of pathParts) { - if (/^\d+$/.test(part)) { - const index = parseInt(part, 10) - if (Array.isArray(current) && current[index] !== undefined) { - current = current[index] - } else { - return false - } - } else if (current && typeof current === 'object' && part in current) { - current = current[part] - } else { - return false - } - } - return true - } catch { - return false - } - } - - private static findPropertyLineIndex(lines: string[], startLineIndex: number, path: string): number { - const pathParts = path.split('/').filter(Boolean) - let currentLineIndex = startLineIndex - - for (const part of pathParts) { - // Skip numeric array indices - they don't appear as keys in JSON - if (/^\d+$/.test(part)) { - continue - } - - const foundIndex = lines.findIndex((line, idx) => idx > currentLineIndex && line.includes(`"${part}"`)) - if (foundIndex < 0) { - return -1 - } - currentLineIndex = foundIndex - } - - return currentLineIndex - } - - private static createDeletedResourceHoverMessage(logicalResourceId: string): string { - return [ - '### ⚠️ Resource Drift Detected', - '', - `**Resource:** \`${logicalResourceId}\``, - '', - '**Status:** Resource Deleted', - '', - '*This resource was deleted sometime after the previous deployment (out-of-band).*', - ].join('\n') - } - - private static createPropertyDriftHoverMessage( - logicalResourceId: string, - path: string, - previousValue: string, - actualValue: string - ): string { - return [ - '### ⚠️ Resource Drift Detected', - '', - `**Resource:** \`${logicalResourceId}\``, - '', - `**Property:** \`${path}\``, - '', - '| Source | Value |', - '|--------|-------|', - `| 📄 Template | \`${previousValue}\` |`, - `| ☁️ Live AWS | \`${actualValue}\` |`, - '', - '*The live resource has drifted from the previously deployed template.*', - ].join('\n') - } - - private static addDriftDecorations(beforeUri: Uri, changes: StackChange[]) { - const driftDecorationType = window.createTextEditorDecorationType({ - after: { - contentText: ' ⚠️ Drifted', - color: new ThemeColor('editorWarning.foreground'), - fontWeight: 'bold', - }, - backgroundColor: new ThemeColor('editorWarning.background'), - cursor: 'pointer', - }) - - setTimeout(() => { - const editors = window.visibleTextEditors.filter( - (editor) => editor.document.uri.toString() === beforeUri.toString() - ) - - for (const editor of editors) { - const decorations: any[] = [] - const lines = editor.document.getText().split('\n') - - for (const change of changes) { - const rc = change.resourceChange - if (!rc?.logicalResourceId) { - continue - } - - const resourceLineIndex = lines.findIndex((line) => line.includes(`"${rc.logicalResourceId}"`)) - if (resourceLineIndex < 0) { - continue - } - - // Handle DELETED drift status - if (rc.resourceDriftStatus === 'DELETED') { - const line = lines[resourceLineIndex] - const endCol = line.trimEnd().length - const range = new Range(resourceLineIndex, endCol, resourceLineIndex, endCol) - const hoverMessage = this.createDeletedResourceHoverMessage(rc.logicalResourceId) - - decorations.push({ range, hoverMessage }) - continue - } - - if (!rc.details) { - continue - } - - for (const detail of rc.details) { - const target = detail.Target - const drift = target?.Drift || target?.LiveResourceDrift - if (drift && target?.Path && drift.ActualValue !== undefined) { - // Check if property exists in afterContext - if (rc.afterContext && !this.propertyExistsInContext(rc.afterContext, target.Path)) { - continue - } - - const currentLineIndex = this.findPropertyLineIndex(lines, resourceLineIndex, target.Path) - if (currentLineIndex <= resourceLineIndex) { - continue - } - - const line = lines[currentLineIndex] - const endCol = line.trimEnd().length - // sets hover range to just the decoration - const range = new Range(currentLineIndex, endCol, currentLineIndex, endCol) - const hoverMessage = this.createPropertyDriftHoverMessage( - rc.logicalResourceId, - target.Path, - drift.PreviousValue, - drift.ActualValue - ) - - decorations.push({ range, hoverMessage }) - } - } - } - - editor.setDecorations(driftDecorationType, decorations) - } - }, 100) - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/diffWebviewProvider.ts b/packages/core/src/awsService/cloudformation/ui/diffWebviewProvider.ts deleted file mode 100644 index f7c06f5f6f..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/diffWebviewProvider.ts +++ /dev/null @@ -1,501 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WebviewView, WebviewViewProvider, commands, Disposable } from 'vscode' -import { DeploymentMode, StackChange, ValidationDetail } from '../stacks/actions/stackActionRequestType' -import { DiffViewHelper } from './diffViewHelper' -import { commandKey } from '../utils' -import { StackViewCoordinator } from './stackViewCoordinator' -import { showWarningConfirmation } from './message' -import { ChangeSetStatus } from '@aws-sdk/client-cloudformation' - -const webviewCommandOpenDiff = 'openDiff' - -export class DiffWebviewProvider implements WebviewViewProvider, Disposable { - private _view?: WebviewView - private stackName = '' - private changes: StackChange[] = [] - private changeSetName?: string - private enableDeployments: boolean = false - private currentPage: number = 0 - private pageSize: number = 50 - private totalPages: number = 0 - private readonly disposables: Disposable[] = [] - private validationDetail: ValidationDetail[] = [] - private deploymentMode?: DeploymentMode - private changeSetStatus?: string - - constructor(private readonly coordinator: StackViewCoordinator) { - this.disposables.push( - coordinator.onDidChangeStack((state) => { - if (!state.isChangeSetMode) { - this.stackName = '' - this.changes = [] - this.changeSetName = undefined - if (this._view) { - this._view.webview.html = this.getHtmlContent() - } - } - }) - ) - } - - async updateData( - stackName: string, - changes: StackChange[] = [], - changeSetName?: string, - enableDeployments = false, - validationDetail?: ValidationDetail[], - deploymentMode?: DeploymentMode, - changeSetStatus?: string - ) { - this.stackName = stackName - this.changes = changes - this.changeSetName = changeSetName - this.enableDeployments = enableDeployments - this.currentPage = 0 - this.totalPages = Math.ceil(changes.length / this.pageSize) - if (validationDetail) { - this.validationDetail = validationDetail - } - this.deploymentMode = deploymentMode - this.changeSetStatus = changeSetStatus - - await this.coordinator.setChangeSetMode(stackName, true) - if (this._view) { - this._view.webview.html = this.getHtmlContent() - } - } - - resolveWebviewView(webviewView: WebviewView) { - this._view = webviewView - webviewView.webview.options = { enableScripts: true } - webviewView.webview.html = this.getHtmlContent() - - webviewView.webview.onDidReceiveMessage(async (message: { command: string; resourceId?: string }) => { - if (message.command === webviewCommandOpenDiff) { - void DiffViewHelper.openDiff(this.stackName, this.changes, message.resourceId) - } else if (message.command === 'confirmDeploy') { - if (this.changeSetName) { - const errorCount = this.getErrorCount() - const warningCount = this.getWarningCount() - - if (errorCount === 0 && warningCount > 0) { - const proceed = await showWarningConfirmation(warningCount) - if (!proceed) { - return - } - } - - void commands.executeCommand(commandKey('api.executeChangeSet'), this.stackName, this.changeSetName) - this.changeSetName = undefined - this.enableDeployments = false - this._view!.webview.html = this.getHtmlContent() - } - } else if (message.command === 'deleteChangeSet') { - void commands.executeCommand(commandKey('stacks.deleteChangeSet'), { - stackName: this.stackName, - changeSetName: this.changeSetName, - }) - this.changeSetName = undefined - this.enableDeployments = false - this._view!.webview.html = this.getHtmlContent() - } else if (message.command === 'nextPage') { - if (this.currentPage < this.totalPages - 1) { - this.currentPage++ - this._view!.webview.html = this.getHtmlContent() - } - } else if (message.command === 'prevPage') { - if (this.currentPage > 0) { - this.currentPage-- - this._view!.webview.html = this.getHtmlContent() - } - } - }) - } - - private getHtmlContent(): string { - const changes = this.changes - - const startIndex = this.currentPage * this.pageSize - const endIndex = startIndex + this.pageSize - const displayedChanges = changes.slice(startIndex, endIndex) - const hasNext = this.currentPage < this.totalPages - 1 - const hasPrev = this.currentPage > 0 - const terminalChangeSetStatuses: string[] = [ - ChangeSetStatus.CREATE_COMPLETE, - ChangeSetStatus.FAILED, - ChangeSetStatus.DELETE_FAILED, - ] - - const deletionButton = ` - - ` - - if (!changes || changes.length === 0) { - return ` - - - - - - -

No changes detected for stack: ${this.stackName}

- ${ - this.changeSetName && - this.changeSetStatus && - terminalChangeSetStatuses.includes(this.changeSetStatus) - ? ` -
- ${deletionButton} -
- - ` - : '' - } - - - ` - } - - // Check if REVERT_DRIFT change set or any resource has drift - // TODO: adapt if we do real backend pagination - // TODO: remove resource fallback once server is passing deploymentMode - const hasDrift = - this.deploymentMode === DeploymentMode.REVERT_DRIFT || - changes.some( - (change) => - change.resourceChange?.resourceDriftStatus || - change.resourceChange?.details?.some( - (detail) => detail.Target?.Drift || detail.Target?.LiveResourceDrift - ) - ) - - let tableHtml = ` - - - - - - - - ${ - hasDrift - ? ` - ` - : '' - } - ` - - for (const [changeIndex, change] of displayedChanges.entries()) { - const rc = change.resourceChange - if (!rc) { - continue - } - - const borderColor = - rc.action === 'Add' - ? 'var(--vscode-gitDecoration-addedResourceForeground)' - : rc.action === 'Remove' - ? 'var(--vscode-gitDecoration-deletedResourceForeground)' - : rc.action === 'Modify' - ? 'var(--vscode-gitDecoration-modifiedResourceForeground)' - : 'transparent' - - const hasDetails = rc.details && rc.details.length > 0 - const expandIcon = hasDetails - ? '' - : '' - - const driftStatus = rc.resourceDriftStatus - const hasDriftDetails = rc.details?.some( - (detail) => detail.Target?.Drift || detail.Target?.LiveResourceDrift - ) - let driftDisplay = '' - if (driftStatus === 'DELETED') { - driftDisplay = '⚠️ Deleted' - } else if (hasDriftDetails) { - driftDisplay = '⚠️ Modified' - } else if (driftStatus && driftStatus !== 'IN_SYNC') { - driftDisplay = `⚠️ ${driftStatus}` - } - - tableHtml += ` - - - - - - ${ - hasDrift - ? ` - ` - : '' - } - ` - - if (hasDetails) { - tableHtml += ` - ` - } - } - - tableHtml += `
ActionLogicalResourceIdPhysicalResourceIdResourceTypeReplacementDrift Status
- ${expandIcon} - ${rc.action ?? 'Unknown'}${rc.logicalResourceId ?? 'Unknown'}${rc.physicalResourceId ?? ' '}${rc.resourceType ?? 'Unknown'}${rc.replacement ?? 'N/A'}${driftDisplay || '-'}
` - - const paginationControls = - this.totalPages > 1 - ? ` -
- Page ${this.currentPage + 1} of ${this.totalPages} - - -
- ` - : '' - - const warningBanner = - this.getWarningCount() > 0 - ? ` -
- ⚠️ ${this.getWarningCount()} warning(s) found -
- ` - : '' - - const viewDiffButton = ` -
- -
- ` - - const deploymentButtons = - this.changeSetName && - this.enableDeployments && - this.changeSetStatus && - terminalChangeSetStatuses.includes(this.changeSetStatus) - ? ` -
- ${ - this.changeSetStatus === ChangeSetStatus.CREATE_COMPLETE - ? ` - ` - : '' - } - ${deletionButton} -
- ` - : '' - - return ` - - - - - - - ${warningBanner} - ${paginationControls} -
- ${viewDiffButton}${deploymentButtons} - ${tableHtml} -
- - - - ` - } - - private getWarningCount(): number { - return this.validationDetail.filter((detail) => detail.Severity === 'INFO').length - } - - private getErrorCount(): number { - return this.validationDetail.filter((detail) => detail.Severity === 'ERROR').length - } - - dispose(): void { - for (const d of this.disposables) { - d.dispose() - } - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/htmlPreview.ts b/packages/core/src/awsService/cloudformation/ui/htmlPreview.ts deleted file mode 100644 index 500596d0ca..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/htmlPreview.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ViewColumn } from 'vscode' -import { docPreview } from '../documents/documentPreview' - -export async function htmlPreview(content: unknown, title: string) { - if (typeof content !== 'string') { - return - } - - await docPreview({ - content: `# ${title}\n${content}`, - language: 'markdown', - viewColumn: ViewColumn.Beside, - preserveFocus: true, - }) -} diff --git a/packages/core/src/awsService/cloudformation/ui/inputBox.ts b/packages/core/src/awsService/cloudformation/ui/inputBox.ts deleted file mode 100644 index cbe7eb2444..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/inputBox.ts +++ /dev/null @@ -1,629 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window, workspace, Uri, commands } from 'vscode' -import { - validateStackName, - validateParameterValue, - validateChangeSetName, -} from '../stacks/actions/stackActionInputValidation' -import { Parameter, Capability, Tag, OnStackFailure, Stack, StackStatus } from '@aws-sdk/client-cloudformation' -import { - TemplateParameter, - ResourceToImport, - TemplateResource, - OptionalFlagMode, - DeploymentMode, -} from '../stacks/actions/stackActionRequestType' -import { DocumentManager } from '../documents/documentManager' -import path from 'path' -import fs from '../../../shared/fs/fs' -import { unselectedValue } from '../cfn-init/cfnProjectTypes' - -export async function getTemplatePath(documentManager: DocumentManager): Promise { - const validTemplates = documentManager - .get() - .filter((doc) => doc.cfnType === 'template') - .map((doc) => { - const uri = doc.uri - - return { - label: doc.fileName, - description: workspace.asRelativePath(Uri.parse(uri)), - uri: uri, - } - }) - .sort((a, b) => a.label.localeCompare(b.label)) - - const options = [ - ...validTemplates, - { - label: '$(file) Browse for template file...', - description: 'Select a CloudFormation template file', - uri: 'browse', - }, - ] - - const selected = await window.showQuickPick(options, { - placeHolder: 'Select CloudFormation template', - ignoreFocusOut: true, - }) - - if (!selected) { - return undefined - } - - if (selected.uri === 'browse') { - const fileUri = await window.showOpenDialog({ - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - filters: { - 'CloudFormation Templates': ['yaml', 'yml', 'json', 'template', 'cfn', 'txt', ''], - }, - title: 'Select CloudFormation Template', - }) - - return fileUri?.[0]?.fsPath - } - - return selected.uri -} - -export async function getStackName(prefill?: string): Promise { - return await window.showInputBox({ - prompt: 'Enter the CloudFormation stack name', - value: prefill, - validateInput: validateStackName, - ignoreFocusOut: true, - }) -} - -export async function getChangeSetName(prefill?: string): Promise { - return await window.showInputBox({ - prompt: 'Enter the CloudFormation change set name', - value: prefill, - validateInput: validateChangeSetName, - ignoreFocusOut: true, - }) -} - -export async function getParameterValues( - templateParameters: TemplateParameter[], - prefillParameters?: Parameter[] -): Promise { - const parameters: Parameter[] = [] - - for (const param of templateParameters) { - const prefillCandidate = prefillParameters?.find((p) => p.ParameterKey === param.name)?.ParameterValue - - // If we are using a previous parameter value, we must ensure that it is compatible with possibly modified template - const prefillValue = - prefillCandidate && !validateParameterValue(prefillCandidate, param) ? prefillCandidate : undefined - - const value = await getParameterValue(param, prefillValue) - if (value) { - parameters.push(value) - } - } - - return parameters -} - -async function getParameterValue(parameter: TemplateParameter, prefill?: string): Promise { - const prompt = `Enter value for parameter "${parameter.name}"${parameter.Description ? ` - ${parameter.Description}` : ''}` - const placeHolder = parameter.Default ? `Default: ${parameter.Default}` : (parameter.Type ?? 'String') - const allowedInfo = parameter.AllowedValues ? ` (Allowed: ${parameter.AllowedValues.join(', ')})` : '' - - const value = await window.showInputBox({ - prompt: prompt + allowedInfo, - placeHolder, - value: prefill ?? parameter.Default?.toString(), - validateInput: (input: string) => validateParameterValue(input, parameter), - ignoreFocusOut: true, - }) - - if (value === undefined) { - return undefined - } - - return { ParameterKey: parameter.name, ParameterValue: value } -} - -export async function confirmCapabilities(capabilities: Capability[]): Promise { - // Confirm if user wants to use detected capabilities - const useDetected = await window.showQuickPick(['Yes', 'No, modify capabilities'], { - placeHolder: `Proceed with detected capabilities: ${capabilities.join(', ') || '(none)'}?`, - canPickMany: false, - }) - - if (!useDetected) { - return undefined // User cancelled - } - - if (useDetected === 'Yes') { - return capabilities - } - - // Allow user to modify capabilities - const allCapabilities = new Map([ - [Capability.CAPABILITY_IAM, 'Allows deployment to create IAM resources'], - [Capability.CAPABILITY_NAMED_IAM, 'Allows deployment to create named IAM resources'], - [Capability.CAPABILITY_AUTO_EXPAND, 'Allows deployment to create resources using macros'], - ]) - - const selected = await window.showQuickPick( - Array.from(allCapabilities.entries()).map(([cap, description]) => ({ - label: cap, - description: description, - picked: capabilities.includes(cap), - })), - { - placeHolder: 'Select capabilities to use', - canPickMany: true, - } - ) - - return selected ? selected.map((item) => item.label) : undefined -} - -export async function shouldImportResources(): Promise { - const choice = await window.showQuickPick(['Deploy new/updated resources', 'Import existing resources'], { - placeHolder: 'Select deployment mode', - ignoreFocusOut: true, - }) - - return choice === 'Import existing resources' -} - -export async function chooseOptionalFlagSuggestion(): Promise { - const choice = await window.showQuickPick( - [ - { - label: OptionalFlagMode.Input, - }, - { - label: OptionalFlagMode.DevFriendly, - }, - { - label: OptionalFlagMode.Skip, - description: 'Skip optional flags', - }, - ], - { - placeHolder: 'Enter optional change set flags?', - ignoreFocusOut: true, - } - ) - - return choice?.label -} - -export async function getTags(previousTags?: Tag[]): Promise { - const prefill = previousTags - ?.filter((tag) => tag.Key && tag.Value) - .map((tag) => `${tag.Key}=${tag.Value}`) - .join(',') - - const input = await window.showInputBox({ - prompt: 'Enter CloudFormation tags (key=value pairs, comma-separated). Enter empty for no tags', - placeHolder: 'key1=value1,key2=value2,key3=value3', - value: prefill, - validateInput: (value) => { - if (!value) { - return undefined - } - const isValid = /^[^=,]+=[^=,]+(,[^=,]+=[^=,]+)*$/.test(value.trim()) - return isValid ? undefined : 'Format: key1=value1,key2=value2' - }, - ignoreFocusOut: true, - }) - - if (!input) { - return undefined - } - - return input.split(',').map((pair) => { - const [key, value] = pair.split('=').map((s) => s.trim()) - return { Key: key, Value: value } - }) -} - -export async function getIncludeNestedStacks(): Promise { - return ( - await window.showQuickPick( - [ - { label: 'Yes', value: true }, - { label: 'No', value: false }, - ], - { placeHolder: 'Include nested stacks?', ignoreFocusOut: true } - ) - )?.value -} - -export async function getImportExistingResources(): Promise { - return ( - await window.showQuickPick( - [ - { label: 'Yes', value: true }, - { label: 'No', value: false }, - ], - { placeHolder: 'Import existing resources?', ignoreFocusOut: true } - ) - )?.value -} - -export async function getOnStackFailure(stackDetails?: Stack): Promise { - const options: Array<{ label: string; description: string; value: OnStackFailure }> = [ - { label: 'Do nothing', description: 'Leave stack in failed state', value: OnStackFailure.DO_NOTHING }, - { label: 'Rollback', description: 'Rollback to previous state', value: OnStackFailure.ROLLBACK }, - ] - - if (!stackDetails || stackDetails.StackStatus === StackStatus.REVIEW_IN_PROGRESS) { - // only a valid option for CREATE - options.unshift({ label: 'Delete', description: 'Delete the stack on failure', value: OnStackFailure.DELETE }) - } - - return (await window.showQuickPick(options, { placeHolder: 'What to do on stack failure?', ignoreFocusOut: true })) - ?.value -} - -export async function getDeploymentMode(): Promise { - return ( - await window.showQuickPick( - [ - { - label: 'Revert Drift', - description: 'Revert drift during deployment (disables dev friendly flags)', - value: DeploymentMode.REVERT_DRIFT, - }, - { label: 'Standard', description: 'No special handling during deployment', value: undefined }, - ], - { placeHolder: 'Select deployment mode', ignoreFocusOut: true } - ) - )?.value -} - -export async function getResourcesToImport( - templateResources: TemplateResource[] -): Promise { - const resourcesToImport: ResourceToImport[] = [] - - const selectedResources = await window.showQuickPick( - templateResources.map((r) => ({ - label: r.logicalId, - description: r.type, - picked: false, - resource: r, - })), - { - placeHolder: 'Select resources to import', - canPickMany: true, - ignoreFocusOut: true, - } - ) - - if (!selectedResources || selectedResources.length === 0) { - return undefined - } - - for (const selected of selectedResources) { - const resourceIdentifier = await getResourceIdentifier( - selected.resource.logicalId, - selected.resource.type, - selected.resource.primaryIdentifierKeys, - selected.resource.primaryIdentifier - ) - - if (!resourceIdentifier) { - return undefined - } - - resourcesToImport.push({ - ResourceType: selected.resource.type, - LogicalResourceId: selected.resource.logicalId, - ResourceIdentifier: resourceIdentifier, - }) - } - - return resourcesToImport -} - -async function getResourceIdentifier( - logicalId: string, - resourceType: string, - primaryIdentifierKeys?: string[], - primaryIdentifier?: Record -): Promise | undefined> { - if (!primaryIdentifierKeys || primaryIdentifierKeys.length === 0) { - void window.showErrorMessage(`No primary identifier keys found for ${resourceType}`) - return undefined - } - - if (primaryIdentifier && Object.keys(primaryIdentifier).length > 0) { - const id = Object.values(primaryIdentifier).join('|') - - const usePrimary = await window.showQuickPick([id, 'Enter manually'], { - placeHolder: `Select primary identifier for ${logicalId}`, - ignoreFocusOut: true, - }) - if (!usePrimary) { - return undefined - } - if (usePrimary === id) { - return primaryIdentifier - } - } - - const identifiers: Record = {} - - for (const key of primaryIdentifierKeys) { - const value = await window.showInputBox({ - prompt: `Enter ${key} for ${logicalId} (${resourceType})`, - placeHolder: `Physical ${key} of existing resource`, - ignoreFocusOut: true, - }) - - if (!value) { - return undefined - } - - identifiers[key] = value - } - - return identifiers -} - -export async function getProjectName(prefillValue: string | undefined) { - return await window.showInputBox({ - prompt: 'Enter project name', - value: prefillValue, - validateInput: (v) => { - if (!v.trim()) { - return 'Required' - } - if (v.trim() === unselectedValue) { - return `Project name cannot be ${unselectedValue}` - } - if (!/^[a-zA-Z0-9_-]{1,64}$/.test(v.trim())) { - return 'Must be 1-64 characters, alphanumeric with hyphens and underscores only' - } - return undefined - }, - }) -} - -export async function getProjectPath(prefillValue: string) { - const selected = await window.showOpenDialog({ - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - title: 'Select project directory', - defaultUri: prefillValue ? Uri.file(prefillValue) : undefined, - }) - - if (!selected || selected.length === 0) { - return undefined // User cancelled - } - - const input = selected[0].fsPath - - // Validate the selected path - try { - const resolvedPath = path.resolve(input) - const parentDir = path.dirname(resolvedPath) - - const parentPathExists = await fs.existsDir(parentDir) - if (!parentPathExists) { - void window.showErrorMessage('Parent directory does not exist.') - return undefined - } - - // Check if we can write to the directory - try { - await fs.checkPerms(resolvedPath, '*w*') - } catch { - // If directory doesn't exist, check parent directory write permissions - try { - await fs.checkPerms(parentDir, '*w*') - } catch { - void window.showErrorMessage( - 'Cannot write to this location. Please choose a path you have write permissions for.' - ) - return undefined - } - } - - // Check if cfn-project directory already exists - const cfnProjectPath = path.join(resolvedPath, 'cfn-project') - const cfnProjectExists = await fs.existsDir(cfnProjectPath) - if (cfnProjectExists) { - void window.showErrorMessage( - 'A cfn-project directory already exists at this location. Please choose a different path.' - ) - return undefined - } - - return input - } catch (error) { - void window.showErrorMessage('Invalid path format.') - return undefined - } -} - -export async function getEnvironmentName() { - return await window.showInputBox({ - prompt: 'Environment name', - validateInput: (v) => { - if (!v.trim()) { - return 'Required' - } - if (!/^[a-zA-Z0-9_-]{1,32}$/.test(v.trim())) { - return 'Must be 1-32 characters, alphanumeric with hyphens and underscores only' - } - return undefined - }, - }) -} - -export async function shouldSaveFlagsToFile(): Promise { - const config = workspace.getConfiguration('aws.cloudformation') - const currentSetting = config.get('environment.saveOptions', 'alwaysAsk') - - if (currentSetting === 'alwaysSave') { - return true - } - if (currentSetting === 'neverSave') { - return false - } - - const choice = await window.showQuickPick( - [ - { - label: 'Save options to file', - description: 'Save the deployment options to a file in your environment', - value: 'save', - }, - { - label: 'Configure in settings', - description: - 'Open CloudFormation Environment settings (settings will not affect this current deployment)', - value: 'configure', - }, - { - label: 'Skip for now', - description: 'Do not save options to environment file', - value: 'skip', - }, - ], - { - placeHolder: 'Choose deployment options configuration for CloudFormation template', - ignoreFocusOut: true, - } - ) - - if (!choice) { - return false - } - - if (choice.value === 'configure') { - await commands.executeCommand('workbench.action.openSettings', 'aws.cloudformation.environment.saveOptions') - return undefined // Exit command, let user configure first - } - - return choice.value === 'save' -} - -export async function getFilePath(environmentDir: string) { - while (true) { - const input = await window.showInputBox({ - prompt: 'Enter File Name to save options to (must be .json, .yaml, or .yml)', - ignoreFocusOut: true, - validateInput: (v) => { - if (!v.trim()) { - return 'Required' - } - if (!/^[a-zA-Z0-9_-]{1,32}\.(json|yaml|yml)$/.test(v.trim())) { - return 'Must be 1-32 characters (alphanumeric with hyphens and underscores) and end with .json, .yaml, or .yml' - } - return undefined - }, - }) - - if (input === undefined) { - return undefined - } // User cancelled - - // Validate after input - try { - const resolvedPath = path.resolve(path.join(environmentDir, input.trim())) - - const parentPathExists = await fs.existsFile(resolvedPath) - if (parentPathExists) { - void window.showErrorMessage('File already exists. Please try again.') - continue // Ask again - } - - return resolvedPath - } catch (error) { - void window.showErrorMessage('Environment directory was not found') - return - } - } -} - -export async function shouldUploadToS3(): Promise { - const config = workspace.getConfiguration('aws.cloudformation') - const currentSetting = config.get('s3', 'alwaysAsk') - - if (currentSetting === 'alwaysUpload') { - return true - } - if (currentSetting === 'neverUpload') { - return false - } - - const choice = await window.showQuickPick( - [ - { - label: 'Upload to S3', - description: 'Upload template to S3', - value: 'upload', - }, - { - label: 'Do not upload to S3', - description: 'Do not upload template to S3', - value: 'skip', - }, - { - label: 'Configure in Settings', - description: 'Open CloudFormation S3 settings', - value: 'configure', - }, - ], - { - placeHolder: 'Choose S3 upload option for CloudFormation template', - } - ) - - if (!choice) { - return false - } - - if (choice.value === 'configure') { - await commands.executeCommand('workbench.action.openSettings', 'aws.cloudformation.s3') - return undefined // Exit command, let user configure first - } - - return choice.value === 'upload' -} - -export async function getS3Bucket(prompt?: string): Promise { - return await window.showInputBox({ - prompt: prompt || 'Enter S3 bucket name', - validateInput: (value) => { - if (!value.trim()) { - return 'Bucket name is required' - } - if (!/^[a-z0-9.-]{3,63}$/.test(value)) { - return 'Invalid bucket name format' - } - return undefined - }, - }) -} - -export async function getS3Key(prefill?: string): Promise { - return await window.showInputBox({ - prompt: 'Enter S3 object key', - value: prefill, - validateInput: (value) => { - if (!value.trim()) { - return 'Object key is required' - } - return undefined - }, - }) -} diff --git a/packages/core/src/awsService/cloudformation/ui/message.ts b/packages/core/src/awsService/cloudformation/ui/message.ts deleted file mode 100644 index 5505600252..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/message.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { getDeploymentStatus } from '../stacks/actions/stackActionApi' -import { StackActionPhase, StackActionState } from '../stacks/actions/stackActionRequestType' - -export async function showDeploymentCompletion( - client: LanguageClient, - deploymentId: string, - stackName: string -): Promise { - try { - const pollResult = await getDeploymentStatus(client, { id: deploymentId }) - - if ( - pollResult.phase === StackActionPhase.DEPLOYMENT_COMPLETE && - pollResult.state === StackActionState.SUCCESSFUL - ) { - void window.showInformationMessage(`Deployment completed successfully for stack: ${stackName}`) - } else if ( - pollResult.phase === StackActionPhase.DEPLOYMENT_FAILED || - pollResult.phase === StackActionPhase.VALIDATION_FAILED || - pollResult.state === StackActionState.FAILED - ) { - void window.showErrorMessage(`Deployment failed for stack: ${stackName}`) - } else { - void window.showWarningMessage(`Deployment status unknown for stack: ${stackName}`) - } - } catch (error) { - void window.showErrorMessage(`Error checking deployment status for stack: ${stackName}`) - } -} - -export function showDeploymentSuccess(stackName: string) { - void window.showInformationMessage(`Deployment completed successfully for stack: ${stackName}`) -} - -export function showChangeSetDeletionSuccess(changeSetName: string, stackName: string) { - void window.showInformationMessage( - `Deletion completed successfully for change set: ${changeSetName}, in stack: ${stackName}` - ) -} - -export function showDeploymentFailure(stackName: string, failureReason: string) { - void window.showErrorMessage(`Deployment failed for stack: ${stackName} with reason: ${failureReason}`) -} - -export function showChangeSetDeletionFailure(changeSetName: string, stackName: string, failureReason: string) { - void window.showErrorMessage( - `Change Set Deletion failed for change set: ${changeSetName}, in stack: ${stackName} with reason: ${failureReason}` - ) -} - -export function showValidationStarted(stackName: string) { - void window.showInformationMessage(`Validation started for stack: ${stackName}`) -} - -export function showValidationSuccess(stackName: string) { - void window.showInformationMessage(`Validation completed successfully for stack: ${stackName}`) -} - -export function showValidationFailure(stackName: string, failureReason: string) { - void window.showErrorMessage(`Validation failed for stack: ${stackName} with reason: ${failureReason}`) -} - -export function showDeploymentStarted(stackName: string) { - void window.showInformationMessage(`Deployment started for stack: ${stackName}`) -} - -export function showChangeSetDeletionStarted(changeSetName: string, stackName: string) { - void window.showInformationMessage(`Deletion started for change set: ${changeSetName}, in stack: ${stackName}`) -} - -export function showErrorMessage(message: string) { - void window.showErrorMessage(message) -} - -export async function showWarningConfirmation(warningCount: number): Promise { - const proceed = await window.showWarningMessage( - `There are ${warningCount} warning(s). Do you want to proceed with deployment?`, - 'Proceed', - 'Cancel' - ) - return proceed === 'Proceed' -} diff --git a/packages/core/src/awsService/cloudformation/ui/relatedResourceSelector.ts b/packages/core/src/awsService/cloudformation/ui/relatedResourceSelector.ts deleted file mode 100644 index 63be2d7c3f..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/relatedResourceSelector.ts +++ /dev/null @@ -1,52 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { getAuthoredResourceTypes, getRelatedResourceTypes } from '../relatedResources/relatedResourcesApi' - -export class RelatedResourceSelector { - constructor(private client: LanguageClient) {} - - async selectAuthoredResourceType(templateUri: string): Promise { - const resourceTypes = await getAuthoredResourceTypes(this.client, templateUri) - if (resourceTypes.length === 0) { - void window.showInformationMessage('No resources found in the current template') - return undefined - } - - return window.showQuickPick(resourceTypes, { - placeHolder: 'Select an existing resource type from your template', - canPickMany: false, - }) - } - - async promptCreateOrImport(): Promise<'create' | 'import' | undefined> { - const action = await window.showQuickPick(['Create new', 'Import existing'], { - placeHolder: 'How would you like to add related resource types?', - canPickMany: false, - }) - - if (!action) { - return undefined - } - - return action === 'Create new' ? 'create' : 'import' - } - - async selectRelatedResourceTypes(selectedResourceType: string): Promise { - const relatedTypes = await getRelatedResourceTypes(this.client, { parentResourceType: selectedResourceType }) - - if (relatedTypes.length === 0) { - void window.showInformationMessage(`No related resources found for ${selectedResourceType}`) - return undefined - } - - return window.showQuickPick(relatedTypes, { - placeHolder: 'Select related resource types', - canPickMany: true, - }) - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/resourceSelector.ts b/packages/core/src/awsService/cloudformation/ui/resourceSelector.ts deleted file mode 100644 index 9444896a48..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/resourceSelector.ts +++ /dev/null @@ -1,202 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { - ResourceTypesRequest, - ListResourcesRequest, - ResourceList, - SearchResourceResult, -} from '../resources/resourceRequestTypes' -import { handleLspError } from '../utils/onlineErrorHandler' -import { getLogger } from '../../../shared/logger/logger' - -export interface ResourceSelectionResult { - resourceType: string - resourceIdentifier: string -} - -export interface ResourceOperations { - getCached: (resourceType: string) => ResourceList | undefined - loadMore: (resourceType: string, nextToken: string) => Promise - search: (resourceType: string, identifier: string) => Promise -} - -export class ResourceSelector { - public refreshCallback?: () => void - - constructor(private client: LanguageClient) {} - - setRefreshCallback(callback: () => void): void { - this.refreshCallback = callback - } - - async selectResourceTypes(selectedTypes: string[] = [], multiSelect = true): Promise { - try { - const response = await this.client.sendRequest(ResourceTypesRequest, {}) - const availableTypes = response.resourceTypes - - if (availableTypes.length === 0) { - void window.showWarningMessage('No resource types available') - return undefined - } - - const quickPickItems = availableTypes.map((type: string) => ({ - label: type, - picked: selectedTypes.includes(type), - })) - - const result = await window.showQuickPick(quickPickItems, { - canPickMany: multiSelect, - placeHolder: 'Select resource types', - title: 'Select Resource Types', - }) - - if (!result) { - return undefined - } - - if (Array.isArray(result)) { - return result.map((item: { label: string }) => item.label) - } - return [(result as { label: string }).label] - } catch (error) { - getLogger().error(`Failed to get resource types: ${error}`) - void window.showErrorMessage('Failed to get available resource types') - return undefined - } - } - - async selectResources( - multiSelect = true, - preSelectedTypes?: string[], - resourceOperations?: ResourceOperations - ): Promise { - try { - let selectedTypes: string[] - - if (preSelectedTypes && preSelectedTypes.length > 0) { - selectedTypes = preSelectedTypes - } else { - const types = await this.selectResourceTypes([], multiSelect) - if (!types || types.length === 0) { - return [] - } - selectedTypes = types - } - - const allSelections: ResourceSelectionResult[] = [] - - for (const resourceType of selectedTypes) { - const selection = await this.selectResourcesForType(resourceType, multiSelect, resourceOperations) - allSelections.push(...selection) - } - - return allSelections - } catch (error) { - await handleLspError(error, 'Error selecting resources') - return [] - } - } - - private async selectResourcesForType( - resourceType: string, - multiSelect: boolean, - resourceOperations?: ResourceOperations - ): Promise { - let resourceList = resourceOperations?.getCached(resourceType) - - if (!resourceList) { - resourceList = await this.fetchResourceList(resourceType) - } - - if (!resourceList || resourceList.resourceIdentifiers.length === 0) { - void window.showWarningMessage(`No resources found for type: ${resourceType}`) - return [] - } - - while (resourceList.nextToken && resourceOperations) { - const action = await this.showLoadMoreMenu(resourceType, resourceList.resourceIdentifiers.length) - - if (action === 'load') { - await resourceOperations.loadMore(resourceType, resourceList.nextToken) - this.refreshCallback?.() - const updatedList = resourceOperations.getCached(resourceType) - if (!updatedList) { - break - } - resourceList = updatedList - } else if (action === 'search') { - const identifier = await window.showInputBox({ - prompt: `Enter ${resourceType} identifier`, - placeHolder: 'Resource identifier must match exactly', - }) - - if (!identifier) { - return [] - } - - const result = await resourceOperations.search(resourceType, identifier) - this.refreshCallback?.() - - if (!result.found) { - void window.showErrorMessage( - `${resourceType} with identifier '${identifier}' was not found. The identifier must match exactly.` - ) - return [] - } - - return [{ resourceType, resourceIdentifier: identifier }] - } else if (action === 'select') { - break - } else { - return [] - } - } - - const result = await window.showQuickPick(resourceList.resourceIdentifiers, { - canPickMany: multiSelect, - placeHolder: `Select ${resourceType} identifiers`, - title: `Select ${resourceType} Resources`, - }) - - if (!result) { - return [] - } - - const identifiers = Array.isArray(result) ? result : [result] - return identifiers.map((identifier) => ({ resourceType, resourceIdentifier: identifier })) - } - - private async showLoadMoreMenu(resourceType: string, loadedCount: number): Promise { - const result = await window.showQuickPick( - [ - { label: `Load more resources (${loadedCount} currently loaded)`, value: 'load' }, - { label: 'Search by identifier', value: 'search' }, - { label: `Select from loaded resources (${loadedCount} available)`, value: 'select' }, - ], - { - placeHolder: `Choose how to select ${resourceType} resources`, - title: resourceType, - } - ) - - return result?.value - } - - private async fetchResourceList(resourceType: string): Promise { - const response = await this.client.sendRequest(ListResourcesRequest, { - resources: [{ resourceType }], - }) - - return response.resources.find((r: { typeName: string }) => r.typeName === resourceType) - } - - async selectSingleResource(resourceOperations?: ResourceOperations): Promise { - const result = await this.selectResources(false, undefined, resourceOperations) - return result[0] - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/sectionUI.ts b/packages/core/src/awsService/cloudformation/ui/sectionUI.ts deleted file mode 100644 index 5471ab9e14..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/sectionUI.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EventEmitter, TreeItem } from 'vscode' - -export interface SectionUI { - base: TreeItem - children(element?: T): (T | null | undefined)[] - registerTreeChangedEvent(event: EventEmitter): void - onChange: () => void -} diff --git a/packages/core/src/awsService/cloudformation/ui/stackEventsWebviewProvider.ts b/packages/core/src/awsService/cloudformation/ui/stackEventsWebviewProvider.ts deleted file mode 100644 index 90417924e6..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/stackEventsWebviewProvider.ts +++ /dev/null @@ -1,531 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WebviewView, WebviewViewProvider, Disposable } from 'vscode' -import { StackEvent } from '@aws-sdk/client-cloudformation' -import { LanguageClient } from 'vscode-languageclient/node' -import { extractErrorMessage, getStackStatusClass, isStackInTransientState } from '../utils' -import { GetStackEventsRequest, ClearStackEventsRequest } from '../stacks/actions/stackActionProtocol' -import { StackViewCoordinator } from './stackViewCoordinator' -import { arnToConsoleTabUrl, operationIdToConsoleUrl, externalLinkSvg, consoleLinkStyles } from '../consoleLinksUtils' - -const EventsPerPage = 50 -const RefreshIntervalMs = 5000 - -interface StackEventWithOperationId extends StackEvent { - OperationId?: string -} - -interface GroupedEvent extends StackEventWithOperationId { - isParent?: boolean - groupId: string - groupParentId?: string -} - -export class StackEventsWebviewProvider implements WebviewViewProvider, Disposable { - private view?: WebviewView - private stackName?: string - private stackArn?: string - private allEvents: StackEventWithOperationId[] = [] - private currentPage = 0 - private nextToken?: string - private refreshTimer?: NodeJS.Timeout - private expandedGroups = new Set() - private readonly coordinatorSubscription: Disposable - - constructor( - private readonly client: LanguageClient, - coordinator: StackViewCoordinator - ) { - this.coordinatorSubscription = coordinator.onDidChangeStack(async (state) => { - try { - if (state.stackName && !state.isChangeSetMode) { - this.stopAutoRefresh() - this.stackArn = state.stackArn - await this.showStackEvents(state.stackName) - } else if (!state.stackName || state.isChangeSetMode) { - this.stopAutoRefresh() - this.stackName = undefined - this.stackArn = undefined - this.allEvents = [] - this.render() - } - - if (state.stackStatus && !isStackInTransientState(state.stackStatus)) { - this.stopAutoRefresh() - } - } catch (error) { - // Silently handle errors to prevent breaking the coordinator - } - }) - } - - async showStackEvents(stackName: string): Promise { - this.stackName = stackName - this.allEvents = [] - this.currentPage = 0 - this.nextToken = undefined - this.expandedGroups.clear() - - try { - const result = await this.client.sendRequest(GetStackEventsRequest, { stackName }) - this.allEvents = result.events - this.nextToken = result.nextToken - - if (this.allEvents.length > 0 && this.allEvents[0].OperationId) { - this.expandedGroups.add(`op-${this.allEvents[0].OperationId}`) - } - } catch (error) { - this.renderError(`Failed to load events: ${extractErrorMessage(error)}`) - } - - this.render() - this.startAutoRefresh() - } - - resolveWebviewView(webviewView: WebviewView): void { - this.view = webviewView - webviewView.webview.options = { enableScripts: true } - webviewView.onDidDispose(() => { - this.stopAutoRefresh() - }) - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible) { - this.render() - this.startAutoRefresh() - } else { - this.stopAutoRefresh() - } - }) - - webviewView.webview.onDidReceiveMessage(async (message: { command: string; groupId?: string }) => { - if (message.command === 'nextPage') { - await this.nextPage() - } else if (message.command === 'prevPage') { - await this.prevPage() - } else if (message.command === 'toggle' && message.groupId) { - if (this.expandedGroups.has(message.groupId)) { - this.expandedGroups.delete(message.groupId) - } else { - this.expandedGroups.add(message.groupId) - } - this.render() - } - }) - - this.render() - } - - dispose(): void { - this.stopAutoRefresh() - if (this.stackName) { - void this.client.sendRequest(ClearStackEventsRequest, { stackName: this.stackName }) - } - this.coordinatorSubscription.dispose() - } - - private async loadEvents(): Promise { - if (!this.stackName || !this.nextToken) { - return - } - - try { - const result = await this.client.sendRequest(GetStackEventsRequest, { - stackName: this.stackName, - nextToken: this.nextToken, - }) - - this.allEvents.push(...result.events) - this.nextToken = result.nextToken - } catch (error) { - this.renderError(`Failed to load events: ${extractErrorMessage(error)}`) - } - } - - private async refresh(): Promise { - if (!this.stackName) { - return - } - - try { - const result = await this.client.sendRequest(GetStackEventsRequest, { - stackName: this.stackName, - refresh: true, - }) - - if (result.gapDetected) { - const initialResult = await this.client.sendRequest(GetStackEventsRequest, { - stackName: this.stackName, - }) - this.allEvents = initialResult.events - this.nextToken = initialResult.nextToken - this.currentPage = 0 - this.render('Event history reloaded due to high activity') - } else if (result.events.length > 0) { - this.allEvents.unshift(...result.events) - this.currentPage = 0 - this.render() - } - } catch (error) { - this.renderError(`Failed to refresh events: ${extractErrorMessage(error)}`) - } - } - - private async nextPage(): Promise { - const totalPages = Math.ceil(this.allEvents.length / EventsPerPage) - const nextPageIndex = this.currentPage + 1 - - if (nextPageIndex < totalPages) { - this.currentPage = nextPageIndex - this.render() - } else if (this.nextToken) { - await this.loadEvents() - this.currentPage = nextPageIndex - this.render() - } - } - - private async prevPage(): Promise { - if (this.currentPage > 0) { - this.currentPage-- - this.render() - } else { - await this.refresh() - } - } - - private startAutoRefresh(): void { - this.stopAutoRefresh() - this.refreshTimer = setInterval(() => void this.refresh(), RefreshIntervalMs) - } - - private stopAutoRefresh(): void { - if (this.refreshTimer) { - clearInterval(this.refreshTimer) - this.refreshTimer = undefined - } - } - - private groupEvents(events: StackEventWithOperationId[]): GroupedEvent[] { - const operationGroups = new Map() - const eventsWithoutOperationId: StackEventWithOperationId[] = [] - - for (const event of events) { - if (event.OperationId) { - if (!operationGroups.has(event.OperationId)) { - operationGroups.set(event.OperationId, []) - } - operationGroups.get(event.OperationId)!.push(event) - } else { - eventsWithoutOperationId.push(event) - } - } - - const grouped: GroupedEvent[] = [] - - for (const [operationId, operationEvents] of operationGroups.entries()) { - const groupId = `op-${operationId}` - grouped.push({ - ...operationEvents[0], - groupId, - isParent: true, - }) - - for (const [index, event] of operationEvents.entries()) { - grouped.push({ - ...event, - groupId: `${groupId}-${index}`, - groupParentId: groupId, - isParent: false, - }) - } - } - - for (const [index, event] of eventsWithoutOperationId.entries()) { - grouped.push({ - ...event, - groupId: `flat-${index}`, - isParent: true, - }) - } - - return grouped - } - - private renderError(message: string): void { - if (!this.view || !this.view.visible) { - return - } - this.view.webview.html = ` - - - - - - -

Error

-

${message}

- -` - } - - private render(notification?: string): void { - if (!this.view || !this.view.visible) { - return - } - - const groupedEvents = this.groupEvents(this.allEvents) - const hasHooks = this.allEvents.some((e) => e.HookType) - const start = this.currentPage * EventsPerPage - const end = start + EventsPerPage - const pageEvents = groupedEvents.slice(start, end) - const totalPages = Math.ceil(groupedEvents.length / EventsPerPage) - const hasMore = this.nextToken !== undefined - - this.view.webview.html = this.getHtml( - pageEvents, - this.currentPage + 1, - totalPages, - hasMore, - this.allEvents.length, - hasHooks, - notification - ) - } - - private getHtml( - events: GroupedEvent[], - currentPage: number, - totalPages: number, - hasMore: boolean, - totalEvents: number, - hasHooks: boolean, - notification?: string - ): string { - const emptyMessage = - totalEvents === 0 - ? '
No events found.
' - : '' - - return /* HTML */ ` - - - - - - -
-
-
- ${this.stackName ?? ''} - ${this.stackArn - ? `${externalLinkSvg()}` - : ''} - (${totalEvents} events${hasMore ? ' loaded' : ''}) -
- -
-
- ${notification ? `
${notification}
` : ''} -
- ${emptyMessage || - ` - - - - - - -${hasHooks ? '' : ''} - - -${events.map((e) => this.renderEventRow(e, hasHooks)).join('')} - -
Operation IDTimestampLogical IDStatusStatus ReasonHook Invocation
`} -
- - - ` - } - - private renderEventRow(event: GroupedEvent, hasHooks: boolean): string { - const hookCell = hasHooks - ? `${event.HookType ? `${event.HookType} (${event.HookStatus ?? '-'})` : '-'}` - : '' - - if (event.isParent) { - const expanded = this.expandedGroups.has(event.groupId) - const chevron = event.OperationId ? `` : '' - const opUrl = - event.OperationId && this.stackArn - ? operationIdToConsoleUrl(this.stackArn, event.OperationId) - : undefined - const opIdDisplay = opUrl ? `${event.OperationId}` : (event.OperationId ?? '-') - - return ` -${chevron} ${opIdDisplay} -${event.Timestamp ? new Date(event.Timestamp).toLocaleString() : '-'} -${event.LogicalResourceId ?? '-'} -${event.ResourceStatus ?? '-'} -${event.ResourceStatusReason ?? '-'} -${hookCell} -` - } - - const opUrl = - event.OperationId && this.stackArn ? operationIdToConsoleUrl(this.stackArn, event.OperationId) : undefined - const opIdDisplay = opUrl ? `${event.OperationId}` : (event.OperationId ?? '-') - - return ` -${opIdDisplay} -${event.Timestamp ? new Date(event.Timestamp).toLocaleString() : '-'} -${event.LogicalResourceId ?? '-'} -${event.ResourceStatus ?? '-'} -${event.ResourceStatusReason ?? '-'} -${hookCell} -` - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/stackOutputsWebviewProvider.ts b/packages/core/src/awsService/cloudformation/ui/stackOutputsWebviewProvider.ts deleted file mode 100644 index bd42f9d6c2..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/stackOutputsWebviewProvider.ts +++ /dev/null @@ -1,229 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WebviewView, WebviewViewProvider, Disposable } from 'vscode' -import { Output } from '@aws-sdk/client-cloudformation' -import { LanguageClient } from 'vscode-languageclient/node' -import { extractErrorMessage } from '../utils' -import { DescribeStackRequest } from '../stacks/actions/stackActionProtocol' -import { StackViewCoordinator } from './stackViewCoordinator' -import { arnToConsoleTabUrl, externalLinkSvg, consoleLinkStyles } from '../consoleLinksUtils' - -export class StackOutputsWebviewProvider implements WebviewViewProvider, Disposable { - private view?: WebviewView - private stackName?: string - private stackArn?: string - private outputs: Output[] = [] - private readonly disposables: Disposable[] = [] - - constructor( - private readonly client: LanguageClient, - private readonly coordinator: StackViewCoordinator - ) { - this.disposables.push( - coordinator.onDidChangeStack(async (state) => { - if (state.stackName && !state.isChangeSetMode) { - this.stackName = state.stackName - this.stackArn = state.stackArn - this.outputs = [] - this.render() - await this.showOutputs(state.stackName) - } else if (!state.stackName || state.isChangeSetMode) { - this.stackName = undefined - this.stackArn = undefined - this.outputs = [] - this.render() - } - }) - ) - } - - async resolveWebviewView(webviewView: WebviewView): Promise { - this.view = webviewView - webviewView.webview.options = { enableScripts: true } - - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible) { - this.render() - } - }) - - if (this.stackName) { - await this.loadOutputs() - } else { - this.render() - } - } - - async showOutputs(stackName: string): Promise { - this.stackName = stackName - this.outputs = [] - - if (this.view) { - await this.loadOutputs() - } - } - - private async loadOutputs(): Promise { - if (!this.stackName) { - return - } - - try { - const result = await this.client.sendRequest(DescribeStackRequest, { - stackName: this.stackName, - }) - - this.outputs = result.stack?.Outputs ?? [] - // Only update coordinator if status changed - if (result.stack?.StackStatus && this.coordinator.currentStackStatus !== result.stack.StackStatus) { - await this.coordinator.setStack(this.stackName, result.stack.StackStatus, result.stack.StackId) - } - this.render() - } catch (error) { - this.renderError(`Failed to load outputs: ${extractErrorMessage(error)}`) - } - } - - private renderError(message: string): void { - if (!this.view || !this.view.visible) { - return - } - this.view.webview.html = ` - - - - - - -

Error

-

${message}

- -` - } - - private render(): void { - if (!this.view || this.view.visible === false) { - return - } - - this.view.webview.html = this.getHtml(this.outputs) - } - - private getHtml(outputs: Output[]): string { - const outputRows = - outputs.length > 0 - ? outputs - .map( - (output) => ` - - ${output.OutputKey ?? ''} - ${output.OutputValue ?? ''} - ${output.Description ?? ''} - ${output.ExportName ?? ''} - - ` - ) - .join('') - : 'No outputs found' - - return ` - - - - - - -
-
- ${this.stackName ?? ''} - ${this.stackArn ? `${externalLinkSvg()}` : ''} - (${outputs.length} outputs) -
-
-
- - - - - - - - - - - ${outputRows} - -
KeyValueDescriptionExport Name
-
- -` - } - - dispose(): void { - this.view = undefined - for (const d of this.disposables) { - d.dispose() - } - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/stackOverviewWebviewProvider.ts b/packages/core/src/awsService/cloudformation/ui/stackOverviewWebviewProvider.ts deleted file mode 100644 index 3b3cbaa52d..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/stackOverviewWebviewProvider.ts +++ /dev/null @@ -1,282 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WebviewView, WebviewViewProvider, Disposable } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { Stack } from '@aws-sdk/client-cloudformation' -import { StackViewCoordinator } from './stackViewCoordinator' -import { DescribeStackRequest } from '../stacks/actions/stackActionProtocol' -import { extractErrorMessage, getStackStatusClass, isStackInTransientState } from '../utils' -import { externalLinkSvg, consoleLinkStyles, arnToConsoleUrl } from '../consoleLinksUtils' - -export class StackOverviewWebviewProvider implements WebviewViewProvider, Disposable { - private view?: WebviewView - private stack?: Stack - private readonly disposables: Disposable[] = [] - private refreshTimer?: NodeJS.Timeout - private currentStackName?: string - - constructor( - private readonly client: LanguageClient, - private readonly coordinator: StackViewCoordinator - ) { - this.disposables.push( - coordinator.onDidChangeStack(async (state) => { - if (state.stackName && !state.isChangeSetMode) { - this.stopAutoRefresh() - this.currentStackName = state.stackName - this.stack = undefined - this.render() - await this.loadStack(state.stackName) - this.startAutoRefresh() - } else { - this.stopAutoRefresh() - this.currentStackName = undefined - this.stack = undefined - this.render() - } - - // Stop auto-refresh if stack is in terminal state - if (state.stackStatus && !isStackInTransientState(state.stackStatus)) { - this.stopAutoRefresh() - } - }) - ) - } - - private startAutoRefresh(): void { - this.stopAutoRefresh() - if (this.currentStackName) { - this.refreshTimer = setInterval(() => { - if (this.currentStackName) { - void this.loadStack(this.currentStackName) - } - }, 5000) - } - } - - private stopAutoRefresh(): void { - if (this.refreshTimer) { - clearInterval(this.refreshTimer) - this.refreshTimer = undefined - } - } - - private async loadStack(stackName: string): Promise { - try { - const result = await this.client.sendRequest(DescribeStackRequest, { stackName }) - if (result.stack) { - this.stack = result.stack - // Only update coordinator if status changed - if (this.coordinator.currentStackStatus !== result.stack.StackStatus) { - await this.coordinator.setStack(stackName, result.stack.StackStatus, result.stack.StackId) - } - this.render() - } - } catch (error) { - this.stack = undefined - this.renderError(`Failed to load stack: ${extractErrorMessage(error)}`) - } - } - - resolveWebviewView(webviewView: WebviewView): void { - this.view = webviewView - webviewView.webview.options = { enableScripts: true } - - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible && this.currentStackName) { - this.render() - this.startAutoRefresh() - } else { - this.stopAutoRefresh() - } - }) - - webviewView.onDidDispose(() => { - this.stopAutoRefresh() - }) - - this.render() - } - - async showStackOverview(stackName: string): Promise { - if (this.view) { - await this.loadStack(stackName) - } - } - - private render(): void { - if (!this.view || !this.view.visible) { - return - } - - if (!this.stack) { - this.view.webview.html = this.getEmptyContent() - return - } - - this.view.webview.html = this.getWebviewContent(this.stack) - } - - private renderError(message: string): void { - if (!this.view || !this.view.visible) { - return - } - this.view.webview.html = ` - - - - - - -

Error

-

${message}

- -` - } - - private getEmptyContent(): string { - return ` - - - - - - -

Select a stack to view details

- -` - } - - private getWebviewContent(stack: Stack): string { - return ` - - - - - - -
-
Stack Name
-
- ${stack.StackName ?? 'N/A'} - ${stack.StackId ? `${externalLinkSvg()}` : ''} -
-
-
-
Status
-
- ${stack.StackStatus ?? 'UNKNOWN'} -
-
- ${ - stack.StackId - ? ` -
-
Stack ID
-
${stack.StackId}
-
` - : '' - } - ${ - stack.Description - ? ` -
-
Description
-
${stack.Description}
-
` - : '' - } - ${ - stack.CreationTime - ? ` -
-
Created
-
${new Date(stack.CreationTime).toLocaleString()}
-
` - : '' - } - ${ - stack.LastUpdatedTime - ? ` -
-
Last Updated
-
${new Date(stack.LastUpdatedTime).toLocaleString()}
-
` - : '' - } - ${ - stack.StackStatusReason - ? ` -
-
Status Reason
-
${stack.StackStatusReason}
-
` - : '' - } - -` - } - - dispose(): void { - this.stopAutoRefresh() - for (const d of this.disposables) { - d.dispose() - } - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/stackResourcesWebviewProvider.ts b/packages/core/src/awsService/cloudformation/ui/stackResourcesWebviewProvider.ts deleted file mode 100644 index 25aaefe504..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/stackResourcesWebviewProvider.ts +++ /dev/null @@ -1,420 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { WebviewView, WebviewViewProvider, Disposable } from 'vscode' -import { LanguageClient } from 'vscode-languageclient/node' -import { showErrorMessage } from './message' -import { GetStackResourcesRequest } from '../stacks/actions/stackActionProtocol' -import { StackResourceSummary, GetStackResourcesParams } from '../stacks/actions/stackActionRequestType' -import { StackViewCoordinator } from './stackViewCoordinator' -import { arnToConsoleTabUrl, externalLinkSvg, consoleLinkStyles } from '../consoleLinksUtils' - -const ResourcesPerPage = 50 - -export class StackResourcesWebviewProvider implements WebviewViewProvider, Disposable { - private _view?: WebviewView - private stackName = '' - private stackArn = '' - private allResources: StackResourceSummary[] = [] - private currentPage = 0 - private nextToken?: string - private updateInterval?: NodeJS.Timeout - private readonly disposables: Disposable[] = [] - - constructor( - private client: LanguageClient, - private readonly coordinator: StackViewCoordinator - ) { - this.disposables.push( - coordinator.onDidChangeStack(async (state) => { - if (state.stackName && !state.isChangeSetMode) { - this.stopAutoRefresh() - this.stackName = state.stackName - this.stackArn = state.stackArn || '' - this.allResources = [] - this.currentPage = 0 - this.nextToken = undefined - if (this._view && this._view.visible) { - this._view.webview.html = this.getHtmlContent() - } - await this.updateData(state.stackName) - } else if (!state.stackName || state.isChangeSetMode) { - this.stopAutoRefresh() - this.stackName = '' - this.stackArn = '' - this.allResources = [] - if (this._view && this._view.visible) { - this._view.webview.html = this.getHtmlContent() - } - } - - // Stop auto-refresh if stack is in terminal state - if (state.stackStatus && !this.isStackInTransientState(state.stackStatus)) { - this.stopAutoUpdate() - } - }) - ) - } - - private isStackInTransientState(status: string): boolean { - return status.includes('_IN_PROGRESS') || status.includes('_CLEANUP_IN_PROGRESS') - } - - async updateData(stackName: string) { - this.stackName = stackName - this.allResources = [] - this.currentPage = 0 - this.nextToken = undefined - await this.loadResources() - - if (this._view && this._view.visible) { - this._view.webview.html = this.getHtmlContent() - } - } - - resolveWebviewView(webviewView: WebviewView) { - this._view = webviewView - this.setupWebview(webviewView) - this.setupMessageHandling(webviewView) - this.setupLifecycleHandlers(webviewView) - } - - private setupWebview(webviewView: WebviewView) { - webviewView.webview.options = { enableScripts: true } - webviewView.webview.html = this.getHtmlContent() - } - - private setupMessageHandling(webviewView: WebviewView) { - webviewView.webview.onDidReceiveMessage(async (message: { command: string }) => { - if (message.command === 'nextPage') { - await this.loadNextPage() - } else if (message.command === 'prevPage') { - await this.loadPrevPage() - } - }) - } - - private setupLifecycleHandlers(webviewView: WebviewView) { - webviewView.onDidChangeVisibility(() => { - if (webviewView.visible) { - this.render() - this.startAutoUpdate() - } else { - this.stopAutoUpdate() - } - }) - - if (webviewView.visible) { - this.startAutoUpdate() - } - - webviewView.onDidDispose(() => { - this.stopAutoUpdate() - }) - } - - private async loadResources(): Promise { - if (!this.client || !this.stackName) { - return - } - - try { - const params: GetStackResourcesParams = { stackName: this.stackName } - if (this.nextToken) { - params.nextToken = this.nextToken - const result = await this.client.sendRequest(GetStackResourcesRequest, params) - this.allResources.push(...result.resources) - this.nextToken = result.nextToken - } else { - const result = await this.client.sendRequest(GetStackResourcesRequest, params) - this.allResources = result.resources - this.nextToken = result.nextToken - } - } catch (error) { - showErrorMessage(`Failed to fetch stack resources: ${error}`) - } - } - - private async loadNextPage(): Promise { - const totalPages = Math.ceil(this.allResources.length / ResourcesPerPage) - const nextPageIndex = this.currentPage + 1 - - // Don't proceed if we're already at the last page and have no more data - if (nextPageIndex >= totalPages && !this.nextToken) { - return - } - - if (nextPageIndex < totalPages) { - this.currentPage = nextPageIndex - this.render() - } else if (this.nextToken) { - await this.loadResources() - if (this.allResources.length > nextPageIndex * ResourcesPerPage) { - this.currentPage = nextPageIndex - this.render() - } - } - } - - private async loadPrevPage(): Promise { - // Don't proceed if we're already at the first page - if (this.currentPage <= 0) { - return - } - - this.currentPage-- - this.render() - } - - private render(): void { - if (this._view && this._view.visible !== false) { - this._view.webview.html = this.getHtmlContent() - } - } - - private startAutoUpdate() { - if (!this.updateInterval && this.stackName) { - this.updateInterval = setInterval(async () => { - if (this._view && !this.coordinator.currentStackStatus?.includes('_IN_PROGRESS')) { - this.stopAutoUpdate() - return - } - - if (this._view) { - // Reset to page 1 with fresh data - this.allResources = [] - this.currentPage = 0 - this.nextToken = undefined - await this.loadResources() - - if (this._view && this._view.visible !== false) { - this._view.webview.html = this.getHtmlContent() - } - } - }, 5000) - } - } - - private stopAutoUpdate() { - if (this.updateInterval) { - clearInterval(this.updateInterval) - this.updateInterval = undefined - } - } - - private stopAutoRefresh() { - this.stopAutoUpdate() - } - - private getHtmlContent(): string { - const start = this.currentPage * ResourcesPerPage - const end = start + ResourcesPerPage - const pageResources = this.allResources.slice(start, end) - const totalPages = Math.ceil(this.allResources.length / ResourcesPerPage) - const hasMore = this.nextToken !== undefined - - if (!pageResources || pageResources.length === 0) { - return ` - - - - - - -
-
- ${this.stackName} - ${this.stackArn ? `${externalLinkSvg()}` : ''} - (0 resources) -
-
-
-

No resources found

-
- -` - } - - const resourceRows = pageResources - .map( - (resource) => ` - - ${resource.LogicalResourceId} - ${resource.PhysicalResourceId || ''} - ${resource.ResourceType} - ${resource.ResourceStatus} - - ` - ) - .join('') - - return ` - - - - - - -
-
-
- ${this.stackName} - ${this.stackArn ? `${externalLinkSvg()}` : ''} - (${this.allResources.length} resources${hasMore ? ' loaded' : ''}) -
- -
-
-
- - - - - - - - - - - ${resourceRows} - -
Logical IDPhysical IDTypeStatus
-
- - -` - } - - dispose(): void { - this.stopAutoRefresh() - for (const d of this.disposables) { - d.dispose() - } - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/stackViewCoordinator.ts b/packages/core/src/awsService/cloudformation/ui/stackViewCoordinator.ts deleted file mode 100644 index 8fc6e9750b..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/stackViewCoordinator.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EventEmitter } from 'vscode' -import { setContext } from '../../../shared/vscode/setContext' - -export interface StackViewState { - stackName?: string - stackArn?: string - isChangeSetMode: boolean - stackStatus?: string -} - -export class StackViewCoordinator { - private readonly _onDidChangeStack = new EventEmitter() - readonly onDidChangeStack = this._onDidChangeStack.event - - private _currentStackName?: string - private _currentStackArn?: string - private _isChangeSetMode = false - private _currentStackStatus?: string - private _stackStatusUpdateCallback?: (stackName: string, stackStatus: string) => void - - get currentStackName(): string | undefined { - return this._currentStackName - } - - get currentStackArn(): string | undefined { - return this._currentStackArn - } - - get isChangeSetMode(): boolean { - return this._isChangeSetMode - } - - get currentStackStatus(): string | undefined { - return this._currentStackStatus - } - - setStackStatusUpdateCallback(callback: (stackName: string, stackStatus: string) => void): void { - this._stackStatusUpdateCallback = callback - } - - async setStack(stackName: string, stackStatus?: string, stackArn?: string): Promise { - const statusChanged = stackStatus && this._currentStackStatus !== stackStatus - - this._currentStackName = stackName - this._currentStackArn = stackArn - this._currentStackStatus = stackStatus - this._isChangeSetMode = false - await this.updateContexts() - this._onDidChangeStack.fire(this.getState()) - - if (statusChanged && stackStatus && this._stackStatusUpdateCallback) { - this._stackStatusUpdateCallback(stackName, stackStatus) - } - } - - async setChangeSetMode(stackName: string, enabled: boolean): Promise { - this._currentStackName = stackName - this._isChangeSetMode = enabled - await this.updateContexts() - this._onDidChangeStack.fire(this.getState()) - } - - async clearStack(): Promise { - this._currentStackName = undefined - this._currentStackArn = undefined - this._currentStackStatus = undefined - this._isChangeSetMode = false - await this.updateContexts() - this._onDidChangeStack.fire(this.getState()) - } - - private async updateContexts(): Promise { - await setContext('aws.cloudformation.stackSelected', !!this._currentStackName) - await setContext('aws.cloudformation.changeSetMode', this._isChangeSetMode) - } - - private getState(): StackViewState { - return { - stackName: this._currentStackName, - stackArn: this._currentStackArn, - isChangeSetMode: this._isChangeSetMode, - stackStatus: this._currentStackStatus, - } - } - - dispose(): void { - this._onDidChangeStack.dispose() - } -} diff --git a/packages/core/src/awsService/cloudformation/ui/statusBar.ts b/packages/core/src/awsService/cloudformation/ui/statusBar.ts deleted file mode 100644 index 0a6623e56e..0000000000 --- a/packages/core/src/awsService/cloudformation/ui/statusBar.ts +++ /dev/null @@ -1,265 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { window, StatusBarAlignment, StatusBarItem, ThemeColor, QuickPickItem, commands } from 'vscode' -import { StackActionPhase } from '../stacks/actions/stackActionRequestType' -import { commandKey } from '../utils' - -const OperationTypeValidation = 'Validation' as const -const OperationTypeDeployment = 'Deployment' as const -const StatusBarLabel = 'AWS CloudFormation' -const StatusValidating = 'Validating' -const StatusValidated = 'Validated' -const StatusDeploying = 'Deploying' -const StatusDeployed = 'Deployed' -const StatusValidationFailed = 'Validation Failed' -const StatusDeploymentFailed = 'Deployment Failed' -const StatusProcessing = 'Processing' -const NoOperationsMessage = 'No active operations' - -interface OperationInfo { - stackName: string - type: typeof OperationTypeValidation | typeof OperationTypeDeployment - changeSetName?: string - startTime: Date - phase: StackActionPhase - released: boolean -} - -interface StatusBarHandle { - update(phase: StackActionPhase): void - release(): void -} - -class SharedStatusBar { - private statusBarItem?: StatusBarItem - private refCount = 0 - private activeCount = 0 - private completedCount = 0 - private failedCount = 0 - private disposeTimer?: NodeJS.Timeout - private operations: Map = new Map() - private nextId = 0 - - acquire( - stackName: string, - type: typeof OperationTypeValidation | typeof OperationTypeDeployment, - changeSetName?: string - ): StatusBarHandle { - if (this.disposeTimer) { - clearTimeout(this.disposeTimer) - this.disposeTimer = undefined - this.activeCount = 0 - this.completedCount = 0 - this.failedCount = 0 - } - - if (!this.statusBarItem) { - this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, 100) - this.statusBarItem.command = commandKey('showOperationStatus') - this.statusBarItem.show() - } - - this.refCount++ - this.activeCount++ - - const id = this.nextId++ - this.operations.set(id, { - stackName, - type, - changeSetName, - startTime: new Date(), - phase: StackActionPhase.VALIDATION_IN_PROGRESS, - released: false, - }) - - this.updateDisplay() - - return { - update: (phase: StackActionPhase) => this.updatePhase(id, phase), - release: () => this.release(id), - } - } - - private updatePhase(id: number, phase: StackActionPhase): void { - const operation = this.operations.get(id) - if (operation) { - operation.phase = phase - } - - if (isTerminalPhase(phase)) { - this.activeCount-- - if (isFailurePhase(phase)) { - this.failedCount++ - } else { - this.completedCount++ - } - } - this.updateDisplay() - } - - private updateDisplay(): void { - if (!this.statusBarItem) { - return - } - - const unreleasedOperations = Array.from(this.operations.values()).filter((op) => !op.released) - const total = unreleasedOperations.length - - if (total === 1) { - const operation = unreleasedOperations[0] - const verb = operation.type === OperationTypeValidation ? StatusValidating : StatusDeploying - const pastVerb = operation.type === OperationTypeValidation ? StatusValidated : StatusDeployed - const isOperationFailed = isFailurePhase(operation.phase) - - if (this.activeCount > 0) { - this.statusBarItem.text = `$(sync~spin) ${verb} ${operation.stackName}` - this.statusBarItem.backgroundColor = undefined - } else if (isOperationFailed) { - const failedStatus = - operation.type === OperationTypeValidation ? StatusValidationFailed : StatusDeploymentFailed - this.statusBarItem.text = `$(error) ${failedStatus}: ${operation.stackName}` - this.statusBarItem.backgroundColor = new ThemeColor('statusBarItem.errorBackground') - } else { - this.statusBarItem.text = `$(check) ${pastVerb} ${operation.stackName}` - this.statusBarItem.backgroundColor = undefined - } - } else if (total > 1) { - if (this.activeCount > 0) { - this.statusBarItem.text = `$(sync~spin) ${StatusBarLabel} (${total})` - this.statusBarItem.backgroundColor = undefined - } else if (this.failedCount > 0) { - this.statusBarItem.text = `$(error) ${StatusBarLabel} (${total})` - this.statusBarItem.backgroundColor = new ThemeColor('statusBarItem.errorBackground') - } else { - this.statusBarItem.text = `$(check) ${StatusBarLabel} (${total})` - this.statusBarItem.backgroundColor = undefined - } - } - } - - private release(id: number): void { - const operation = this.operations.get(id) - if (operation) { - operation.released = true - } - this.refCount-- - - if (this.refCount === 0) { - this.disposeTimer = setTimeout(() => { - this.statusBarItem?.dispose() - this.statusBarItem = undefined - this.activeCount = 0 - this.completedCount = 0 - this.failedCount = 0 - this.operations.clear() - }, 5000) - } - } - - showOperations(): void { - const items: QuickPickItem[] = [] - - for (const operation of this.operations.values()) { - const icon = getPhaseIcon(operation.phase) - const elapsed = formatElapsed(operation.startTime) - const changeSetInfo = operation.changeSetName ? ` • ${operation.changeSetName}` : '' - - items.push({ - label: `${icon} ${operation.stackName}`, - description: `${operation.type}${changeSetInfo}`, - detail: `${getPhaseLabel(operation.phase)} • Started ${elapsed}`, - }) - } - - if (items.length === 0) { - items.push({ - label: `$(info) ${NoOperationsMessage}`, - description: '', - }) - } - - const quickPick = window.createQuickPick() - quickPick.items = items - quickPick.placeholder = `${StatusBarLabel} Operations` - quickPick.canSelectMany = false - quickPick.matchOnDescription = false - quickPick.matchOnDetail = false - quickPick.show() - - quickPick.onDidHide(() => quickPick.dispose()) - } -} - -const sharedStatusBar = new SharedStatusBar() - -function isTerminalPhase(phase: StackActionPhase): boolean { - return [ - StackActionPhase.VALIDATION_COMPLETE, - StackActionPhase.VALIDATION_FAILED, - StackActionPhase.DEPLOYMENT_COMPLETE, - StackActionPhase.DEPLOYMENT_FAILED, - ].includes(phase) -} - -function isFailurePhase(phase: StackActionPhase): boolean { - return [StackActionPhase.VALIDATION_FAILED, StackActionPhase.DEPLOYMENT_FAILED].includes(phase) -} - -function getPhaseIcon(phase: StackActionPhase): string { - if (isFailurePhase(phase)) { - return '$(error)' - } - if (isTerminalPhase(phase)) { - return '$(check)' - } - return '$(sync~spin)' -} - -function getPhaseLabel(phase: StackActionPhase): string { - switch (phase) { - case StackActionPhase.VALIDATION_IN_PROGRESS: - return StatusValidating - case StackActionPhase.VALIDATION_COMPLETE: - return StatusValidated - case StackActionPhase.VALIDATION_FAILED: - return StatusValidationFailed - case StackActionPhase.DEPLOYMENT_IN_PROGRESS: - return StatusDeploying - case StackActionPhase.DEPLOYMENT_COMPLETE: - return StatusDeployed - case StackActionPhase.DEPLOYMENT_FAILED: - return StatusDeploymentFailed - default: - return StatusProcessing - } -} - -function formatElapsed(startTime: Date): string { - const elapsed = Math.floor((Date.now() - startTime.getTime()) / 1000) - if (elapsed < 60) { - return `${elapsed}s ago` - } - const minutes = Math.floor(elapsed / 60) - return `${minutes}m ago` -} - -export function createDeploymentStatusBar( - stackName: string, - type: typeof OperationTypeValidation | typeof OperationTypeDeployment, - changeSetName?: string -): StatusBarHandle { - return sharedStatusBar.acquire(stackName, type, changeSetName) -} - -export function updateWorkflowStatus(handle: StatusBarHandle, status: StackActionPhase): void { - handle.update(status) -} - -export function registerStatusBarCommand(): void { - commands.registerCommand(commandKey('showOperationStatus'), () => { - sharedStatusBar.showOperations() - }) -} diff --git a/packages/core/src/awsService/cloudformation/utils.ts b/packages/core/src/awsService/cloudformation/utils.ts deleted file mode 100644 index 214f1f4b6e..0000000000 --- a/packages/core/src/awsService/cloudformation/utils.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ExtensionConfigKey, ExtensionId } from './extensionConfig' -import { Position } from 'vscode' - -export function toString(value: unknown): string { - if (value === undefined || !['object', 'function'].includes(typeof value)) { - return String(value) - } - - return JSON.stringify(value) -} - -export function formatMessage(message: string): string { - return `${ExtensionId}: ${message}` -} - -export function commandKey(key: string): string { - return `${ExtensionConfigKey}.${key}` -} - -export const cloudFormationUiClickMetric = 'cloudformation_nodeExpansion' - -export function getStackStatusClass(status?: string): string { - if (!status) { - return '' - } - // Terminal success states - if (status.includes('COMPLETE') && !status.includes('ROLLBACK')) { - return 'status-complete' - } - // Terminal failed states - if (status.includes('FAILED') || status.includes('ROLLBACK')) { - return 'status-failed' - } - // Transient states (in progress) - if (status.includes('PROGRESS')) { - return 'status-progress' - } - return '' -} - -export function isStackInTransientState(status: string): boolean { - return status.includes('_IN_PROGRESS') || status.includes('_CLEANUP_IN_PROGRESS') -} - -export function extractErrorMessage(error: unknown) { - if (error instanceof Error) { - const prefix = error.name === 'Error' ? '' : `${error.name}: ` - return `${prefix}${error.message}` - } - - return toString(error) -} - -/** - * Finds the position of the parameter description value where the cursor should be placed. - * Returns the position between the quotes of the Description property. - */ -export function findParameterDescriptionPosition( - text: string, - parameterName: string, - documentType: string -): Position | undefined { - const lines = text.split('\n') - - if (documentType === 'JSON') { - return findJsonParameterDescriptionPosition(lines, parameterName) - } else { - return findYamlParameterDescriptionPosition(lines, parameterName) - } -} - -/** - * Finds the description position in JSON format. - * Looks for: "ParameterName": { ... "Description": "HERE" ... } - */ -function findParameterDescription( - lines: string[], - parameterPattern: RegExp, - descriptionMatcher: (line: string) => { match: RegExpMatchArray; character: number } | undefined, - endMatcher: (line: string) => boolean -): Position | undefined { - let inParameter = false - - for (let i = 0; i < lines.length; i++) { - const line = lines[i] - - if (!inParameter && parameterPattern.test(line)) { - inParameter = true - continue - } - - if (inParameter) { - const result = descriptionMatcher(line) - if (result) { - return new Position(i, result.character) - } - - if (endMatcher(line)) { - break - } - } - } - - return undefined -} - -function findJsonParameterDescriptionPosition(lines: string[], parameterName: string): Position | undefined { - const parameterPattern = new RegExp(`^\\s*"${escapeRegex(parameterName)}"\\s*:\\s*\\{`) - - return findParameterDescription( - lines, - parameterPattern, - (line) => { - const match = line.match(/^(\s*)"Description"\s*:\s*"([^"]*)"/) - return match - ? { match, character: match[1].length + '"Description": "'.length + match[2].length } - : undefined - }, - (line) => !!line.match(/^\s*\}/) - ) -} - -/** - * Finds the description position in YAML format. - * Looks for: ParameterName: ... Description: "HERE" ... - */ -function findYamlParameterDescriptionPosition(lines: string[], parameterName: string): Position | undefined { - const parameterPattern = new RegExp(`^\\s*${escapeRegex(parameterName)}\\s*:`) - - return findParameterDescription( - lines, - parameterPattern, - (line) => { - const match = line.match(/^(\s*)Description\s*:\s*(['"]?)([^'"]*)\2/) - return match - ? { match, character: match[1].length + 'Description: '.length + match[2].length + match[3].length } - : undefined - }, - (line) => !!line.match(/^\s*\w+\s*:/) && !line.match(/^\s*(Type|Default|Description|AllowedValues)\s*:/) - ) -} - -/** - * Escapes special regex characters in a string. - */ -function escapeRegex(str: string): string { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') -} diff --git a/packages/core/src/awsService/cloudformation/utils/onlineErrorHandler.ts b/packages/core/src/awsService/cloudformation/utils/onlineErrorHandler.ts deleted file mode 100644 index 292ad7bef1..0000000000 --- a/packages/core/src/awsService/cloudformation/utils/onlineErrorHandler.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { commands, window } from 'vscode' -import { ResponseError } from 'vscode-languageclient' -import { extractErrorMessage } from '../utils' - -// LSP error codes from server (must match OnlineFeatureErrorCode enum) -const OnlineFeatureErrorCode = { - NoInternet: -32_001, - NoAuthentication: -32_002, - ExpiredCredentials: -32_003, - AwsServiceError: -32_004, -} as const - -interface OnlineFeatureErrorData { - retryable: boolean - requiresReauth: boolean -} - -function isLspError(error: unknown): error is ResponseError { - return error instanceof ResponseError -} - -export async function handleLspError(error: unknown, context?: string): Promise { - if (!isLspError(error)) { - const message = context ? `${context}: ${extractErrorMessage(error)}` : extractErrorMessage(error) - void window.showErrorMessage(message) - return - } - - const { code, message, data } = error - const fullMessage = context ? `${context}: ${message}` : message - - switch (code) { - case OnlineFeatureErrorCode.ExpiredCredentials: - case OnlineFeatureErrorCode.NoAuthentication: - if (data?.requiresReauth) { - const action = await window.showErrorMessage(fullMessage, 'Re-authenticate') - if (action === 'Re-authenticate') { - await commands.executeCommand('aws.toolkit.login') - } - } else { - void window.showErrorMessage(fullMessage) - } - break - - case OnlineFeatureErrorCode.NoInternet: - case OnlineFeatureErrorCode.AwsServiceError: - void window.showErrorMessage(fullMessage) - break - - default: - void window.showErrorMessage(fullMessage) - } -} diff --git a/packages/core/src/awsService/ec2/activation.ts b/packages/core/src/awsService/ec2/activation.ts deleted file mode 100644 index 1866073275..0000000000 --- a/packages/core/src/awsService/ec2/activation.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { ExtContext } from '../../shared/extensions' -import { Commands } from '../../shared/vscode/commands2' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Ec2InstanceNode, tryRefreshNode } from './explorer/ec2InstanceNode' -import { copyTextCommand } from '../../awsexplorer/commands/copyText' -import { Ec2Node } from './explorer/ec2ParentNode' -import { - openRemoteConnection, - openTerminal, - rebootInstance, - startInstance, - stopInstance, - linkToLaunchInstance, - openLogDocument, -} from './commands' -import { Ec2ConnecterMap } from './connectionManagerMap' -import { ec2LogsScheme } from '../../shared/constants' -import { Ec2LogDocumentProvider } from './ec2LogDocumentProvider' - -const connectionManagers = new Ec2ConnecterMap() - -export async function activate(ctx: ExtContext): Promise { - ctx.extensionContext.subscriptions.push( - vscode.workspace.registerTextDocumentContentProvider(ec2LogsScheme, new Ec2LogDocumentProvider()) - ) - ctx.extensionContext.subscriptions.push( - Commands.register('aws.ec2.openTerminal', async (node?: Ec2InstanceNode) => { - await telemetry.ec2_connectToInstance.run(async (span) => { - span.record({ ec2ConnectionType: 'ssm' }) - await openTerminal(connectionManagers, node) - }) - }), - - Commands.register('aws.ec2.copyInstanceId', async (node: Ec2InstanceNode) => { - await copyTextCommand(node, 'id') - }), - Commands.register('aws.ec2.viewLogs', async (node?: Ec2InstanceNode) => { - await openLogDocument(node) - }), - - Commands.register('aws.ec2.openRemoteConnection', async (node?: Ec2Node) => { - await telemetry.ec2_connectToInstance.run(async (span) => { - span.record({ ec2ConnectionType: 'remoteWorkspace' }) - await openRemoteConnection(connectionManagers, node) - }) - }), - - Commands.register('aws.ec2.startInstance', async (node?: Ec2Node) => { - await telemetry.ec2_changeState.run(async (span) => { - span.record({ ec2InstanceState: 'start' }) - await startInstance(node) - await tryRefreshNode(node) - }) - }), - - Commands.register('aws.ec2.stopInstance', async (node?: Ec2Node) => { - await telemetry.ec2_changeState.run(async (span) => { - span.record({ ec2InstanceState: 'stop' }) - await stopInstance(node) - await tryRefreshNode(node) - }) - }), - - Commands.register('aws.ec2.rebootInstance', async (node?: Ec2Node) => { - await telemetry.ec2_changeState.run(async (span) => { - span.record({ ec2InstanceState: 'reboot' }) - await rebootInstance(node) - await tryRefreshNode(node) - }) - }), - - Commands.register('aws.ec2.linkToLaunch', async (node?: Ec2Node) => { - await telemetry.ec2_launchInstance.run(async (span) => { - await linkToLaunchInstance(node) - }) - }) - ) -} - -export async function deactivate(): Promise { - // eslint-disable-next-line unicorn/no-array-for-each - connectionManagers.forEach(async (manager) => await manager.dispose()) -} diff --git a/packages/core/src/awsService/ec2/commands.ts b/packages/core/src/awsService/ec2/commands.ts deleted file mode 100644 index f0eefe8035..0000000000 --- a/packages/core/src/awsService/ec2/commands.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { Ec2InstanceNode } from './explorer/ec2InstanceNode' -import { Ec2Node } from './explorer/ec2ParentNode' -import { Ec2Instance, Ec2Client } from '../../shared/clients/ec2' -import { copyToClipboard } from '../../shared/utilities/messages' -import { ec2LogSchema } from './ec2LogDocumentProvider' -import { getAwsConsoleUrl } from '../../shared/awsConsole' -import { showRegionPrompter } from '../../auth/utils' -import { openUrl } from '../../shared/utilities/vsCodeUtils' -import { showFile } from '../../shared/utilities/textDocumentUtilities' -import { Ec2ConnecterMap } from './connectionManagerMap' -import { getSelection } from './prompter' - -export async function openTerminal(connectionManagers: Ec2ConnecterMap, node?: Ec2Node) { - const selection = await getSelection(node) - const connectionManager = connectionManagers.getOrInit(selection.region) - await connectionManager.attemptToOpenEc2Terminal(selection) -} - -export async function openRemoteConnection(connectionManagers: Ec2ConnecterMap, node?: Ec2Node) { - const selection = await getSelection(node) - const connectionManager = connectionManagers.getOrInit(selection.region) - await connectionManager.tryOpenRemoteConnection(selection) -} - -export async function startInstance(node?: Ec2Node) { - const prompterFilter = (instance: Ec2Instance) => instance.LastSeenStatus !== 'running' - const selection = await getSelection(node, prompterFilter) - const client = new Ec2Client(selection.region) - await client.startInstanceWithCancel(selection.instanceId) -} - -export async function stopInstance(node?: Ec2Node) { - const prompterFilter = (instance: Ec2Instance) => instance.LastSeenStatus !== 'stopped' - const selection = await getSelection(node, prompterFilter) - const client = new Ec2Client(selection.region) - await client.stopInstanceWithCancel(selection.instanceId) -} - -export async function rebootInstance(node?: Ec2Node) { - const selection = await getSelection(node) - const client = new Ec2Client(selection.region) - await client.rebootInstanceWithCancel(selection.instanceId) -} - -export async function linkToLaunchInstance(node?: Ec2Node) { - const region = node ? node.regionCode : (await showRegionPrompter('Select Region', '')).id - const url = getAwsConsoleUrl('ec2-launch', region) - await openUrl(url) -} - -export async function copyInstanceId(instanceId: string): Promise { - await copyToClipboard(instanceId, 'Id') -} - -export async function openLogDocument(node?: Ec2InstanceNode): Promise { - return await showFile(ec2LogSchema.form(await getSelection(node))) -} diff --git a/packages/core/src/awsService/ec2/connectionManagerMap.ts b/packages/core/src/awsService/ec2/connectionManagerMap.ts deleted file mode 100644 index fa1a258fa2..0000000000 --- a/packages/core/src/awsService/ec2/connectionManagerMap.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger } from '../../shared/logger/logger' -import { Ec2Connecter } from './model' - -export class Ec2ConnecterMap extends Map { - private static warnSize: number = 25 - - public getOrInit(regionCode: string) { - return this.has(regionCode) ? this.get(regionCode)! : this.initManager(regionCode) - } - - private initManager(regionCode: string): Ec2Connecter { - if (this.size >= Ec2ConnecterMap.warnSize) { - getLogger().warn( - `Connection manager exceeded threshold of ${Ec2ConnecterMap.warnSize} with ${this.size} active connections` - ) - } - const newConnectionManager = new Ec2Connecter(regionCode) - this.set(regionCode, newConnectionManager) - return newConnectionManager - } -} diff --git a/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts b/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts deleted file mode 100644 index 2f42e79a5d..0000000000 --- a/packages/core/src/awsService/ec2/ec2LogDocumentProvider.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { Ec2Selection } from './prompter' -import { Ec2Client } from '../../shared/clients/ec2' -import { ec2LogsScheme } from '../../shared/constants' -import { UriSchema } from '../../shared/utilities/uriUtils' - -export class Ec2LogDocumentProvider implements vscode.TextDocumentContentProvider { - public constructor() {} - - public async provideTextDocumentContent(uri: vscode.Uri): Promise { - if (!ec2LogSchema.isValid(uri)) { - throw new Error(`Invalid EC2 Logs URI: ${uri.toString()}`) - } - const ec2Selection = ec2LogSchema.parse(uri) - const ec2Client = new Ec2Client(ec2Selection.region) - const consoleOutput = await ec2Client.getConsoleOutput(ec2Selection.instanceId, false) - return consoleOutput.Output - } -} - -export const ec2LogSchema = new UriSchema(parseEc2Uri, formEc2Uri) - -function parseEc2Uri(uri: vscode.Uri): Ec2Selection { - const parts = uri.path.split(':') - - if (uri.scheme !== ec2LogsScheme) { - throw new Error(`URI ${uri} is not parseable for EC2 Logs`) - } - - return { - instanceId: parts[1], - region: parts[0], - } -} - -function formEc2Uri(selection: Ec2Selection): vscode.Uri { - return vscode.Uri.parse(`${ec2LogsScheme}:${selection.region}:${selection.instanceId}`) -} diff --git a/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts b/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts deleted file mode 100644 index 70726c863a..0000000000 --- a/packages/core/src/awsService/ec2/explorer/ec2InstanceNode.ts +++ /dev/null @@ -1,115 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { Ec2Client, getNameOfInstance } from '../../../shared/clients/ec2' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { Ec2Instance } from '../../../shared/clients/ec2' -import globals from '../../../shared/extensionGlobals' -import { getIconCode } from '../utils' -import { Ec2Selection } from '../prompter' -import { Ec2Node, Ec2ParentNode } from './ec2ParentNode' -import { getLogger } from '../../../shared/logger/logger' -import { InstanceStateName } from '@aws-sdk/client-ec2' - -export const Ec2InstanceRunningContext = 'awsEc2RunningNode' -export const Ec2InstanceStoppedContext = 'awsEc2StoppedNode' -export const Ec2InstancePendingContext = 'awsEc2PendingNode' - -type Ec2InstanceNodeContext = 'awsEc2RunningNode' | 'awsEc2StoppedNode' | 'awsEc2PendingNode' - -export class Ec2InstanceNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public readonly parent: Ec2ParentNode, - public readonly client: Ec2Client, - public override readonly regionCode: string, - private readonly partitionId: string, - // XXX: this variable is marked as readonly, but the 'status' attribute is updated when polling the nodes. - public readonly instance: Ec2Instance - ) { - super('') - this.parent.addChild(this) - this.updateInstance(instance) - this.id = this.InstanceId - } - - public updateInstance(newInstance: Ec2Instance) { - this.setInstanceStatus(newInstance.LastSeenStatus) - this.label = `${this.name} (${this.InstanceId}) ${this.instance.LastSeenStatus.toUpperCase()}` - this.contextValue = this.getContext() - this.iconPath = new vscode.ThemeIcon(getIconCode(this.instance)) - this.tooltip = `${this.name}\n${this.InstanceId}\n${this.instance.LastSeenStatus}\n${this.arn}` - - if (this.isPending()) { - this.parent.trackPendingNode(this.InstanceId) - } - } - - public isPending(): boolean { - return this.getStatus() !== 'running' && this.getStatus() !== 'stopped' - } - - public async updateStatus() { - const newStatus = await this.client.getInstanceStatus(this.InstanceId) - this.updateInstance({ ...this.instance, LastSeenStatus: newStatus }) - } - - private getContext(): Ec2InstanceNodeContext { - if (this.instance.LastSeenStatus === 'running') { - return Ec2InstanceRunningContext - } - - if (this.instance.LastSeenStatus === 'stopped') { - return Ec2InstanceStoppedContext - } - - return Ec2InstancePendingContext - } - - public setInstanceStatus(instanceStatus: InstanceStateName) { - this.instance.LastSeenStatus = instanceStatus - } - - public toSelection(): Ec2Selection { - return { - region: this.regionCode, - instanceId: this.InstanceId, - } - } - - public getStatus(): InstanceStateName { - return this.instance.LastSeenStatus - } - - public get name(): string { - return this.instance.Name ?? getNameOfInstance(this.instance) ?? `(no name)` - } - - public get InstanceId(): string { - return this.instance.InstanceId! - } - - public get arn(): string { - return `arn:${this.partitionId}:ec2:${ - this.regionCode - }:${globals.awsContext.getCredentialAccountId()}:instance/${this.InstanceId}` - } - - public async refreshNode(): Promise { - await this.updateStatus() - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} - -export async function tryRefreshNode(node?: Ec2Node) { - if (node) { - const n = node instanceof Ec2InstanceNode ? node.parent : node - try { - await n.refreshNode() - } catch (e) { - getLogger().error('refreshNode failed: %s', (e as Error).message) - } - } -} diff --git a/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts b/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts deleted file mode 100644 index 88da65536c..0000000000 --- a/packages/core/src/awsService/ec2/explorer/ec2ParentNode.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { Ec2InstanceNode } from './ec2InstanceNode' -import { Ec2Client } from '../../../shared/clients/ec2' -import { updateInPlace } from '../../../shared/utilities/collectionUtils' -import { PollingSet } from '../../../shared/utilities/pollingSet' - -export const parentContextValue = 'awsEc2ParentNode' -export type Ec2Node = Ec2InstanceNode | Ec2ParentNode - -export class Ec2ParentNode extends AWSTreeNodeBase { - protected readonly placeHolderMessage = '[No EC2 Instances Found]' - protected ec2InstanceNodes: Map - public override readonly contextValue: string = parentContextValue - public readonly pollingSet: PollingSet = new PollingSet(5000, this.updatePendingNodes.bind(this)) - - public constructor( - public override readonly regionCode: string, - public readonly partitionId: string, - protected readonly ec2Client: Ec2Client - ) { - super('EC2', vscode.TreeItemCollapsibleState.Collapsed) - this.ec2InstanceNodes = new Map() - } - - public override async getChildren(): Promise { - const result = await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.ec2InstanceNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => new PlaceholderNode(this, this.placeHolderMessage), - sort: (nodeA, nodeB) => nodeA.name.localeCompare(nodeB.name), - }) - return result - } - - public trackPendingNode(instanceId: string) { - if (!this.ec2InstanceNodes.has(instanceId)) { - throw new Error(`Attempt to track ec2 node ${instanceId} that isn't a child`) - } - this.pollingSet.add(instanceId) - } - // TODO: make use of childNodeLoader to avoid loading all of this at once. - public async updateChildren(): Promise { - const instanceMap = await this.ec2Client - .getInstances() - .flatten() - .toMap((instance) => instance.InstanceId) - - updateInPlace( - this.ec2InstanceNodes, - instanceMap.keys(), - (key) => this.ec2InstanceNodes.get(key)!.updateInstance(instanceMap.get(key)!), - (key) => new Ec2InstanceNode(this, this.ec2Client, this.regionCode, this.partitionId, instanceMap.get(key)!) - ) - } - - public getInstanceNode(instanceId: string): Ec2InstanceNode { - const childNode = this.ec2InstanceNodes.get(instanceId) - if (childNode) { - return childNode - } else { - throw new Error(`Node with id ${instanceId} from polling set not found`) - } - } - - private async updatePendingNodes() { - for (const instanceId of this.pollingSet.values()) { - const childNode = this.getInstanceNode(instanceId) - await this.updatePendingNode(childNode) - } - } - - private async updatePendingNode(node: Ec2InstanceNode) { - await node.updateStatus() - if (!node.isPending()) { - this.pollingSet.delete(node.InstanceId) - await node.refreshNode() - } - } - - public async clearChildren() { - this.ec2InstanceNodes = new Map() - } - - public addChild(node: Ec2InstanceNode) { - this.ec2InstanceNodes.set(node.InstanceId, node) - } - - public async refreshNode(): Promise { - await this.clearChildren() - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} diff --git a/packages/core/src/awsService/ec2/model.ts b/packages/core/src/awsService/ec2/model.ts deleted file mode 100644 index ad37357fe8..0000000000 --- a/packages/core/src/awsService/ec2/model.ts +++ /dev/null @@ -1,368 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { Ec2Selection } from './prompter' -import { getOrInstallCli } from '../../shared/utilities/cliUtils' -import { isCloud9 } from '../../shared/extensionUtilities' -import { ToolkitError } from '../../shared/errors' -import { SsmClient } from '../../shared/clients/ssm' -import { Ec2Client } from '../../shared/clients/ec2' -import { - VscodeRemoteConnection, - createBoundProcess, - ensureDependencies, - getDeniedSsmActions, - openRemoteTerminal, - promptToAddInlinePolicy, -} from '../../shared/remoteSession' -import { IamClient, IamRole } from '../../shared/clients/iam' -import { ErrorInformation } from '../../shared/errors' -import { - sshAgentSocketVariable, - SshError, - startSshAgent, - startVscodeRemote, - testSshConnection, -} from '../../shared/extensions/ssh' -import { getLogger } from '../../shared/logger/logger' -import { CancellationError, Timeout, waitUntil } from '../../shared/utilities/timeoutUtils' -import { showMessageWithCancel } from '../../shared/utilities/messages' -import { SshConfig } from '../../shared/sshConfig' -import { SshKeyPair } from './sshKeyPair' -import { Ec2SessionTracker } from './remoteSessionManager' -import { getEc2SsmEnv } from './utils' -import { Session, StartSessionResponse } from '@aws-sdk/client-ssm' - -export type Ec2ConnectErrorCode = 'EC2SSMStatus' | 'EC2SSMPermission' | 'EC2SSMTestConnect' | 'EC2SSMAgentStatus' - -export interface Ec2RemoteEnv extends VscodeRemoteConnection { - selection: Ec2Selection - keyPair: SshKeyPair - ssmSession: StartSessionResponse -} - -export type Ec2OS = 'Amazon Linux' | 'Ubuntu' | 'macOS' -interface RemoteUser { - os: Ec2OS - name: string -} - -export class Ec2Connecter implements vscode.Disposable { - protected ssm: SsmClient - protected ec2Client: Ec2Client - protected iamClient: IamClient - protected sessionManager: Ec2SessionTracker - - private policyDocumentationUri = vscode.Uri.parse( - 'https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-instance-profile.html' - ) - - private ssmAgentDocumentationUri = vscode.Uri.parse( - 'https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html' - ) - - public constructor(readonly regionCode: string) { - this.ssm = this.createSsmSdkClient() - this.ec2Client = this.createEc2SdkClient() - this.iamClient = this.createIamSdkClient() - this.sessionManager = new Ec2SessionTracker(regionCode, this.ssm) - } - - protected createSsmSdkClient(): SsmClient { - return new SsmClient(this.regionCode) - } - - protected createEc2SdkClient(): Ec2Client { - return new Ec2Client(this.regionCode) - } - - protected createIamSdkClient(): IamClient { - return new IamClient(this.regionCode) - } - - public async addActiveSession(sessionId: string, instanceId: string): Promise { - await this.sessionManager.addSession(instanceId, sessionId) - } - - public async dispose(): Promise { - await this.sessionManager.dispose() - } - - public isConnectedTo(instanceId: string): boolean { - return this.sessionManager.isConnectedTo(instanceId) - } - - public async getAttachedIamRole(instanceId: string): Promise { - const IamInstanceProfile = await this.ec2Client.getAttachedIamInstanceProfile(instanceId) - if (IamInstanceProfile && IamInstanceProfile.Arn) { - const IamRole = await this.iamClient.getIAMRoleFromInstanceProfile(IamInstanceProfile.Arn) - return IamRole - } - } - - public async hasProperPermissions(IamRoleArn: string): Promise { - const deniedActions = await getDeniedSsmActions(this.iamClient, IamRoleArn) - - return deniedActions.length === 0 - } - - public async isInstanceRunning(instanceId: string): Promise { - const instanceStatus = await this.ec2Client.getInstanceStatus(instanceId) - return instanceStatus === 'running' - } - - protected throwConnectionError(message: string, selection: Ec2Selection, errorInfo: ErrorInformation) { - const generalErrorMessage = `Unable to connect to target instance ${selection.instanceId} on region ${selection.region}. ` - throw new ToolkitError(generalErrorMessage + message, errorInfo) - } - - private async checkForInstanceStatusError(selection: Ec2Selection): Promise { - const isInstanceRunning = await this.isInstanceRunning(selection.instanceId) - - if (!isInstanceRunning) { - const message = 'Ensure the target instance is running.' - this.throwConnectionError(message, selection, { code: 'EC2SSMStatus' }) - } - } - - private async checkForInstancePermissionsError(selection: Ec2Selection): Promise { - const IamRole = await this.getAttachedIamRole(selection.instanceId) - - if (!IamRole) { - const message = `No IAM role attached to instance: ${selection.instanceId}` - this.throwConnectionError(message, selection, { - code: 'EC2SSMPermission', - documentationUri: this.policyDocumentationUri, - }) - } - - const hasPermission = await this.hasProperPermissions(IamRole!.Arn) - - if (!hasPermission) { - const policiesAdded = await promptToAddInlinePolicy(this.iamClient, IamRole!.Arn!) - - if (!policiesAdded) { - throw new CancellationError('user') - } - } - } - - public async checkForInstanceSsmError( - selection: Ec2Selection, - options?: Partial<{ interval: number; timeout: number }> - ): Promise { - const isSsmAgentRunning = await waitUntil( - async () => (await this.ssm.getInstanceAgentPingStatus(selection.instanceId)) === 'Online', - { interval: options?.interval ?? 500, timeout: options?.timeout ?? 5000 } - ) - - if (!isSsmAgentRunning) { - this.throwConnectionError('Is SSM Agent running on the target instance?', selection, { - code: 'EC2SSMAgentStatus', - documentationUri: this.ssmAgentDocumentationUri, - }) - } - } - - public async checkForStartSessionError(selection: Ec2Selection): Promise { - await this.checkForInstanceStatusError(selection) - - await this.checkForInstancePermissionsError(selection) - - await this.checkForInstanceSsmError(selection) - } - - private async openSessionInTerminal(session: Session, selection: Ec2Selection) { - const ssmPlugin = await getOrInstallCli('session-manager-plugin', !isCloud9) - const shellArgs = [JSON.stringify(session), selection.region, 'StartSession'] - const terminalOptions = { - name: `${selection.region}/${selection.instanceId}`, - shellPath: ssmPlugin, - shellArgs: shellArgs, - } - - await openRemoteTerminal(terminalOptions, () => this.ssm.terminateSession(session)).catch((err) => { - throw ToolkitError.chain(err, 'Failed to open ec2 instance.') - }) - } - - public async attemptToOpenEc2Terminal(selection: Ec2Selection): Promise { - await this.checkForStartSessionError(selection) - try { - const response = await this.ssm.startSession(selection.instanceId) - await this.openSessionInTerminal(response, selection) - } catch (err: unknown) { - this.throwConnectionError('', selection, err as Error) - } - } - - public async tryOpenRemoteConnection(selection: Ec2Selection): Promise { - await this.checkForStartSessionError(selection) - - const remoteUser = await this.getRemoteUser(selection.instanceId) - const remoteEnv = await this.prepareEc2RemoteEnvWithProgress(selection, remoteUser) - const testSession = await this.ssm.startSession(selection.instanceId, 'AWS-StartSSHSession') - try { - await testSshConnection( - remoteEnv.SessionProcess, - remoteEnv.hostname, - remoteEnv.sshPath, - remoteUser.name, - testSession - ) - await startVscodeRemote( - remoteEnv.SessionProcess, - remoteEnv.hostname, - '/', - remoteEnv.vscPath, - remoteUser.name - ) - } catch (err) { - const message = err instanceof SshError ? `Testing SSM connection to instance failed: ${err.message}` : '' - this.throwConnectionError(message, selection, { ...(err as Error), code: 'EC2SSMTestConnect' }) - } finally { - await this.ssm.terminateSession(testSession) - } - } - - public async prepareEc2RemoteEnvWithProgress( - selection: Ec2Selection, - remoteUser: RemoteUser - ): Promise { - const timeout = new Timeout(60000) - await showMessageWithCancel('AWS: Opening remote connection...', timeout) - const remoteEnv = await this.prepareEc2RemoteEnv(selection, remoteUser).finally(() => timeout.cancel()) - return remoteEnv - } - - private async startSSMSession(instanceId: string): Promise { - const ssmSession = await this.ssm.startSession(instanceId, 'AWS-StartSSHSession') - await this.addActiveSession(instanceId, ssmSession.SessionId!) - return ssmSession - } - - public async prepareEc2RemoteEnv(selection: Ec2Selection, remoteUser: RemoteUser): Promise { - const logger = this.configureRemoteConnectionLogger(selection.instanceId) - const { ssm, vsc, ssh } = (await ensureDependencies()).unwrap() - const keyPair = await this.configureSshKeys(selection, remoteUser) - const hostnamePrefix = 'aws-ec2-' - const hostname = `${hostnamePrefix}${selection.instanceId}` - const sshConfig = new SshConfig(ssh, hostnamePrefix, 'ec2_connect', keyPair.getPrivateKeyPath()) - - const config = await sshConfig.ensureValid() - if (config.isErr()) { - const err = config.err() - getLogger().error(`ec2: failed to add ssh config section: ${err.message}`) - - throw err - } - - const ssmSession = await this.startSSMSession(selection.instanceId) - - const vars = getEc2SsmEnv(selection, ssm, ssmSession) - getLogger().debug(`ec2: connect script logs at ${vars.LOG_FILE_LOCATION}`) - - const envProvider = async () => { - return { [sshAgentSocketVariable]: await startSshAgent(), ...vars } - } - const SessionProcess = createBoundProcess(envProvider).extend({ - onStdout: logger, - onStderr: logger, - rejectOnErrorCode: true, - }) - - return { - hostname, - envProvider, - sshPath: ssh, - vscPath: vsc, - SessionProcess, - selection, - keyPair, - ssmSession, - } - } - - private configureRemoteConnectionLogger(instanceId: string) { - const logPrefix = `ec2 (${instanceId})` - const logger = (data: string) => getLogger().verbose(`${logPrefix}: ${data}`) - return logger - } - - public async configureSshKeys(selection: Ec2Selection, remoteUser: RemoteUser): Promise { - const keyPair = await SshKeyPair.getSshKeyPair(`aws-ec2-key`, 30000) - await this.sendSshKeyToInstance(selection, keyPair, remoteUser) - return keyPair - } - - /** Removes old key(s) that we added to the remote ~/.ssh/authorized_keys file. */ - public async tryCleanKeys( - instanceId: string, - hintComment: string, - hostOS: Ec2OS, - remoteAuthorizedKeysPath: string - ) { - try { - const deleteExistingKeyCommand = getRemoveLinesCommand(hintComment, hostOS, remoteAuthorizedKeysPath) - await this.sendCommandAndWait(instanceId, deleteExistingKeyCommand) - } catch (e) { - getLogger().warn(`ec2: failed to clean keys: %O`, e) - } - } - - private async sendCommandAndWait(instanceId: string, command: string) { - return await this.ssm.sendCommandAndWait(instanceId, 'AWS-RunShellScript', { - commands: [command], - }) - } - - public async sendSshKeyToInstance( - selection: Ec2Selection, - sshKeyPair: SshKeyPair, - remoteUser: RemoteUser - ): Promise { - const sshPubKey = await sshKeyPair.getPublicKey() - const hintComment = '#AWSToolkitForVSCode' - - const remoteAuthorizedKeysPath = `/home/${remoteUser.name}/.ssh/authorized_keys` - - const appendStr = (s: string) => `echo "${s}" >> ${remoteAuthorizedKeysPath}` - const writeKeyCommand = appendStr([sshPubKey.replace('\n', ''), hintComment].join(' ')) - - await this.tryCleanKeys(selection.instanceId, hintComment, remoteUser.os, remoteAuthorizedKeysPath) - await this.sendCommandAndWait(selection.instanceId, writeKeyCommand) - } - - public async getRemoteUser(instanceId: string): Promise { - const os = await this.ssm.getTargetPlatformName(instanceId) - if (os === 'Amazon Linux') { - return { name: 'ec2-user', os } - } - - if (os === 'Ubuntu') { - return { name: 'ubuntu', os } - } - - throw new ToolkitError(`Unrecognized OS name ${os} on instance ${instanceId}`, { code: 'UnknownEc2OS' }) - } -} - -/** - * Generate bash command (as string) to remove lines containing `pattern`. - * @param pattern pattern for deleted lines. - * @param filepath filepath (as string) to target with the command. - * @returns bash command to remove lines from file. - */ -export function getRemoveLinesCommand(pattern: string, hostOS: Ec2OS, filepath: string): string { - if (pattern.includes('/')) { - throw new ToolkitError(`ec2: cannot match pattern containing '/', given: ${pattern}`) - } - // Linux allows not passing extension to -i, whereas macOS requires zero length extension. - return `sed -i${isLinux(hostOS) ? '' : " ''"} /${pattern}/d ${filepath}` -} - -function isLinux(os: Ec2OS): boolean { - return os === 'Amazon Linux' || os === 'Ubuntu' -} diff --git a/packages/core/src/awsService/ec2/prompter.ts b/packages/core/src/awsService/ec2/prompter.ts deleted file mode 100644 index 64d96e00f5..0000000000 --- a/packages/core/src/awsService/ec2/prompter.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RegionSubmenu, RegionSubmenuResponse } from '../../shared/ui/common/regionSubmenu' -import { DataQuickPickItem } from '../../shared/ui/pickerPrompter' -import { Ec2Client, Ec2Instance } from '../../shared/clients/ec2' -import { isValidResponse } from '../../shared/wizards/wizard' -import { CancellationError } from '../../shared/utilities/timeoutUtils' -import { getIconCode } from './utils' -import { Ec2Node } from './explorer/ec2ParentNode' -import { Ec2InstanceNode } from './explorer/ec2InstanceNode' -import { AsyncCollection } from '../../shared/utilities/asyncCollection' - -export type InstanceFilter = (instance: Ec2Instance) => boolean -export interface Ec2Selection { - instanceId: string - region: string -} - -interface Ec2PrompterOptions { - instanceFilter: InstanceFilter - getInstancesFromRegion: (regionCode: string) => AsyncCollection -} - -export class Ec2Prompter { - protected instanceFilter: InstanceFilter - protected getInstancesFromRegion: (regionCode: string) => AsyncCollection - - public constructor(options?: Partial) { - this.instanceFilter = options?.instanceFilter ?? ((_) => true) - this.getInstancesFromRegion = - options?.getInstancesFromRegion ?? ((regionCode: string) => new Ec2Client(regionCode).getInstances()) - } - - public static getLabel(instance: Ec2Instance) { - const icon = `$(${getIconCode(instance)})` - return `${instance.Name ?? '(no name)'} \t ${icon} ${instance.LastSeenStatus.toUpperCase()}` - } - - public static asQuickPickItem(instance: Ec2Instance): DataQuickPickItem { - return { - label: Ec2Prompter.getLabel(instance), - detail: instance.InstanceId, - data: instance.InstanceId, - } - } - - public static getSelectionFromResponse(response: RegionSubmenuResponse): Ec2Selection { - return { - instanceId: response.data, - region: response.region, - } - } - - public async promptUser(): Promise { - const prompter = this.createEc2ConnectPrompter() - const response = await prompter.prompt() - - if (isValidResponse(response)) { - return Ec2Prompter.getSelectionFromResponse(response) - } else { - throw new CancellationError('user') - } - } - - public getInstancesAsQuickPickItems(region: string): AsyncIterable[]> { - return this.getInstancesFromRegion(region).map((instancePage: Ec2Instance[]) => - instancePage.filter(this.instanceFilter).map((i) => Ec2Prompter.asQuickPickItem(i)) - ) - } - - private createEc2ConnectPrompter(): RegionSubmenu { - return new RegionSubmenu( - (region) => this.getInstancesAsQuickPickItems(region), - { title: 'Select EC2 Instance', matchOnDetail: true }, - { title: 'Select Region for EC2 Instance' }, - 'Instances' - ) - } -} - -export async function getSelection(node?: Ec2Node, instanceFilter?: InstanceFilter): Promise { - const prompter = new Ec2Prompter({ instanceFilter }) - const selection = node && node instanceof Ec2InstanceNode ? node.toSelection() : await prompter.promptUser() - return selection -} diff --git a/packages/core/src/awsService/ec2/remoteSessionManager.ts b/packages/core/src/awsService/ec2/remoteSessionManager.ts deleted file mode 100644 index 12cc77ba61..0000000000 --- a/packages/core/src/awsService/ec2/remoteSessionManager.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SsmClient } from '../../shared/clients/ssm' -import { Disposable } from 'vscode' - -export class Ec2SessionTracker extends Map implements Disposable { - public constructor( - readonly regionCode: string, - protected ssm: SsmClient - ) { - super() - } - - public async addSession(instanceId: string, sessionId: string): Promise { - if (this.isConnectedTo(instanceId)) { - const existingSessionId = this.get(instanceId)! - await this.ssm.terminateSessionFromId(existingSessionId) - this.set(instanceId, sessionId) - } else { - this.set(instanceId, sessionId) - } - } - - private async disconnectEnv(instanceId: string): Promise { - await this.ssm.terminateSessionFromId(this.get(instanceId)!) - this.delete(instanceId) - } - - public async dispose(): Promise { - // eslint-disable-next-line unicorn/no-array-for-each - this.forEach(async (_sessionId, instanceId) => await this.disconnectEnv(instanceId)) - } - - public isConnectedTo(instanceId: string): boolean { - return this.has(instanceId) - } -} diff --git a/packages/core/src/awsService/ec2/sshKeyPair.ts b/packages/core/src/awsService/ec2/sshKeyPair.ts deleted file mode 100644 index 75e1426bf2..0000000000 --- a/packages/core/src/awsService/ec2/sshKeyPair.ts +++ /dev/null @@ -1,134 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import os from 'os' -import globals from '../../shared/extensionGlobals' -import { fs } from '../../shared/fs/fs' -import { ToolkitError } from '../../shared/errors' -import { tryRun } from '../../shared/utilities/pathFind' -import { Timeout } from '../../shared/utilities/timeoutUtils' -import { findAsync } from '../../shared/utilities/collectionUtils' -import { RunParameterContext } from '../../shared/utilities/processUtils' -import path from 'path' - -type sshKeyType = 'rsa' | 'ed25519' - -export class SshKeyPair { - private publicKeyPath: string - private lifeTimeout: Timeout - - private constructor( - private readonly keyPath: string, - lifetime: number - ) { - this.publicKeyPath = `${this.keyPath}.pub` - this.lifeTimeout = new Timeout(lifetime) - - this.lifeTimeout.onCompletion(async () => { - await this.delete() - }) - } - - private static getKeypath(keyName: string): string { - return path.join(globals.context.globalStorageUri.fsPath, keyName) - } - - public static async getSshKeyPair(keyName: string, lifetime: number) { - const keyPath = SshKeyPair.getKeypath(keyName) - await SshKeyPair.generateSshKeyPair(keyPath) - return new SshKeyPair(keyPath, lifetime) - } - - private static isValidKeyPath(keyPath: string): boolean { - const relative = path.relative(globals.context.globalStorageUri.fsPath, keyPath) - return relative !== undefined && !relative.startsWith('..') && !path.isAbsolute(relative) && keyPath.length > 4 - } - - private static assertValidKeypath(keyPath: string, message: string): void | never { - if (!SshKeyPair.isValidKeyPath(keyPath)) { - throw new ToolkitError(message) - } - } - - private static async assertGenerated(keyPath: string, keyGenerated: boolean): Promise { - if (!keyGenerated) { - throw new ToolkitError('ec2: Unable to generate ssh key pair with either ed25519 or rsa') - } - - if (!(await fs.exists(keyPath))) { - throw new ToolkitError(`ec2: Failed to generate keys, resulting key not found at ${keyPath}`) - } - } - - public static async generateSshKeyPair(keyPath: string): Promise { - const keyGenerated = await SshKeyPair.tryKeyTypes(keyPath, ['ed25519', 'rsa']) - // Should already be the case, but just in case we assert permissions. - // skip on Windows since it only allows write permission to be changed. - if (!globals.isWeb && os.platform() !== 'win32') { - await fs.chmod(keyPath, 0o600) - await SshKeyPair.assertGenerated(keyPath, keyGenerated) - } - } - /** - * Attempts to generate an ssh key pair. Returns true if successful, false otherwise. - * @param keyPath where to generate key. - * @param keyType type of key to generate. - */ - public static async tryKeyGen(keyPath: string, keyType: sshKeyType): Promise { - const overrideKeys = async (_t: string, proc: RunParameterContext) => { - await proc.send('yes') - } - return await tryRun( - 'ssh-keygen', - ['-t', keyType, '-N', '', '-q', '-f', keyPath], - 'yes', - /^(?!.*Unknown key type).*/i, - { - onStdout: overrideKeys, - timeout: new Timeout(5000), - } - ) - } - - public static async tryKeyTypes(keyPath: string, keyTypes: sshKeyType[]): Promise { - const keyTypeUsed = await findAsync(keyTypes, async (type) => await this.tryKeyGen(keyPath, type)) - return keyTypeUsed !== undefined - } - - public getPublicKeyPath(): string { - return this.publicKeyPath - } - - public getPrivateKeyPath(): string { - return this.keyPath - } - - public async getPublicKey(): Promise { - const contents = await fs.readFileText(this.publicKeyPath) - return contents - } - - public async delete(): Promise { - SshKeyPair.assertValidKeypath( - this.keyPath, - `ec2: keyPath became invalid after creation, not deleting key at ${this.keyPath}` - ) - await fs.delete(this.keyPath, { force: true }) - await fs.delete(this.publicKeyPath, { force: true }) - - if (!this.lifeTimeout.completed) { - this.lifeTimeout.cancel() - } - } - - public async isDeleted(): Promise { - const privateKeyDeleted = !(await fs.existsFile(this.getPrivateKeyPath())) - const publicKeyDeleted = !(await fs.existsFile(this.getPublicKeyPath())) - return privateKeyDeleted || publicKeyDeleted - } - - public timeAlive(): number { - return this.lifeTimeout.elapsedTime - } -} diff --git a/packages/core/src/awsService/ec2/utils.ts b/packages/core/src/awsService/ec2/utils.ts deleted file mode 100644 index a86f358eab..0000000000 --- a/packages/core/src/awsService/ec2/utils.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Ec2Instance } from '../../shared/clients/ec2' -import { copyToClipboard } from '../../shared/utilities/messages' -import { Ec2Selection } from './prompter' -import { sshLogFileLocation } from '../../shared/sshConfig' -import { StartSessionResponse } from '@aws-sdk/client-ssm' -import { getLogger } from '../../shared/logger/logger' - -export function getIconCode(instance: Ec2Instance) { - if (instance.LastSeenStatus === 'running') { - return 'pass' - } - - if (instance.LastSeenStatus === 'stopped') { - return 'circle-slash' - } - - if (instance.LastSeenStatus === 'terminated') { - return 'stop' - } - - return 'loading~spin' -} - -export async function copyInstanceId(instanceId: string): Promise { - await copyToClipboard(instanceId, 'Id') -} - -export function getEc2SsmEnv( - selection: Ec2Selection, - ssmPath: string, - session: StartSessionResponse -): NodeJS.ProcessEnv { - return Object.assign( - { - AWS_REGION: selection.region, - AWS_SSM_CLI: ssmPath, - LOG_FILE_LOCATION: sshLogFileLocation('ec2', selection.instanceId), - STREAM_URL: session.StreamUrl, - SESSION_ID: session.SessionId, - TOKEN: session.TokenValue, - DEBUG_LOG: getLogger().logLevelEnabled('debug') ? 1 : 0, - }, - process.env - ) -} diff --git a/packages/core/src/awsService/ecr/activation.ts b/packages/core/src/awsService/ecr/activation.ts deleted file mode 100644 index e690d298c9..0000000000 --- a/packages/core/src/awsService/ecr/activation.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { copyTagUri } from './commands/copyTagUri' -import { copyRepositoryUri } from './commands/copyRepositoryUri' -import { createRepository } from './commands/createRepository' -import { deleteRepository } from './commands/deleteRepository' -import { EcrNode } from './explorer/ecrNode' -import { EcrRepositoryNode } from './explorer/ecrRepositoryNode' -import { EcrTagNode } from './explorer/ecrTagNode' -import { deleteTag } from './commands/deleteTag' -import { Commands } from '../../shared/vscode/commands2' - -/** - * Activates ECR components. - */ -export async function activate(extensionContext: vscode.ExtensionContext): Promise { - extensionContext.subscriptions.push( - Commands.register('aws.ecr.createRepository', async (node: EcrNode) => { - await createRepository(node) - }), - Commands.register('aws.ecr.deleteRepository', async (node: EcrRepositoryNode) => { - await deleteRepository(node) - }), - Commands.register('aws.ecr.copyRepositoryUri', async (node: EcrRepositoryNode) => { - await copyRepositoryUri(node) - }), - Commands.register('aws.ecr.copyTagUri', async (node: EcrTagNode) => { - await copyTagUri(node) - }), - Commands.register('aws.ecr.deleteTag', async (node: EcrTagNode) => { - await deleteTag(node) - }) - ) -} diff --git a/packages/core/src/awsService/ecr/commands/copyRepositoryUri.ts b/packages/core/src/awsService/ecr/commands/copyRepositoryUri.ts deleted file mode 100644 index 87e37d13a6..0000000000 --- a/packages/core/src/awsService/ecr/commands/copyRepositoryUri.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { copyToClipboard } from '../../../shared/utilities/messages' -import { EcrRepositoryNode } from '../explorer/ecrRepositoryNode' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export async function copyRepositoryUri(node: EcrRepositoryNode): Promise { - const uri = node.repository.repositoryUri - await copyToClipboard(uri, 'URI') - telemetry.ecr_copyRepositoryUri.emit() -} diff --git a/packages/core/src/awsService/ecr/commands/copyTagUri.ts b/packages/core/src/awsService/ecr/commands/copyTagUri.ts deleted file mode 100644 index 21b71cb447..0000000000 --- a/packages/core/src/awsService/ecr/commands/copyTagUri.ts +++ /dev/null @@ -1,14 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EcrTagNode } from '../explorer/ecrTagNode' -import { copyToClipboard } from '../../../shared/utilities/messages' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export async function copyTagUri(node: EcrTagNode): Promise { - const uri = `${node.repository.repositoryUri}:${node.tag}` - await copyToClipboard(uri, 'URI') - telemetry.ecr_copyTagUri.emit() -} diff --git a/packages/core/src/awsService/ecr/commands/createRepository.ts b/packages/core/src/awsService/ecr/commands/createRepository.ts deleted file mode 100644 index d2c75ff3c4..0000000000 --- a/packages/core/src/awsService/ecr/commands/createRepository.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { EcrNode } from '../explorer/ecrNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { validateRepositoryName } from '../utils' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export async function createRepository(node: EcrNode): Promise { - getLogger().debug('createRepository called for %O', node) - - const repositoryName = await vscode.window.showInputBox({ - prompt: localize('AWS.ecr.createRepository.prompt', 'Enter a new repository name'), - placeHolder: localize('AWS.ecr.createRepository.placeHolder', 'Repository Name'), - validateInput: validateRepositoryName, - }) - - if (!repositoryName) { - getLogger().info('createRepository cancelled') - telemetry.ecr_createRepository.emit({ result: 'Cancelled' }) - return - } - - getLogger().info(`Creating repository ${repositoryName}`) - try { - const repository = await node.createRepository(repositoryName) - - getLogger().info('created repository: %O', repository) - void vscode.window.showInformationMessage( - localize('AWS.ecr.createRepository.success', 'Created repository: {0}', repositoryName) - ) - telemetry.ecr_createRepository.emit({ result: 'Succeeded' }) - } catch (e) { - getLogger().error(`Failed to create repository ${repositoryName}: %s`, e) - void showViewLogsMessage( - localize('AWS.ecr.createRepository.failure', 'Failed to create repository: {0}', repositoryName) - ) - telemetry.ecr_createRepository.emit({ result: 'Failed' }) - } finally { - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) - } -} diff --git a/packages/core/src/awsService/ecr/commands/deleteRepository.ts b/packages/core/src/awsService/ecr/commands/deleteRepository.ts deleted file mode 100644 index ec14841d0d..0000000000 --- a/packages/core/src/awsService/ecr/commands/deleteRepository.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { EcrRepositoryNode } from '../explorer/ecrRepositoryNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export async function deleteRepository(node: EcrRepositoryNode): Promise { - getLogger().debug('DeleteRepository called for %O', node) - - const repositoryName = node.repository.repositoryName - - const isConfirmed = await showConfirmationDialog(repositoryName) - if (!isConfirmed) { - getLogger().info('DeleteRepository cancelled') - telemetry.ecr_deleteRepository.emit({ result: 'Cancelled' }) - return - } - - getLogger().info(`Deleting repository ${repositoryName}`) - try { - await node.deleteRepository() - - getLogger().info(`deleted repository: ${repositoryName}`) - - void vscode.window.showInformationMessage( - localize('AWS.ecr.deleteRepository.success', 'Deleted repository: {0}', repositoryName) - ) - telemetry.ecr_deleteRepository.emit({ result: 'Succeeded' }) - } catch (e) { - getLogger().error(`Failed to delete repository ${repositoryName}: %s`, e) - void showViewLogsMessage( - localize('AWS.ecr.deleteRepository.failure', 'Failed to delete repository: {0}', repositoryName) - ) - telemetry.ecr_deleteRepository.emit({ result: 'Failed' }) - } finally { - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node.parent) - } -} - -async function showConfirmationDialog(repositoryName: string): Promise { - const prompt = localize('AWS.s3.deleteBucket.prompt', 'Enter {0} to confirm deletion', repositoryName) - const confirmationInput = await vscode.window.showInputBox({ - prompt, - placeHolder: repositoryName, - validateInput: (input) => (input !== repositoryName ? prompt : undefined), - }) - - return confirmationInput === repositoryName -} diff --git a/packages/core/src/awsService/ecr/commands/deleteTag.ts b/packages/core/src/awsService/ecr/commands/deleteTag.ts deleted file mode 100644 index a037db0934..0000000000 --- a/packages/core/src/awsService/ecr/commands/deleteTag.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { EcrTagNode } from '../explorer/ecrTagNode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showConfirmationMessage, showViewLogsMessage } from '../../../shared/utilities/messages' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export async function deleteTag(node: EcrTagNode): Promise { - getLogger().debug('deleteTag called for %O', node) - const ok = await showConfirmationMessage({ - prompt: localize( - 'AWS.ecr.deleteTag.prompt', - 'Are you sure you want to delete tag {0} from repository {1}', - node.tag, - node.repository.repositoryName - ), - confirm: localize('AWS.generic.delete', 'Delete'), - cancel: localize('AWS.generic.cancel', 'Cancel'), - }) - if (!ok) { - getLogger().info(`Cancelled delete tag ${node.tag} from repository ${node.repository.repositoryName}`) - telemetry.ecr_deleteTags.emit({ result: 'Cancelled', value: 1 }) - return - } - try { - await node.deleteTag() - - getLogger().info(`Successfully deleted tag ${node.tag} from repository ${node.repository.repositoryName}`) - - void vscode.window.showInformationMessage( - localize( - 'AWS.ecr.deleteTag.success', - 'Deleted tag {0} from repository {1}', - node.tag, - node.repository.repositoryName - ) - ) - telemetry.ecr_deleteTags.emit({ result: 'Succeeded', value: 1 }) - } catch (e) { - getLogger().error(`Failed to delete tag ${node.tag} from repository ${node.repository.repositoryName}: %s`, e) - void showViewLogsMessage( - localize( - 'AWS.ecr.deleteTag.failure', - 'Failed to delete tag {0} from repository {1}', - node.tag, - node.repository.repositoryName - ) - ) - telemetry.ecr_deleteTags.emit({ result: 'Failed', value: 1 }) - } finally { - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node.parent) - } -} diff --git a/packages/core/src/awsService/ecr/explorer/ecrNode.ts b/packages/core/src/awsService/ecr/explorer/ecrNode.ts deleted file mode 100644 index a4a0279a28..0000000000 --- a/packages/core/src/awsService/ecr/explorer/ecrNode.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { toArrayAsync } from '../../../shared/utilities/collectionUtils' -import { inspect } from 'util' -import { EcrClient } from '../../../shared/clients/ecrClient' -import { EcrRepositoryNode } from './ecrRepositoryNode' - -/** - * An AWS Explorer node representing S3. - * - * Contains buckets for a specific region as child nodes. - */ -export class EcrNode extends AWSTreeNodeBase { - public constructor(private readonly ecr: EcrClient) { - super('ECR', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsEcrNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - const response = await toArrayAsync(this.ecr.describeRepositories()) - - return response.map((item) => new EcrRepositoryNode(this, this.ecr, item)) - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.ecr.noRepositories', '[No repositories found]')), - sort: (item1, item2) => item1.repository.repositoryName.localeCompare(item2.repository.repositoryName), - }) - } - - public async createRepository(repositoryName: string): Promise { - await this.ecr.createRepository(repositoryName) - } - - public [inspect.custom](): string { - return 'ECRNode' - } -} diff --git a/packages/core/src/awsService/ecr/explorer/ecrRepositoryNode.ts b/packages/core/src/awsService/ecr/explorer/ecrRepositoryNode.ts deleted file mode 100644 index 8f4d996060..0000000000 --- a/packages/core/src/awsService/ecr/explorer/ecrRepositoryNode.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { EcrNode } from './ecrNode' -import { EcrClient, EcrRepository } from '../../../shared/clients/ecrClient' - -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { toArrayAsync } from '../../../shared/utilities/collectionUtils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { EcrTagNode } from './ecrTagNode' -import { getIcon } from '../../../shared/icons' - -export class EcrRepositoryNode extends AWSTreeNodeBase implements AWSResourceNode { - name: string = this.repository.repositoryName - arn: string = this.repository.repositoryArn - public override readonly regionCode: string - - constructor( - public readonly parent: EcrNode, - private readonly ecr: EcrClient, - public readonly repository: EcrRepository - ) { - super(repository.repositoryName, vscode.TreeItemCollapsibleState.Collapsed) - this.iconPath = getIcon('aws-ecr-registry') - this.contextValue = 'awsEcrRepositoryNode' - this.regionCode = ecr.regionCode - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - const response = await toArrayAsync(this.ecr.describeTags(this.repository.repositoryName)) - - return response.map((item) => new EcrTagNode(this, this.ecr, this.repository, item)) - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.ecr.noTags', '[No tags found]')), - sort: (item1, item2) => item1.tag.localeCompare(item2.tag), - }) - } - - public async deleteRepository(): Promise { - await this.ecr.deleteRepository(this.repository.repositoryName) - } -} diff --git a/packages/core/src/awsService/ecr/explorer/ecrTagNode.ts b/packages/core/src/awsService/ecr/explorer/ecrTagNode.ts deleted file mode 100644 index 88b0357b61..0000000000 --- a/packages/core/src/awsService/ecr/explorer/ecrTagNode.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { EcrClient, EcrRepository } from '../../../shared/clients/ecrClient' -import { EcrRepositoryNode } from './ecrRepositoryNode' - -export class EcrTagNode extends AWSTreeNodeBase { - public override readonly regionCode = this.parent.regionCode - - public constructor( - public readonly parent: EcrRepositoryNode, - private readonly ecr: EcrClient, - public readonly repository: EcrRepository, - public readonly tag: string - ) { - super(tag, vscode.TreeItemCollapsibleState.None) - this.contextValue = 'awsEcrTagNode' - } - - public async deleteTag(): Promise { - await this.ecr.deleteTag(this.repository.repositoryName, this.tag) - } -} diff --git a/packages/core/src/awsService/ecr/utils.ts b/packages/core/src/awsService/ecr/utils.ts deleted file mode 100644 index 64f46e4856..0000000000 --- a/packages/core/src/awsService/ecr/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { localize } from '../../shared/utilities/vsCodeUtils' - -/** - * Validates an ECR repository name. There are actually more rules, but they aren't documented, so - * let the service fail the creation if one of the more complicated rules is violated - * - * @see https://docs.aws.amazon.com/AmazonECR/latest/APIReference/API_Repository.html - * @returns undefined if the name passes validation. Otherwise, a localized error message is returned. - */ -export function validateRepositoryName(name: string): string | undefined { - if (!/^[a-z]/.test(name)) { - return localize( - 'AWS.ecr.validateRepoName.error.invalidStart', - 'Repository name must start with a lowercase letter' - ) - } - - if (!/^[a-z0-9\-_/]+$/.test(name)) { - return localize( - 'AWS.ecr.validateRepoName.error.invalidCharacters', - 'Repository name must only contain lowercase letters, numbers, hyphens, underscores, and forward slashes' - ) - } - - // See the APIReference document above for the source of this. ^ and $ added to make sure - // it matches the whole string - if (!/^(?:[a-z0-9]+(?:[._-][a-z0-9]+)*\/)*[a-z0-9]+(?:[._-][a-z0-9]+)*$/.test(name)) { - return localize('AWS.ecr.validateRepoName.error.invalidString', 'Invalid repository name') - } - - return undefined -} diff --git a/packages/core/src/awsService/ecs/activation.ts b/packages/core/src/awsService/ecs/activation.ts deleted file mode 100644 index bd0dbe7069..0000000000 --- a/packages/core/src/awsService/ecs/activation.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ExtContext } from '../../shared/extensions' -import { ecsDocumentationUrl } from '../../shared/constants' -import { Commands } from '../../shared/vscode/commands2' -import { openTaskInTerminal, runCommandInContainer, toggleExecuteCommandFlag } from './commands' -import { getResourceFromTreeNode } from '../../shared/treeview/utils' -import { Instance } from '../../shared/utilities/typeConstructors' -import { Service } from './model' -import { telemetry } from '../../shared/telemetry/telemetry' -import { openUrl } from '../../shared/utilities/vsCodeUtils' - -export async function activate(ctx: ExtContext): Promise { - ctx.extensionContext.subscriptions.push(runCommandInContainer.register(), openTaskInTerminal.register()) - ctx.extensionContext.subscriptions.push( - Commands.register('aws.ecs.enableEcsExec', (param?: unknown) => { - return telemetry.ecs_enableExecuteCommand.run(async () => { - const service = getResourceFromTreeNode(param, Instance(Service)) - await toggleExecuteCommandFlag(service) - }) - }), - Commands.register('aws.ecs.disableEcsExec', (param?: unknown) => { - return telemetry.ecs_disableExecuteCommand.run(async () => { - const service = getResourceFromTreeNode(param, Instance(Service)) - await toggleExecuteCommandFlag(service) - }) - }), - Commands.register('aws.ecs.viewDocumentation', async () => { - void openUrl(vscode.Uri.parse(ecsDocumentationUrl)) - }) - ) -} diff --git a/packages/core/src/awsService/ecs/commands.ts b/packages/core/src/awsService/ecs/commands.ts deleted file mode 100644 index acf3daa804..0000000000 --- a/packages/core/src/awsService/ecs/commands.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import globals from '../../shared/extensionGlobals' -import { ToolkitPromptSettings } from '../../shared/settings' -import { ChildProcess } from '../../shared/utilities/processUtils' -import { showMessageWithCancel, showOutputMessage } from '../../shared/utilities/messages' -import { removeAnsi } from '../../shared/utilities/textUtilities' -import { CancellationError, Timeout } from '../../shared/utilities/timeoutUtils' -import { Commands } from '../../shared/vscode/commands2' -import { EcsSettings } from './util' -import { CommandWizard, CommandWizardState } from './wizards/executeCommand' -import { isUserCancelledError, ToolkitError } from '../../shared/errors' -import { getResourceFromTreeNode } from '../../shared/treeview/utils' -import { Container, Service } from './model' -import { Instance } from '../../shared/utilities/typeConstructors' -import { telemetry } from '../../shared/telemetry/telemetry' -import { openRemoteTerminal } from '../../shared/remoteSession' -import { formatDateTimestamp } from '../../shared/datetime' - -async function runCommandWizard( - param?: unknown, - command?: string -): Promise { - const container = getResourceFromTreeNode(param, Instance(Container)) - - const wizard = new CommandWizard( - container, - ToolkitPromptSettings.instance.isPromptEnabled('ecsRunCommand'), - command - ) - const response = await wizard.run() - - if (!response) { - throw new CancellationError('user') - } - - if (response.confirmation === 'suppress') { - await ToolkitPromptSettings.instance.disablePrompt('ecsRunCommand') - } - - return { container, ...response } -} - -export enum EcsRunCommandPrompt { - Enable = 'ecsRunCommandEnable', - Disable = 'ecsRunCommandDisable', -} - -export async function toggleExecuteCommandFlag( - service: Service, - window = vscode.window, - settings = ToolkitPromptSettings.instance -): Promise { - const yes = localize('AWS.generic.response.yes', 'Yes') - const yesDontAskAgain = localize('AWS.message.prompt.yesDontAskAgain', "Yes, don't ask again") - const no = localize('AWS.generic.response.no', 'No') - - const isNotEnabled = !service.description.enableExecuteCommand ?? false - const prompt = isNotEnabled ? EcsRunCommandPrompt.Enable : EcsRunCommandPrompt.Disable - - const warningMessage = isNotEnabled - ? localize( - 'AWS.command.ecs.runCommandInContainer.warning.enableExecuteFlag', - 'Enabling command execution will change the state of resources in your AWS account, including but not limited to stopping and restarting the service.\nAltering the state of resources while the Execute Command is enabled can lead to unpredictable results.\n Continue?' - ) - : localize( - 'AWS.command.ecs.runCommandInContainer.warning.disableExecuteFlag', - 'Disabling command execution will change the state of resources in your AWS account, including but not limited to stopping and restarting the service.\n Continue?' - ) - - if (settings.isPromptEnabled(prompt)) { - const choice = await window.showWarningMessage(warningMessage, yes, yesDontAskAgain, no) - if (choice === undefined || choice === no) { - throw new CancellationError('user') - } else if (choice === yesDontAskAgain) { - await settings.disablePrompt(prompt) - } - } - - await service.toggleExecuteCommand() -} - -export const runCommandInContainer = Commands.declare('aws.ecs.runCommandInContainer', () => (obj?: unknown) => { - return telemetry.ecs_runExecuteCommand.run(async (span) => { - span.record({ ecsExecuteCommandType: 'command' }) - - const { container, task, command } = await runCommandWizard(obj) - const timeout = new Timeout(600000) - void showMessageWithCancel('Running command...', timeout) - - try { - const { path, args, dispose } = await container.prepareCommandForTask(command, task) - showOutputMessage( - `${formatDateTimestamp(false)}: Container: "${ - container.description.name - }" Task ID: "${task}" Command: "${command}"`, - globals.outputChannel - ) - - const proc = new ChildProcess(path, args, { logging: 'noparams' }) - await proc - .run({ - timeout, - rejectOnError: true, - rejectOnErrorCode: true, - // TODO: `showOutputMessage` should not be writing to the logs... - onStdout: (text) => { - showOutputMessage(removeAnsi(text), globals.outputChannel) - }, - onStderr: (text) => { - showOutputMessage(removeAnsi(text), globals.outputChannel) - }, - }) - .finally(dispose) - } catch (err) { - if (isUserCancelledError(err)) { - showOutputMessage('Cancelled command execution', globals.outputChannel) - } - - const failedMessage = localize( - 'AWS.ecs.runCommandInContainer.error', - 'Failed to execute command in container.' - ) - throw ToolkitError.chain(err, failedMessage) - } finally { - timeout.dispose() - } - }) -}) - -export const openTaskInTerminal = Commands.declare('aws.ecs.openTaskInTerminal', () => (obj?: unknown) => { - return telemetry.ecs_runExecuteCommand.run(async (span) => { - span.record({ ecsExecuteCommandType: 'shell' }) - - const startCommand = new EcsSettings().get('openTerminalCommand') - const { container, task, command } = await runCommandWizard(obj, startCommand) - const session = await container.prepareCommandForTask(command, task) - - const terminalOptions = { - name: `${container.description.name}/${task}`, - shellPath: session.path, - shellArgs: session.args, - } - - await openRemoteTerminal(terminalOptions, session.dispose).catch((err) => { - throw ToolkitError.chain(err, localize('AWS.ecs.openTaskInTerminal.error', 'Failed to open terminal.')) - }) - }) -}) diff --git a/packages/core/src/awsService/ecs/model.ts b/packages/core/src/awsService/ecs/model.ts deleted file mode 100644 index 57f49576e2..0000000000 --- a/packages/core/src/awsService/ecs/model.ts +++ /dev/null @@ -1,212 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { Cluster as SdkCluster, ContainerDefinition, Service as SdkService, Task } from '@aws-sdk/client-ecs' -import { DefaultEcsClient } from '../../shared/clients/ecsClient' -import { ResourceTreeNode } from '../../shared/treeview/resource' -import { getIcon } from '../../shared/icons' -import { AsyncCollection } from '../../shared/utilities/asyncCollection' -import { prepareCommand } from './util' - -function createValidTaskFilter(containerName: string) { - return function (t: Task): t is Task & { taskArn: string } { - const managed = !!t.containers?.find( - (c) => c?.name === containerName && c.managedAgents?.find((a) => a.name === 'ExecuteCommandAgent') - ) - - return t.taskArn !== undefined && managed - } -} - -interface ContainerDescription extends ContainerDefinition { - readonly clusterArn: string - readonly taskRoleArn: string - readonly enableExecuteCommand?: boolean -} - -export class Container { - public readonly id = this.description.name! - - public constructor( - private readonly client: DefaultEcsClient, - public readonly serviceName: string, - public readonly description: ContainerDescription - ) {} - - public async listTasks() { - const resp = await this.client.listTasks({ - cluster: this.description.clusterArn, - serviceName: this.serviceName, - }) - const tasks = await this.client.describeTasks(this.description.clusterArn, resp) - - return tasks.filter(createValidTaskFilter(this.description.name!)) - } - - public prepareCommandForTask(command: string, task: string) { - return prepareCommand(this.client, command, this.description.taskRoleArn, { - task, - container: this.description.name!, - cluster: this.description.clusterArn, - }) - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.description.name!) - - return Object.assign(item, { - iconPath: getIcon('aws-ecs-container'), - contextValue: this.description.enableExecuteCommand - ? 'awsEcsContainerNodeExecEnabled' - : 'awsEcsContainerNodeExecDisabled', - }) - } - - public toTreeNode(): ResourceTreeNode { - return new ResourceTreeNode(this) - } -} - -export class Service { - public readonly id = this.description.serviceArn! - public readonly arn = this.description.serviceArn! - - private readonly onDidChangeEmitter = new vscode.EventEmitter() - public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event - - public constructor( - private readonly client: DefaultEcsClient, - public readonly description: SdkService - ) {} - - public async listContainers(): Promise { - const definition = await this.getDefinition() - const containers = definition.containerDefinitions ?? [] - - return containers.map( - (c) => - new Container(this.client, this.description.serviceName!, { - ...c, - enableExecuteCommand: this.description.enableExecuteCommand, - taskRoleArn: definition.taskRoleArn!, - clusterArn: this.description.clusterArn!, - }) - ) - } - - public async getDefinition() { - if (!this.description.taskDefinition) { - throw new Error(`No task definition found for ECS service ${this.id}`) - } - - const resp = await this.client.describeTaskDefinition(this.description.taskDefinition!) - - return resp.taskDefinition! - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.description.serviceName!) - item.description = this.description.status - item.iconPath = getIcon('aws-ecs-service') - item.tooltip = `${this.description.serviceArn}\nTask Definition: ${this.description.taskDefinition}` - item.contextValue = this.description.enableExecuteCommand - ? 'awsEcsServiceNode.ENABLED' - : 'awsEcsServiceNode.DISABLED' - - return item - } - - public async toggleExecuteCommand() { - await this.client.updateService({ - cluster: this.description.clusterArn!, - service: this.description.serviceName!, - forceNewDeployment: true, - enableExecuteCommand: !this.description.enableExecuteCommand, - }) - - this.description.enableExecuteCommand = !this.description.enableExecuteCommand - this.onDidChangeEmitter.fire() - } - - public toTreeNode(): ResourceTreeNode { - return new ResourceTreeNode(this, { - placeholder: localize('AWS.explorerNode.ecs.noContainers', '[No Containers found]'), - childrenProvider: { - onDidChange: this.onDidChangeEmitter.event, - listResources: async () => { - const containers = await this.listContainers() - - return containers.map((c) => c.toTreeNode()) - }, - }, - }) - } -} - -export class Cluster { - public readonly id = this.cluster.clusterArn! - - public constructor( - private readonly client: DefaultEcsClient, - private readonly cluster: SdkCluster - ) {} - - public listServices(): AsyncCollection { - return this.client - .listServices({ cluster: this.cluster.clusterArn! }) - .map((services) => services.map((s) => new Service(this.client, s))) - } - - public getTreeItem() { - const item = new vscode.TreeItem(this.cluster.clusterName!) - item.tooltip = this.cluster.clusterArn! - item.iconPath = getIcon('aws-ecs-cluster') - item.contextValue = 'awsEcsClusterNode' - - return item - } - - public toTreeNode(): ResourceTreeNode { - return new ResourceTreeNode(this, { - placeholder: localize('AWS.explorerNode.ecs.noContainers', '[No Containers found]'), - childrenProvider: { - paginated: true, - listResources: () => this.listServices().map((services) => services.map((s) => s.toTreeNode())), - }, - }) - } -} - -class Ecs { - public readonly id = 'ecs' - public constructor(private readonly client: DefaultEcsClient) {} - - public getTreeItem() { - const item = new vscode.TreeItem('ECS') - item.contextValue = 'awsEcsNode' - - return item - } - - public listClusters(): AsyncCollection { - return this.client.listClusters().map((clusters) => clusters.map((c) => new Cluster(this.client, c))) - } -} - -export function getEcsRootNode(region: string) { - const controller = new Ecs(new DefaultEcsClient(region)) - - return new ResourceTreeNode(controller, { - placeholder: localize('AWS.explorerNode.ecs.noClusters', '[No Clusters found]'), - childrenProvider: { - paginated: true, - listResources: () => controller.listClusters().map((clusters) => clusters.map((c) => c.toTreeNode())), - }, - }) -} diff --git a/packages/core/src/awsService/ecs/util.ts b/packages/core/src/awsService/ecs/util.ts deleted file mode 100644 index c25c229104..0000000000 --- a/packages/core/src/awsService/ecs/util.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../../shared/extensionGlobals' - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { EcsClient } from '../../shared/clients/ecsClient' -import { IamClient } from '../../shared/clients/iam' -import { ToolkitError } from '../../shared/errors' -import { isCloud9 } from '../../shared/extensionUtilities' -import { getOrInstallCli } from '../../shared/utilities/cliUtils' -import { Session, TaskDefinition } from '@aws-sdk/client-ecs' -import { getLogger } from '../../shared/logger/logger' -import { SSMClient, TerminateSessionCommand } from '@aws-sdk/client-ssm' -import { fromExtensionManifest } from '../../shared/settings' -import { ecsTaskPermissionsUrl } from '../../shared/constants' - -interface EcsTaskIdentifer { - readonly task: string - readonly cluster: string - readonly container: string -} - -/** - * See also: https://github.com/aws-containers/amazon-ecs-exec-checker - */ -export async function checkPermissionsForSsm( - client: IamClient, - task: Pick -): Promise { - if (!task.taskRoleArn) { - throw new ToolkitError('Containers must have a task role ARN', { - code: 'NoTaskRoleArn', - documentationUri: ecsTaskPermissionsUrl, - }) - } - - // https://github.com/aws-containers/amazon-ecs-exec-checker/blob/b1d163bd95c5b6f915e2bb3ad810e6f2aecae985/check-ecs-exec.sh#L536-L539 - const deniedActions = await client.getDeniedActions({ - PolicySourceArn: task.taskRoleArn, - ActionNames: [ - 'ssmmessages:CreateControlChannel', - 'ssmmessages:CreateDataChannel', - 'ssmmessages:OpenControlChannel', - 'ssmmessages:OpenDataChannel', - ], - }) - - if (deniedActions.length !== 0) { - const deniedMsg = deniedActions.map((o) => o.EvalActionName).join(', ') - const message = localize( - 'AWS.command.ecs.runCommandInContainer.missingPermissions', - 'Insufficient permissions to execute command, ensure the [task role is configured]({0}). Task role {1} is not authorized to perform: {2}', - ecsTaskPermissionsUrl.toString(), - task.taskRoleArn, - deniedMsg - ) - - throw new ToolkitError(message, { - code: 'MissingPermissions', - documentationUri: ecsTaskPermissionsUrl, - details: { deniedActions: deniedActions.map((a) => a.EvalActionName) }, - }) - } -} - -export async function prepareCommand( - client: EcsClient, - command: string, - taskRoleArn: string, - task: EcsTaskIdentifer -): Promise<{ path: string; args: string[]; dispose: () => void }> { - const ssmPlugin = await getOrInstallCli('session-manager-plugin', !isCloud9()) - - let session: Session - try { - session = (await client.executeCommand({ ...task, command })).session! - } catch (execErr) { - await checkPermissionsForSsm(new IamClient(globals.regionProvider.defaultRegionId), { - taskRoleArn: taskRoleArn, - }).catch((permErr) => { - throw ToolkitError.chain(permErr, `${execErr}`) - }) - - throw execErr - } - - const args = [JSON.stringify(session), client.regionCode, 'StartSession'] - - async function terminateSession() { - const sessionId = session.sessionId! - const ssm = globals.sdkClientBuilderV3.createAwsService({ - serviceClient: SSMClient, - clientOptions: { region: client.regionCode }, - }) - ssm.send(new TerminateSessionCommand({ SessionId: sessionId })).catch((err) => { - getLogger().warn(`ecs: failed to terminate session "${sessionId}": %s`, err) - }) - } - - return { path: ssmPlugin, args, dispose: () => void terminateSession() } -} - -export class EcsSettings extends fromExtensionManifest('aws.ecs', { openTerminalCommand: String }) {} diff --git a/packages/core/src/awsService/ecs/wizards/executeCommand.ts b/packages/core/src/awsService/ecs/wizards/executeCommand.ts deleted file mode 100644 index a3db19a998..0000000000 --- a/packages/core/src/awsService/ecs/wizards/executeCommand.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ecsExecToolkitGuideUrl } from '../../../shared/constants' -import { codicon, getIcon } from '../../../shared/icons' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { WIZARD_EXIT, Wizard, WIZARD_BACK } from '../../../shared/wizards/wizard' -import { Container } from '../model' - -export interface CommandWizardState { - task: string - command: string - confirmation?: 'yes' | 'suppress' -} - -function createTaskPrompter(node: Container) { - const taskItems = (async () => { - // Filter for only 'Running' tasks - return (await node.listTasks()).map((task) => { - // TODO: get task definition name and include it in the item detail - // The last 32 digits of the task arn is the task identifier - const taskId = task.taskArn.substring(task.taskArn.length - 32) - const invalidSelection = task.lastStatus !== 'RUNNING' - - return { - label: codicon`${invalidSelection ? getIcon('vscode-error') : ''}${taskId}`, - detail: `Status: ${task.lastStatus} Desired status: ${task.desiredStatus}`, - description: - invalidSelection && task.desiredStatus === 'RUNNING' - ? 'Container instance starting, try again later.' - : undefined, - data: taskId, - invalidSelection, - } - }) - })() - - return createQuickPick(taskItems, { - title: localize('AWS.command.ecs.runCommandInContainer.chooseInstance', 'Choose a container instance'), - buttons: createCommonButtons(ecsExecToolkitGuideUrl), - noItemsFoundItem: { - label: localize( - 'AWS.command.ecs.runCommandInContainer.noInstances', - 'No valid instances for this container' - ), - detail: localize( - 'AWS.command.ecs.runCommandInContainer.noInstances.description', - 'If command execution was recently enabled, try again in a few minutes.' - ), - data: WIZARD_BACK, - }, - compare: (a, b) => (a.invalidSelection ? 1 : b.invalidSelection ? -1 : 0), - }) -} - -function createCommandPrompter(node: Container) { - return createInputBox({ - title: localize( - 'AWS.command.ecs.runCommandInContainer.prompt', - 'Enter the command to run in container: {0}', - node.description.name! - ), - placeholder: localize('AWS.command.ecs.runCommandInContainer.placeHolder', 'Command to run'), - buttons: createCommonButtons(ecsExecToolkitGuideUrl), - }) -} - -function createConfirmationPrompter(node: Container, task: string, command: string) { - const choices: DataQuickPickItem<'yes' | 'suppress'>[] = [ - { - label: localize('AWS.generic.response.yes', 'Yes'), - data: 'yes' as 'yes' | 'suppress', - detail: `Task: ${task} Command: ${command}`, - }, - { - label: localize('AWS.message.prompt.yesDontAskAgain', "Yes, and don't ask again"), - data: 'suppress' as 'yes' | 'suppress', - }, - { label: localize('AWS.generic.response.no', 'No'), data: WIZARD_EXIT }, - ] - return createQuickPick(choices, { - title: localize( - 'AWS.command.ecs.runCommandInContainer.warnBeforeExecute', - 'Command may modify the running container {0}. Are you sure?', - node.description.name! - ), - buttons: createCommonButtons(ecsExecToolkitGuideUrl), - }) -} - -export class CommandWizard extends Wizard { - public constructor(container: Container, shouldShowConfirmation: boolean, command?: string) { - super({ initState: { command } }) - - this.form.task.bindPrompter(() => createTaskPrompter(container)) - this.form.command.bindPrompter(() => createCommandPrompter(container)) - if (shouldShowConfirmation) { - this.form.confirmation.bindPrompter((state) => - createConfirmationPrompter(container, state.task!, state.command!) - ) - } - } -} diff --git a/packages/core/src/awsService/iot/activation.ts b/packages/core/src/awsService/iot/activation.ts deleted file mode 100644 index ab28ee788b..0000000000 --- a/packages/core/src/awsService/iot/activation.ts +++ /dev/null @@ -1,97 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { IotThingNode } from './explorer/iotThingNode' -import { ExtContext } from '../../shared/extensions' -import { IotThingFolderNode } from './explorer/iotThingFolderNode' -import { createThingCommand } from './commands/createThing' -import { deleteThingCommand } from './commands/deleteThing' -import { IotCertificateNode, IotCertWithPoliciesNode, IotThingCertNode } from './explorer/iotCertificateNode' -import { detachThingCertCommand } from './commands/detachCert' -import { IotPolicyCertNode, IotPolicyWithVersionsNode } from './explorer/iotPolicyNode' -import { detachPolicyCommand } from './commands/detachPolicy' -import { deletePolicyCommand } from './commands/deletePolicy' -import { - activateCertificateCommand, - deactivateCertificateCommand, - revokeCertificateCommand, -} from './commands/updateCert' -import { deleteCertCommand } from './commands/deleteCert' -import { IotCertsFolderNode } from './explorer/iotCertFolderNode' -import { createCertificateCommand } from './commands/createCert' -import { attachCertificateCommand } from './commands/attachCertificate' -import { attachPolicyCommand } from './commands/attachPolicy' -import { IotPolicyFolderNode } from './explorer/iotPolicyFolderNode' -import { createPolicyCommand } from './commands/createPolicy' -import { IotNode } from './explorer/iotNodes' -import { copyEndpointCommand } from './commands/copyEndpoint' -import { IotPolicyVersionNode } from './explorer/iotPolicyVersionNode' -import { deletePolicyVersionCommand } from './commands/deletePolicyVersion' -import { setDefaultPolicy } from './commands/setDefaultPolicy' -import { createPolicyVersionCommand } from './commands/createPolicyVersion' -import { viewPolicyVersionCommand } from './commands/viewPolicyVersion' -import { Commands } from '../../shared/vscode/commands2' - -/** - * Activate IoT components. - */ -export async function activate(context: ExtContext): Promise { - context.extensionContext.subscriptions.push( - Commands.register('aws.iot.createThing', async (node: IotThingFolderNode) => { - await createThingCommand(node) - }), - Commands.register('aws.iot.deleteThing', async (node: IotThingNode) => { - await deleteThingCommand(node) - }), - Commands.register('aws.iot.createCert', async (node: IotCertsFolderNode) => { - await createCertificateCommand(node) - }), - Commands.register('aws.iot.deleteCert', async (node: IotCertWithPoliciesNode) => { - await deleteCertCommand(node) - }), - Commands.register('aws.iot.attachCert', async (node: IotThingNode) => { - await attachCertificateCommand(node) - }), - Commands.register('aws.iot.detachCert', async (node: IotThingCertNode) => { - await detachThingCertCommand(node) - }), - Commands.register('aws.iot.createPolicy', async (node: IotPolicyFolderNode) => { - await createPolicyCommand(node) - }), - Commands.register('aws.iot.deletePolicy', async (node: IotPolicyWithVersionsNode) => { - await deletePolicyCommand(node) - }), - Commands.register('aws.iot.attachPolicy', async (node: IotCertWithPoliciesNode) => { - await attachPolicyCommand(node) - }), - Commands.register('aws.iot.detachPolicy', async (node: IotPolicyCertNode) => { - await detachPolicyCommand(node) - }), - Commands.register('aws.iot.activateCert', async (node: IotCertificateNode) => { - await activateCertificateCommand(node) - }), - Commands.register('aws.iot.deactivateCert', async (node: IotCertificateNode) => { - await deactivateCertificateCommand(node) - }), - Commands.register('aws.iot.revokeCert', async (node: IotCertificateNode) => { - await revokeCertificateCommand(node) - }), - Commands.register('aws.iot.createPolicyVersion', async (node: IotPolicyWithVersionsNode) => { - await createPolicyVersionCommand(node) - }), - Commands.register('aws.iot.deletePolicyVersion', async (node: IotPolicyVersionNode) => { - await deletePolicyVersionCommand(node) - }), - Commands.register('aws.iot.setDefaultPolicy', async (node: IotPolicyVersionNode) => { - await setDefaultPolicy(node) - }), - Commands.register('aws.iot.viewPolicyVersion', async (node: IotPolicyVersionNode) => { - await viewPolicyVersionCommand(node) - }), - Commands.register('aws.iot.copyEndpoint', async (node: IotNode) => { - await copyEndpointCommand(node) - }) - ) -} diff --git a/packages/core/src/awsService/iot/commands/attachCertificate.ts b/packages/core/src/awsService/iot/commands/attachCertificate.ts deleted file mode 100644 index 684160dafb..0000000000 --- a/packages/core/src/awsService/iot/commands/attachCertificate.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotThingNode } from '../explorer/iotThingNode' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { PromptResult } from '../../../shared/ui/prompter' -import { IotClient } from '../../../shared/clients/iotClient' -import { isValidResponse } from '../../../shared/wizards/wizard' -import { Certificate, ListCertificatesResponse } from '@aws-sdk/client-iot' - -export type CertGen = typeof getCertList - -/** - * Attaches a certificate to the thing represented by the given node. - * - * Prompts the user to select a certificate - * Attaches the certificate. - * Refreshes the thing node. - */ -export async function attachCertificateCommand(node: IotThingNode, promptFun = promptForCert): Promise { - getLogger().debug('AttachCertificate called for %O', node) - - const thingName = node.thing.name - - const cert = await promptFun(node.iot, getCertList) - if (!isValidResponse(cert)) { - getLogger().info('No certificate chosen') - return undefined - } - getLogger().info('Picker returned: %O', cert) - try { - await node.iot.attachThingPrincipal({ thingName, principal: cert.certificateArn! }) - } catch (e) { - getLogger().error(`Failed to attach certificate ${cert.certificateId}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.attachCert.error', 'Failed to attach certificate {0}', cert.certificateId) - ) - return undefined - } - - getLogger().debug('Attached certificate %O', cert.certificateId) - - // Refresh the Thing node - await node.refreshNode() -} - -/** - * Prompts the user to pick a certificate to attach. - */ -async function promptForCert(iot: IotClient, certFetch: CertGen): Promise> { - const placeHolder: DataQuickPickItem = { - label: 'No certificates found', - data: undefined, - } - const picker = createQuickPick(certFetch(iot), { - title: localize('AWS.iot.attachCert', 'Select a certificate'), - noItemsFoundItem: placeHolder, - buttons: [vscode.QuickInputButtons.Back], - }) - return picker.prompt() -} - -/** - * Async generator function to get the list of certificates when creating a quick pick. - */ -async function* getCertList(iot: IotClient) { - let marker: string | undefined = undefined - let filteredCerts: Certificate[] - do { - try { - const certResponse: ListCertificatesResponse = await iot.listCertificates({ marker }) - marker = certResponse.nextMarker - - /* These fields should always be defined when using the above API, - * but we filter here anyway for when we use ! later. */ - filteredCerts = - certResponse.certificates?.filter( - (cert) => cert.certificateArn && cert.certificateId && cert.status && cert.creationDate - ) ?? [] - } catch (e) { - getLogger().error(`Failed to retrieve certificates: %s`, e) - void showViewLogsMessage(localize('AWS.iot.attachCert.error', 'Failed to retrieve certificates')) - return - } - yield filteredCerts.map((cert) => ({ label: cert.certificateId!, data: cert })) - } while (marker !== undefined) -} diff --git a/packages/core/src/awsService/iot/commands/attachPolicy.ts b/packages/core/src/awsService/iot/commands/attachPolicy.ts deleted file mode 100644 index add93a022f..0000000000 --- a/packages/core/src/awsService/iot/commands/attachPolicy.ts +++ /dev/null @@ -1,106 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { createQuickPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { PromptResult } from '../../../shared/ui/prompter' -import { IotClient } from '../../../shared/clients/iotClient' -import { isValidResponse } from '../../../shared/wizards/wizard' -import { IotCertWithPoliciesNode, IotThingCertNode } from '../explorer/iotCertificateNode' -import { ListPoliciesResponse, Policy } from '@aws-sdk/client-iot' -import { IotNode } from '../explorer/iotNodes' - -export type PolicyGen = typeof getPolicyList - -/** - * Attaches a policy to the certificate represented by the given node. - * - * Prompts the user to select a policy. - * Attaches the policy. - * Refreshes the certificate node. - */ -export async function attachPolicyCommand( - node: IotThingCertNode | IotCertWithPoliciesNode, - promptFun = promptForPolicy -): Promise { - getLogger().debug('AttachPolicy called for %O', node) - - const certArn = node.certificate.arn - - const policy = await promptFun(node.iot, getPolicyList) - if (!isValidResponse(policy)) { - getLogger().info('No policy chosen') - return undefined - } - getLogger().info('Picker returned: %O', policy) - try { - await node.iot.attachPolicy({ policyName: policy.policyName!, target: certArn }) - } catch (e) { - getLogger().error(`Failed to attach policy ${policy.policyName}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.attachCert.error', 'Failed to attach policy {0}', policy.policyName)) - return undefined - } - - getLogger().debug('Attached policy %O', policy.policyName) - - /* Refresh both things and certificates nodes so the status is updated in - * both trees. */ - const baseNode = getBaseNode(node) - await baseNode.thingFolderNode?.refreshNode() - await baseNode.certFolderNode?.refreshNode() -} - -/** - * Gets the node at the root of the IoT tree. This is so nodes in multiple - * subtrees can be refreshed when an action affects more than one node. - */ -function getBaseNode(node: IotThingCertNode | IotCertWithPoliciesNode): IotNode { - if (node instanceof IotThingCertNode) { - return node.parent.parent.parent - } - return node.parent.parent -} - -/** - * Prompts the user to pick a policy to attach. - */ -async function promptForPolicy(iot: IotClient, policyFetch: PolicyGen): Promise> { - const placeHolder: DataQuickPickItem = { - label: 'No policies found', - data: undefined, - } - const picker = createQuickPick(policyFetch(iot), { - title: localize('AWS.iot.attachPolicy', 'Select a policy'), - noItemsFoundItem: placeHolder, - buttons: [vscode.QuickInputButtons.Back], - }) - return picker.prompt() -} - -/** - * Async generator function to get the list of policies when creating a quick pick. - */ -async function* getPolicyList(iot: IotClient) { - let marker: string | undefined = undefined - let filteredPolicies: Policy[] - do { - try { - const policyResponse: ListPoliciesResponse = await iot.listPolicies({ marker }) - marker = policyResponse.nextMarker - - /* The policy name and arn should always be defined when using the - * above API, but we filter here anyway for when we use ! later. */ - filteredPolicies = policyResponse.policies?.filter((policy) => policy.policyArn && policy.policyName) ?? [] - } catch (e) { - getLogger().error(`Failed to retrieve policies: %s`, e) - void showViewLogsMessage(localize('AWS.iot.attachPolicy.error', 'Failed to retrieve policies')) - return - } - yield filteredPolicies.map((policy) => ({ label: policy.policyName!, data: policy })) - } while (marker !== undefined) -} diff --git a/packages/core/src/awsService/iot/commands/copyEndpoint.ts b/packages/core/src/awsService/iot/commands/copyEndpoint.ts deleted file mode 100644 index 0846250c5c..0000000000 --- a/packages/core/src/awsService/iot/commands/copyEndpoint.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotNode } from '../explorer/iotNodes' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { copyToClipboard } from '../../../shared/utilities/messages' -import { getLogger } from '../../../shared/logger/logger' - -/** - * Copies the path to the folder or file represented by the given node. - * - * Note that the path does not contain the bucket name or a leading slash. - */ -export async function copyEndpointCommand(node: IotNode): Promise { - getLogger().debug('CopyPath called for %O', node) - - let endpoint: string - try { - endpoint = await node.getEndpoint() - } catch (e) { - getLogger().error('Failed to retrieve endpoint: %s', e) - void showViewLogsMessage(localize('AWS.iot.copyEndpoint.error', 'Failed to retrieve endpoint')) - return - } - - await copyToClipboard(endpoint, 'URL') -} diff --git a/packages/core/src/awsService/iot/commands/createCert.ts b/packages/core/src/awsService/iot/commands/createCert.ts deleted file mode 100644 index 7cd9d4b62e..0000000000 --- a/packages/core/src/awsService/iot/commands/createCert.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { IotCertsFolderNode } from '../explorer/iotCertFolderNode' -import { fileExists } from '../../../shared/filesystemUtilities' -import { CreateKeysAndCertificateResponse } from '@aws-sdk/client-iot' -import { fs } from '../../../shared/fs/fs' - -// eslint-disable-next-line @typescript-eslint/naming-convention -const MODE_RW_R_R = 0o644 // File permission 0644 rw-r--r-- for PEM files. -// eslint-disable-next-line @typescript-eslint/naming-convention -const PEM_FILE_ENCODING = 'ascii' - -/** - * Command to create a certificate key pair and save them to the filesystem. - */ -export async function createCertificateCommand( - node: IotCertsFolderNode, - promptFunc = promptForSaveLocation, - saveFunc = saveCredentials -): Promise { - getLogger().debug('CreateCertificate called for %O', node) - - const folderLocation = await promptFunc() - if (!folderLocation) { - getLogger().info('CreateCertificate canceled: No folder selected') - return - } - - let certificate: CreateKeysAndCertificateResponse - - try { - certificate = await node.iot.createCertificateAndKeys({ - setAsActive: false, - }) - } catch (e) { - getLogger().error('Failed to create certificate: %s', e) - void showViewLogsMessage(localize('AWS.iot.createCert.error', 'Failed to create certificate')) - return undefined - } - - const certId = certificate!.certificateId - const certPem = certificate!.certificatePem - const privateKey = certificate!.keyPair?.PrivateKey - const publicKey = certificate!.keyPair?.PublicKey - - if (!certPem || !privateKey || !publicKey) { - getLogger().error('Could not download certificate. Certificate is missing either the PEM or key pair.') - void showViewLogsMessage(localize('AWS.iot.createCert.error', 'Failed to create certificate')) - return undefined - } - - getLogger().info(`Downloaded certificate ${certId}`) - void vscode.window.showInformationMessage(localize('AWS.iot.createCert.success', 'Created certificate {0}', certId)) - - // Save resources - const saveSuccessful = await saveFunc(folderLocation, certId!, certPem, privateKey, publicKey) - if (!saveSuccessful) { - // Delete the certificate if the key pair cannot be saved - try { - await node.iot.deleteCertificate({ certificateId: certId! }) - } catch (e) { - getLogger().error(`Failed to delete Certificate ${certId}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.deleteCert.error', 'Failed to delete Certificate {0}', certId)) - } - } - - // Refresh the Certificate Folder node - await node.refreshNode() -} - -/** - * Prompts for folder in which to save certificate and keys - */ -async function promptForSaveLocation(): Promise { - const folderLocation = await vscode.window.showOpenDialog({ - openLabel: localize('AWS.iot.downloadCert.openButton', 'Save certificate here'), - canSelectFolders: true, - canSelectFiles: false, - canSelectMany: false, - }) - - if (!folderLocation || folderLocation.length === 0) { - return undefined - } - - return folderLocation[0] -} - -/** - * Saves certificate and key pair to the file system - * @returns `true` if writes succeed, else `false`. - */ -async function saveCredentials( - basePath: vscode.Uri, - certName: string, - certPem: string, - privateKey: string, - publicKey: string -): Promise { - const dir = basePath.fsPath - const certPath = path.join(dir, `${certName}-certificate.pem.crt`) - const privateKeyPath = path.join(dir, `${certName}-private.pem.key`) - const publicKeyPath = path.join(dir, `${certName}-public.pem.key`) - - const certExists = await fileExists(certPath) - const privateKeyExists = await fileExists(privateKeyPath) - const publicKeyExists = await fileExists(publicKeyPath) - - if (certExists) { - getLogger().error('Certificate path %s already exists', certPath) - void vscode.window.showErrorMessage( - localize('AWS.iot.createCert.error', 'Failed to create certificate. Path {0} already exists.', certPath) - ) - return false - } - if (privateKeyExists) { - getLogger().error('Key path %s already exists', privateKeyPath) - void vscode.window.showErrorMessage( - localize( - 'AWS.iot.createCert.error', - 'Failed to create certificate. Path {0} already exists.', - privateKeyPath - ) - ) - return false - } - if (publicKeyExists) { - getLogger().error('Key path %s already exists', publicKeyPath) - void vscode.window.showErrorMessage( - localize( - 'AWS.iot.createCert.error', - 'Failed to create certificate. Path {0} already exists.', - publicKeyPath - ) - ) - return false - } - try { - await fs.writeFile(certPath, certPem, { encoding: PEM_FILE_ENCODING, mode: MODE_RW_R_R }) - await fs.writeFile(privateKeyPath, privateKey, { encoding: PEM_FILE_ENCODING, mode: MODE_RW_R_R }) - await fs.writeFile(publicKeyPath, publicKey, { encoding: PEM_FILE_ENCODING, mode: MODE_RW_R_R }) - } catch (e) { - getLogger().error('Could not save certificate: %s', e) - void showViewLogsMessage(localize('AWS.iot.createCert.saveError', 'Failed to save certificate')) - return false - } - return true -} diff --git a/packages/core/src/awsService/iot/commands/createPolicy.ts b/packages/core/src/awsService/iot/commands/createPolicy.ts deleted file mode 100644 index 1bb04618d7..0000000000 --- a/packages/core/src/awsService/iot/commands/createPolicy.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { IotPolicyFolderNode } from '../explorer/iotPolicyFolderNode' -import { fs } from '../../../shared/fs/fs' - -/** - * Creates a policy from a policy document. - */ -export async function createPolicyCommand(node: IotPolicyFolderNode, getPolicyDoc = getPolicyDocument): Promise { - getLogger().debug('CreatePolicy called for %O', node) - - const data = await getPolicyDoc() - if (!data) { - return - } - - const policyName = await vscode.window.showInputBox({ - prompt: localize('AWS.iot.createPolicy.prompt', 'Enter a new policy name'), - placeHolder: localize('AWS.iot.createPolicy.placeHolder', 'Policy Name'), - validateInput: validatePolicyName, - }) - - if (!policyName) { - getLogger().info('CreatePolicy canceled') - return - } - - try { - // Parse to ensure this is a valid JSON - const policyJSON = JSON.parse(data.toString()) - await node.iot.createPolicy({ policyName, policyDocument: JSON.stringify(policyJSON) }) - void vscode.window.showInformationMessage( - localize('AWS.iot.createPolicy.success', 'Created Policy {0}', policyName) - ) - } catch (e) { - getLogger().error('Failed to create policy document: %s', e) - void showViewLogsMessage(localize('AWS.iot.createPolicy.error', 'Failed to create policy {0}', policyName)) - return - } - - // Refresh the Policy Folder node - await node.refreshNode() -} - -export async function getPolicyDocument(): Promise { - const fileLocation = await vscode.window.showOpenDialog({ - canSelectFolders: false, - canSelectFiles: true, - canSelectMany: false, - filters: { JSON: ['json'] }, - }) - - if (!fileLocation || fileLocation.length === 0) { - getLogger().info('CreatePolicy canceled: No document selected') - return undefined - } - - const policyLocation = fileLocation[0] - - let data: Uint8Array - try { - data = await fs.readFileBytes(policyLocation.fsPath) - } catch (e) { - getLogger().error('Failed to read policy document: %s', e) - void showViewLogsMessage(localize('AWS.iot.createPolicy.error', 'Failed to read policy document')) - return undefined - } - - return data -} - -/** - * Validates a Policy name for the CreatePolicy API. See - * https://docs.aws.amazon.com/iot/latest/apireference/API_CreatePolicy.html - * for more information. Pattern: `[\w+=,.@-]+`. - */ -function validatePolicyName(name: string): string | undefined { - if (name.length < 1 || name.length > 128) { - return localize( - 'AWS.iot.validatePolicyName.error.invalidLength', - 'Policy name must be between 1 and 128 characters long' - ) - } - if (!/^[\w+=,.@-]+$/.test(name)) { - return localize( - 'AWS.iot.validatePolicyName.error.invalidCharacters', - 'Policy name must contain only alphanumeric characters and/or the following: +=.,@-' - ) - } - return undefined -} diff --git a/packages/core/src/awsService/iot/commands/createPolicyVersion.ts b/packages/core/src/awsService/iot/commands/createPolicyVersion.ts deleted file mode 100644 index 4719277a5e..0000000000 --- a/packages/core/src/awsService/iot/commands/createPolicyVersion.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { IotPolicyWithVersionsNode } from '../explorer/iotPolicyNode' -import { getPolicyDocument } from './createPolicy' - -/** - * Creates a new policy version from a policy document. - */ -export async function createPolicyVersionCommand( - node: IotPolicyWithVersionsNode, - getPolicyDoc = getPolicyDocument -): Promise { - getLogger().debug('CreatePolicyVersion called for %O', node) - - const policyName = node.policy.name - - const data = await getPolicyDoc() - if (!data) { - return - } - - try { - // Parse to ensure this is a valid JSON - const policyJSON = JSON.parse(data.toString()) - await node.iot.createPolicyVersion({ - policyName, - policyDocument: JSON.stringify(policyJSON), - setAsDefault: true, - }) - void vscode.window.showInformationMessage( - localize('AWS.iot.createPolicy.success', 'Created new version of {0}', policyName) - ) - } catch (e) { - getLogger().error('Failed to create new policy version: %s', e) - void showViewLogsMessage( - localize('AWS.iot.createPolicyVersion.error', 'Failed to create new version of {0}', policyName) - ) - return - } - - // Refresh the node - node.refresh() -} diff --git a/packages/core/src/awsService/iot/commands/createThing.ts b/packages/core/src/awsService/iot/commands/createThing.ts deleted file mode 100644 index 8909d89491..0000000000 --- a/packages/core/src/awsService/iot/commands/createThing.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { IotThingFolderNode } from '../explorer/iotThingFolderNode' - -/** - * Creates an IoT Thing. - * - * Prompts the user for the thing name. - * Creates the thing. - * Refreshes the node. - */ -export async function createThingCommand(node: IotThingFolderNode): Promise { - getLogger().debug('CreateThing called for: %O', node) - - const thingName = await vscode.window.showInputBox({ - prompt: localize('AWS.iot.createThing.prompt', 'Enter a new Thing name'), - placeHolder: localize('AWS.iot.createThing.placeHolder', 'Thing Name'), - validateInput: validateThingName, - }) - - if (!thingName) { - getLogger().info('CreateThing canceled') - return - } - - getLogger().info(`Creating thing: ${thingName}`) - try { - const thing = await node.iot.createThing({ thingName }) - - getLogger().info('Created thing: %O', thing) - void vscode.window.showInformationMessage( - localize('AWS.iot.createThing.success', 'Created Thing {0}', thingName) - ) - } catch (e) { - getLogger().error(`Failed to create Thing ${thingName}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.createThing.error', 'Failed to create Thing: {0}', thingName)) - } - - // Refresh the Things Folder node - await node.refreshNode() -} - -/** - * Validates a Thing name for the CreateThing API. See - * https://docs.aws.amazon.com/iot/latest/apireference/API_CreateThing.html - * for more information. Pattern: `[a-zA-Z0-9:_-]+`. - */ -function validateThingName(name: string): string | undefined { - if (name.length < 1 || name.length > 128) { - return localize( - 'AWS.iot.validateThingName.error.invalidLength', - 'Thing name must be between 1 and 128 characters long' - ) - } - if (!/^[a-zA-Z0-9:_-]+$/.test(name)) { - return localize( - 'AWS.iot.validateThingName.error.invalidCharacters', - 'Thing name must only contain alphanumeric characters, hyphens, underscores, or colons' - ) - } - return undefined -} diff --git a/packages/core/src/awsService/iot/commands/deleteCert.ts b/packages/core/src/awsService/iot/commands/deleteCert.ts deleted file mode 100644 index 592bbb6633..0000000000 --- a/packages/core/src/awsService/iot/commands/deleteCert.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' -import { IotCertWithPoliciesNode } from '../explorer/iotCertificateNode' - -/** - * Deletes the certificate represented by the given node. - * - * Checks if certificate is not attached to any things. - * Prompts the user for confirmation. - * Deletes the policy. - * Refreshes the parent node. - */ -export async function deleteCertCommand(node: IotCertWithPoliciesNode): Promise { - getLogger().debug('DeleteThing called for %O', node) - - const certArn = node.certificate.arn - - if (node.certificate.activeStatus === 'ACTIVE') { - getLogger().error('Certificate is active') - void vscode.window.showErrorMessage( - localize('AWS.iot.deleteCert.activeError', 'Active certificates cannot be deleted') - ) - return - } - - try { - const things = await node.iot.listThingsForCert({ principal: certArn }) - if (things.length > 0) { - getLogger().error(`Certificate ${node.certificate.id} has attached Things`) - void vscode.window.showErrorMessage( - localize( - 'AWS.iot.deleteCert.attachedError', - 'Cannot delete certificate. Certificate has attached resources: {0}', - things.join(', ') - ) - ) - return - } - } catch (e) { - getLogger().error(`Failed to retrieve Things attached to cert ${node.certificate.id}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.deleteCert.retrieveError', 'Failed to retrieve {0} attached to certificate', 'Things') - ) - return - } - - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.iot.deleteCert.prompt', - 'Are you sure you want to delete Certificate {0}?', - node.certificate.id - ), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DeleteCert canceled') - return - } - - let forceDelete: boolean = false - try { - const policies = (await node.iot.listPrincipalPolicies({ principal: certArn })).policies - if (policies?.length ?? 0 > 0) { - forceDelete = await showConfirmationMessage({ - prompt: localize( - 'AWS.iot.deleteCert.attachedError', - 'Certificate has attached {0}', - 'policies. Delete anyway?' - ), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!forceDelete) { - getLogger().info('DeleteCert canceled') - return - } - } - } catch (e) { - getLogger().error(`Failed to retrieve Policies attached to cert ${node.certificate.id}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.deleteCert.retrieveError', 'Failed to retrieve {0} attached to certificate', 'policies') - ) - } - - getLogger().info(`Deleting certificate ${node.certificate.id}`) - try { - await node.iot.deleteCertificate({ certificateId: node.certificate.id, forceDelete: forceDelete }) - - getLogger().info(`deleted certificate: ${node.certificate.id}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.deleteCert.success', 'Deleted certificate: {0}', node.certificate.id) - ) - } catch (e) { - getLogger().error(`Failed to delete Certificate ${node.certificate.id}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.deleteCert.error', 'Failed to delete certificate: {0}', node.certificate.id) - ) - } - - // Refresh the Certificate Folder node - await node.parent.refreshNode() -} diff --git a/packages/core/src/awsService/iot/commands/deletePolicy.ts b/packages/core/src/awsService/iot/commands/deletePolicy.ts deleted file mode 100644 index 4d09ffd0a2..0000000000 --- a/packages/core/src/awsService/iot/commands/deletePolicy.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotPolicyWithVersionsNode } from '../explorer/iotPolicyNode' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' - -/** - * Deletes the policy represented by the given node. - * - * Checks if policy is not attached to any certificates. - * Prompts the user for confirmation. - * Deletes the policy. - * Refreshes the parent node. - */ -export async function deletePolicyCommand(node: IotPolicyWithVersionsNode): Promise { - getLogger().debug('DeletePolicy called for %O', node) - - const policyName = node.policy.name - - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.iot.deletePolicy.prompt', 'Are you sure you want to delete Policy {0}?', policyName), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DeletePolicy canceled') - return - } - - getLogger().info(`Deleting policy ${policyName}`) - try { - const certs = await node.iot.listPolicyTargets({ policyName }) - if (certs.length > 0) { - getLogger().error(`Policy ${policyName} has attached Certificates`) - void vscode.window.showErrorMessage( - localize( - 'AWS.iot.deletePolicy.attachedError', - 'Cannot delete {0}. Policy has attached certificates: {1}', - policyName, - certs.join(', ') - ) - ) - return - } - const versions = node.iot.listPolicyVersions({ policyName }) - let numVersions: number = 0 - for await (const _version of versions) { - numVersions++ - } - if (numVersions !== 1) { - getLogger().error(`Policy ${policyName} has non-default versions`) - void vscode.window.showErrorMessage( - localize('AWS.iot.deletePolicy.versionError', 'Policy {0} has non-default versions', policyName) - ) - return - } - await node.iot.deletePolicy({ policyName }) - - getLogger().info(`deleted Policy: ${policyName}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.deletePolicy.success', 'Deleted Policy: {0}', node.policy.name) - ) - } catch (e) { - getLogger().error(`Failed to delete Policy: ${policyName}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.deletePolicy.error', 'Failed to delete Policy: {0}', node.policy.name) - ) - } - - // Refresh the Policy Folder node - await node.parent.refreshNode() -} diff --git a/packages/core/src/awsService/iot/commands/deletePolicyVersion.ts b/packages/core/src/awsService/iot/commands/deletePolicyVersion.ts deleted file mode 100644 index b34708c4ee..0000000000 --- a/packages/core/src/awsService/iot/commands/deletePolicyVersion.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' -import { IotPolicyVersionNode } from '../explorer/iotPolicyVersionNode' - -/** - * Deletes the policy version represented by the given node. - * - * Prompts the user for confirmation. - * Deletes the policy version. - * Refreshes the parent node. - */ -export async function deletePolicyVersionCommand(node: IotPolicyVersionNode): Promise { - getLogger().debug('DeletePolicyVersion called for %O', node) - - const policyName = node.policy.name - const policyVersionId = node.version.versionId! - - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.iot.deletePolicyVersion.prompt', - 'Are you sure you want to delete Version {0} of Policy {1}?', - policyVersionId, - policyName - ), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DeletePolicyVersion canceled') - return - } - - getLogger().info(`Deleting version ${policyVersionId} of policy ${policyName}`) - try { - await node.iot.deletePolicyVersion({ policyName, policyVersionId }) - - getLogger().info(`deleted Policy Version: ${policyVersionId}`) - void vscode.window.showInformationMessage( - localize( - 'AWS.iot.deletePolicyVersion.success', - 'Deleted Version {0} of Policy {1}', - policyVersionId, - policyName - ) - ) - } catch (e) { - getLogger().error(`Failed to delete Policy Version: ${policyVersionId}: %s`, e) - void showViewLogsMessage( - localize( - 'AWS.iot.deletePolicyVersion.error', - 'Failed to delete Version {0} of Policy {1}', - policyVersionId, - policyName - ) - ) - } - - // Refresh the policy node - await node.parent.refreshNode() -} diff --git a/packages/core/src/awsService/iot/commands/deleteThing.ts b/packages/core/src/awsService/iot/commands/deleteThing.ts deleted file mode 100644 index 4aecb0d126..0000000000 --- a/packages/core/src/awsService/iot/commands/deleteThing.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotThingNode } from '../explorer/iotThingNode' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' - -/** - * Deletes the thing represented by the given node. - * - * Prompts the user for confirmation. - * Deletes the thing. - * Refreshes the parent node. - */ -export async function deleteThingCommand(node: IotThingNode): Promise { - getLogger().debug('DeleteThing called for %O', node) - - const thingName = node.thing.name - - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.iot.deleteThing.prompt', 'Are you sure you want to delete Thing {0}?', thingName), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DeleteThing canceled') - return - } - - getLogger().info(`Deleting thing ${thingName}`) - try { - const principalList = (await node.iot.listThingPrincipals({ thingName })).principals - if (principalList?.length ?? 0 > 0) { - getLogger().error(`Thing ${thingName} has attached principals: %O`, principalList) - void vscode.window.showErrorMessage( - localize( - 'AWS.iot.deleteThing.error', - 'Cannot delete Thing {0}. Thing {0} has attached principals: {1}', - thingName, - principalList?.join(', ') - ) - ) - return undefined - } - await node.iot.deleteThing({ thingName }) - - getLogger().info(`deleted Thing: ${thingName}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.deleteThing.success', 'Deleted Thing: {0}', thingName) - ) - } catch (e) { - getLogger().error(`Failed to delete Thing: ${thingName}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.deleteThing.error', 'Failed to delete Thing: {0}', thingName)) - } - - // Refresh the Things Folder node - await node.parent.refreshNode() -} diff --git a/packages/core/src/awsService/iot/commands/detachCert.ts b/packages/core/src/awsService/iot/commands/detachCert.ts deleted file mode 100644 index 536869894d..0000000000 --- a/packages/core/src/awsService/iot/commands/detachCert.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotThingCertNode } from '../explorer/iotCertificateNode' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' - -/** - * Detaches a certificate from an IoT Thing. - * - * Prompts the user for confirmation. - * Detaches the certificate. - * Refreshes the parent node. - */ -export async function detachThingCertCommand(node: IotThingCertNode): Promise { - getLogger().debug('DetachCert called for %O', node) - - const certId = node.certificate.id - const certArn = node.certificate.arn - const thingName = node.parent.thing.name - - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.iot.detachCert.prompt', - 'Are you sure you want to detach certificate from Thing {0}?', - thingName - ), - confirm: localize('AWS.iot.detachCert.confirm', 'Detach'), - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DetachCert canceled') - return - } - - getLogger().info(`Detaching certificate: ${certId}`) - try { - await node.iot.detachThingPrincipal({ thingName, principal: certArn }) - - getLogger().info(`detached certificate from Thing: ${thingName}`) - void vscode.window.showInformationMessage(localize('AWS.iot.detachCert.success', 'Detached: {0}', certId)) - } catch (e) { - getLogger().error(`Failed to detach certificate: ${certId}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.detachCert.error', 'Failed to detach: {0}', certId)) - } - - // Refresh the parent Thing node - await node.parent.refreshNode() -} diff --git a/packages/core/src/awsService/iot/commands/detachPolicy.ts b/packages/core/src/awsService/iot/commands/detachPolicy.ts deleted file mode 100644 index a85ad8fe5a..0000000000 --- a/packages/core/src/awsService/iot/commands/detachPolicy.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' -import { IotPolicyCertNode } from '../explorer/iotPolicyNode' -import { IotPolicyFolderNode } from '../explorer/iotPolicyFolderNode' -import { IotCertificateNode, IotCertWithPoliciesNode, IotThingCertNode } from '../explorer/iotCertificateNode' -import { IotNode } from '../explorer/iotNodes' - -/** - * Detaches an IoT Policy from a certificate. - * - * Prompts the user for confirmation. - * Detaches the policy. - * Refreshes the parent node. - */ -export async function detachPolicyCommand(node: IotPolicyCertNode): Promise { - getLogger().debug('DetachPolicy called for %O', node) - - const policyName = node.policy.name - if (node.parent instanceof IotPolicyFolderNode) { - return undefined - } - const certId = node.parent.certificate.id - const certArn = node.parent.certificate.arn - - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.iot.detachPolicy.prompt', 'Are you sure you want to detach policy {0}?', policyName), - confirm: localize('AWS.iot.detachCert.confirm', 'Detach'), - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DetachCert canceled') - return - } - - getLogger().info(`Detaching certificate: ${certId}`) - try { - await node.iot.detachPolicy({ policyName, target: certArn }) - - getLogger().info(`detached policy: ${policyName}`) - void vscode.window.showInformationMessage(localize('AWS.iot.detachPolicy.success', 'Detached: {0}', policyName)) - } catch (e) { - getLogger().error(`Failed to detach certificate: ${certId}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.detachPolicy.error', 'Failed to detach: {0}', policyName)) - } - - /* Refresh both things and certificates nodes so the status is updated in - * both trees. */ - const baseNode = getBaseNode(node.parent) - await baseNode?.thingFolderNode?.refreshNode() - await baseNode?.certFolderNode?.refreshNode() -} - -/** - * Gets the node at the root of the IoT tree. This is so nodes in multiple - * subtrees can be refreshed when an action affects more than one node. - */ -function getBaseNode(node: IotCertificateNode): IotNode | undefined { - if (node instanceof IotThingCertNode) { - return node.parent.parent.parent - } - if (node instanceof IotCertWithPoliciesNode) { - return node.parent.parent - } - return undefined -} diff --git a/packages/core/src/awsService/iot/commands/setDefaultPolicy.ts b/packages/core/src/awsService/iot/commands/setDefaultPolicy.ts deleted file mode 100644 index 8e406719bf..0000000000 --- a/packages/core/src/awsService/iot/commands/setDefaultPolicy.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotPolicyVersionNode } from '../explorer/iotPolicyVersionNode' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { IotPolicyWithVersionsNode } from '../explorer/iotPolicyNode' - -/** - * Copies the path to the folder or file represented by the given node. - * - * Note that the path does not contain the bucket name or a leading slash. - */ -export async function setDefaultPolicy(node: IotPolicyVersionNode): Promise { - getLogger().debug('SetDefaultPolicy called for %O', node) - - try { - await node.iot.setDefaultPolicyVersion({ - policyName: node.policy.name, - policyVersionId: node.version.versionId!, - }) - void vscode.window.showInformationMessage( - localize( - 'AWS.iot.setDefaultPolicy.success', - 'Set {0} as default version of {1}', - node.version.versionId, - node.policy.name - ) - ) - } catch (e) { - getLogger().error('Failed to set default policy version: %s', e) - void showViewLogsMessage(localize('AWS.iot.setDefaultPolicy.error', 'Failed to set default policy version')) - } - - await refreshBase(node.parent) -} - -async function refreshBase(node: IotPolicyWithVersionsNode): Promise { - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/awsService/iot/commands/updateCert.ts b/packages/core/src/awsService/iot/commands/updateCert.ts deleted file mode 100644 index 53a7aa3418..0000000000 --- a/packages/core/src/awsService/iot/commands/updateCert.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotCertificateNode } from '../explorer/iotCertificateNode' -import { showViewLogsMessage, showConfirmationMessage } from '../../../shared/utilities/messages' -import { IotThingNode } from '../explorer/iotThingNode' -import { IotCertsFolderNode } from '../explorer/iotCertFolderNode' -import { IotNode } from '../explorer/iotNodes' - -const statusRevoked = 'REVOKED' -const statusActive = 'ACTIVE' -const statusInactive = 'INACTIVE' - -/** - * Deactivates an active certificate. - * - * Prompts the user for confirmation. - * Deactivates the certificate. - * Refreshes the parent node. - */ -export async function deactivateCertificateCommand(node: IotCertificateNode): Promise { - getLogger().debug('DeactivateCert called for %O', node) - - const certId = node.certificate.id - - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.iot.deactivateCert.prompt', - 'Are you sure you want to deactivate certificate {0}?', - certId - ), - confirm: localize('AWS.iot.deactivateCert.confirm', 'Deactivate'), - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('DeactivateCert canceled') - return - } - - getLogger().info(`Deactivating certificate ${certId}`) - try { - await node.iot.updateCertificate({ certificateId: certId, newStatus: statusInactive }) - - getLogger().info(`deactivated certificate: ${certId}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.deactivateCert.success', 'Deactivated: {0}', node.certificate.id) - ) - } catch (e) { - getLogger().error(`Failed to deactivate certificate ${certId}: %s`, e) - void showViewLogsMessage( - localize('AWS.iot.deactivateCert.error', 'Failed to deactivate: {0}', node.certificate.id) - ) - } - - /* Refresh both things and certificates nodes so the status is updated in - * both trees. */ - const baseNode = getBaseNode(node.parent) - await baseNode.thingFolderNode?.refreshNode() - await baseNode.certFolderNode?.refreshNode() -} - -/** - * Activates an inactive certificate. - * - * Prompts the user for confirmation. - * Activates the certificate. - * Refreshes the parent node. - */ -export async function activateCertificateCommand(node: IotCertificateNode): Promise { - getLogger().debug('ActivateCert called for %O', node) - - const certId = node.certificate.id - - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.iot.activateCert.prompt', 'Are you sure you want to activate certificate {0}?', certId), - confirm: localize('AWS.iot.activateCert.confirm', 'Activate'), - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('ActivateCert canceled') - return - } - - getLogger().info(`Activating certificate ${certId}`) - try { - await node.iot.updateCertificate({ certificateId: certId, newStatus: statusActive }) - - getLogger().info(`activated certificate: ${certId}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.activateCert.success', 'Activated: {0}', node.certificate.id) - ) - } catch (e) { - getLogger().error(`Failed to activate certificate ${certId}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.activateCert.error', 'Failed to activate: {0}', node.certificate.id)) - } - - /* Refresh both things and certificates nodes so the status is updated in - * both trees. */ - const baseNode = getBaseNode(node.parent) - await baseNode.thingFolderNode?.refreshNode() - await baseNode.certFolderNode?.refreshNode() -} - -/** - * Revokes an active certificate. - * - * Prompts the user for confirmation. - * Revokes the certificate. - * Refreshes the parent node. - */ -export async function revokeCertificateCommand(node: IotCertificateNode): Promise { - getLogger().debug('RevokeCert called for %O', node) - - const certId = node.certificate.id - - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.iot.revokeCert.prompt', 'Are you sure you want to revoke certificate {0}?', certId), - confirm: localize('AWS.iot.revokeCert.confirm', 'Revoke'), - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - getLogger().info('RevokeCert canceled') - return - } - - getLogger().info(`Revoking certificate: ${certId}`) - try { - await node.iot.updateCertificate({ certificateId: certId, newStatus: statusRevoked }) - - getLogger().info(`revoked certificate: ${certId}`) - void vscode.window.showInformationMessage( - localize('AWS.iot.revokeCert.success', 'Revoked: {0}', node.certificate.id) - ) - } catch (e) { - getLogger().error(`Failed to revoke certificate ${certId}: %s`, e) - void showViewLogsMessage(localize('AWS.iot.revokeCert.error', 'Failed to revoke: {0}', node.certificate.id)) - } - - /* Refresh both things and certificates nodes so the status is updated in - * both trees. */ - const baseNode = getBaseNode(node.parent) - await baseNode.thingFolderNode?.refreshNode() - await baseNode.certFolderNode?.refreshNode() -} - -function getBaseNode(node: IotThingNode | IotCertsFolderNode): IotNode { - if (node instanceof IotThingNode) { - return node.parent.parent - } - return node.parent -} diff --git a/packages/core/src/awsService/iot/commands/viewPolicyVersion.ts b/packages/core/src/awsService/iot/commands/viewPolicyVersion.ts deleted file mode 100644 index a6c33bbeb8..0000000000 --- a/packages/core/src/awsService/iot/commands/viewPolicyVersion.ts +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { getLogger } from '../../../shared/logger/logger' -import { getTabSizeSetting } from '../../../shared/utilities/editorUtilities' -import { IotPolicyVersionNode } from '../explorer/iotPolicyVersionNode' -import { showViewLogsMessage } from '../../../shared/utilities/messages' - -export async function viewPolicyVersionCommand(node: IotPolicyVersionNode) { - getLogger().debug('viewPolicyVersion called for %O', node) - - const policyVersionId = node.version.versionId! - const policyName = node.policy.name - - try { - const policy = await node.iot.getPolicyVersion({ policyName, policyVersionId }) - const document = policy.policyDocument! - - await showPolicyContent(document) - } catch (e) { - getLogger().error('Failed to retrieve policy document') - void showViewLogsMessage(localize('AWS.iot.viewPolicyVersion.error', 'Failed to retrieve policy document')) - return undefined - } -} - -export function policyFormatter(rawPolicyContent: string, tabSize: number = getTabSizeSetting()): string { - const prettyPolicyContent = JSON.stringify(JSON.parse(rawPolicyContent), undefined, tabSize) - - return prettyPolicyContent -} - -export async function showPolicyContent( - rawPolicyContent: string, - tabSize: number = getTabSizeSetting() -): Promise { - const prettyPolicyContent = policyFormatter(rawPolicyContent, tabSize) - const newDoc = await vscode.workspace.openTextDocument({ - language: 'json', - content: prettyPolicyContent, - }) - await vscode.window.showTextDocument(newDoc, vscode.ViewColumn.One, false) -} diff --git a/packages/core/src/awsService/iot/explorer/iotCertFolderNode.ts b/packages/core/src/awsService/iot/explorer/iotCertFolderNode.ts deleted file mode 100644 index 240da3136f..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotCertFolderNode.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { IotClient } from '../../../shared/clients/iotClient' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { IotCertWithPoliciesNode } from './iotCertificateNode' -import { IotNode } from './iotNodes' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -/** - * Represents the group of all IoT Certificates. - */ -export class IotCertsFolderNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly iot: IotClient, - public readonly parent: IotNode, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super('Certificates', vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = 'IoT Certificates' - this.contextValue = 'awsIotCertsNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noCerts', '[No Certificates found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.iot.listCertificates({ - marker: continuationToken, - pageSize: this.getMaxItemsPerPage(), - }) - - const newCerts = - response.certificates - ?.filter((cert) => cert.certificateArn && cert.certificateId && cert.status && cert.creationDate) - .map( - async (cert) => - new IotCertWithPoliciesNode( - { - arn: cert.certificateArn!, - id: cert.certificateId!, - activeStatus: cert.status!, - creationDate: cert.creationDate!, - }, - this, - this.iot, - await this.iot.listThingsForCert({ principal: cert.certificateArn! }) - ) - ) ?? [] - - const resolvedCerts = await Promise.all(newCerts) - - getLogger().debug(`Loaded certificates: %O`, newCerts) - return { - newContinuationToken: response.nextMarker ?? undefined, - newChildren: [...resolvedCerts], - } - } - - public async refreshNode(): Promise { - this.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } - - public [inspect.custom](): string { - return `IotCertificates` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('iot.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotCertificateNode.ts b/packages/core/src/awsService/iot/explorer/iotCertificateNode.ts deleted file mode 100644 index a90afec36b..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotCertificateNode.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { IotClient, IotCertificate } from '../../../shared/clients/iotClient' - -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { IotCertsFolderNode } from './iotCertFolderNode' -import { IotThingNode } from './iotThingNode' -import { IotPolicyCertNode } from './iotPolicyNode' -import { getIcon } from '../../../shared/icons' -import { truncate } from '../../../shared/utilities/textUtilities' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' -import { formatLocalized } from '../../../shared/datetime' - -const contextBase = 'awsIotCertificateNode' -/** - * Represents an IoT Certificate that may have either a Thing Node or the - * Certificate Folder Node as a parent. - */ -export abstract class IotCertificateNode extends AWSTreeNodeBase implements AWSResourceNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly certificate: IotCertificate, - public readonly parent: IotCertsFolderNode | IotThingNode, - public readonly iot: IotClient, - collapsibleState: vscode.TreeItemCollapsibleState, - public readonly things?: string[], - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - // Show only 8 characters in the explorer instead of the full 64. The entire - // ID can be copied from the context menu or viewed when hovered over. - super(truncate(certificate.id, 8), collapsibleState) - - this.tooltip = localize( - 'AWS.explorerNode.iot.certTooltip', - '{0}\nStatus: {1}\nCreated: {2}{3}', - this.certificate.id, - this.certificate.activeStatus, - formatLocalized(this.certificate.creationDate), - (things?.length ?? 0 > 0) ? `\nAttached to: ${things!.join(', ')}` : '' - ) - this.iconPath = getIcon('aws-iot-certificate') - this.description = `\t[${this.certificate.activeStatus}]` - this.contextValue = `${contextBase}.${this.certificate.activeStatus}` - } - - public update(): void { - return undefined - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noPolicy', '[No Policies found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.iot.listPrincipalPolicies({ - principal: this.certificate.arn, - marker: continuationToken, - pageSize: this.getMaxItemsPerPage(), - }) - - const newPolicies = - response.policies - ?.filter((policy) => policy.policyArn && policy.policyName) - .map( - (policy) => - new IotPolicyCertNode({ arn: policy.policyArn!, name: policy.policyName! }, this, this.iot) - ) ?? [] - - getLogger().debug(`Loaded policies: %O`, newPolicies) - return { - newContinuationToken: response.nextMarker ?? undefined, - newChildren: [...newPolicies], - } - } - - public async refreshNode(): Promise { - this.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('iot.maxItemsPerPage') - } - - public get arn(): string { - return this.certificate.arn - } - - public get name(): string { - return this.certificate.id - } - - public [inspect.custom](): string { - return `IotCertificateNode (certificate=${this.certificate.id})` - } -} - -export class IotThingCertNode extends IotCertificateNode { - public constructor( - public override readonly certificate: IotCertificate, - public override readonly parent: IotThingNode, - public override readonly iot: IotClient, - public override readonly things?: string[], - protected override readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(certificate, parent, iot, vscode.TreeItemCollapsibleState.Collapsed, things, settings) - this.contextValue = `${contextBase}.Things.${this.certificate.activeStatus}` - } -} - -/** - * Represents an IoT Certificate with the Certificate Folder Node as parent. - */ -export class IotCertWithPoliciesNode extends IotCertificateNode implements LoadMoreNode { - public constructor( - public override readonly certificate: IotCertificate, - public override readonly parent: IotCertsFolderNode, - public override readonly iot: IotClient, - public override readonly things?: string[], - protected override readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(certificate, parent, iot, vscode.TreeItemCollapsibleState.Collapsed, things, settings) - this.contextValue = `${contextBase}.Policies.${this.certificate.activeStatus}` - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotNodes.ts b/packages/core/src/awsService/iot/explorer/iotNodes.ts deleted file mode 100644 index aaab68b3fd..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotNodes.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { IotClient } from '../../../shared/clients/iotClient' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { inspect } from 'util' -import { IotThingFolderNode } from './iotThingFolderNode' -import { IotCertsFolderNode } from './iotCertFolderNode' -import { IotPolicyFolderNode } from './iotPolicyFolderNode' - -/** - * An AWS Explorer node representing IoT. - * - * Contains folders for Things, Certificates, and Policies as child nodes. - */ -export class IotNode extends AWSTreeNodeBase { - /* These nodes are declared here to be used when refreshing resources that - * occur multiple times in the tree (e.g. certificates that are under both - * Things and the Certificates folder). However, they cannot be assinged in - * the constructor due to a circular dependency with this node, so they are - * initially undefined. */ - public thingFolderNode: IotThingFolderNode | undefined - public certFolderNode: IotCertsFolderNode | undefined - public policyFolderNode: IotPolicyFolderNode | undefined - - public constructor(private readonly iot: IotClient) { - super('IoT', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsIotNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - const thingFolderNode = new IotThingFolderNode(this.iot, this) - this.thingFolderNode = thingFolderNode - const certFolderNode = new IotCertsFolderNode(this.iot, this) - this.certFolderNode = certFolderNode - const policyFolderNode = new IotPolicyFolderNode(this.iot, this) - this.policyFolderNode = policyFolderNode - const categories: AWSTreeNodeBase[] = [thingFolderNode, certFolderNode, policyFolderNode] - return categories - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noThings', '[No Things found]')), - }) - } - - public async getEndpoint(): Promise { - return await this.iot.getEndpoint() - } - - public [inspect.custom](): string { - return 'IotNode' - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts deleted file mode 100644 index ed7a39270d..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotPolicyFolderNode.ts +++ /dev/null @@ -1,106 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { IotClient } from '../../../shared/clients/iotClient' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { IotPolicyWithVersionsNode } from './iotPolicyNode' -import { IotNode } from './iotNodes' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -// Length of certificate ID. The certificate ID is the last segment of the ARN. -const certIdLength = 64 - -// Number of digits of the certificate ID to show -const certPreviewLength = 8 - -/** - * Represents the group of all IoT Policies. - */ -export class IotPolicyFolderNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly iot: IotClient, - public readonly parent: IotNode, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super('Policies', vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = 'IoT Policies' - this.contextValue = 'awsIotPoliciesNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noPolicy', '[No Policies found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.iot.listPolicies({ - marker: continuationToken, - pageSize: this.getMaxItemsPerPage(), - }) - const newPolicies = - response.policies - ?.filter((policy) => policy.policyArn && policy.policyName) - .map( - async (policy) => - new IotPolicyWithVersionsNode( - { arn: policy.policyArn!, name: policy.policyName! }, - this, - this.iot, - (await this.iot.listPolicyTargets({ policyName: policy.policyName! })).map((certId) => - certId.slice(-certIdLength, -certIdLength + certPreviewLength) - ) - ) - ) ?? [] - - const resolvedPolicies = await Promise.all(newPolicies) - - getLogger().debug(`Loaded policies: %O`, newPolicies) - return { - newContinuationToken: response.nextMarker ?? undefined, - newChildren: [...resolvedPolicies], - } - } - - public async refreshNode(): Promise { - this.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } - - public [inspect.custom](): string { - return `IotPolicies` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('iot.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts deleted file mode 100644 index ce104a5e7f..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotPolicyNode.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { IotClient, IotPolicy } from '../../../shared/clients/iotClient' - -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { inspect } from 'util' -import { IotPolicyFolderNode } from './iotPolicyFolderNode' -import { IotCertificateNode } from './iotCertificateNode' -import { IotPolicyVersionNode } from './iotPolicyVersionNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { toArrayAsync, toMap, updateInPlace } from '../../../shared/utilities/collectionUtils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { getIcon } from '../../../shared/icons' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' -import { PolicyVersion } from '@aws-sdk/client-iot' - -/** - * Represents an IoT Policy that may have either a Certificate Node or the - * Policy Folder Node as a parent. - */ -export class IotPolicyNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public readonly policy: IotPolicy, - public readonly parent: IotPolicyFolderNode | IotCertificateNode, - public readonly iot: IotClient, - collapsibleState: vscode.TreeItemCollapsibleState, - certs?: string[], - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(policy.name, collapsibleState) - this.tooltip = localize( - 'AWS.explorerNode.iot.policyToolTip', - '{0}{1}', - policy.name, - (certs?.length ?? 0 > 0) ? `\nAttached to: ${certs?.join(', ')}` : '' - ) - this.iconPath = getIcon('aws-iot-policy') - this.contextValue = 'awsIotPolicyNode.Certificates' - } - - public get arn(): string { - return this.policy.arn - } - - public get name(): string { - return this.policy.name - } - - public [inspect.custom](): string { - return `IotPolicyNode (policy=${this.policy.name})` - } -} - -export class IotPolicyCertNode extends IotPolicyNode { - public constructor( - public override readonly policy: IotPolicy, - public override readonly parent: IotCertificateNode, - public override readonly iot: IotClient, - protected override readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(policy, parent, iot, vscode.TreeItemCollapsibleState.None, undefined, settings) - this.contextValue = 'awsIotPolicyNode.Certificates' - } -} - -export class IotPolicyWithVersionsNode extends IotPolicyNode { - private readonly versionNodes: Map - - public constructor( - public override readonly policy: IotPolicy, - public override readonly parent: IotPolicyFolderNode, - public override readonly iot: IotClient, - certs?: string[], - protected override readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(policy, parent, iot, vscode.TreeItemCollapsibleState.Collapsed, certs, settings) - this.contextValue = 'awsIotPolicyNode.WithVersions' - this.versionNodes = new Map() - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.versionNodes.values()] - }, - sort: (a: IotPolicyVersionNode, b: IotPolicyVersionNode) => { - return b.version.createDate!.getTime() - a.version.createDate!.getTime() - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noVersions', '[No Policy Versions found]')), - }) - } - - public async updateChildren(): Promise { - const versions: Map = toMap( - await toArrayAsync(this.iot.listPolicyVersions({ policyName: this.policy.name })), - (version) => version.versionId - ) - - updateInPlace( - this.versionNodes, - versions.keys(), - (key) => this.versionNodes.get(key)!.update(versions.get(key)!), - (key) => new IotPolicyVersionNode(this.policy, versions.get(key)!, false, this, this.iot) - ) - } - - public async refreshNode(): Promise { - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts b/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts deleted file mode 100644 index bd85eb6e8c..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotPolicyVersionNode.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { PolicyVersion } from '@aws-sdk/client-iot' -import { IotClient, IotPolicy } from '../../../shared/clients/iotClient' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { inspect } from 'util' -import { IotPolicyWithVersionsNode } from './iotPolicyNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { formatLocalized } from '../../../shared/datetime' - -/** - * Represents an IoT Policy that may have either a Certificate Node or the - * Policy Folder Node as a parent. - */ -export class IotPolicyVersionNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public policy: IotPolicy, - public version: PolicyVersion, - public isDefault: boolean, - public readonly parent: IotPolicyWithVersionsNode, - public readonly iot: IotClient - ) { - super( - localize( - 'AWS.explorerNode.iot.versionName', - 'Version {0}{1}', - version.versionId, - version.isDefaultVersion ? '*' : '' - ) - ) - this.update(version) - } - - public update(version: PolicyVersion): void { - this.version = version - this.isDefault = version.isDefaultVersion ?? false - this.tooltip = localize( - 'AWS.explorerNode.iot.versionTooltip', - 'Policy: {0}\nVersion: {1}\n{2}Created: {3}', - this.policy.name, - this.version.versionId, - this.isDefault ? 'DEFAULT\n' : '', - formatLocalized(this.version.createDate) - ) - this.label = localize( - 'AWS.explorerNode.iot.versionName', - 'Version {0}{1}', - version.versionId, - version.isDefaultVersion ? '*' : '' - ) - this.contextValue = 'awsIotPolicyVersionNode.' + (this.isDefault ? 'DEFAULT' : 'NONDEFAULT') - } - - public get arn(): string { - return this.parent.arn - } - - public get name(): string { - return this.parent.name - } - - public [inspect.custom](): string { - return `IotPolicyNode (policy=${this.policy.name})` - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotThingFolderNode.ts b/packages/core/src/awsService/iot/explorer/iotThingFolderNode.ts deleted file mode 100644 index e3dee64c89..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotThingFolderNode.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { IotClient } from '../../../shared/clients/iotClient' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { IotThingNode } from './iotThingNode' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { IotNode } from './iotNodes' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -/** - * Represents the group of all IoT Things. - */ -export class IotThingFolderNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly iot: IotClient, - public readonly parent: IotNode, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super('Things', vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = 'IoT Things' - this.contextValue = 'awsIotThingsNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noThings', '[No Things found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.iot.listThings({ - nextToken: continuationToken, - maxResults: this.getMaxItemsPerPage(), - }) - - let newThings: IotThingNode[] = [] - if (response.things) { - newThings = response.things - .filter((thing) => thing.thingName && thing.thingArn) - .map((thing) => new IotThingNode({ name: thing.thingName!, arn: thing.thingArn! }, this, this.iot)) - } - - getLogger().debug(`Loaded things: %O`, newThings) - return { - newContinuationToken: response.nextToken ?? undefined, - newChildren: [...newThings], - } - } - - public async refreshNode(): Promise { - this.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } - - public [inspect.custom](): string { - return `IotThings` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('iot.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/iot/explorer/iotThingNode.ts b/packages/core/src/awsService/iot/explorer/iotThingNode.ts deleted file mode 100644 index c6bc14014a..0000000000 --- a/packages/core/src/awsService/iot/explorer/iotThingNode.ts +++ /dev/null @@ -1,115 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { IotThing, IotClient } from '../../../shared/clients/iotClient' - -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { IotThingFolderNode } from './iotThingFolderNode' -import { IotThingCertNode } from './iotCertificateNode' -import { getIcon } from '../../../shared/icons' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -/** - * Represents an IoT Thing that may have attached certificates. - */ -export class IotThingNode extends AWSTreeNodeBase implements AWSResourceNode, LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly thing: IotThing, - public readonly parent: IotThingFolderNode, - public readonly iot: IotClient, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(thing.name, vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = thing.name - this.iconPath = getIcon('aws-iot-thing') - this.contextValue = 'awsIotThingNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.iot.noCerts', '[No Certificates found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.iot.listThingCertificates({ - thingName: this.thing.name, - nextToken: continuationToken, - maxResults: this.getMaxItemsPerPage(), - }) - - const newCerts = - response.certificates - ?.filter((cert) => cert.certificateArn && cert.certificateId && cert.status && cert.creationDate) - .map( - (cert) => - new IotThingCertNode( - { - arn: cert.certificateArn!, - id: cert.certificateId!, - activeStatus: cert.status!, - creationDate: cert.creationDate!, - }, - this, - this.iot - ) - ) ?? [] - - getLogger().debug(`Loaded certificates: %O`, newCerts) - return { - newContinuationToken: response.nextToken ?? undefined, - newChildren: [...newCerts], - } - } - - public get arn(): string { - return this.thing.arn - } - - public get name(): string { - return this.thing.name - } - - public async refreshNode(): Promise { - this.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } - - public [inspect.custom](): string { - return `IotThingNode (thing=${this.thing.name})` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('iot.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/redshift/activation.ts b/packages/core/src/awsService/redshift/activation.ts deleted file mode 100644 index e7af970c26..0000000000 --- a/packages/core/src/awsService/redshift/activation.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable header/header */ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { ExtContext } from '../../shared/extensions' -import * as vscode from 'vscode' -import { RedshiftNotebookSerializer } from './notebook/redshiftNotebookSerializer' -import { RedshiftNotebookController } from './notebook/redshiftNotebookController' -import { CellStatusBarItemProvider } from './notebook/cellStatusBarItemProvider' -import { Commands } from '../../shared/vscode/commands2' -import { NotebookConnectionWizard, RedshiftNodeConnectionWizard } from './wizards/connectionWizard' -import { deleteConnection, ConnectionParams, ConnectionType } from './models/models' -import { DefaultRedshiftClient } from '../../shared/clients/redshiftClient' -import { localize } from '../../shared/utilities/vsCodeUtils' -import { RedshiftWarehouseNode } from './explorer/redshiftWarehouseNode' -import { ToolkitError } from '../../shared/errors' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { showConnectionMessage } from './messageUtils' -import globals from '../../shared/extensionGlobals' - -export async function activate(ctx: ExtContext): Promise { - if ('NotebookEdit' in vscode) { - ctx.extensionContext.subscriptions.push( - vscode.workspace.registerNotebookSerializer('aws-redshift-sql-notebook', new RedshiftNotebookSerializer()) - ) - vscode.notebooks.registerNotebookCellStatusBarItemProvider( - 'aws-redshift-sql-notebook', - new CellStatusBarItemProvider() - ) - const redshiftNotebookController = new RedshiftNotebookController() - ctx.extensionContext.subscriptions.push( - Commands.register( - 'aws.redshift.notebookConnectClicked', - getNotebookConnectClickedHandler(ctx, redshiftNotebookController) - ) - ) - - ctx.extensionContext.subscriptions.push( - Commands.register( - 'aws.redshift.createNotebookClicked', - getCreateNotebookClickedHandler(redshiftNotebookController) - ) - ) - - ctx.extensionContext.subscriptions.push( - Commands.register('aws.redshift.editConnection', getEditConnectionHandler()) - ) - - ctx.extensionContext.subscriptions.push( - Commands.register('aws.redshift.deleteConnection', getDeleteConnectionHandler()) - ) - } -} - -function getCreateNotebookClickedHandler(redshiftNotebookController: RedshiftNotebookController) { - return async (parentNode: RedshiftWarehouseNode) => { - try { - const region = parentNode.redshiftClient.regionCode - const celldata = new vscode.NotebookCellData(vscode.NotebookCellKind.Code, '', 'sql') - celldata.metadata = { connectionParams: parentNode.connectionParams } - const nbdata = new vscode.NotebookData([celldata]) - nbdata.metadata = { connectionParams: parentNode.connectionParams } - const nbdoc = await vscode.workspace.openNotebookDocument('aws-redshift-sql-notebook', nbdata) - await (vscode.window as any).showNotebookDocument(nbdoc) - redshiftNotebookController.redshiftClient = new DefaultRedshiftClient(region) - } catch (e) { - const err = e as Error - void showViewLogsMessage( - localize('AWS.command.notebook.fail', 'Failed to create notebook: {0}', err.message) - ) - } - } -} - -function getNotebookConnectClickedHandler(ctx: ExtContext, redshiftNotebookController: RedshiftNotebookController) { - return async (cell: vscode.NotebookCell, refreshCellStatusBar: () => void) => { - const warehouseConnectionWizard = new NotebookConnectionWizard(ctx.regionProvider) - let connectionParams: ConnectionParams | undefined = await warehouseConnectionWizard.run() - if (!connectionParams) { - return - } - redshiftNotebookController.redshiftClient = new DefaultRedshiftClient(connectionParams.region!.id) - try { - const redshiftClient = (redshiftNotebookController.redshiftClient = new DefaultRedshiftClient( - connectionParams.region!.id - )) - if (connectionParams.connectionType === ConnectionType.DatabaseUser) { - const secretArnFetched = await redshiftClient.createSecretFromConnectionParams(connectionParams) - if (!secretArnFetched) { - throw new ToolkitError('secret arn could not be fetched') - } - connectionParams.secret = secretArnFetched - } - await redshiftNotebookController.redshiftClient.listDatabases(connectionParams!) - showConnectionMessage(connectionParams.warehouseIdentifier, undefined) - } catch (error) { - showConnectionMessage(connectionParams.warehouseIdentifier, error as Error) - connectionParams = undefined - } - const edit = new vscode.WorkspaceEdit() - // NotebookEdit is only available for engine version > 1.68.0 - const nbEdit = (vscode as any).NotebookEdit.updateNotebookMetadata({ - connectionParams: connectionParams, - }) - edit.set(cell.notebook.uri, [nbEdit]) - await vscode.workspace.applyEdit(edit) - refreshCellStatusBar() - } -} - -function getEditConnectionHandler() { - return async (redshiftWarehouseNode: RedshiftWarehouseNode) => { - try { - const connectionParams = await new RedshiftNodeConnectionWizard(redshiftWarehouseNode).run() - if (connectionParams) { - if (connectionParams.connectionType === ConnectionType.DatabaseUser) { - const secretArnFetched = - await redshiftWarehouseNode.redshiftClient.createSecretFromConnectionParams(connectionParams) - if (!secretArnFetched) { - throw new Error('secret arn could not be fetched') - } - connectionParams.secret = secretArnFetched - } - redshiftWarehouseNode.setConnectionParams(connectionParams) - await globals.globalState.saveRedshiftConnection( - redshiftWarehouseNode.arn, - redshiftWarehouseNode.connectionParams - ) - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', redshiftWarehouseNode) - } - } catch (error) { - showConnectionMessage(redshiftWarehouseNode.name, error as Error) - } - } -} - -function getDeleteConnectionHandler() { - return async (redshiftWarehouseNode: RedshiftWarehouseNode) => { - redshiftWarehouseNode.connectionParams = undefined - await globals.globalState.saveRedshiftConnection(redshiftWarehouseNode.arn, deleteConnection) - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', redshiftWarehouseNode) - } -} diff --git a/packages/core/src/awsService/redshift/explorer/redshiftDatabaseNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftDatabaseNode.ts deleted file mode 100644 index d882e50797..0000000000 --- a/packages/core/src/awsService/redshift/explorer/redshiftDatabaseNode.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { RedshiftSchemaNode } from './redshiftSchemaNode' -import { ConnectionParams } from '../models/models' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { ChildNodeLoader, ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { getIcon } from '../../../shared/icons' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { showViewLogsFetchMessage } from '../messageUtils' - -export class RedshiftDatabaseNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly databaseName: string, - private readonly redshiftClient: DefaultRedshiftClient, - private readonly connectionParams: ConnectionParams - ) { - super(databaseName, vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsReshiftDatabaseNode' - this.tooltip = databaseName - this.iconPath = getIcon('aws-redshift-database') - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(token?: string): Promise> { - return telemetry.redshift_listSchemas.run(async () => { - const newChildren: RedshiftSchemaNode[] = [] - try { - // this ensures that when listing schemas (thereafter, when we list tables) we maintain the database name for which we're listing schemas/tables. - if (this.connectionParams.database !== this.databaseName) { - this.connectionParams.database = this.databaseName - } - const listSchemaResponse = await this.redshiftClient.listSchemas(this.connectionParams, token) - if (listSchemaResponse.Schemas?.sort()) { - newChildren.push( - ...listSchemaResponse.Schemas.map((schema) => { - return new RedshiftSchemaNode(schema, this.redshiftClient, this.connectionParams) - }) - ) - } - return { - newChildren: newChildren, - newContinuationToken: listSchemaResponse.NextToken, - } - } catch (error) { - showViewLogsFetchMessage('schemas', this.databaseName, error as Error) - return Promise.reject(error) - } - }) - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.redshiftClient.noSchemas', 'No schemas found')), - }) - } -} diff --git a/packages/core/src/awsService/redshift/explorer/redshiftNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftNode.ts deleted file mode 100644 index 4c245b7f4d..0000000000 --- a/packages/core/src/awsService/redshift/explorer/redshiftNode.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// eslint-disable-next-line header/header -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { inspect } from 'util' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { RedshiftWarehouseNode } from './redshiftWarehouseNode' -import { RedshiftWarehouseType } from '../models/models' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { ChildNodeLoader, ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { getLogger } from '../../../shared/logger/logger' - -/** - * An AWS Explorer node representing Redshift. - * - * Contains clusters for a specific region as child nodes. - */ -export class RedshiftNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - // the constructor below single parameter : 'redshiftClient' which is an instance of the DefaultRedshiftClient used for interacting with Redshift services. - public constructor(public readonly redshiftClient: DefaultRedshiftClient) { - super('Redshift', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsRedshiftNode' - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - /** - * Loads a page of provisioned clusters and serverless workgroups - * There are four cases: - * 1. @param token is undefined -> this is only for the first load use-case, then loads both provisioned clusters and serverless workgroups - * 2. @param token is defined but one of the underlying tokens is empty string -> loads the next page for the non-empty one. - * 3. @param token is defined but both of the underlying tokens are empty string (this is not a real case since we handle this at the end) - * 4. @param token is defined and none of the underlying tokens are empty -> loads both - * @param token compositeContinuationToken (@link: CompositeContinuationToken) stringified - * @returns - */ - private async loadPage(token?: string): Promise> { - let newProvisionedToken: string - let newServerlessToken: string - let newChildrenNodes: RedshiftWarehouseNode[] = [] - if (token) { - const compositeContinuationToken = JSON.parse(token) as CompositeContinuationToken - ;[newChildrenNodes, newProvisionedToken, newServerlessToken] = - await this.loadNodes(compositeContinuationToken) - } else { - ;[newChildrenNodes, newProvisionedToken, newServerlessToken] = await this.loadNodes(undefined) - } - - // This check is needed since ChildNodeLoader will continue to show the Load more button if the newContinuationToken is not undefined - if (newProvisionedToken === '' && newServerlessToken === '') { - return { - newChildren: newChildrenNodes, - newContinuationToken: undefined, - } - } else { - return { - newChildren: newChildrenNodes, - newContinuationToken: JSON.stringify({ - provisionedToken: newProvisionedToken, - serverlessToken: newServerlessToken, - }), - } - } - } - - private async loadNodes( - compositeContinuationToken: CompositeContinuationToken | undefined - ): Promise<[RedshiftWarehouseNode[], string, string]> { - const childNodes: RedshiftWarehouseNode[] = [] - let newServerlessToken: string = '' - let newProvisionedToken: string = '' - // Handle provisioned clusters - if (compositeContinuationToken === undefined || compositeContinuationToken.provisionedToken !== '') { - const response = await this.redshiftClient.describeProvisionedClusters( - compositeContinuationToken?.provisionedToken - ) - if (response.Clusters) { - const provisionedNodes = response.Clusters.map((cluster) => { - return new RedshiftWarehouseNode( - this, - { - arn: cluster.ClusterNamespaceArn, - name: cluster.ClusterIdentifier, - } as AWSResourceNode, - RedshiftWarehouseType.PROVISIONED - ) - }) - childNodes.push(...provisionedNodes) - newProvisionedToken = response.Marker ?? '' - } - } - // Handle serverless workgroups - if (compositeContinuationToken === undefined || compositeContinuationToken.serverlessToken !== '') { - try { - const response = await this.redshiftClient.listServerlessWorkgroups( - compositeContinuationToken?.serverlessToken - ) - if (response.workgroups) { - const serverlessNodes = response.workgroups.map((workgroup) => { - return new RedshiftWarehouseNode( - this, - { arn: workgroup.workgroupArn, name: workgroup.workgroupName } as AWSResourceNode, - RedshiftWarehouseType.SERVERLESS - ) - }) - childNodes.push(...serverlessNodes) - newServerlessToken = response.nextToken ?? '' - } - } catch (error) { - getLogger().error("Serverless workgroup operation isn't supported or failed: %O", error) - // Continue without interrupting the provisioned cluster loading - } - } - return [childNodes, newProvisionedToken, newServerlessToken] - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.redshiftClient.noClusters', 'No cluster found')), - }) - } - - public async createCluster(clusterName: string): Promise { - // Code for creating redshiftClient cluster - } - - public [inspect.custom](): string { - return 'RedshiftNode' - } -} - -interface CompositeContinuationToken { - provisionedToken: string - serverlessToken: string -} diff --git a/packages/core/src/awsService/redshift/explorer/redshiftSchemaNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftSchemaNode.ts deleted file mode 100644 index 4b257e4d1e..0000000000 --- a/packages/core/src/awsService/redshift/explorer/redshiftSchemaNode.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { RedshiftTableNode } from './redshiftTableNode' -import { ConnectionParams } from '../models/models' -import { ChildNodeLoader, ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { getIcon } from '../../../shared/icons' -import { showViewLogsFetchMessage } from '../messageUtils' - -export class RedshiftSchemaNode extends AWSTreeNodeBase implements LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - public constructor( - public readonly schemaName: string, - public readonly redshiftClient: DefaultRedshiftClient, - private readonly connectionParams: ConnectionParams - ) { - super(schemaName, vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsRedshiftSchemaNode' - this.tooltip = schemaName - this.iconPath = getIcon('aws-redshift-schema') - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(token?: string): Promise> { - return telemetry.redshift_listTables.run(async () => { - const newChildren: RedshiftTableNode[] = [] - try { - const listTablesResponse = await this.redshiftClient.listTables( - this.connectionParams, - this.schemaName, - token - ) - if (listTablesResponse.Tables?.sort()) { - newChildren.push( - ...listTablesResponse.Tables.filter((table) => !table.name?.endsWith('_pkey')).map((table) => { - return new RedshiftTableNode(table.name ?? 'UnknownTable') - }) - ) - } - return { - newChildren: newChildren, - newContinuationToken: listTablesResponse.NextToken, - } - } catch (error) { - showViewLogsFetchMessage('tables', this.schemaName, error as Error) - return Promise.reject(error) - } - }) - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.redshiftClient.noTables', 'No tables found')), - }) - } -} diff --git a/packages/core/src/awsService/redshift/explorer/redshiftTableNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftTableNode.ts deleted file mode 100644 index f590dab45f..0000000000 --- a/packages/core/src/awsService/redshift/explorer/redshiftTableNode.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -// eslint-disable-next-line header/header -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { getIcon } from '../../../shared/icons' - -export class RedshiftTableNode extends AWSTreeNodeBase { - public constructor(public readonly tableName: string) { - super(tableName, vscode.TreeItemCollapsibleState.None) - this.contextValue = 'awsRedshiftTableNode' - this.tooltip = tableName - this.iconPath = getIcon('aws-redshift-table') - } -} diff --git a/packages/core/src/awsService/redshift/explorer/redshiftWarehouseNode.ts b/packages/core/src/awsService/redshift/explorer/redshiftWarehouseNode.ts deleted file mode 100644 index 26947893c8..0000000000 --- a/packages/core/src/awsService/redshift/explorer/redshiftWarehouseNode.ts +++ /dev/null @@ -1,178 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import * as vscode from 'vscode' -import globals from '../../../shared/extensionGlobals' -import { RedshiftNode } from './redshiftNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { RedshiftDatabaseNode } from './redshiftDatabaseNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { ChildNodeLoader, ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { deleteConnection, ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../models/models' -import { RedshiftNodeConnectionWizard } from '../wizards/connectionWizard' -import { ListDatabasesResponse } from '@aws-sdk/client-redshift-data' -import { getIcon } from '../../../shared/icons' -import { AWSCommandTreeNode } from '../../../shared/treeview/nodes/awsCommandTreeNode' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { createLogsConnectionMessage, showConnectionMessage } from '../messageUtils' - -export class CreateNotebookNode extends AWSCommandTreeNode { - constructor(parent: RedshiftWarehouseNode) { - super(parent, 'Create-Notebook', 'aws.redshift.createNotebookClicked', [parent]) - this.iconPath = getIcon('vscode-debug-start') - } - toJSON() { - // Exclude the 'redshiftWarehouse' property from serialization - return { ...this, parent: undefined } - } -} - -export class RedshiftWarehouseNode extends AWSTreeNodeBase implements AWSResourceNode, LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - public arn: string - public name: string - public redshiftClient: DefaultRedshiftClient - public connectionParams?: ConnectionParams - - constructor( - public readonly parent: RedshiftNode, - public readonly redshiftWarehouse: AWSResourceNode, - public readonly warehouseType: RedshiftWarehouseType - ) { - super(redshiftWarehouse.name, vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = redshiftWarehouse.name - this.contextValue = 'awsRedshiftWarehouseNode' - this.arn = redshiftWarehouse.arn - this.name = redshiftWarehouse.name - this.redshiftClient = parent.redshiftClient - const existingConnectionParams = globals.globalState.getRedshiftConnection(this.arn) - if (existingConnectionParams && existingConnectionParams !== deleteConnection) { - this.connectionParams = existingConnectionParams as ConnectionParams - this.iconPath = getIcon('aws-redshift-cluster-connected') - } else { - this.iconPath = getIcon('aws-redshift-cluster') - } - } - - public setConnectionParams(connectionParams: ConnectionParams) { - this.connectionParams = connectionParams - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(token?: string): Promise> { - return telemetry.redshift_listDatabases.run(async () => { - const childNodes: RedshiftDatabaseNode[] = [] - // this.connectionParams cannot null here because loadPage should be called only after the connection wizard runs. - try { - const listDatabasesResponse: ListDatabasesResponse = await this.redshiftClient.listDatabases( - this.connectionParams!, - token - ) - - if (listDatabasesResponse.Databases?.sort()) { - childNodes.push( - ...listDatabasesResponse.Databases.map((db) => { - return new RedshiftDatabaseNode(db, this.redshiftClient, this.connectionParams!) - }) - ) - } - - return { - newContinuationToken: listDatabasesResponse.NextToken, - newChildren: childNodes, - } - } catch (error) { - createLogsConnectionMessage(this.redshiftWarehouse.name, error as Error) - return Promise.reject(error) - } - }) - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - this.childLoader.clearChildren() - const existingConnectionParams = globals.globalState.getRedshiftConnection(this.arn) - if (existingConnectionParams && existingConnectionParams === deleteConnection) { - // connection is deleted but explorer is not refreshed: return clickToEstablishConnectionNode - await globals.globalState.saveRedshiftConnection(this.arn, undefined) - return this.getClickToEstablishConnectionNode() - } else if (existingConnectionParams) { - // valid connectionParams: update the redshiftWarehouseNode - this.connectionParams = existingConnectionParams - } else { - // No connectionParams: trigger connection wizard to get user input - this.connectionParams = await new RedshiftNodeConnectionWizard(this).run() - if (!this.connectionParams) { - return this.getClickToEstablishConnectionNode() - } - - if (this.connectionParams.connectionType === ConnectionType.DatabaseUser) { - const secretArnFetched = await this.redshiftClient.createSecretFromConnectionParams( - this.connectionParams - ) - if (!secretArnFetched) { - throw new Error('secret arn could not be fetched') - } - this.connectionParams.secret = secretArnFetched - } - } - try { - const childNodes = await this.childLoader.getChildren() - const startButtonNode = new CreateNotebookNode(this) - childNodes.unshift(startButtonNode) - await globals.globalState.saveRedshiftConnection(this.arn, this.connectionParams) - return childNodes - } catch (error) { - showConnectionMessage(this.redshiftWarehouse.name, error as Error) - await globals.globalState.saveRedshiftConnection(this.arn, undefined) - return this.getRetryNode() - } - }, - getNoChildrenPlaceholderNode: async () => this.getClickToEstablishConnectionNode()[0], - }) - } - - private getClickToEstablishConnectionNode(): AWSCommandTreeNode[] { - return [ - new AWSCommandTreeNode( - this, - localize('AWS.redshift.clickToEstablishConnection', 'Click to connect'), - 'aws.refreshAwsExplorerNode', - [this] - ), - ] - } - - private getRetryNode(): AWSCommandTreeNode[] { - return [ - new AWSCommandTreeNode( - this, - localize('AWS.redshift.connectionError.retry', 'Unable to get databases, click here to retry'), - 'aws.refreshAwsExplorerNode', - [this] - ), - ] - } - - toJSON() { - // Exclude the 'redshiftWarehouse' property from serialization - return { ...this, redshiftWarehouse: undefined } - } -} diff --git a/packages/core/src/awsService/redshift/messageUtils.ts b/packages/core/src/awsService/redshift/messageUtils.ts deleted file mode 100644 index a1e8420b84..0000000000 --- a/packages/core/src/awsService/redshift/messageUtils.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../../shared/extensionGlobals' -import { getLogger } from '../../shared/logger/logger' -import { showViewLogsMessage } from '../../shared/utilities/messages' - -/** - * Show the connection message in output channel. Log the error message if connection fails. - * @param warehouseIdentifier - * @param error - */ -export function showConnectionMessage(warehouseIdentifier: string, error: Error | undefined) { - const outputChannel = globals.outputChannel - outputChannel.show(true) - const outputMessage = createLogsConnectionMessage(warehouseIdentifier, error) - outputChannel.appendLine(outputMessage) -} - -/** - * Create the connection message and log the error if the connection fails. - * @param warehouseIdentifier - * @param error - * @returns the connection message to show - */ -export function createLogsConnectionMessage(warehouseIdentifier: string, error: Error | undefined): string { - let connectionMessage = '' - if (!error) { - connectionMessage = `Redshift: connected to: ${warehouseIdentifier}` - } else { - connectionMessage = `Redshift: failed to connect to: ${warehouseIdentifier}: ${(error as Error).message}` - getLogger().error(connectionMessage) - } - return connectionMessage -} - -/** - * Log the fetching error message and show a window with "View Logs" button - * @param fetchType - * @param identifier - * @param error - */ -export function showViewLogsFetchMessage(fetchType: string, identifier: string, error: Error) { - const message = `Redshift: failed to fetch ${fetchType} for ${identifier}: ${(error as Error).message}` - getLogger().error(message) - void showViewLogsMessage(message) -} diff --git a/packages/core/src/awsService/redshift/models/models.ts b/packages/core/src/awsService/redshift/models/models.ts deleted file mode 100644 index 603cf495fb..0000000000 --- a/packages/core/src/awsService/redshift/models/models.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Region } from '../../../shared/regions/endpoints' - -// Sigil treated such that the connection wizard is not triggered during explorer node refresh after -// connection deletion. -export const deleteConnection = 'DELETE_CONNECTION' as const - -export class ConnectionParams { - constructor( - public readonly connectionType: ConnectionType, - public database: string, - public readonly warehouseIdentifier: string, - public readonly warehouseType: RedshiftWarehouseType, - public username?: string, - public readonly region?: Region, - public password?: string, - public secret?: string - ) {} -} - -export enum ConnectionType { - DatabaseUser = 'Database user name and password', - SecretsManager = 'AWS Secrets Manager', - TempCreds = 'Temporary credentials', -} - -export enum RedshiftWarehouseType { - PROVISIONED, - SERVERLESS, -} diff --git a/packages/core/src/awsService/redshift/notebook/cellStatusBarItemProvider.ts b/packages/core/src/awsService/redshift/notebook/cellStatusBarItemProvider.ts deleted file mode 100644 index 7846ead15e..0000000000 --- a/packages/core/src/awsService/redshift/notebook/cellStatusBarItemProvider.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable header/header */ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - CancellationToken, - NotebookCell, - NotebookCellStatusBarAlignment, - NotebookCellStatusBarItem, - NotebookCellStatusBarItemProvider, - ProviderResult, - Command, - EventEmitter, -} from 'vscode' -import { getIcon } from '../../../shared/icons' - -export class CellStatusBarItemProvider implements NotebookCellStatusBarItemProvider { - private _item: NotebookCellStatusBarItem - private _onDidChangeCellStatusBarItems = new EventEmitter() - onDidChangeCellStatusBarItems = this._onDidChangeCellStatusBarItems.event - - public constructor() { - this._item = new NotebookCellStatusBarItem( - `${getIcon('vscode-notebook-state-error')} Connect`, - NotebookCellStatusBarAlignment.Right - ) - } - - provideCellStatusBarItems( - cell: NotebookCell, - token: CancellationToken - ): ProviderResult { - const metadata = cell.notebook.metadata - if (metadata?.connectionParams) { - this._item.text = `${getIcon('vscode-notebook-state-success')} Connected to ${ - metadata.connectionParams?.warehouseIdentifier - }` - } else { - this._item.text = `${getIcon('vscode-notebook-state-error')} Connect` - } - this._item.command = { - command: 'aws.redshift.notebookConnectClicked', - arguments: [cell, this.refreshCellStatusBar.bind(this)], - } as Command - return [this._item] - } - - refreshCellStatusBar() { - this._onDidChangeCellStatusBarItems.fire() - } -} diff --git a/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts b/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts deleted file mode 100644 index f71b93362e..0000000000 --- a/packages/core/src/awsService/redshift/notebook/redshiftNotebookController.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* eslint-disable header/header */ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { ConnectionParams } from '../models/models' -import { ColumnMetadata, Field } from '@aws-sdk/client-redshift-data' -import { telemetry } from '../../../shared/telemetry/telemetry' - -export class RedshiftNotebookController { - readonly id = 'aws-redshift-sql-notebook' - public readonly label = 'Redshift SQL notebook' - readonly supportedLanguages = ['sql'] - - private _executionOrder = 0 - private readonly _controller: vscode.NotebookController - - constructor(public redshiftClient?: DefaultRedshiftClient) { - this._controller = vscode.notebooks.createNotebookController(this.id, 'aws-redshift-sql-notebook', this.label) - this._controller.supportedLanguages = this.supportedLanguages - this._controller.supportsExecutionOrder = true - this._controller.executeHandler = this._executeAll.bind(this) - } - - dispose(): void { - this._controller.dispose() - } - - private async _executeAll( - cells: vscode.NotebookCell[], - _notebook: vscode.NotebookDocument, - _controller: vscode.NotebookController - ): Promise { - for (const cell of cells) { - await this._doExecution(cell) - } - } - - private async _doExecution(cell: vscode.NotebookCell): Promise { - const execution = this._controller.createNotebookCellExecution(cell) - execution.executionOrder = ++this._executionOrder - execution.start(Date.now()) - let success = false - try { - // Used Promise.race to handle both execution and cancellation - const resultPromise = this.executeCell(cell) - const cancellationPromise = new Promise((_, reject) => { - execution.token.onCancellationRequested(() => { - reject(new Error('Cell execution was cancelled.')) - }) - }) - const cellOutput = await Promise.race([resultPromise, cancellationPromise]) - success = true - await execution.replaceOutput([cellOutput]) - } catch (err: unknown) { - await execution.replaceOutput([ - new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.error(err as Error)]), - ]) - success = false - } finally { - execution.end(success, Date.now()) - } - } - - private async executeCell(cell: vscode.NotebookCell): Promise { - return telemetry.redshift_executeQuery.run(async () => { - const connectionParams = cell.notebook.metadata?.connectionParams as ConnectionParams - // check cell connection before execute the query - if (connectionParams === undefined) { - throw Error('This cell is not connected to any cluster or workgroup.') - } - - // This handles cases where the users had connected to the database with the wizard in a previous session and they opened and closed the editor - if (!this.redshiftClient && connectionParams) { - this.redshiftClient = new DefaultRedshiftClient(connectionParams.region!.id) - } - - let executionId: string | undefined - let columnMetadata: ColumnMetadata[] | undefined - const records: Field[][] = [] - let nextToken: string | undefined - // get all the pages of the result - do { - const result = await this.redshiftClient!.executeQuery( - connectionParams, - cell.document.getText(), - nextToken, - executionId - ) - if (result && result.statementResultResponse.Records) { - nextToken = result.statementResultResponse.NextToken - executionId = result.executionId - columnMetadata = result.statementResultResponse.ColumnMetadata - records.push(...result.statementResultResponse.Records) - } else { - return new vscode.NotebookCellOutput([ - vscode.NotebookCellOutputItem.text('Query completed — No rows returned.', 'text/plain'), - ]) - } - } while (nextToken) - - if (columnMetadata) { - const columnNames: string[] = columnMetadata.map((column) => column.name || 'UnknownColumnName') - if (columnNames) { - const htmlTable = this.getAsTable(connectionParams, columnNames, records) - return new vscode.NotebookCellOutput([vscode.NotebookCellOutputItem.text(htmlTable, 'text/html')]) - } else { - throw Error('Column metadata did not contain column names') - } - } else { - throw Error('Result did not contain column metadata') - } - }) - } - - public getAsTable(connectionParams: ConnectionParams, columns: string[], records: Field[][]) { - if (!records || records.length === 0) { - return '

No records to display

' - } - let tableHtml = `

Results from ${connectionParams.warehouseIdentifier} - database: ${connectionParams.database}

` - - // Adding column headers - for (const column of columns) { - tableHtml += `` - } - tableHtml += '' - - // Adding data rows - for (const row of records) { - tableHtml += '' - for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { - let cellValue: undefined - if (Object.keys(row[columnIndex])[0] !== 'isNull') { - cellValue = Object.values(row[columnIndex])[0] - } - tableHtml += `` - } - tableHtml += '' - } - tableHtml += '
${column}
${cellValue}
' - return tableHtml - } -} diff --git a/packages/core/src/awsService/redshift/notebook/redshiftNotebookSerializer.ts b/packages/core/src/awsService/redshift/notebook/redshiftNotebookSerializer.ts deleted file mode 100644 index cdbcdb9476..0000000000 --- a/packages/core/src/awsService/redshift/notebook/redshiftNotebookSerializer.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable header/header */ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' - -interface RawNotebookData { - cells: RawNotebookCell[] -} - -interface RawNotebookCell { - language: string - value: string - kind: vscode.NotebookCellKind - metadata: { [key: string]: any } - editable?: boolean -} - -export class RedshiftNotebookSerializer implements vscode.NotebookSerializer { - public readonly label: string = 'Redshift notebook serializer' - - public async deserializeNotebook(data: Uint8Array, token: vscode.CancellationToken): Promise { - const contents = new TextDecoder().decode(data) // convert to String to make JSON object - - // Read file contents - let raw: RawNotebookData - try { - raw = JSON.parse(contents) as RawNotebookData - } catch { - raw = { cells: [] } - } - - // Create array of Notebook cells for the VS Code API from file contents - const metadata = raw.cells[0]?.metadata ?? {} - const cells = raw.cells.map((item) => { - const newCell = new vscode.NotebookCellData(item.kind, item.value, item.language) - return newCell - }) - - const notebookData = new vscode.NotebookData(cells) - notebookData.metadata = metadata - // Pass read and formatted Notebook Data to VS Code to display Notebook with saved cells - return notebookData - } - - public async serializeNotebook( - notebookData: vscode.NotebookData, - token: vscode.CancellationToken - ): Promise { - // Map the Notebook data into the format we want to save the Notebook data as - const contents: RawNotebookData = { cells: [] } - - for (const cell of notebookData.cells) { - contents.cells.push({ - kind: cell.kind, - language: cell.languageId, - value: cell.value, - metadata: notebookData.metadata ?? {}, - }) - } - - // Give a string of all the data to save and VS Code will handle the rest - return new TextEncoder().encode(JSON.stringify(contents)) - } -} diff --git a/packages/core/src/awsService/redshift/wizards/connectionWizard.ts b/packages/core/src/awsService/redshift/wizards/connectionWizard.ts deleted file mode 100644 index 251cfd22e2..0000000000 --- a/packages/core/src/awsService/redshift/wizards/connectionWizard.ts +++ /dev/null @@ -1,284 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Wizard } from '../../../shared/wizards/wizard' -import { createInputBox } from '../../../shared/ui/inputPrompter' -import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../../shared/ui/pickerPrompter' -import { createCommonButtons } from '../../../shared/ui/buttons' -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../models/models' -import { RedshiftWarehouseNode } from '../explorer/redshiftWarehouseNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import { Region } from '../../../shared/regions/endpoints' -import { RegionProvider } from '../../../shared/regions/regionProvider' -import { createRegionPrompter } from '../../../shared/ui/common/region' -import { ClustersMessage } from '@aws-sdk/client-redshift' -import { Prompter } from '../../../shared/ui/prompter' -import { ListSecretsResponse } from '@aws-sdk/client-secrets-manager' -import { SecretsManagerClient } from '../../../shared/clients/secretsManagerClient' -import { redshiftHelpUrl } from '../../../shared/constants' - -export class RedshiftNodeConnectionWizard extends Wizard { - public constructor(node: RedshiftWarehouseNode) { - super({ - initState: { - warehouseIdentifier: node.name, - warehouseType: node.warehouseType, - }, - }) - this.form.connectionType.bindPrompter( - (state) => getConnectionTypePrompter(node.connectionParams?.connectionType, state?.warehouseType), - { - relativeOrder: 1, - } - ) - - this.form.database.bindPrompter(getDatabasePrompter, { - relativeOrder: 2, - }) - this.form.username.bindPrompter(getUsernamePrompter, { - showWhen: (state) => - (state.database !== undefined && state.connectionType === ConnectionType.TempCreds) || - state.connectionType === ConnectionType.DatabaseUser, - relativeOrder: 3, - }) - this.form.password.bindPrompter(getPasswordPrompter, { - showWhen: (state) => state.username !== undefined && state.connectionType === ConnectionType.DatabaseUser, - relativeOrder: 4, - }) - - this.form.secret.bindPrompter((state) => getSecretPrompter(node.redshiftClient.regionCode), { - showWhen: (state) => state.database !== undefined && state.connectionType === ConnectionType.SecretsManager, - relativeOrder: 4, - }) - } -} - -export class NotebookConnectionWizard extends Wizard { - static readonly SERVERLESSPREFIX = 'Serverless:' - public constructor( - regionProvider: RegionProvider, - region?: Region | undefined, - warehouseIdentifier?: string | undefined, - warehouseType?: RedshiftWarehouseType, - connectionType?: ConnectionType | undefined, - database?: string | undefined, - username?: string | undefined, - secret?: string | undefined - ) { - super({ - initState: { - connectionType: connectionType, - database: database, - username: username, - warehouseIdentifier: warehouseIdentifier, - region: region, - warehouseType: warehouseType, - secret: secret, - }, - }) - - this.form.region.bindPrompter( - () => { - const regions = regionProvider - .getRegions() - .filter((r) => regionProvider.isServiceInRegion('redshift', r.id)) - return createRegionPrompter(regions) - }, - { relativeOrder: 1 } - ) - - this.form.warehouseIdentifier.bindPrompter((state) => getWarehouseIdentifierPrompter(state.region!.id), { - relativeOrder: 2, - }) - - this.form.warehouseType.setDefault((state) => { - if (!state.warehouseType && state.warehouseIdentifier) { - if (state.warehouseIdentifier?.startsWith(NotebookConnectionWizard.SERVERLESSPREFIX)) { - state.warehouseType = RedshiftWarehouseType.SERVERLESS - state.warehouseIdentifier = state.warehouseIdentifier.replace( - NotebookConnectionWizard.SERVERLESSPREFIX, - '' - ) - return RedshiftWarehouseType.SERVERLESS - } else { - state.warehouseType = RedshiftWarehouseType.PROVISIONED - return RedshiftWarehouseType.PROVISIONED - } - } - }) - this.form.connectionType.bindPrompter((state) => getConnectionTypePrompter(undefined, state?.warehouseType), { - relativeOrder: 3, - }) - - this.form.database.bindPrompter(getDatabasePrompter, { relativeOrder: 4 }) - this.form.username.bindPrompter(getUsernamePrompter, { - showWhen: (state) => - (state.database !== undefined && state.connectionType === ConnectionType.TempCreds) || - state.connectionType === ConnectionType.DatabaseUser, - relativeOrder: 5, - }) - this.form.password.bindPrompter(getPasswordPrompter, { - showWhen: (state) => state.username !== undefined && state.connectionType === ConnectionType.DatabaseUser, - relativeOrder: 6, - }) - - this.form.secret.bindPrompter((state) => getSecretPrompter(state.region!.id), { - showWhen: (state) => state.database !== undefined && state.connectionType === ConnectionType.SecretsManager, - relativeOrder: 5, - }) - } -} - -function getUsernamePrompter(): Prompter { - return createInputBox({ - value: '', - title: localize('AWS.redshift.username', 'Enter the username you want to use to connect to the database'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Enter username', - validateInput: (value) => { - return value.trim() ? undefined : localize('AWS.redshift.usernameValidation', 'Username cannot be empty') - }, - }) -} - -function getPasswordPrompter(): Prompter { - return createInputBox({ - value: '', - title: localize('AWS.redshift.password', 'Enter password'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Enter password', - validateInput: (value) => { - return value.trim() ? undefined : localize('AWS.redshift.passwordValidation', 'Password cannot be empty') - }, - password: true, - }) -} - -function getDatabasePrompter(): Prompter { - return createInputBox({ - value: '', - title: localize('AWS.redshift.database', 'Enter the name of the database you want to connect to'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Enter database name', - validateInput: (value) => { - return value.trim() ? undefined : localize('AWS.redshift.databaseValidation', 'Database cannot be empty') - }, - }) -} - -function createSelectConnectionQuickPick(items: DataQuickPickItem[]): Prompter { - return createQuickPick(items, { - title: localize('AWS.redshift.connectionType', 'Select Connection Type'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Select Connection Type', - }) -} - -function getConnectionTypePrompter( - existingConnectionType: ConnectionType | undefined, - warehouseType?: RedshiftWarehouseType -): Prompter { - const items: DataQuickPickItem[] = Object.values(ConnectionType).map((type) => ({ - label: type, - data: type, - })) - if (existingConnectionType) { - const updatedItems = items.map((item) => { - if (item.data === existingConnectionType) { - return { - ...item, - label: `${existingConnectionType} - current`, - } - } - return item - }) - const selectedUpdatedItems = - warehouseType === RedshiftWarehouseType.SERVERLESS ? [updatedItems[0], updatedItems[1]] : updatedItems - return createSelectConnectionQuickPick(selectedUpdatedItems) - } else { - // Determine which items to use based on warehouseType - const selectedItems = warehouseType === RedshiftWarehouseType.SERVERLESS ? [items[0], items[1]] : items - return createSelectConnectionQuickPick(selectedItems) - } -} - -async function* fetchWarehouses(redshiftClient: DefaultRedshiftClient) { - let serverlessToken: string | undefined - let provisionedToken: string | undefined - let hasMoreServerless = true - let hasMoreProvisioned = true - while (hasMoreProvisioned || hasMoreServerless) { - if (hasMoreProvisioned) { - const provisionedResponse: ClustersMessage = - await redshiftClient.describeProvisionedClusters(provisionedToken) - provisionedToken = provisionedResponse.Marker - hasMoreProvisioned = provisionedToken !== undefined - if (provisionedResponse.Clusters) { - for await (const cluster of provisionedResponse.Clusters) { - yield [ - { - label: cluster.ClusterIdentifier || 'UnknownProvisionedCluster', - data: cluster.ClusterIdentifier || 'UnknownProvisionedCluster', - }, - ] - } - } - } - if (hasMoreServerless) { - const serverlessResponse = await redshiftClient.listServerlessWorkgroups(serverlessToken) - serverlessToken = serverlessResponse.nextToken - hasMoreServerless = serverlessToken !== undefined - if (serverlessResponse.workgroups) { - for await (const workgroup of serverlessResponse.workgroups) { - yield [ - { - label: `${NotebookConnectionWizard.SERVERLESSPREFIX} ${ - workgroup.workgroupName ?? 'UnknownServerlessWorkgroup' - }`, - data: `${NotebookConnectionWizard.SERVERLESSPREFIX}${ - workgroup.workgroupName ?? 'UnknownServerlessWorkgroup' - }`, - }, - ] - } - } - } - } -} - -function getWarehouseIdentifierPrompter(region: string): QuickPickPrompter { - const redshiftClient = new DefaultRedshiftClient(region) - - return createQuickPick(fetchWarehouses(redshiftClient), { - title: localize('AWS.redshift.chooseAWarehousePrompt', 'Choose a warehouse to connect to'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Choose a warehouse to connect to', - }) -} - -function getSecretPrompter(region: string): QuickPickPrompter { - const secretsManagerClient = new SecretsManagerClient(region) - return createQuickPick(fetchSecretList(secretsManagerClient), { - title: localize('AWS.redshift.chooseSecretPrompt', 'Choose a secret'), - buttons: createCommonButtons(redshiftHelpUrl), - placeholder: 'Choose a secret', - }) -} - -async function* fetchSecretList(secretsManagerClient: SecretsManagerClient) { - const secretFilter = 'Redshift' - const listSecretsResponse: ListSecretsResponse = await secretsManagerClient.listSecrets(secretFilter) - if (listSecretsResponse.SecretList) { - for await (const secret of listSecretsResponse.SecretList) { - yield [ - { - label: secret.Name || 'UnknownSecret', - data: secret.ARN || 'UnknownSecret', - }, - ] - } - } -} diff --git a/packages/core/src/awsService/s3/activation.ts b/packages/core/src/awsService/s3/activation.ts deleted file mode 100644 index 85a050582a..0000000000 --- a/packages/core/src/awsService/s3/activation.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { copyPathCommand } from './commands/copyPath' -import { createBucketCommand } from './commands/createBucket' -import { createFolderCommand } from './commands/createFolder' -import { deleteBucketCommand } from './commands/deleteBucket' -import { deleteFileCommand } from './commands/deleteFile' -import { downloadFileAsCommand } from './commands/downloadFileAs' -import { presignedURLCommand } from './commands/presignedURL' -import { editFileCommand, openFileReadModeCommand } from './commands/openFile' -import { uploadFileCommand } from './commands/uploadFile' -import { uploadFileToParentCommand } from './commands/uploadFileToParent' -import { S3BucketNode } from './explorer/s3BucketNode' -import { S3FolderNode } from './explorer/s3FolderNode' -import { S3Node } from './explorer/s3Nodes' -import { S3FileNode } from './explorer/s3FileNode' -import { ExtContext } from '../../shared/extensions' -import { S3FileViewerManager, s3EditScheme, s3ReadScheme } from './fileViewerManager' -import { VirtualFileSystem } from '../../shared/virtualFilesystem' -import { Commands } from '../../shared/vscode/commands2' - -import * as nls from 'vscode-nls' -import { S3Client } from '../../shared/clients/s3' -import { TreeNode } from '../../shared/treeview/resourceTreeDataProvider' -import { getSourceNode } from '../../shared/utilities/treeNodeUtils' -const localize = nls.loadMessageBundle() - -/** - * Activates S3 components. - */ - -export async function activate(ctx: ExtContext): Promise { - const fs = new VirtualFileSystem( - localize('AWS.s3.fileViewer.genericError', 'Unable to open S3 file, try reopening from the explorer') - ) - const manager = new S3FileViewerManager((region) => new S3Client(region), fs) - - ctx.extensionContext.subscriptions.push(manager) - ctx.extensionContext.subscriptions.push( - vscode.workspace.registerFileSystemProvider(s3EditScheme, fs, { isCaseSensitive: true }), - vscode.workspace.registerFileSystemProvider(s3ReadScheme, fs, { isReadonly: true, isCaseSensitive: true }), - Commands.register('aws.s3.copyPath', async (node: S3FolderNode | S3FileNode) => { - await copyPathCommand(node) - }), - Commands.register('aws.s3.presignedURL', async (node: S3FileNode) => { - await presignedURLCommand(node) - }), - Commands.register('aws.s3.downloadFileAs', async (node: S3FileNode) => { - await downloadFileAsCommand(node) - }), - Commands.register('aws.s3.openFile', async (node: S3FileNode) => { - await openFileReadModeCommand(node, manager) - }), - Commands.register('aws.s3.editFile', async (uriOrNode: vscode.Uri | S3FileNode) => { - await editFileCommand(uriOrNode, manager) - }), - Commands.register( - { id: 'aws.s3.uploadFile', autoconnect: true }, - async (node?: S3BucketNode | S3FolderNode | TreeNode) => { - if (!node) { - const awsContext = ctx.awsContext - const regionCode = awsContext.getCredentialDefaultRegion() - const s3Client = new S3Client(regionCode) - const document = vscode.window.activeTextEditor?.document.uri - await uploadFileCommand(s3Client, document) - } else { - const sourceNode = getSourceNode(node) - await uploadFileCommand(sourceNode.s3, sourceNode) - } - } - ), - Commands.register('aws.s3.uploadFileToParent', async (node: S3FileNode) => { - await uploadFileToParentCommand(node) - }), - Commands.register('aws.s3.createBucket', async (node: S3Node) => { - await createBucketCommand(node) - }), - Commands.register('aws.s3.createFolder', async (node: S3BucketNode | S3FolderNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await createFolderCommand(sourceNode) - }), - Commands.register('aws.s3.deleteBucket', async (node: S3BucketNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await deleteBucketCommand(sourceNode) - }), - Commands.register('aws.s3.deleteFile', async (node: S3FileNode) => { - await deleteFileCommand(node) - }) - ) -} diff --git a/packages/core/src/awsService/s3/commands/copyPath.ts b/packages/core/src/awsService/s3/commands/copyPath.ts deleted file mode 100644 index 0671a97aa8..0000000000 --- a/packages/core/src/awsService/s3/commands/copyPath.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { copyToClipboard } from '../../../shared/utilities/messages' -import { S3FolderNode } from '../explorer/s3FolderNode' -import { S3FileNode } from '../explorer/s3FileNode' -import { telemetry } from '../../../shared/telemetry/telemetry' - -/** - * Copies the path to the folder or file represented by the given node. - * - * Note that the path does not contain the bucket name or a leading slash. - */ -export async function copyPathCommand(node: S3FolderNode | S3FileNode): Promise { - await telemetry.s3_copyPath.run(() => copyToClipboard(node.path, 'path')) -} diff --git a/packages/core/src/awsService/s3/commands/createBucket.ts b/packages/core/src/awsService/s3/commands/createBucket.ts deleted file mode 100644 index cd451a9da5..0000000000 --- a/packages/core/src/awsService/s3/commands/createBucket.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { S3Node } from '../explorer/s3Nodes' -import { validateBucketName } from '../util' -import { ToolkitError } from '../../../shared/errors' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { telemetry } from '../../../shared/telemetry/telemetry' - -/** - * Creates a bucket in the s3 region represented by the given node. - * - * Prompts the user for the bucket name, applying basic validations. - * Creates the bucket. - * Refreshes the node. - */ -export async function createBucketCommand(node: S3Node): Promise { - await telemetry.s3_createBucket.run(async () => { - const bucketName = await vscode.window.showInputBox({ - prompt: localize('AWS.s3.createBucket.prompt', 'Enter a new bucket name'), - placeHolder: localize('AWS.s3.createBucket.placeHolder', 'Bucket Name'), - validateInput: validateBucketName, - }) - - if (!bucketName) { - throw new CancellationError('user') - } - - getLogger().info(`Creating bucket: ${bucketName}`) - const bucket = await node - .createBucket({ bucketName }) - .catch((e) => { - const message = localize( - 'AWS.s3.createBucket.error.general', - 'Failed to create bucket: {0}', - bucketName - ) - throw ToolkitError.chain(e, message) - }) - .finally(() => refreshNode(node)) - - getLogger().info('Created bucket: %O', bucket) - void vscode.window.showInformationMessage( - localize('AWS.s3.createBucket.success', 'Created bucket: {0}', bucketName) - ) - }) -} - -async function refreshNode(node: S3Node): Promise { - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/awsService/s3/commands/createFolder.ts b/packages/core/src/awsService/s3/commands/createFolder.ts deleted file mode 100644 index d56cb0c14c..0000000000 --- a/packages/core/src/awsService/s3/commands/createFolder.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { DEFAULT_DELIMITER } from '../../../shared/clients/s3' -import { getLogger } from '../../../shared/logger/logger' -import { S3BucketNode } from '../explorer/s3BucketNode' -import { S3FolderNode } from '../explorer/s3FolderNode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { readablePath } from '../util' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { ToolkitError } from '../../../shared/errors' - -/** - * Creates a subfolder in the bucket or folder represented by the given node. - * - * Prompts the user for the folder name, applying basic validations. - * Creates the folder. - * Refreshes the node. - * - * Node that the node is reset to displaying its first page of results. - * The folder that is created won't necessary fall on the first page. - * The user may need to load more pages to see the created folder reflected in the tree. - */ -export async function createFolderCommand(node: S3BucketNode | S3FolderNode): Promise { - getLogger().debug('CreateFolder called for %O', node) - - await telemetry.s3_createFolder.run(async () => { - const folderName = await vscode.window.showInputBox({ - prompt: localize('AWS.s3.createFolder.prompt', 'Enter a folder to create in {0}', readablePath(node)), - placeHolder: localize('AWS.s3.createFolder.placeHolder', 'Folder Name'), - validateInput: validateFolderName, - }) - - if (!folderName) { - throw new CancellationError('user') - } - - const path = node.path + folderName + DEFAULT_DELIMITER - getLogger().info(`Creating folder "${path}" in bucket '${node.bucket.Name}'`) - - const { folder } = await node - .createFolder({ path, bucketName: node.bucket.Name }) - .catch((e) => { - const message = localize( - 'AWS.s3.createFolder.error.general', - 'Failed to create folder: {0}', - folderName - ) - throw ToolkitError.chain(e, message) - }) - .finally(() => refreshNode(node)) - - getLogger().info('created folder: %O', folder) - void vscode.window.showInformationMessage( - localize('AWS.s3.createFolder.success', 'Created folder: {0}', folderName) - ) - }) -} - -function validateFolderName(name: string): string | undefined { - if (name.includes(DEFAULT_DELIMITER)) { - return localize( - 'AWS.s3.createFolder.error.illegalCharacter', - `Folder name must not contain '{0}'`, - DEFAULT_DELIMITER - ) - } - - if (name === '') { - return localize('AWS.s3.createFolder.error.emptyName', 'Folder name must not be empty') - } - return undefined -} - -async function refreshNode(node: S3BucketNode | S3FolderNode): Promise { - node.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/awsService/s3/commands/deleteBucket.ts b/packages/core/src/awsService/s3/commands/deleteBucket.ts deleted file mode 100644 index 9390f93cf5..0000000000 --- a/packages/core/src/awsService/s3/commands/deleteBucket.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../../shared/logger/logger' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { S3BucketNode } from '../explorer/s3BucketNode' -import { S3Node } from '../explorer/s3Nodes' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { ToolkitError } from '../../../shared/errors' - -/** - * Deletes the bucket represented by the given node. - * - * Prompts the user for confirmation (user must type the bucket name). - * Empties and deletes the bucket, showing a progress bar. - * Refreshes the parent node. - * - * Note that this just repeatedly calls list and delete to empty the bucket before deletion. - * Failures during the emptying or deleting step can leave the bucket in a state where - * some (or all) objects are deleted, but the bucket remains. - * - * This is unfortunate, but it's still a valuable feature and partial failures - * don't result in a state of too much confusion for the user. - */ -export async function deleteBucketCommand(node: S3BucketNode): Promise { - getLogger().debug('DeleteBucket called for %O', node) - - await telemetry.s3_deleteBucket.run(async () => { - const isConfirmed = await showConfirmationDialog(node.bucket.Name) - if (!isConfirmed) { - throw new CancellationError('user') - } - - getLogger().info(`Deleting bucket: ${node.bucket.Name}`) - await deleteWithProgress(node) - .catch((e) => { - const message = localize( - 'AWS.s3.deleteBucket.error.general', - 'Failed to delete bucket {0}', - node.bucket.Name - ) - throw ToolkitError.chain(e, message) - }) - .finally(() => refreshNode(node.parent)) - getLogger().info(`deleted bucket: ${node.bucket.Name}`) - }) -} - -async function showConfirmationDialog(bucketName: string): Promise { - const prompt = localize('AWS.s3.deleteBucket.prompt', 'Enter {0} to confirm deletion', bucketName) - const confirmationInput = await vscode.window.showInputBox({ - prompt, - placeHolder: bucketName, - validateInput: (input) => (input !== bucketName ? prompt : undefined), - }) - - return confirmationInput === bucketName -} - -async function deleteWithProgress(node: S3BucketNode): Promise { - return vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: localize('AWS.s3.deleteBucket.progressTitle', 'Deleting {0}...', node.bucket.Name), - }, - () => { - return node.deleteBucket() - } - ) -} - -async function refreshNode(node: S3Node): Promise { - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/awsService/s3/commands/deleteFile.ts b/packages/core/src/awsService/s3/commands/deleteFile.ts deleted file mode 100644 index cb54331b3a..0000000000 --- a/packages/core/src/awsService/s3/commands/deleteFile.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as localizedText from '../../../shared/localizedText' -import { getLogger } from '../../../shared/logger/logger' -import { addCodiconToString } from '../../../shared/utilities/textUtilities' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { S3BucketNode } from '../explorer/s3BucketNode' -import { S3FileNode } from '../explorer/s3FileNode' -import { S3FolderNode } from '../explorer/s3FolderNode' -import { readablePath } from '../util' -import { showConfirmationMessage } from '../../../shared/utilities/messages' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { ToolkitError } from '../../../shared/errors' - -const deleteFileDisplayTimeoutMs = 2000 - -/** - * Deletes the file represented by the given node. - * - * Prompts the user for confirmation. - * Deletes the file. - * Shows status bar message. - * Refreshes the parent node. - */ -export async function deleteFileCommand(node: S3FileNode): Promise { - const filePath = readablePath(node) - getLogger().debug('DeleteFile called for %O', node) - - await telemetry.s3_deleteObject.run(async () => { - const isConfirmed = await showConfirmationMessage({ - prompt: localize('AWS.s3.deleteFile.prompt', 'Are you sure you want to delete file {0}?', filePath), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - throw new CancellationError('user') - } - - getLogger().info(`Deleting file ${filePath}`) - - await node - .deleteFile() - .catch((e) => { - const message = localize('AWS.s3.deleteFile.error.general', 'Failed to delete file {0}', node.file.name) - throw ToolkitError.chain(e, message) - }) - .finally(() => refreshNode(node.parent)) - - getLogger().info(`deleted file: ${filePath}`) - vscode.window.setStatusBarMessage( - addCodiconToString('trash', localize('AWS.deleteFile.success', 'Deleted: {0}', node.file.name)), - deleteFileDisplayTimeoutMs - ) - }) -} - -async function refreshNode(node: S3BucketNode | S3FolderNode): Promise { - node.clearChildren() - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/awsService/s3/commands/downloadFileAs.ts b/packages/core/src/awsService/s3/commands/downloadFileAs.ts deleted file mode 100644 index 396875b3bd..0000000000 --- a/packages/core/src/awsService/s3/commands/downloadFileAs.ts +++ /dev/null @@ -1,151 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' - -import { getDefaultDownloadPath, setDefaultDownloadPath } from '../../../shared/filesystemUtilities' -import { S3FileNode } from '../explorer/s3FileNode' -import { readablePath } from '../util' -import { progressReporter } from '../progressReporter' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showOutputMessage } from '../../../shared/utilities/messages' -import { S3Client } from '../../../shared/clients/s3' -import { Timeout, CancellationError } from '../../../shared/utilities/timeoutUtils' -import { ToolkitError } from '../../../shared/errors' -import { streamToBuffer, streamToFile } from '../../../shared/utilities/streamUtilities' -import { S3File } from '../fileViewerManager' -import globals from '../../../shared/extensionGlobals' -import { telemetry } from '../../../shared/telemetry/telemetry' - -interface DownloadFileOptions { - /** - * Setting this field will show a progress notification for - * the provided location, otherwise no progress will shown. - */ - readonly progressLocation?: vscode.ProgressLocation - /** - * Destination for the downloaded file. - * - * If provided then the promise will not resolve into a Buffer. - */ - readonly saveLocation?: vscode.Uri - /** - * Timeout associated with the download. If provided, it is assumed that - * the download is also cancellable. - */ - readonly timeout?: Timeout - /** - * Client to use for the download. Creates one if not provided. - */ - readonly client?: S3Client -} - -interface FileOptions extends DownloadFileOptions { - readonly saveLocation: vscode.Uri -} -interface BufferOptions extends DownloadFileOptions { - readonly saveLocation?: never -} - -async function downloadS3File( - client: S3Client, - file: S3File, - options?: FileOptions | BufferOptions -): Promise { - const downloadStream = await client.downloadFileStream(file.bucket.Name, file.key) - const result = options?.saveLocation - ? streamToFile(downloadStream, options.saveLocation) - : streamToBuffer(downloadStream, file.sizeBytes) - - options?.timeout?.token.onCancellationRequested(({ agent }) => downloadStream.destroy(new CancellationError(agent))) - - if (options?.progressLocation) { - void vscode.window.withProgress( - { - location: options.progressLocation, - title: localize('AWS.s3.downloadFile.progressTitle', 'Downloading {0}...', file.name), - cancellable: !!options.timeout, - }, - (progress, token) => { - const reporter = progressReporter(progress, { totalBytes: file.sizeBytes }) - const report = (chunk: Buffer | string) => reporter(chunk.length) - token.onCancellationRequested(() => downloadStream.destroy(new CancellationError('user'))) - downloadStream.on('data', report) - - return new Promise((resolve) => result.finally(resolve)) - } - ) - } - - return result -} - -export async function downloadFile(file: S3File, options: FileOptions): Promise -export async function downloadFile(file: S3File, options?: BufferOptions): Promise -export async function downloadFile(file: S3File, options?: FileOptions | BufferOptions): Promise { - const client = options?.client ?? new S3Client(file.bucket.BucketRegion) - - return downloadS3File(client, file, options).catch((err) => { - const message = localize('AWS.s3.downloadFile.error.general', 'Failed to download file {0}', file.name) - - throw ToolkitError.chain(err, message, { - details: { bucket: file.bucket, path: file.key, destination: options?.saveLocation?.path }, - }) - }) -} - -/** - * Downloads a file represented by the given node. - * - * Prompts the user for the save location. - * Shows the output channel with "download started" message. - * Downloads the file (showing a progress bar). - * Shows the output channel with "download completed" message. - */ -export async function downloadFileAsCommand(node: S3FileNode, outputChannel = globals.outputChannel): Promise { - const { bucket, file } = node - const sourcePath = readablePath(node) - - await telemetry.s3_downloadObject.run(async () => { - const downloadPath = await getDefaultDownloadPath() - - const saveLocation = await promptForSaveLocation(file.name, downloadPath) - if (!saveLocation) { - throw new CancellationError('user') - } - await setDefaultDownloadPath(saveLocation.fsPath) - - showOutputMessage(`Downloading "${sourcePath}" to: ${saveLocation}`, outputChannel) - - await downloadFile( - { ...file, bucket }, - { - saveLocation, - client: node.s3, - progressLocation: vscode.ProgressLocation.Notification, - } - ) - - showOutputMessage(`Downloaded: ${saveLocation}`, outputChannel) - }) -} - -async function promptForSaveLocation(fileName: string, saveLocation: string): Promise { - const extension = path.extname(fileName) - - // Insertion order matters, as it determines the ordering in the filters dropdown - // First inserted item is at the top (this should be the extension, if present) - const filters: vscode.SaveDialogOptions['filters'] = extension - ? { [`*${extension}`]: [extension.slice(1)], 'All Files': ['*'] } - : { 'All Files': ['*'] } - - const downloadPath = path.join(saveLocation, fileName) - return vscode.window.showSaveDialog({ - defaultUri: vscode.Uri.file(downloadPath), - saveLabel: localize('AWS.s3.downloadFile.saveButton', 'Download'), - filters: filters, - }) -} diff --git a/packages/core/src/awsService/s3/commands/openFile.ts b/packages/core/src/awsService/s3/commands/openFile.ts deleted file mode 100644 index 9f7a6713d0..0000000000 --- a/packages/core/src/awsService/s3/commands/openFile.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { S3FileNode } from '../explorer/s3FileNode' -import { S3FileViewerManager, S3Tab } from '../fileViewerManager' -import { downloadFileAsCommand } from './downloadFileAs' -import { telemetry } from '../../../shared/telemetry/telemetry' - -const sizeLimit = 50 * Math.pow(10, 6) - -export async function openFileReadModeCommand( - node: S3FileNode, - manager: S3FileViewerManager -): Promise { - if (await isFileSizeValid(node.file.sizeBytes, node)) { - return telemetry.s3_openEditor.run(() => manager.openInReadMode({ bucket: node.bucket, ...node.file })) - } -} - -export async function editFileCommand( - uriOrNode: vscode.Uri | S3FileNode, - manager: S3FileViewerManager -): Promise { - if (uriOrNode instanceof S3FileNode) { - const size = uriOrNode.file.sizeBytes - - if (!(await isFileSizeValid(size, uriOrNode))) { - return - } - - return telemetry.s3_editObject.run(() => - manager.openInEditMode({ bucket: uriOrNode.bucket, ...uriOrNode.file }) - ) - } - - return telemetry.s3_editObject.run(() => manager.openInEditMode(uriOrNode)) -} - -async function isFileSizeValid(size: number | undefined, fileNode: S3FileNode): Promise { - if (size && size > sizeLimit) { - const downloadAs = localize('AWS.s3.button.downloadAs', 'Download as..') - void vscode.window - .showErrorMessage( - localize( - 'AWS.s3.fileViewer.error.invalidSize', - 'Files over 50MB cannot be viewed and instead must be downloaded manually.' - ), - downloadAs - ) - .then(async (response) => { - if (response === downloadAs) { - await downloadFileAsCommand(fileNode) - } - }) - return false - } - - return true -} diff --git a/packages/core/src/awsService/s3/commands/presignedURL.ts b/packages/core/src/awsService/s3/commands/presignedURL.ts deleted file mode 100644 index f641345bae..0000000000 --- a/packages/core/src/awsService/s3/commands/presignedURL.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SignedUrlRequest } from '../../../shared/clients/s3' -import { copyToClipboard } from '../../../shared/utilities/messages' -import { S3FileNode } from '../explorer/s3FileNode' -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { invalidNumberWarning } from '../../../shared/localizedText' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { ToolkitError } from '../../../shared/errors' - -export async function presignedURLCommand(node: S3FileNode): Promise { - await telemetry.s3_copyUrl.run(async (span) => { - span.record({ presigned: true }) - - const validTime = await promptTime(node.file.key) - const s3Client = node.s3 - const request: SignedUrlRequest = { - bucketName: node.bucket.Name, - key: node.file.key, - time: validTime * 60, - } - - const url = await s3Client.getSignedUrlForObject(request).catch((e) => { - throw ToolkitError.chain( - e, - 'Error creating the presigned URL. Make sure you have access to the requested file.' - ) - }) - - await copyToClipboard(url, 'URL') - }) -} - -export async function promptTime(fileName: string): Promise { - const timeStr = await vscode.window.showInputBox({ - value: '15', - prompt: localize( - 'AWS.s3.promptTime.prompt', - 'Specify the time (minutes) until URL will expire for path: {0}', - fileName - ), - placeHolder: localize('AWS.s3.promptTime.placeHolder', 'Defaults to 15 minutes'), - validateInput: validateTime, - }) - - const time = Number(timeStr) - - if (isNaN(time) || time < 0) { - throw new ToolkitError('The provided expiration time is not a positive number', { code: 'InvalidInput' }) - } - - return time -} - -function validateTime(time: string): string | undefined { - const number = Number(time) - - if (isNaN(Number(time)) || !Number.isSafeInteger(number) || Number(time) <= 0) { - return invalidNumberWarning - } - - return undefined -} diff --git a/packages/core/src/awsService/s3/commands/uploadFile.ts b/packages/core/src/awsService/s3/commands/uploadFile.ts deleted file mode 100644 index a6a4a27544..0000000000 --- a/packages/core/src/awsService/s3/commands/uploadFile.ts +++ /dev/null @@ -1,600 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as mime from 'mime-types' -import * as vscode from 'vscode' -import { statSync } from 'fs' // eslint-disable-line no-restricted-imports -import { getLogger } from '../../../shared/logger/logger' -import { S3Node } from '../explorer/s3Nodes' -import { readablePath } from '../util' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { showOutputMessage } from '../../../shared/utilities/messages' -import { createQuickPick, promptUser, verifySinglePickerOutput } from '../../../shared/ui/picker' -import { addCodiconToString } from '../../../shared/utilities/textUtilities' -import { S3Bucket, Folder, S3Client } from '../../../shared/clients/s3' -import { createBucketCommand } from './createBucket' -import { S3BucketNode } from '../explorer/s3BucketNode' -import { S3FolderNode } from '../explorer/s3FolderNode' -import * as localizedText from '../../../shared/localizedText' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { progressReporter } from '../progressReporter' -import globals from '../../../shared/extensionGlobals' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { Upload } from '@aws-sdk/lib-storage' - -export interface FileSizeBytes { - /** - * Returns the file size in bytes. - */ - (file: vscode.Uri): number -} - -interface UploadRequest { - bucketName: string - key: string - fileLocation: vscode.Uri - fileSizeBytes: number - s3Client: S3Client - ongoingUpload?: Upload -} - -/** - * Wizard to upload a file. - * - * @param s3Client account to upload the file to - * @param nodeOrDocument node to upload to or file currently open, if undefined then there was no active editor - * - */ -export async function uploadFileCommand( - s3Client: S3Client, - nodeOrDocument: S3BucketNode | S3FolderNode | vscode.Uri | undefined, - fileSizeBytes: FileSizeBytes = statFile, - getBucket = promptUserForBucket, - getFile = getFilesToUpload, - outputChannel = globals.outputChannel -): Promise { - let node: S3BucketNode | S3FolderNode | undefined - let document: vscode.Uri | undefined - const uploadRequests: UploadRequest[] = [] - - if (nodeOrDocument) { - if (nodeOrDocument instanceof S3BucketNode || nodeOrDocument instanceof S3FolderNode) { - node = nodeOrDocument as S3BucketNode | S3FolderNode - document = undefined - } else { - node = undefined - document = nodeOrDocument as vscode.Uri - } - } else { - node = undefined - document = undefined - } - - const fileToUploadRequest = (bucketName: string, key: string, file: vscode.Uri) => ({ - bucketName, - key: key, - fileLocation: file, - fileSizeBytes: fileSizeBytes(file), - s3Client, - }) - - if (node) { - const filesToUpload = await getFile(undefined) - - if (!filesToUpload) { - showOutputMessage( - localize('AWS.message.error.uploadFileCommand.noFileSelected', 'No file selected, cancelling upload'), - outputChannel - ) - getLogger().info('UploadFile cancelled') - telemetry.s3_uploadObject.emit({ result: 'Cancelled' }) - return - } - - uploadRequests.push( - ...filesToUpload.map((file) => { - const key = node!.path + path.basename(file.fsPath) - return fileToUploadRequest(node!.bucket.Name, key, file) - }) - ) - if (node instanceof S3FolderNode) { - globals.globalState.tryUpdate('aws.lastUploadedToS3Folder', { - bucket: node.bucket, - folder: node.folder, - }) - } - } else { - while (true) { - const filesToUpload = await getFile(document) - - if (!filesToUpload || filesToUpload.length === 0) { - // if file is undefined, means the back button was pressed(there is no step before) or no file was selected - // thus break the loop of the 'wizard' - showOutputMessage( - localize( - 'AWS.message.error.uploadFileCommand.noFileSelected', - 'No file selected, cancelling upload' - ), - outputChannel - ) - getLogger().info('UploadFile cancelled') - telemetry.s3_uploadObject.emit({ result: 'Cancelled' }) - return - } - - const bucketResponse = await getBucket(s3Client).catch((e) => {}) - - if (!bucketResponse) { - telemetry.s3_uploadObject.emit({ result: 'Failed' }) - return - } - - if (typeof bucketResponse === 'string') { - if (bucketResponse === 'back') { - continue - } - - showOutputMessage( - localize( - 'AWS.message.error.uploadFileCommand.noBucketSelected', - 'No bucket selected, cancelling upload' - ), - outputChannel - ) - getLogger().info('No bucket selected, cancelling upload') - telemetry.s3_uploadObject.emit({ result: 'Cancelled' }) - return - } - - const bucketName = bucketResponse.bucket!.Name - if (!bucketName) { - throw Error(`bucketResponse is not a S3.Bucket`) - } - - uploadRequests.push( - ...filesToUpload.map((file) => { - const key = - bucketResponse.folder !== undefined - ? bucketResponse.folder.path + path.basename(file.fsPath) - : path.basename(file.fsPath) - return fileToUploadRequest(bucketName, key, file) - }) - ) - - if (bucketResponse.folder) { - globals.globalState.tryUpdate('aws.lastUploadedToS3Folder', { - bucket: bucketResponse.bucket, - folder: bucketResponse.folder, - }) - } - - break - } - } - - await runBatchUploads(uploadRequests, outputChannel) - - void vscode.commands.executeCommand('aws.refreshAwsExplorer', true) -} - -async function promptForFileLocation(): Promise { - const fileLocations = await vscode.window.showOpenDialog({ - canSelectMany: true, - openLabel: localize('AWS.s3.uploadFile.openButton', 'Upload'), - }) - - return fileLocations -} - -function statFile(file: vscode.Uri) { - return statSync(file.fsPath).size -} - -/** - * Continously attempts to upload the files until all succeed or the user cancels. - */ -async function runBatchUploads(uploadRequests: UploadRequest[], outputChannel = globals.outputChannel): Promise { - let failedRequests = await uploadBatchOfFiles(uploadRequests, outputChannel) - - showOutputMessage( - localize( - 'AWS.s3.uploadFile.complete', - 'Uploaded {0}/{1} files', - uploadRequests.length - failedRequests.length, - uploadRequests.length - ), - outputChannel - ) - - while (failedRequests.length > 0) { - const failedKeys = failedRequests.map((request) => request.key) - getLogger().error(`List of requests failed to upload:\n${failedRequests.toString().split(',').join('\n')}`) - - if (failedRequests.length > 5) { - showOutputMessage( - localize( - 'AWS.s3.uploadFile.failedMany', - 'Failed uploads:\n{0}\nSee logs for full list of failed items', - failedKeys.toString().split(',').slice(0, 5).join('\n') - ), - outputChannel - ) - } else { - showOutputMessage( - localize( - 'AWS.s3.uploadFile.failed', - 'Failed uploads:\n{0}', - failedKeys.toString().split(',').join('\n') - ), - outputChannel - ) - } - // at least one request failed - const response = await vscode.window.showErrorMessage( - localize( - 'AWS.s3.uploadFile.retryPrompt', - 'S3 Upload: {0}/{1} failed.', - failedRequests.length, - uploadRequests.length - ), - localizedText.retry, - localizedText.skip - ) - - if (response === localizedText.retry) { - // No tail call optimization in node :( - failedRequests = await uploadBatchOfFiles(failedRequests, outputChannel) - } else { - break - } - } -} - -/** - * Uploads an array of requests to their specified s3 location. - * - * @returns array of unsuccessful requests - */ - -async function uploadBatchOfFiles( - uploadRequests: UploadRequest[], - outputChannel = globals.outputChannel -): Promise { - const totalBytes = uploadRequests.map((r) => r.fileSizeBytes).reduce((a, b) => a + b, 0) - const response = await vscode.window.withProgress( - { - cancellable: true, - location: vscode.ProgressLocation.Notification, - title: localize( - 'AWS.s3.uploadFile.progressTitle.batch', - 'Uploading {0} files to {1}', - uploadRequests.length, - uploadRequests[0].bucketName - ), - }, - async (progress, token) => { - let requestIdx: number = 0 - const failedRequests: UploadRequest[] = [] - - token.onCancellationRequested((e) => { - if (uploadRequests[requestIdx].ongoingUpload) { - void uploadRequests[requestIdx].ongoingUpload?.abort() - } - return failedRequests - }) - - while (!token.isCancellationRequested && requestIdx < uploadRequests.length) { - const request = uploadRequests[requestIdx] - const fileName = path.basename(request.key) - const destinationPath = readablePath({ bucket: { Name: request.bucketName }, path: request.key }) - showOutputMessage( - localize('AWS.s3.uploadFile.startUpload', 'Uploading file {0} to {1}', fileName, destinationPath), - outputChannel - ) - let remainder = 0 - let lastLoaded = 0 - // TODO: don't use `withProgress`, it makes it hard to have control over the individual outputs - // For now we will hide the noisy info to the channel. - const progressWithCount: typeof progress = { - report(value) { - const loaded = ((value.increment ?? 0) / 100) * request.fileSizeBytes + remainder - const rounded = Math.floor(loaded) - const increment = ((rounded - lastLoaded) / totalBytes) * 100 - - remainder = loaded - rounded - lastLoaded = rounded - progress.report({ message: `${fileName} (${value.message})`, increment }) - }, - } - - const uploadResult = await uploadWithProgress(request, progressWithCount, token).catch((err) => { - showOutputMessage( - localize( - 'AWS.s3.uploadFile.error.general', - 'Failed to upload file {0}: {1}', - fileName, - err.message - ), - outputChannel - ) - - return request - }) - - if (uploadResult) { - // this request failed to upload - failedRequests.push(uploadResult) - } - - requestIdx += 1 - } - - return failedRequests.concat(uploadRequests.slice(requestIdx)) - } - ) - - telemetry.s3_uploadObject.emit({ - result: response.length > 0 ? 'Failed' : 'Succeeded', - value: uploadRequests.length, - failedCount: response.length, - successCount: uploadRequests.length - response.length, - }) - - return response -} - -/** - * Uploads a single request to s3 with a progress window - * - * @param request File to be uploaded - * @param progress Progress to report to - * @param token Cancellation token - * @returns The same request if failed, undefined otherwise - */ -async function uploadWithProgress( - request: UploadRequest, - progress: vscode.Progress<{ message?: string; increment?: number }>, - token: vscode.CancellationToken -): Promise { - const progressListener = progressReporter(progress, { - reportMessage: true, - totalBytes: request.fileSizeBytes, - }) - - const currentStream = await request.s3Client.uploadFile({ - bucketName: request.bucketName, - key: request.key, - content: request.fileLocation, - progressListener, - contentType: mime.contentType(path.extname(request.fileLocation.fsPath)) || undefined, - }) - - progressListener(0) - request.ongoingUpload = currentStream - - const cancelled = new Promise((_, reject) => { - token.onCancellationRequested((e) => { - void currentStream.abort() - reject(new CancellationError('user')) - }) - }) - - await Promise.race([currentStream.done(), cancelled]) - - return (request.ongoingUpload = undefined) -} - -export interface BucketQuickPickItem extends vscode.QuickPickItem { - bucket: (Partial & { Name: string }) | undefined - folder?: Folder | undefined -} - -interface SavedFolder { - bucket: S3Bucket - folder: Folder -} - -// TODO:: extract and reuse logic from sam deploy wizard (bucket selection) -/** - * Will display a quick pick with the list of all buckets owned by the user. - * @param s3client client to get the list of buckets - * - * @returns Bucket selected by the user, 'back' or 'cancel' - * - * @throws Error if there is an error calling s3 - */ -export async function promptUserForBucket( - s3client: S3Client, - promptUserFunction = promptUser, - createBucket = createBucketCommand -): Promise { - let allBuckets: S3Bucket[] - try { - allBuckets = (await s3client.listBuckets()).buckets - } catch (e) { - getLogger().error('Failed to list buckets from client %O', e) - void vscode.window.showErrorMessage( - localize('AWS.message.error.promptUserForBucket.listBuckets', 'Failed to list buckets from client') - ) - throw new Error('Failed to list buckets from client') - } - - const s3Buckets = allBuckets.filter((bucket) => { - return bucket && bucket.Name - }) - - const createNewBucket: BucketQuickPickItem = { - label: localize('AWS.command.s3.createBucket', 'Create new bucket'), - bucket: undefined, - } - const bucketItems: BucketQuickPickItem[] = s3Buckets.map((bucket) => { - return { - label: bucket.Name!, - bucket, - } - }) - - const lastTouchedFolder = globals.globalState.tryGet('aws.lastTouchedS3Folder', Object) - let lastFolderItem: BucketQuickPickItem | undefined = undefined - if (lastTouchedFolder) { - lastFolderItem = { - label: lastTouchedFolder.folder.name, - description: '(last opened S3 folder)', - bucket: { Name: lastTouchedFolder.bucket.Name }, - folder: lastTouchedFolder.folder, - } - } - - const lastUploadedToFolder = globals.globalState.tryGet('aws.lastUploadedToS3Folder', Object) - let lastUploadedFolderItem: BucketQuickPickItem | undefined = undefined - if (lastUploadedToFolder) { - lastUploadedFolderItem = { - label: lastUploadedToFolder.folder.name, - description: '(last uploaded-to S3 folder)', - bucket: { Name: lastUploadedToFolder.bucket.Name }, - folder: lastUploadedToFolder.folder, - } - } - - const folderItems = [] - if (lastUploadedFolderItem !== undefined) { - folderItems.push(lastUploadedFolderItem) - } - // de-dupe if folders are the same - if ( - lastFolderItem !== undefined && - (lastUploadedFolderItem === undefined || lastFolderItem.folder?.path !== lastUploadedFolderItem.folder?.path) - ) { - folderItems.push(lastFolderItem) - } - - const items: BucketQuickPickItem[] = [ - ...(folderItems.length > 0 - ? [ - { - label: localize('AWS.s3.uploadFile.folderSeparator', 'Folders'), - kind: vscode.QuickPickItemKind.Separator, - bucket: undefined, - } as BucketQuickPickItem, - ] - : []), - ...folderItems, - { - label: localize('AWS.s3.uploadFile.bucketSeparator', 'Buckets'), - kind: vscode.QuickPickItemKind.Separator, - bucket: undefined, - } as BucketQuickPickItem, - ...bucketItems, - createNewBucket, - ] - - const picker = createQuickPick({ - options: { - canPickMany: false, - ignoreFocusOut: true, - title: localize('AWS.message.selectBucket', 'Select an S3 bucket or folder to upload to'), - step: 2, - totalSteps: 2, - }, - items, - buttons: [vscode.QuickInputButtons.Back], - }) - const response = verifySinglePickerOutput( - await promptUserFunction({ - picker: picker, - onDidTriggerButton: (button, resolve, reject) => { - if (button === vscode.QuickInputButtons.Back) { - resolve([ - { - label: 'back', - bucket: undefined, - }, - ]) - } - }, - }) - ) - - if (!response) { - return 'cancel' - } - - if (!response.bucket) { - if (response.label === 'back') { - return response.label - } - if (response.label === 'Create new bucket') { - const s3Node = new S3Node(s3client) - await createBucket(s3Node) - return promptUserForBucket(s3client) - } - } else { - return response - } - return 'cancel' -} - -/** - * Gets the open file in the current editor - * Asks the user to browse for more files - * If no file is open it prompts the user to select file - * @param document document to use as currently open - * - * @returns file selected by the user - */ -export async function getFilesToUpload( - document?: vscode.Uri, - promptUserFunction = promptUser -): Promise { - let fileLocations: vscode.Uri[] | undefined - - if (!document) { - fileLocations = await promptForFileLocation() - } else { - fileLocations = [document] - const fileNameToDisplay = path.basename(fileLocations[0].fsPath) - - const fileOption: vscode.QuickPickItem = { - label: addCodiconToString('file', fileNameToDisplay), - } - const selectMore: vscode.QuickPickItem = { - label: localize('AWS.message.browseMoreFiles', 'Browse for more files...'), - } - - const picker = createQuickPick({ - options: { - canPickMany: false, - ignoreFocusOut: true, - title: localize('AWS.message.selectFileUpload', 'Select a file to upload'), - step: 1, - totalSteps: 2, - }, - items: [fileOption, selectMore], - buttons: [vscode.QuickInputButtons.Back], - }) - - const response = verifySinglePickerOutput( - await promptUserFunction({ - picker: picker, - onDidTriggerButton: (button, resolve, reject) => { - if (button === vscode.QuickInputButtons.Back) { - resolve(undefined) - } - }, - }) - ) - - if (!response) { - return - } - - if (response.label === selectMore.label) { - fileLocations = await promptForFileLocation() - } - } - - return fileLocations -} diff --git a/packages/core/src/awsService/s3/commands/uploadFileToParent.ts b/packages/core/src/awsService/s3/commands/uploadFileToParent.ts deleted file mode 100644 index 211ceb326e..0000000000 --- a/packages/core/src/awsService/s3/commands/uploadFileToParent.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger } from '../../../shared/logger/logger' -import { S3FileNode } from '../explorer/s3FileNode' -import { uploadFileCommand } from './uploadFile' - -/** - * Uploads a file to the parent bucket or folder. - */ -export async function uploadFileToParentCommand(node: S3FileNode): Promise { - getLogger().debug('UploadFileToParent called for %O', node) - const parent = node.parent - return uploadFileCommand(parent.s3, parent) -} diff --git a/packages/core/src/awsService/s3/explorer/s3BucketNode.ts b/packages/core/src/awsService/s3/explorer/s3BucketNode.ts deleted file mode 100644 index 631c55f141..0000000000 --- a/packages/core/src/awsService/s3/explorer/s3BucketNode.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { S3Bucket, CreateFolderRequest, CreateFolderResponse, S3Client } from '../../../shared/clients/s3' - -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { S3FileNode } from './s3FileNode' -import { S3FolderNode } from './s3FolderNode' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { S3Node } from './s3Nodes' -import { getIcon } from '../../../shared/icons' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -/** - * Represents an S3 bucket that may contain folders and/or objects. - */ -export class S3BucketNode extends AWSTreeNodeBase implements AWSResourceNode, LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly bucket: S3Bucket, - public readonly parent: S3Node, - public readonly s3: S3Client, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(bucket.Name, vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = bucket.Name - this.iconPath = getIcon('aws-s3-bucket') - this.contextValue = 'awsS3BucketNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.s3.noObjects', '[No Objects found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.s3.listFiles({ - bucketName: this.bucket.Name, - continuationToken, - maxResults: this.getMaxItemsPerPage(), - }) - - const newFolders = response.folders.map((folder) => new S3FolderNode(this.bucket, folder, this.s3)) - const newFiles = response.files.map((file) => new S3FileNode(this.bucket, file, this, this.s3)) - - getLogger().debug(`Loaded folders: %O and files: %O`, newFolders, newFiles) - return { - newContinuationToken: response.continuationToken, - newChildren: [...newFolders, ...newFiles], - } - } - - /** - * See {@link S3Client.createFolder}. - */ - public async createFolder(request: CreateFolderRequest): Promise { - return this.s3.createFolder(request) - } - - /** - * See {@link S3Client.deleteBucket}. - */ - public async deleteBucket(): Promise { - await this.s3.deleteBucket({ bucketName: this.bucket.Name }) - } - - public get arn(): string { - return this.bucket.Arn - } - - public get name(): string { - return this.bucket.Name - } - - public get path(): string { - // Even though the bucket is the "root", there is no '/' prefix on objects inside - return '' - } - - public [inspect.custom](): string { - return `S3BucketNode (bucket=${this.bucket.Name})` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('s3.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/s3/explorer/s3FileNode.ts b/packages/core/src/awsService/s3/explorer/s3FileNode.ts deleted file mode 100644 index 37ee633435..0000000000 --- a/packages/core/src/awsService/s3/explorer/s3FileNode.ts +++ /dev/null @@ -1,84 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import bytes from 'bytes' -import { S3Bucket, DownloadFileRequest, File, S3Client } from '../../../shared/clients/s3' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { inspect } from 'util' -import { S3BucketNode } from './s3BucketNode' -import { S3FolderNode } from './s3FolderNode' -import globals from '../../../shared/extensionGlobals' -import { getIcon } from '../../../shared/icons' -import { formatLocalized, getRelativeDate } from '../../../shared/datetime' - -/** - * Represents an object in an S3 bucket. - */ -export class S3FileNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public readonly bucket: S3Bucket, - public readonly file: File, - public readonly parent: S3BucketNode | S3FolderNode, - public readonly s3: S3Client, - now: Date = new globals.clock.Date() - ) { - super(file.name) - if (file.sizeBytes !== undefined && file.lastModified) { - const readableSize = formatBytes(file.sizeBytes) - - this.tooltip = localize( - 'AWS.explorerNode.s3.fileTooltip', - '{0}\nSize: {1}\nLast Modified: {2}', - this.file.key, - readableSize, - formatLocalized(file.lastModified) - ) - this.description = `${readableSize}, ${getRelativeDate(file.lastModified, now)}` - } - this.iconPath = getIcon('vscode-file') - this.contextValue = 'awsS3FileNode' - this.command = { - command: 'aws.s3.openFile', - title: localize('AWS.command.s3.openFile', 'Open File'), - arguments: [this], - } - } - - /** - * See {@link S3Client.downloadFile}. - */ - public async downloadFile(request: DownloadFileRequest): Promise { - return this.s3.downloadFile(request) - } - - /** - * See {@link S3Client.deleteFile}. - */ - public async deleteFile(): Promise { - await this.s3.deleteObject({ bucketName: this.bucket.Name, key: this.file.key }) - } - - public get arn(): string { - return this.file.arn - } - - public get name(): string { - return this.file.name - } - - public get path(): string { - return this.file.key - } - - public [inspect.custom](): string { - return `S3FileNode (bucket=${this.bucket.Name}, file=${this.file.key}}` - } -} - -function formatBytes(numBytes: number): string { - return bytes(numBytes, { unitSeparator: ' ', decimalPlaces: 0 }) -} diff --git a/packages/core/src/awsService/s3/explorer/s3FolderNode.ts b/packages/core/src/awsService/s3/explorer/s3FolderNode.ts deleted file mode 100644 index 7eea80f3ae..0000000000 --- a/packages/core/src/awsService/s3/explorer/s3FolderNode.ts +++ /dev/null @@ -1,106 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { S3Bucket, CreateFolderRequest, CreateFolderResponse, Folder, S3Client } from '../../../shared/clients/s3' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { ChildNodeLoader } from '../../../awsexplorer/childNodeLoader' -import { ChildNodePage } from '../../../awsexplorer/childNodeLoader' -import { S3FileNode } from './s3FileNode' -import { inspect } from 'util' -import { getLogger } from '../../../shared/logger/logger' -import { getIcon } from '../../../shared/icons' -import { Settings } from '../../../shared/settings' -import { ClassToInterfaceType } from '../../../shared/utilities/tsUtils' - -/** - * Represents a folder in an S3 bucket that may contain subfolders and/or objects. - */ -export class S3FolderNode extends AWSTreeNodeBase implements AWSResourceNode, LoadMoreNode { - private readonly childLoader = new ChildNodeLoader(this, (token) => this.loadPage(token)) - - public constructor( - public readonly bucket: S3Bucket, - public readonly folder: Folder, - public readonly s3: S3Client, - protected readonly settings: ClassToInterfaceType = Settings.instance - ) { - super(folder.name, vscode.TreeItemCollapsibleState.Collapsed) - this.tooltip = folder.path - this.iconPath = getIcon('vscode-folder') - this.contextValue = 'awsS3FolderNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => this.childLoader.getChildren(), - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.s3.noObjects', '[No Objects found]')), - }) - } - - public async loadMoreChildren(): Promise { - await this.childLoader.loadMoreChildren() - } - - public isLoadingMoreChildren(): boolean { - return this.childLoader.isLoadingMoreChildren() - } - - public clearChildren(): void { - this.childLoader.clearChildren() - } - - private async loadPage(continuationToken: string | undefined): Promise> { - getLogger().debug(`Loading page for %O using continuationToken %s`, this, continuationToken) - const response = await this.s3.listFiles({ - bucketName: this.bucket.Name, - folderPath: this.folder.path, - continuationToken, - maxResults: this.getMaxItemsPerPage(), - }) - - const newFolders = response.folders.map((folder) => new S3FolderNode(this.bucket, folder, this.s3)) - const newFiles = response.files.map((file) => new S3FileNode(this.bucket, file, this, this.s3)) - - getLogger().debug(`Loaded folders: %O and files: %O`, newFolders, newFiles) - return { - newContinuationToken: response.continuationToken, - newChildren: [...newFolders, ...newFiles], - } - } - - /** - * See {@link S3Client.createFolder}. - */ - public async createFolder(request: CreateFolderRequest): Promise { - return this.s3.createFolder(request) - } - - public get arn(): string { - return this.folder.arn - } - - public get name(): string { - return this.folder.name - } - - public get path(): string { - return this.folder.path - } - - public [inspect.custom](): string { - return `S3FolderNode (bucket=${this.bucket.Name}, folder=${this.folder.path})` - } - - private getMaxItemsPerPage(): number | undefined { - return this.settings.getSection('aws').get('s3.maxItemsPerPage') - } -} diff --git a/packages/core/src/awsService/s3/explorer/s3Nodes.ts b/packages/core/src/awsService/s3/explorer/s3Nodes.ts deleted file mode 100644 index fcc7b9e541..0000000000 --- a/packages/core/src/awsService/s3/explorer/s3Nodes.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { localize } from '../../../shared/utilities/vsCodeUtils' -import { CreateBucketRequest, CreateBucketResponse, S3Client } from '../../../shared/clients/s3' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { S3BucketNode } from './s3BucketNode' -import { inspect } from 'util' - -/** - * An AWS Explorer node representing S3. - * - * Contains buckets for a specific region as child nodes. - */ -export class S3Node extends AWSTreeNodeBase { - public constructor(private readonly s3: S3Client) { - super('S3', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = 'awsS3Node' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - const response = await this.s3.listBuckets() - - return response.buckets.map((bucket) => new S3BucketNode(bucket, this, this.s3)) - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, localize('AWS.explorerNode.s3.noBuckets', '[No Buckets found]')), - }) - } - - /** - * See {@link S3Client.createBucket}. - */ - public async createBucket(request: CreateBucketRequest): Promise { - return this.s3.createBucket(request) - } - - public [inspect.custom](): string { - return 'S3Node' - } -} diff --git a/packages/core/src/awsService/s3/fileViewerManager.ts b/packages/core/src/awsService/s3/fileViewerManager.ts deleted file mode 100644 index 883971513e..0000000000 --- a/packages/core/src/awsService/s3/fileViewerManager.ts +++ /dev/null @@ -1,379 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import * as mime from 'mime-types' -import * as S3 from '../../shared/clients/s3' -import { getLogger } from '../../shared/logger/logger' -import { showConfirmationMessage } from '../../shared/utilities/messages' -import { localize, openUrl } from '../../shared/utilities/vsCodeUtils' -import { CancellationError } from '../../shared/utilities/timeoutUtils' -import { downloadFile } from './commands/downloadFileAs' -import { s3FileViewerHelpUrl } from '../../shared/constants' -import { FileProvider, VirtualFileSystem } from '../../shared/virtualFilesystem' -import { ToolkitPromptSettings } from '../../shared/settings' -import { telemetry } from '../../shared/telemetry/telemetry' -import { ToolkitError } from '../../shared/errors' - -export const s3EditScheme = 's3' -export const s3ReadScheme = 's3-readonly' -export const enum TabMode { - Read = 'read', - Edit = 'edit', -} - -const sizeLimit = 4 * Math.pow(10, 6) // 4 MB -const promptOnEditKey = 'fileViewerEdit' - -export interface S3Tab { - dispose(): Promise | void - readonly mode: TabMode - readonly file: S3File - readonly editor: vscode.TextEditor | undefined -} - -// TODO: just use this everywhere? A bucket-less S3 file doesn't make sense. -// Combines the File and Bucket interface as they mostly belong together -export interface S3File extends S3.File { - readonly bucket: S3.S3Bucket -} - -export class S3FileProvider implements FileProvider { - private readonly _onDidChange = new vscode.EventEmitter() - private readonly _file: { -readonly [P in keyof S3File]: S3File[P] } - public readonly onDidChange = this._onDidChange.event - - public constructor( - private readonly client: S3.S3Client, - file: S3File - ) { - this._file = { ...file } - } - - public async refresh(): Promise { - const { bucket, key } = this._file - const stats = await this.client.headObject({ bucketName: bucket.Name, key }) - - this.updateETag(stats.ETag) - this._file.sizeBytes = stats.ContentLength ?? this._file.sizeBytes - this._file.lastModified = stats.LastModified - this._file.ContentType = stats.ContentType - } - - public async read(): Promise { - return telemetry.s3_downloadObject.run((span) => { - span.record({ component: 'viewer' }) - - const result = downloadFile(this._file, { - client: this.client, - progressLocation: - (this._file.sizeBytes ?? 0) < sizeLimit - ? vscode.ProgressLocation.Window - : vscode.ProgressLocation.Notification, - }) - - return result - }) - } - - public async stat(): Promise<{ ctime: number; mtime: number; size: number }> { - await this.refresh() - - return { - ctime: 0, - size: this._file.sizeBytes ?? 0, - mtime: this._file.lastModified?.getTime() ?? 0, - } - } - - public async write(content: Uint8Array): Promise { - return telemetry.s3_uploadObject.run(async (span) => { - span.record({ component: 'viewer' }) - - const mimeType = mime.contentType(path.extname(this._file.name)) || undefined - const result = await this.client - .uploadFile({ - content, - key: this._file.key, - bucketName: this._file.bucket.Name, - contentType: this._file.ContentType ?? mimeType, - }) - .then((u) => u.done()) - - this.updateETag(result.ETag) - this._file.lastModified = new Date() - this._file.sizeBytes = content.byteLength - }) - } - - private updateETag(newTag: string | undefined): void { - if (this._file.eTag !== newTag) { - this._onDidChange.fire() - } - this._file.eTag = newTag - } -} - -type S3ClientFactory = (region: string) => S3.S3Client - -export class S3FileViewerManager { - private readonly activeTabs: { [uri: string]: S3Tab | undefined } = {} - private readonly providers: { [uri: string]: vscode.Disposable | undefined } = {} - private readonly disposables: vscode.Disposable[] = [] - - public constructor( - private readonly clientFactory: S3ClientFactory, - private readonly fs: VirtualFileSystem, - private readonly settings = ToolkitPromptSettings.instance, - private readonly schemes = { read: s3ReadScheme, edit: s3EditScheme } - ) { - this.disposables.push(this.registerTabCleanup()) - } - - /** Disposes all active editors and underlying files. */ - public async closeEditors(): Promise { - await Promise.all(Object.values(this.activeTabs).map((v) => v?.dispose())) - } - - /** Disposes all active editors, underlying files, providers, and other resources. */ - public async dispose(): Promise { - await Promise.all([ - ...Object.values(this.activeTabs).map((v) => v?.dispose()), - ...Object.values(this.providers).map((v) => v?.dispose()), - ]) - vscode.Disposable.from(...this.disposables).dispose() - } - - private registerTabCleanup(): vscode.Disposable { - return vscode.workspace.onDidCloseTextDocument(async (doc) => { - const key = this.fs.uriToKey(doc.uri) - await this.activeTabs[key]?.dispose() - }) - } - - /** - * Opens a new editor, closing the previous one if it exists - */ - private async openEditor( - fileUri: vscode.Uri, - options?: vscode.TextDocumentShowOptions - ): Promise { - const fsPath = fileUri.fsPath - await this.activeTabs[this.fs.uriToKey(fileUri)]?.dispose() - - try { - // Defer to `vscode.open` for non-text files - const contentType = mime.contentType(path.extname(fsPath)) - if (!contentType || mime.charset(contentType) !== 'UTF-8') { - await vscode.commands.executeCommand('vscode.open', fileUri) - return vscode.window.visibleTextEditors.find( - (e) => this.fs.uriToKey(e.document.uri) === this.fs.uriToKey(fileUri) - ) - } - - const document = await vscode.workspace.openTextDocument(fileUri) - return await vscode.window.showTextDocument(document, options) - } catch (err) { - throw ToolkitError.chain(err, 'Failed to open document', { code: 'FailedToOpen' }) - } - } - - private async closeEditor(editor: vscode.TextEditor | undefined): Promise { - if (editor && !editor.document.isClosed) { - await vscode.window.showTextDocument(editor.document, { preserveFocus: false }).then( - (r) => vscode.commands.executeCommand('workbench.action.closeActiveEditor'), - (e) => { - getLogger().warn('S3FileViewer: showTextDocument failed to open: "%s"', editor.document.uri) - } - ) - } - } - - /** - * Tries to focus a tab from a list of URIs. First match is returned. - */ - private async tryFocusTab(...uris: vscode.Uri[]): Promise { - for (const uri of uris) { - const activeTab = this.activeTabs[this.fs.uriToKey(uri)] - - if (!activeTab) { - continue - } else if (activeTab.editor) { - getLogger().verbose(`S3FileViewer: Editor already opened, refocusing`) - await vscode.window.showTextDocument(activeTab.editor.document) - } else { - getLogger().verbose(`S3FileViewer: Reopening non-text document`) - await vscode.commands.executeCommand('vscode.open', uri) - } - - return activeTab - } - } - - /** - * Given an {@link S3File}, this function opens the tab on read-only with the use of an S3Tab - * Focus is shifted to an edit tab if any. - * - * @param file - */ - public async openInReadMode(file: S3File): Promise { - const contentType = mime.contentType(path.extname(file.name)) - const isTextDocument = contentType && mime.charset(contentType) === 'UTF-8' - - const uri = this.fileToUri(file, TabMode.Read) - if (await this.tryFocusTab(uri, uri.with({ scheme: this.schemes.edit }))) { - return - } - - if (!isTextDocument) { - getLogger().warn(`Unable to determine if ${file.name} is a text document, opening in edit-mode`) - return this.openInEditMode(file) - } - - return this.createTab(file, TabMode.Read) - } - - /** - * Opens the tab in edit mode with the use of an S3Tab, or shifts focus to an edit tab if any. - * Existing read-only tabs are closed as they cannot be converted to edit tabs. - * - * @param uriOrFile to be opened - */ - public async openInEditMode(uriOrFile: vscode.Uri | S3File): Promise { - const uri = uriOrFile instanceof vscode.Uri ? uriOrFile : this.fileToUri(uriOrFile, TabMode.Edit) - const activeTab = await this.tryFocusTab(uri, uri.with({ scheme: this.schemes.read })) - const file = activeTab?.file ?? uriOrFile - - if (activeTab?.mode === TabMode.Edit) { - return - } - - if (file instanceof vscode.Uri) { - throw new TypeError('Unable to open file in edit mode without a valid editor.') - } - - await activeTab?.dispose() - void this.showEditNotification() - - return this.createTab(file, TabMode.Edit) - } - - private registerProvider(file: S3File, uri: vscode.Uri): vscode.Disposable { - const provider = new S3FileProvider(this.clientFactory(file.bucket.BucketRegion), file) - - return vscode.Disposable.from( - this.fs.registerProvider(uri, provider), - provider.onDidChange(() => { - // TODO: find the correct node instead of refreshing it all - void vscode.commands.executeCommand('aws.refreshAwsExplorer', true) - }) - ) - } - - /** - * Creates a new tab based on the mode - */ - private async createTab(file: S3File, mode: TabMode): Promise { - if (!(await this.canContinueDownload(file))) { - throw new CancellationError('user') - } - - const uri = this.fileToUri(file, mode) - const key = this.fs.uriToKey(uri) - const provider = (this.providers[key] ??= this.registerProvider(file, uri)) - const editor = await this.openEditor(uri, { preview: mode === TabMode.Read }) - - this.activeTabs[key] = { - file, - mode, - editor, - dispose: async () => { - await this.closeEditor(editor) - delete this.activeTabs[key] - // Note that providers without an editor will persist for the lifetime of the extension - // since we have no way of detecting when a webview-type editor closes - if (editor) { - provider.dispose() - delete this.providers[key] - } - }, - } - - return this.activeTabs[key] - } - - private async canContinueDownload(file: S3File): Promise { - const fileSize = file.sizeBytes - // JS needs a `when` syntax like Kotlin - const warningMessage = (function () { - if (fileSize === undefined) { - getLogger().debug(`FileViewer: File size couldn't be determined, prompting user file: ${file.name}`) - - return localize( - 'AWS.s3.fileViewer.warning.noSize', - "File size couldn't be determined. Continue with download?" - ) - } else if (fileSize > sizeLimit) { - getLogger().debug(`FileViewer: File size ${fileSize} is >4MB, prompting user`) - - return localize('AWS.s3.fileViewer.warning.4mb', 'File size is more than 4MB. Continue with download?') - } - })() - - if (warningMessage && !(await this.showDownloadConfirmation(warningMessage))) { - return false - } - - return true - } - - private async showDownloadConfirmation(warningMessage: string): Promise { - const args = { - prompt: warningMessage, - confirm: localize('AWS.generic.continueDownload', 'Continue with download'), - cancel: localize('AWS.generic.cancel', 'Cancel'), - } - - if (!(await showConfirmationMessage(args))) { - getLogger().debug(`FileViewer: User cancelled download`) - return false - } - - return true - } - - private async showEditNotification(): Promise { - if (!this.settings.isPromptEnabled(promptOnEditKey)) { - return - } - - const message = localize( - 'AWS.s3.fileViewer.warning.editStateWarning', - 'You are now editing an S3 file. Saved changes will be uploaded to your S3 bucket.' - ) - - const dontShow = localize('AWS.s3.fileViewer.button.dismiss', "Don't show again") - const help = localize('AWS.generic.message.learnMore', 'Learn more') - - await vscode.window.showWarningMessage(message, dontShow, help).then((selection) => { - if (selection === dontShow) { - return this.settings.disablePrompt(promptOnEditKey) - } - - if (selection === help) { - return openUrl(vscode.Uri.parse(s3FileViewerHelpUrl, true)) - } - }) - } - - private fileToUri(file: S3File, mode: TabMode): vscode.Uri { - const scheme = mode === TabMode.Read ? this.schemes.read : this.schemes.edit - - return vscode.Uri.parse(`${scheme}:`, true).with({ - path: ['', file.bucket.BucketRegion, file.bucket.Name, file.key].join('/'), - }) - } -} diff --git a/packages/core/src/awsService/s3/progressReporter.ts b/packages/core/src/awsService/s3/progressReporter.ts deleted file mode 100644 index 9ccf371d19..0000000000 --- a/packages/core/src/awsService/s3/progressReporter.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { inspect } from 'util' -import { throttle } from 'lodash' -import { getLogger } from '../../shared/logger/logger' -import bytes from 'bytes' - -const defaultReportingIntervalMillis = 250 - -interface ProgressReporterOptions { - readonly totalBytes?: number - readonly reportMessage?: boolean - readonly minIntervalMillis?: number -} - -/** - * Returns a function that pipes incremental byte progress to VSCode's incremental percentage Progress. - * - * @param progress the VSCode progress to update. - * @param totalBytes the total bytes. - * Used in combination with cumulative bytes to calculate incremental progress. - * @param minIntervalMillis the minimum delay between progress updates. - * Used to throttle updates. Updates that occur between intervals are dropped. - */ -export function progressReporter( - progress: vscode.Progress<{ message?: string; increment?: number }>, - options: ProgressReporterOptions = {} -): (loadedBytes: number) => void { - const { totalBytes, reportMessage, minIntervalMillis } = options - const reporter = new ProgressReporter(progress, { totalBytes, reportMessage }) - const reportProgressThrottled = throttle( - () => reporter.report(), - minIntervalMillis ?? defaultReportingIntervalMillis, - { leading: true, trailing: false } - ) - - return (newBytes) => { - reporter.update(newBytes) - - if (reporter.done) { - reporter.report() - } else { - reportProgressThrottled() - } - } -} - -/** - * Tracks incremental and total progress in terms of bytes and percentages. - */ -class ProgressReporter { - private _incrementalProgress = 0 - private _loadedBytes = 0 - private _totalBytes: number | undefined - - /** - * Creates a ProgressReport. - * - * @param _totalBytes the total bytes, or undefined if total bytes are unknown. - * Used in combination with cumulative bytes to calculate incremental progress. - * - * If 0 is provided, progress is returned as 100%. - * - * @throw Error if _totalBytes is not an integer or is negative. - */ - public constructor( - private readonly progress: vscode.Progress<{ message?: string; increment?: number }>, - private readonly options?: Omit - ) { - this._totalBytes = options?.totalBytes - if (this._totalBytes !== undefined) { - if (!Number.isInteger(this._totalBytes)) { - throw new TypeError(`totalBytes: ${this._totalBytes} must be an integer`) - } - if (this._totalBytes < 0) { - throw new Error(`totalBytes ${this._totalBytes} cannot be negative`) - } - } - } - - public get done(): boolean { - return this._loadedBytes === this._totalBytes - } - - private formatMessage(): string { - const format = (b: number) => bytes(b, { unitSeparator: ' ', decimalPlaces: 0 }) - return this._totalBytes ? `${format(this._loadedBytes)} / ${format(this._totalBytes)}` : '' - } - - /** - * Reports the new progress (if any) and flushes the internal counter. - */ - public report(): void { - const message = this.options?.reportMessage ? this.formatMessage() : undefined - this.progress.report({ message, increment: this._incrementalProgress }) - this._incrementalProgress = 0 - getLogger().verbose('%O', this) - } - - /** - * Returns the last incremental progress made in terms of percentage as an integer (rounded up). - * - * If the total bytes is undefined or 0, always returns 100%. - */ - private incrementalPercentage(newBytes: number): number { - return this._totalBytes ? (newBytes / this._totalBytes) * 100 : 100 - } - - /** - * Returns the total progress made in terms of percentage as an integer (rounded up). - * - * If the total bytes is unknown, always returns undefined. - * If the total bytes is 0, always returns 100%. - */ - public get loadedPercentage(): number | undefined { - switch (this._totalBytes) { - case undefined: - return undefined - case 0: - return 100 - default: - return Math.ceil((this._loadedBytes / this._totalBytes) * 100) - } - } - - /** - * Updates the cumulated bytes, signifying that additional progress has been made. - * - * @param newLoadedBytes the the new cumulative total bytes loaded. - * This value must be greater than or equal to the previous cumulative bytes (progress cannot go backwards). - * - * @throws Error if newLoadedBytes is not an integer, exceeds the total bytes, or is negative. - */ - public update(newBytes: number): void { - if (!Number.isInteger(newBytes)) { - throw new TypeError(`newBytes: ${newBytes} must be an integer`) - } else if (newBytes < 0) { - throw new Error(`newBytes: ${newBytes} cannot be negative`) - } else if (this._totalBytes !== undefined && newBytes > this._totalBytes) { - throw new Error(`newBytes: ${newBytes} cannot be greater than totalBytes`) - } - - this._incrementalProgress += this.incrementalPercentage(newBytes) - this._loadedBytes += newBytes - } - - public [inspect.custom](): string { - switch (this._totalBytes) { - case undefined: - return `ProgressReport: ${this._loadedBytes} bytes loaded)` - default: - return `ProgressReport: ${this._loadedBytes} / ${this._totalBytes} bytes loaded (${this.loadedPercentage}%)` - } - } -} diff --git a/packages/core/src/awsService/s3/util.ts b/packages/core/src/awsService/s3/util.ts deleted file mode 100644 index 39c3bf0104..0000000000 --- a/packages/core/src/awsService/s3/util.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { localize } from '../../shared/utilities/vsCodeUtils' -/** - * Creates a readable path to an s3 bucket or object (e.g. s3://...). - * - * This is the format used by AWS CLI. - * @see https://docs.aws.amazon.com/cli/latest/reference/s3/#path-argument-type - * - * @param bucket contains the name of the bucket. - * @param path to the object, or an empty string if this is the root of the bucket. - * @returns the readable path to the s3 bucket or object (e.g. s3://...). - */ -export function readablePath({ bucket, path }: { bucket: { Name: string }; path: string }): string { - return path ? `s3://${bucket.Name}/${path}` : `s3://${bucket.Name}` -} - -/** - * Validates an S3 bucket name. - * - * @see https://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules - * @returns undefined if the name passes validation. Otherwise, an error message is returned. - */ -export function validateBucketName(name: string): string | undefined { - if (name.length < 3 || name.length > 63) { - return localize( - 'AWS.s3.validateBucketName.error.invalidLength', - 'Bucket name must be between 3 and 63 characters long' - ) - } - - if (!/^[a-z0-9]/.test(name)) { - return localize( - 'AWS.s3.validateBucketName.error.invalidStart', - 'Bucket name must start with a lowercase letter or number' - ) - } - - if (!/[a-z0-9]$/.test(name)) { - return localize( - 'AWS.s3.validateBucketName.error.invalidEnd', - 'Bucket name must end with a lowercase letter or number' - ) - } - - if (!/^[a-z0-9\-.]+$/.test(name)) { - return localize( - 'AWS.s3.validateBucketName.error.invalidCharacters', - 'Bucket name must only contain lowercase letters, numbers, hyphens, and periods' - ) - } - - if (name.includes('..') || name.includes('.-') || name.includes('-.')) { - return localize( - 'AWS.s3.validateBucketName.error.misusedPeriods', - 'Periods in bucket name must be surrounded by a lowercase letter or number' - ) - } - - if (/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/.test(name)) { - return localize( - 'AWS.s3.validateBucketName.error.resemblesIpAddress', - 'Bucket name must not resemble an IP address' - ) - } - - return undefined -} diff --git a/packages/core/src/awsService/sagemaker/activation.ts b/packages/core/src/awsService/sagemaker/activation.ts deleted file mode 100644 index 1a537fbc0f..0000000000 --- a/packages/core/src/awsService/sagemaker/activation.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { Commands } from '../../shared/vscode/commands2' -import { SagemakerSpaceNode } from './explorer/sagemakerSpaceNode' -import { SagemakerStudioNode } from './explorer/sagemakerStudioNode' -import * as uriHandlers from './uriHandlers' -import { openRemoteConnect, filterSpaceAppsByDomainUserProfiles, stopSpace } from './commands' -import { updateIdleFile, startMonitoringTerminalActivity, ActivityCheckInterval } from './utils' -import { ExtContext } from '../../shared/extensions' -import { telemetry } from '../../shared/telemetry/telemetry' -import { isSageMaker, UserActivity } from '../../shared/extensionUtilities' -import { SagemakerDevSpaceNode } from './explorer/sagemakerDevSpaceNode' -import { - filterDevSpacesByNamespaceCluster, - openHyperPodRemoteConnection, - stopHyperPodSpaceCommand, -} from './hyperpodCommands' -import { SagemakerHyperpodNode } from './explorer/sagemakerHyperpodNode' - -let terminalActivityInterval: NodeJS.Timeout | undefined - -export async function activate(ctx: ExtContext): Promise { - ctx.extensionContext.subscriptions.push( - uriHandlers.register(ctx), - Commands.register('aws.sagemaker.openRemoteConnection', async (node: SagemakerSpaceNode) => { - if (!validateNode(node)) { - return - } - await telemetry.sagemaker_openRemoteConnection.run(async () => { - await openRemoteConnect(node, ctx.extensionContext) - }) - }), - - Commands.register('aws.sagemaker.filterSpaceApps', async (node: SagemakerStudioNode) => { - await telemetry.sagemaker_filterSpaces.run(async () => { - await filterSpaceAppsByDomainUserProfiles(node) - }) - }), - - Commands.register('aws.sagemaker.stopSpace', async (node: SagemakerSpaceNode) => { - if (!validateNode(node)) { - return - } - await telemetry.sagemaker_stopSpace.run(async () => { - await stopSpace(node, ctx.extensionContext) - }) - }), - - Commands.register('aws.hyperpod.filterDevSpaces', async (node: SagemakerHyperpodNode) => { - await telemetry.hyperpod_filterSpaces.run(async () => { - await filterDevSpacesByNamespaceCluster(node) - }) - }), - - Commands.register('aws.hyperpod.stopSpace', async (node: SagemakerDevSpaceNode) => { - if (!validateNode(node)) { - return - } - await telemetry.hyperpod_stopSpace.run(async () => { - await stopHyperPodSpaceCommand(node) - }) - }), - - Commands.register('aws.hyperpod.openRemoteConnection', async (node: SagemakerDevSpaceNode) => { - await telemetry.hyperpod_openRemoteConnection.run(async () => { - if (!validateNode(node)) { - return - } - await openHyperPodRemoteConnection(node) - }) - }) - ) - - // If running in SageMaker AI Space, track user activity for autoshutdown feature - if (isSageMaker('SMAI')) { - // Use /tmp/ directory so the file is cleared on each reboot to prevent stale timestamps. - const tmpDirectory = '/tmp/' - const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp') - - const userActivity = new UserActivity(ActivityCheckInterval) - userActivity.onUserActivity(() => updateIdleFile(idleFilePath)) - - terminalActivityInterval = startMonitoringTerminalActivity(idleFilePath) - - // Write initial timestamp - await updateIdleFile(idleFilePath) - - ctx.extensionContext.subscriptions.push(userActivity, { - dispose: () => { - if (terminalActivityInterval) { - clearInterval(terminalActivityInterval) - terminalActivityInterval = undefined - } - }, - }) - } -} - -/** - * Checks if a node is undefined and shows a warning message if so. - */ -function validateNode(node: unknown): boolean { - if (!node) { - void vscode.window.showWarningMessage('Space information is being refreshed. Please try again shortly.') - return false - } - return true -} diff --git a/packages/core/src/awsService/sagemaker/commands.ts b/packages/core/src/awsService/sagemaker/commands.ts deleted file mode 100644 index c0160fa19b..0000000000 --- a/packages/core/src/awsService/sagemaker/commands.ts +++ /dev/null @@ -1,471 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { SagemakerConstants } from './explorer/constants' -import { SagemakerStudioNode } from './explorer/sagemakerStudioNode' -import { DomainKeyDelimiter } from './utils' -import { startVscodeRemote } from '../../shared/extensions/ssh' -import { getLogger } from '../../shared/logger/logger' -import { SagemakerSpaceNode, tryRefreshNode } from './explorer/sagemakerSpaceNode' -import { isRemoteWorkspace } from '../../shared/vscode/env' -import _ from 'lodash' -import { prepareDevEnvConnection, tryRemoteConnection } from './model' -import { ExtContext } from '../../shared/extensions' -import { SagemakerClient } from '../../shared/clients/sagemaker' -import { AccessDeniedException } from '@amzn/sagemaker-client' -import { ToolkitError, isUserCancelledError } from '../../shared/errors' -import { showConfirmationMessage } from '../../shared/utilities/messages' -import { RemoteSessionError } from '../../shared/remoteSession' -import { - ConnectFromRemoteWorkspaceMessage, - InstanceTypeInsufficientMemory, - InstanceTypeInsufficientMemoryMessage, - RemoteAccess, - RemoteAccessRequiredMessage, - SpaceStatus, -} from './constants' -import { SagemakerUnifiedStudioSpaceNode } from '../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { node } from 'webpack' - -const localize = nls.loadMessageBundle() - -export async function filterSpaceAppsByDomainUserProfiles(studioNode: SagemakerStudioNode): Promise { - if (studioNode.domainUserProfiles.size === 0) { - // if studioNode has not been expanded, domainUserProfiles will be empty - // if so, this will attempt to populate domainUserProfiles - await studioNode.updateChildren() - if (studioNode.domainUserProfiles.size === 0) { - getLogger().info(SagemakerConstants.NoSpaceToFilter) - void vscode.window.showInformationMessage(SagemakerConstants.NoSpaceToFilter) - return - } - } - - // Sort by domain name and user profile - const sortedDomainUserProfiles = new Map( - [...studioNode.domainUserProfiles].sort((a, b) => { - const domainNameA = a[1].domain.DomainName || '' - const domainNameB = b[1].domain.DomainName || '' - - const [_domainIdA, userProfileA] = a[0].split(DomainKeyDelimiter) - const [_domainIdB, userProfileB] = b[0].split(DomainKeyDelimiter) - - return domainNameA.localeCompare(domainNameB) || userProfileA.localeCompare(userProfileB) - }) - ) - - const previousSelection = await studioNode.getSelectedDomainUsers() - const items: (vscode.QuickPickItem & { key: string })[] = [] - - for (const [key, userMetadata] of sortedDomainUserProfiles) { - const [_, userProfile] = key.split(DomainKeyDelimiter) - items.push({ - label: userProfile, - detail: `In domain: ${userMetadata.domain?.DomainName}`, - picked: previousSelection.has(key), - key, - }) - } - - const placeholder = localize(SagemakerConstants.FilterPlaceholderKey, SagemakerConstants.FilterPlaceholderMessage) - const result = await vscode.window.showQuickPick(items, { - placeHolder: placeholder, - canPickMany: true, - matchOnDetail: true, - }) - - if (!result) { - return // User canceled. - } - - const newSelection = result.map((r) => r.key) - if (newSelection.length !== previousSelection.size || newSelection.some((key) => !previousSelection.has(key))) { - studioNode.saveSelectedDomainUsers(newSelection) - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', studioNode) - } -} - -export async function deeplinkConnect( - ctx: ExtContext, - connectionIdentifier: string, - session: string, - wsUrl: string, - token: string, - domain: string, - appType?: string, - workspaceName?: string, - namespace?: string, - eksClusterArn?: string, - isSMUS: boolean = false -) { - getLogger().debug( - 'sm:deeplinkConnect: connectionIdentifier: %s session: %s wsUrl: %s token: %s isSMUS: %s', - connectionIdentifier, - session, - wsUrl, - token, - isSMUS - ) - - getLogger().info( - `sm:deeplinkConnect: - domain: ${domain}, - appType: ${appType}, - workspaceName: ${workspaceName}, - namespace: ${namespace}, - eksClusterArn: ${eksClusterArn}` - ) - - getLogger().info( - `sm:deeplinkConnect: domain: ${domain}, appType: ${appType}, workspaceName: ${workspaceName}, namespace: ${namespace}, eksClusterArn: ${eksClusterArn}` - ) - - if (isRemoteWorkspace()) { - void vscode.window.showErrorMessage(ConnectFromRemoteWorkspaceMessage) - return - } - - try { - let connectionType = 'sm_dl' - if (domain === '') { - connectionType = 'sm_hp' - } - const remoteEnv = await prepareDevEnvConnection( - connectionIdentifier, - ctx.extensionContext, - connectionType, - isSMUS /* isSMUS */, - undefined /* node */, - session, - wsUrl, - token, - domain, - appType - ) - - try { - await startVscodeRemote( - remoteEnv.SessionProcess, - remoteEnv.hostname, - '/home/sagemaker-user', - remoteEnv.vscPath, - 'sagemaker-user' - ) - } catch (remoteErr: any) { - throw new ToolkitError( - `Failed to establish remote connection: ${remoteErr.message}. Check Remote-SSH logs for details.`, - { cause: remoteErr, code: remoteErr.code || 'RemoteConnectionFailed' } - ) - } - } catch (err: any) { - getLogger().error( - 'sm:OpenRemoteConnect: Unable to connect to target space with arn: %s error: %s isSMUS: %s', - connectionIdentifier, - err, - isSMUS - ) - - if (![RemoteSessionError.MissingExtension, RemoteSessionError.ExtensionVersionTooLow].includes(err.code)) { - void vscode.window.showErrorMessage( - `Remote connection failed: ${err.message || 'Unknown error'}. Check Output > Log (Window) for details.` - ) - throw err - } - } -} - -export async function stopSpace( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - sageMakerClient?: SagemakerClient -) { - await tryRefreshNode(node) - if (node.getStatus() === SpaceStatus.STOPPED || node.getStatus() === SpaceStatus.STOPPING) { - void vscode.window.showWarningMessage(`Space ${node.spaceApp.SpaceName} is already in Stopped/Stopping state.`) - return - } else if (node.getStatus() === SpaceStatus.STARTING) { - void vscode.window.showWarningMessage( - `Space ${node.spaceApp.SpaceName} is in Starting state. Wait until it is Running to attempt stop again.` - ) - return - } - const spaceName = node.spaceApp.SpaceName! - const confirmed = await showConfirmationMessage({ - prompt: `You are about to stop this space. Any active resource will also be stopped. Are you sure you want to stop the space?`, - confirm: 'Stop Space', - cancel: 'Cancel', - type: 'warning', - }) - - if (!confirmed) { - return - } - // In case of SMUS, we pass in a SM Client and for SM AI, it creates a new SM Client. - const client = sageMakerClient ? sageMakerClient : new SagemakerClient(node.regionCode) - try { - await client.deleteApp({ - DomainId: node.spaceApp.DomainId!, - SpaceName: spaceName, - AppType: node.spaceApp.SpaceSettingsSummary!.AppType!, - AppName: node.spaceApp.App?.AppName, - }) - } catch (err) { - const error = err as Error - if (error instanceof AccessDeniedException) { - throw new ToolkitError('You do not have permission to stop spaces. Please contact your administrator', { - cause: error, - code: error.name, - }) - } else { - throw new ToolkitError(`Failed to stop space ${spaceName}: ${(error as Error).message}`, { - cause: error, - code: error.name, - }) - } - } - await tryRefreshNode(node) -} - -export async function openRemoteConnect( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - sageMakerClient?: SagemakerClient -) { - if (isRemoteWorkspace()) { - void vscode.window.showErrorMessage(ConnectFromRemoteWorkspaceMessage) - return - } - - try { - const spaceName = node.spaceApp.SpaceName! - await tryRefreshNode(node) - - const remoteAccess = node.spaceApp.SpaceSettingsSummary?.RemoteAccess - const nodeStatus = node.getStatus() - - // Route to appropriate handler based on space state - if (nodeStatus === SpaceStatus.RUNNING && remoteAccess !== RemoteAccess.ENABLED) { - return await handleRunningSpaceWithDisabledAccess(node, ctx, spaceName, sageMakerClient) - } else if (nodeStatus === SpaceStatus.STOPPED) { - return await handleStoppedSpace(node, ctx, spaceName, sageMakerClient) - } else if (nodeStatus === SpaceStatus.RUNNING) { - return await handleRunningSpaceWithEnabledAccess(node, ctx, spaceName) - } - } catch (err: any) { - // Suppress errors that don't need additional error messages: - // - User cancellations (checked by isUserCancelledError) - // - SSH config errors (already shown via modal in prepareDevEnvConnection) - if (isUserCancelledError(err) || (err instanceof ToolkitError && err.code === 'SshConfigError')) { - return - } - throw err - } -} - -/** - * Checks if an instance type upgrade will be needed for remote access - */ -export async function checkInstanceTypeUpgradeNeeded( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - sageMakerClient?: SagemakerClient -): Promise<{ upgradeNeeded: boolean; currentType?: string; recommendedType?: string }> { - const client = sageMakerClient || new SagemakerClient(node.regionCode) - - try { - const spaceDetails = await client.describeSpace({ - DomainId: node.spaceApp.DomainId!, - SpaceName: node.spaceApp.SpaceName!, - }) - - const appType = spaceDetails.SpaceSettings!.AppType! - - // Get current instance type - const currentResourceSpec = - appType === 'JupyterLab' - ? spaceDetails.SpaceSettings!.JupyterLabAppSettings?.DefaultResourceSpec - : spaceDetails.SpaceSettings!.CodeEditorAppSettings?.DefaultResourceSpec - - const currentInstanceType = currentResourceSpec?.InstanceType - - // Check if upgrade is needed - if (currentInstanceType && currentInstanceType in InstanceTypeInsufficientMemory) { - // Current type has insufficient memory - return { - upgradeNeeded: true, - currentType: currentInstanceType, - recommendedType: InstanceTypeInsufficientMemory[currentInstanceType], - } - } - - return { upgradeNeeded: false, currentType: currentInstanceType } - } catch (err) { - const error = err as Error - if (error instanceof AccessDeniedException) { - throw new ToolkitError('You do not have permission to describe spaces. Please contact your administrator', { - cause: error, - code: error.name, - }) - } - throw err - } -} - -/** - * Handles connecting to a running space with disabled remote access - * Requires stopping the space, enabling remote access, and restarting - */ -async function handleRunningSpaceWithDisabledAccess( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - spaceName: string, - sageMakerClient?: SagemakerClient -) { - // Check if instance type upgrade will be needed - const instanceTypeInfo = await checkInstanceTypeUpgradeNeeded(node, sageMakerClient) - - let prompt: string - if (instanceTypeInfo.upgradeNeeded) { - prompt = InstanceTypeInsufficientMemoryMessage( - spaceName, - instanceTypeInfo.currentType!, - instanceTypeInfo.recommendedType! - ) - } else { - // Only remote access needs to be enabled - prompt = RemoteAccessRequiredMessage - } - - const confirmed = await showConfirmationMessage({ - prompt, - confirm: 'Restart Space and Connect', - cancel: 'Cancel', - type: 'warning', - }) - - if (!confirmed) { - return - } - - // Enable remote access and connect - const client = sageMakerClient || new SagemakerClient(node.regionCode) - - return await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: `Connecting to ${spaceName}`, - }, - async (progress) => { - try { - // Show initial progress message - progress.report({ message: 'Stopping the space' }) - - // Stop the running space - await client.deleteApp({ - DomainId: node.spaceApp.DomainId!, - SpaceName: spaceName, - AppType: node.spaceApp.SpaceSettingsSummary!.AppType!, - AppName: node.spaceApp.App?.AppName, - }) - - // Update progress message - progress.report({ message: 'Starting the space' }) - - // Start the space with remote access enabled (skip prompts since user already consented) - await client.startSpace(spaceName, node.spaceApp.DomainId!, true) - await tryRefreshNode(node) - await client.waitForAppInService( - node.spaceApp.DomainId!, - spaceName, - node.spaceApp.SpaceSettingsSummary!.AppType!, - progress - ) - await tryRemoteConnection(node, ctx, progress) - } catch (err: any) { - // Suppress errors that don't need additional error messages: - // - User cancellations (checked by isUserCancelledError) - // - SSH config errors (already shown via modal in prepareDevEnvConnection) - if (isUserCancelledError(err) || (err instanceof ToolkitError && err.code === 'SshConfigError')) { - return - } - throw new ToolkitError(`Remote connection failed: ${err.message}`, { - cause: err, - code: err.code, - }) - } - } - ) -} - -/** - * Handles connecting to a stopped space - * Starts the space and connects (remote access enabled automatically if needed) - */ -async function handleStoppedSpace( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - spaceName: string, - sageMakerClient?: SagemakerClient -) { - const client = sageMakerClient || new SagemakerClient(node.regionCode) - - try { - await client.startSpace(spaceName, node.spaceApp.DomainId!) - await tryRefreshNode(node) - - return await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: `Connecting to ${spaceName}`, - }, - async (progress) => { - progress.report({ message: 'Starting the space' }) - await client.waitForAppInService( - node.spaceApp.DomainId!, - spaceName, - node.spaceApp.SpaceSettingsSummary!.AppType!, - progress - ) - await tryRemoteConnection(node, ctx, progress) - } - ) - } catch (err: any) { - // Suppress errors that don't need additional error messages: - // - User cancellations (checked by isUserCancelledError) - // - SSH config errors (already shown via modal in prepareDevEnvConnection) - if (isUserCancelledError(err) || (err instanceof ToolkitError && err.code === 'SshConfigError')) { - return - } - throw new ToolkitError(`Remote connection failed: ${(err as Error).message}`, { - cause: err as Error, - code: err.code, - }) - } -} - -/** - * Handles connecting to a running space with enabled remote access - * Direct connection without any space modifications - */ -async function handleRunningSpaceWithEnabledAccess( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - spaceName: string, - sageMakerClient?: SagemakerClient -) { - return await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - cancellable: false, - title: `Connecting to ${spaceName}`, - }, - async (progress) => { - await tryRemoteConnection(node, ctx, progress) - } - ) -} diff --git a/packages/core/src/awsService/sagemaker/constants.ts b/packages/core/src/awsService/sagemaker/constants.ts deleted file mode 100644 index 8e2ce84a0e..0000000000 --- a/packages/core/src/awsService/sagemaker/constants.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const ConnectFromRemoteWorkspaceMessage = - 'Unable to establish new remote connection. Your last active VS Code window is connected to a remote workspace. To open a new SageMaker Studio connection, select your local VS Code window and try again.' - -export const InstanceTypeError = 'InstanceTypeError' -export const SshConfigError = 'SshConfigError' - -export const InstanceTypeMinimum = 'ml.t3.large' - -export const InstanceTypeInsufficientMemory: Record = { - 'ml.t3.medium': 'ml.t3.large', - 'ml.c7i.large': 'ml.c7i.xlarge', - 'ml.c6i.large': 'ml.c6i.xlarge', - 'ml.c6id.large': 'ml.c6id.xlarge', - 'ml.c5.large': 'ml.c5.xlarge', -} - -// Remote access constants -export const RemoteAccess = { - ENABLED: 'ENABLED', - DISABLED: 'DISABLED', -} as const - -export const SpaceStatus = { - RUNNING: 'Running', - STOPPED: 'Stopped', - STARTING: 'Starting', - STOPPING: 'Stopping', -} as const - -export const InstanceTypeInsufficientMemoryMessage = ( - spaceName: string, - chosenInstanceType: string, - recommendedInstanceType: string -) => { - return `[${chosenInstanceType}] does not support remote access. Use an instanceType with at least 8 GiB memory. Would you like to start your space with instanceType [${recommendedInstanceType}]?` -} - -export const InstanceTypeNotSelectedMessage = (spaceName: string) => { - return `No instanceType specified for [${spaceName}]. ${InstanceTypeMinimum} is the default instance type, which meets minimum 8 GiB memory requirements for remote access. Continuing will start your space with instanceType [${InstanceTypeMinimum}] and remotely connect.` -} - -export const RemoteAccessRequiredMessage = - 'This space requires remote access to be enabled.\nWould you like to restart the space and connect?\nAny unsaved work will be lost.' - -export const SshConfigErrorMessage = () => { - return `Unable to connect. Your SSH config file contains errors. Fix the errors to continue.` -} - -export const SmusDeeplinkSessionExpiredError = { - title: 'Session Disconnected', - message: - 'Your SageMaker Unified Studio session has been disconnected. Select a local (non-remote) VS Code window and use the SageMaker Unified Studio portal to connect again.', - code: 'SMUS_SESSION_DISCONNECTED', - shortMessage: 'Session disconnected, re-connect from SageMaker Unified Studio portal.', -} as const diff --git a/packages/core/src/awsService/sagemaker/credentialMapping.ts b/packages/core/src/awsService/sagemaker/credentialMapping.ts deleted file mode 100644 index 931384e881..0000000000 --- a/packages/core/src/awsService/sagemaker/credentialMapping.ts +++ /dev/null @@ -1,221 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as os from 'os' -import { fs } from '../../shared/fs/fs' -import globals from '../../shared/extensionGlobals' -import { ToolkitError } from '../../shared/errors' -import { DevSettings } from '../../shared/settings' -import { Auth } from '../../auth/auth' -import { SpaceMappings, SsmConnectionInfo } from './types' -import { getLogger } from '../../shared/logger/logger' -import { parseArn } from './detached-server/utils' -import { SagemakerUnifiedStudioSpaceNode } from '../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' -import { isSmusSsoConnection } from '../../sagemakerunifiedstudio/auth/model' - -const mappingFileName = '.sagemaker-space-profiles' -const mappingFilePath = path.join(os.homedir(), '.aws', mappingFileName) - -export async function loadMappings(): Promise { - try { - if (!(await fs.existsFile(mappingFilePath))) { - return {} - } - - const raw = await fs.readFileText(mappingFilePath) - return raw ? JSON.parse(raw) : {} - } catch (error) { - getLogger().error(`Failed to load space mappings from ${mappingFilePath}:`, error) - return {} - } -} - -export async function saveMappings(data: SpaceMappings): Promise { - try { - await fs.writeFile(mappingFilePath, JSON.stringify(data, undefined, 2), { - mode: 0o600, - atomic: true, - }) - } catch (error) { - getLogger().error(`Failed to save space mappings to ${mappingFilePath}:`, error) - } -} - -/** - * Persists the current profile to the appropriate space mapping based on connection type and profile format. - * @param spaceArn - The arn for the SageMaker space. - */ -export async function persistLocalCredentials(spaceArn: string): Promise { - const currentProfileId = Auth.instance.getCurrentProfileId() - if (!currentProfileId) { - throw new ToolkitError('No current profile ID available for saving space credentials.') - } - - if (currentProfileId.startsWith('sso:')) { - const credentials = globals.loginManager.store.credentialsCache[currentProfileId] - await setSpaceSsoProfile( - spaceArn, - credentials.credentials.accessKeyId, - credentials.credentials.secretAccessKey, - credentials.credentials.sessionToken ?? '' - ) - } else { - await setSpaceIamProfile(spaceArn, currentProfileId) - } -} - -/** - * Persists the current selected SMUS Project Role creds to the appropriate space mapping. - * @param spaceArn - The identifier for the SageMaker Space. - */ -export async function persistSmusProjectCreds(spaceArn: string, node: SagemakerUnifiedStudioSpaceNode): Promise { - const nodeParent = node.getParent() as SageMakerUnifiedStudioSpacesParentNode - const authProvider = nodeParent.getAuthProvider() - const activeConnection = authProvider.activeConnection - const projectId = nodeParent.getProjectId() - const projectAuthProvider = await authProvider.getProjectCredentialProvider(projectId) - await projectAuthProvider.getCredentials() - await setSmusSpaceProfile(spaceArn, projectId, isSmusSsoConnection(activeConnection) ? 'sso' : 'iam') - // Trigger SSH credential refresh for the project - projectAuthProvider.startProactiveCredentialRefresh() -} - -/** - * Persists deep link credentials for a SageMaker space using a derived refresh URL based on environment. - * - * @param spaceArn - ARN of the SageMaker space. - * @param domain - The domain ID associated with the space. - * @param session - SSM session ID. - * @param wsUrl - SSM WebSocket URL. - * @param token - Bearer token for the session. - * @param appType - Application type (e.g., 'jupyterlab', 'codeeditor'). - * @param isSMUS - If true, skip refreshUrl construction (SMUS connections cannot refresh). - */ -export async function persistSSMConnection( - spaceArn: string, - domain: string, - session?: string, - wsUrl?: string, - token?: string, - appType?: string, - isSMUS?: boolean -): Promise { - let refreshUrl: string | undefined - - if (!isSMUS) { - // Construct refreshUrl for SageMaker AI connections - const { region } = parseArn(spaceArn) - const endpoint = DevSettings.instance.get('endpoints', {})['sagemaker'] ?? '' - - let appSubDomain = 'jupyterlab' - if (appType && appType.toLowerCase() === 'codeeditor') { - appSubDomain = 'code-editor' - } - - let envSubdomain: string - - if (endpoint.includes('beta')) { - envSubdomain = 'devo' - } else if (endpoint.includes('gamma')) { - envSubdomain = 'loadtest' - } else { - envSubdomain = 'studio' - } - - // Use the standard AWS domain for 'studio' (prod). - // For non-prod environments, use the obfuscated domain 'asfiovnxocqpcry.com'. - const baseDomain = - envSubdomain === 'studio' - ? `studio.${region}.sagemaker.aws` - : `${envSubdomain}.studio.${region}.asfiovnxocqpcry.com` - - refreshUrl = `https://studio-${domain}.${baseDomain}/${appSubDomain}` - } - // For SMUS connections, refreshUrl remains undefined - - await setSpaceCredentials(spaceArn, refreshUrl, { - sessionId: session ?? '-', - url: wsUrl ?? '-', - token: token ?? '-', - }) -} - -/** - * Sets or updates an IAM credential profile for a given space. - * @param spaceArn - The name of the SageMaker space. - * @param profileName - The local AWS profile name to associate. - */ -export async function setSpaceIamProfile(spaceArn: string, profileName: string): Promise { - const data = await loadMappings() - data.localCredential ??= {} - data.localCredential[spaceArn] = { type: 'iam', profileName } - await saveMappings(data) -} - -/** - * Sets or updates an SSO credential profile for a given space. - * @param spaceArn - The arn of the SageMaker space. - * @param accessKey - Temporary access key from SSO. - * @param secret - Temporary secret key from SSO. - * @param token - Session token from SSO. - */ -export async function setSpaceSsoProfile( - spaceArn: string, - accessKey: string, - secret: string, - token: string -): Promise { - const data = await loadMappings() - data.localCredential ??= {} - data.localCredential[spaceArn] = { type: 'sso', accessKey, secret, token } - await saveMappings(data) -} - -/** - * Sets the SM Space to map to SageMaker Unified Studio Project. - * @param spaceArn - The arn of the SageMaker Unified Studio space. - * @param projectId - The project ID associated with the SageMaker Unified Studio space. - * @param credentialType - The type of credential ('sso' or 'iam'). - */ -export async function setSmusSpaceProfile( - spaceArn: string, - projectId: string, - credentialType: 'iam' | 'sso' -): Promise { - const data = await loadMappings() - data.localCredential ??= {} - data.localCredential[spaceArn] = { type: credentialType, smusProjectId: projectId } - await saveMappings(data) -} - -/** - * Stores SSM connection information for a given space, typically from a deep link session. - * This initializes the request as 'fresh' and includes a refresh URL if provided. - * @param spaceArn - The arn of the SageMaker space. - * @param refreshUrl - URL to use for refreshing session tokens (undefined for SMUS connections). - * @param credentials - The session information used to initiate the connection. - */ -export async function setSpaceCredentials( - spaceArn: string, - refreshUrl: string | undefined, - credentials: SsmConnectionInfo -): Promise { - const data = await loadMappings() - data.deepLink ??= {} - - data.deepLink[spaceArn] = { - refreshUrl, - requests: { - 'initial-connection': { - ...credentials, - status: 'fresh', - }, - }, - } - - await saveMappings(data) -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/credentials.ts b/packages/core/src/awsService/sagemaker/detached-server/credentials.ts deleted file mode 100644 index 033cf1d3b8..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/credentials.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fromIni } from '@aws-sdk/credential-providers' -import { LocalCredentialProfile } from '../types' -import { readMapping } from './utils' - -/** - * Resolves AWS credentials for a given SageMaker space connection identifier - * using the 'lc' (local connection) credential mapping. - * - * Supported profile types: - * - 'iam': Looks up credentials from AWS config using profile name. - * - 'sso': Uses accessKey, secret, and sessionToken from the mapping file. - * - * @param connectionIdentifier - The ARN or space ID used to locate the profile in the mapping. - * @returns A Promise that resolves to AWS credentials compatible with AWS SDK v3. - * @throws If the profile is missing, malformed, or unsupported. - */ -export async function resolveCredentialsFor(connectionIdentifier: string): Promise { - const mapping = await readMapping() - const profile = mapping.localCredential?.[connectionIdentifier] as LocalCredentialProfile - - if (!profile) { - throw new Error(`No profile found for "${connectionIdentifier}"`) - } - - switch (profile.type) { - case 'iam': { - if ('profileName' in profile) { - const name = profile.profileName?.split(':')[1] - if (!name) { - throw new Error(`Invalid IAM profile name for "${connectionIdentifier}"`) - } - return fromIni({ profile: name }) - } else if ('smusProjectId' in profile) { - const { accessKey, secret, token } = mapping.smusProjects?.[profile.smusProjectId] || {} - if (!accessKey || !secret || !token) { - throw new Error(`Missing ProjectRole credentials for SMUS Space "${connectionIdentifier}"`) - } - return { - accessKeyId: accessKey, - secretAccessKey: secret, - sessionToken: token, - } - } else { - throw new Error(`Missing IAM credentials for "${connectionIdentifier}"`) - } - } - case 'sso': { - if ('accessKey' in profile && 'secret' in profile && 'token' in profile) { - const { accessKey, secret, token } = profile - if (!accessKey || !secret || !token) { - throw new Error(`Missing SSO credentials for "${connectionIdentifier}"`) - } - return { - accessKeyId: accessKey, - secretAccessKey: secret, - sessionToken: token, - } - } else if ('smusProjectId' in profile) { - // Handle SMUS project ID case - const { accessKey, secret, token } = mapping.smusProjects?.[profile.smusProjectId] || {} - if (!accessKey || !secret || !token) { - throw new Error(`Missing ProjectRole credentials for SMUS Space "${connectionIdentifier}"`) - } - return { - accessKeyId: accessKey, - secretAccessKey: secret, - sessionToken: token, - } - } else { - throw new Error(`Missing SSO credentials for "${connectionIdentifier}"`) - } - } - default: - throw new Error(`Unsupported profile type "${profile}"`) - } -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/errorPage.ts b/packages/core/src/awsService/sagemaker/detached-server/errorPage.ts deleted file mode 100644 index d1c88ca3fe..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/errorPage.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable no-restricted-imports */ -import { randomUUID } from 'crypto' -import { join } from 'path' -import { promises as fs } from 'fs' -import os from 'os' -import { SageMakerServiceException } from '@amzn/sagemaker-client' -import { open } from './utils' - -export enum ExceptionType { - ACCESS_DENIED = 'AccessDeniedException', - DEFAULT = 'Default', - EXPIRED_TOKEN = 'ExpiredTokenException', - INTERNAL_FAILURE = 'InternalFailure', - RESOURCE_LIMIT_EXCEEDED = 'ResourceLimitExceeded', - THROTTLING = 'ThrottlingException', - VALIDATION = 'ValidationException', -} - -export const getVSCodeErrorTitle = (error: SageMakerServiceException): string => { - const exceptionType = error.name as ExceptionType - - if (exceptionType in ErrorText.StartSession) { - return ErrorText.StartSession[exceptionType].Title - } - - return ErrorText.StartSession[ExceptionType.DEFAULT].Title -} - -export const getVSCodeErrorText = ( - error: SageMakerServiceException, - isSmus?: boolean, - isSmusIamConn?: boolean -): string => { - const exceptionType = error.name as ExceptionType - - switch (exceptionType) { - case ExceptionType.ACCESS_DENIED: - case ExceptionType.VALIDATION: - return ErrorText.StartSession[exceptionType].Text.replace('{message}', error.message) - case ExceptionType.EXPIRED_TOKEN: - // Use SMUS-specific message if in SMUS context - if (isSmus) { - return isSmusIamConn - ? ErrorText.StartSession[ExceptionType.EXPIRED_TOKEN].SmusIamText - : ErrorText.StartSession[ExceptionType.EXPIRED_TOKEN].SmusSsoText - } - return ErrorText.StartSession[exceptionType].Text - case ExceptionType.INTERNAL_FAILURE: - case ExceptionType.RESOURCE_LIMIT_EXCEEDED: - case ExceptionType.THROTTLING: - return ErrorText.StartSession[exceptionType].Text - default: - return ErrorText.StartSession[ExceptionType.DEFAULT].Text.replace('{exceptionType}', exceptionType) - } -} - -export const ErrorText = { - StartSession: { - [ExceptionType.ACCESS_DENIED]: { - Title: 'Remote access denied', - Text: 'Unable to connect because: [{message}]', - }, - [ExceptionType.DEFAULT]: { - Title: 'Unexpected system error', - Text: 'We encountered an unexpected error: [{exceptionType}]. Please contact your administrator and provide them with this error so they can investigate the issue.', - }, - [ExceptionType.EXPIRED_TOKEN]: { - Title: 'Authentication expired', - Text: 'Your session has expired. Please refresh your credentials and try again.', - SmusSsoText: - 'Your session has expired. This is likely due to network connectivity issues after machine sleep/resume. Wait 10-30 seconds for automatic credential refresh, then try again. If the issue persists, try reconnecting through AWS Toolkit.', - SmusIamText: - 'Your session has expired. Update the credentials associated with the IAM profile or use a valid IAM profile, then try again.', - }, - [ExceptionType.INTERNAL_FAILURE]: { - Title: 'Failed to connect remotely to VSCode', - Text: 'Unable to establish remote connection to VSCode. This could be due to several factors. Please try again by clicking the VSCode button. If the problem persists, please contact your admin.', - }, - [ExceptionType.RESOURCE_LIMIT_EXCEEDED]: { - Title: 'Connection limit reached', - Text: 'You have 10 active remote connections to this space. Stop an existing connection to start a new one.', - }, - [ExceptionType.THROTTLING]: { - Title: 'Too many connection attempts', - Text: "You're connecting too quickly. Wait a moment and try again.", - }, - [ExceptionType.VALIDATION]: { - Title: 'Configuration error', - Text: 'The operation cannot be completed due to: [{message}]', - }, - }, -} - -export async function openErrorPage(title: string, message: string) { - const html = ` - - - - ${title} - - - -
-
-
${title}
-
${message}
-
- -` - - const filePath = join(os.tmpdir(), `sagemaker-error-${randomUUID()}.html`) - await fs.writeFile(filePath, html, 'utf8') - await open(filePath) -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/routes/getSession.ts b/packages/core/src/awsService/sagemaker/detached-server/routes/getSession.ts deleted file mode 100644 index 2db2d11dde..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/routes/getSession.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable aws-toolkits/no-console-log */ -import { IncomingMessage, ServerResponse } from 'http' -import { startSagemakerSession, parseArn, isSmusConnection, isSmusIamConnection } from '../utils' -import { resolveCredentialsFor } from '../credentials' -import url from 'url' -import { SageMakerServiceException } from '@amzn/sagemaker-client' -import { getVSCodeErrorText, getVSCodeErrorTitle, openErrorPage } from '../errorPage' - -export async function handleGetSession(req: IncomingMessage, res: ServerResponse): Promise { - const parsedUrl = url.parse(req.url || '', true) - const connectionIdentifier = parsedUrl.query.connection_identifier as string - - if (!connectionIdentifier) { - res.writeHead(400, { 'Content-Type': 'text/plain' }) - res.end(`Missing required query parameter: "connection_identifier" (${connectionIdentifier})`) - return - } - - let credentials - try { - credentials = await resolveCredentialsFor(connectionIdentifier) - } catch (err) { - console.error('Failed to resolve credentials:', err) - res.writeHead(500, { 'Content-Type': 'text/plain' }) - res.end((err as Error).message) - return - } - - const { region } = parseArn(connectionIdentifier) - // Detect if this is a SMUS connection for specialized error handling - const isSmus = await isSmusConnection(connectionIdentifier) - const isSmusIamConn = await isSmusIamConnection(connectionIdentifier) - - try { - const session = await startSagemakerSession({ region, connectionIdentifier, credentials }) - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end( - JSON.stringify({ - SessionId: session.SessionId, - StreamUrl: session.StreamUrl, - TokenValue: session.TokenValue, - }) - ) - } catch (err) { - const error = err as SageMakerServiceException - console.error(`Failed to start SageMaker session for ${connectionIdentifier}:`, err) - const errorTitle = getVSCodeErrorTitle(error) - const errorText = getVSCodeErrorText(error, isSmus, isSmusIamConn) - await openErrorPage(errorTitle, errorText) - res.writeHead(500, { 'Content-Type': 'text/plain' }) - res.end('Failed to start SageMaker session') - return - } -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/routes/getSessionAsync.ts b/packages/core/src/awsService/sagemaker/detached-server/routes/getSessionAsync.ts deleted file mode 100644 index c0db2712d0..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/routes/getSessionAsync.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable aws-toolkits/no-console-log */ -import { IncomingMessage, ServerResponse } from 'http' -import url from 'url' -import { SessionStore } from '../sessionStore' -import { open, parseArn, readServerInfo } from '../utils' -import { openErrorPage } from '../errorPage' -import { SmusDeeplinkSessionExpiredError } from '../../constants' - -export async function handleGetSessionAsync(req: IncomingMessage, res: ServerResponse): Promise { - const parsedUrl = url.parse(req.url || '', true) - const connectionIdentifier = parsedUrl.query.connection_identifier as string - const requestId = parsedUrl.query.request_id as string - - if (!connectionIdentifier || !requestId) { - res.writeHead(400, { 'Content-Type': 'text/plain' }) - res.end( - `Missing required query parameters: "connection_identifier" (${connectionIdentifier}), "request_id" (${requestId})` - ) - return - } - - const store = new SessionStore() - - try { - const freshEntry = await store.getFreshEntry(connectionIdentifier, requestId) - - if (freshEntry) { - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end( - JSON.stringify({ - SessionId: freshEntry.sessionId, - StreamUrl: freshEntry.url, - TokenValue: freshEntry.token, - }) - ) - return - } - - const status = await store.getStatus(connectionIdentifier, requestId) - if (status === 'pending') { - res.writeHead(204) - res.end() - return - } else if (status === 'not-started') { - const refreshUrl = await store.getRefreshUrl(connectionIdentifier) - - // Check if this is a SMUS connection (no refreshUrl available) - if (refreshUrl === undefined) { - console.log(`SMUS session expired for connection: ${connectionIdentifier}`) - - // Clean up the expired connection entry - try { - await store.cleanupExpiredConnection(connectionIdentifier) - console.log(`Cleaned up expired connection: ${connectionIdentifier}`) - } catch (cleanupErr) { - console.error(`Failed to cleanup expired connection: ${cleanupErr}`) - // Continue with error response even if cleanup fails - } - - await openErrorPage(SmusDeeplinkSessionExpiredError.title, SmusDeeplinkSessionExpiredError.message) - res.writeHead(400, { 'Content-Type': 'application/json' }) - res.end( - JSON.stringify({ - error: SmusDeeplinkSessionExpiredError.code, - message: SmusDeeplinkSessionExpiredError.shortMessage, - }) - ) - return - } - - // Continue with existing SageMaker AI refresh flow - const serverInfo = await readServerInfo() - const { spaceName } = parseArn(connectionIdentifier) - - const url = `${refreshUrl}/${encodeURIComponent(spaceName)}?remote_access_token_refresh=true&reconnect_identifier=${encodeURIComponent( - connectionIdentifier - )}&reconnect_request_id=${encodeURIComponent(requestId)}&reconnect_callback_url=${encodeURIComponent( - `http://localhost:${serverInfo.port}/refresh_token` - )}` - - await open(url) - res.writeHead(202, { 'Content-Type': 'text/plain' }) - res.end('Session is not ready yet. Please retry in a few seconds.') - await store.markPending(connectionIdentifier, requestId) - return - } - } catch (err) { - console.error('Error handling session async request:', err) - res.writeHead(500, { 'Content-Type': 'text/plain' }) - res.end('Unexpected error') - } -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/routes/refreshToken.ts b/packages/core/src/awsService/sagemaker/detached-server/routes/refreshToken.ts deleted file mode 100644 index 34152aa042..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/routes/refreshToken.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable aws-toolkits/no-console-log */ -import { IncomingMessage, ServerResponse } from 'http' -import url from 'url' -import { SessionStore } from '../sessionStore' - -export async function handleRefreshToken(req: IncomingMessage, res: ServerResponse): Promise { - const parsedUrl = url.parse(req.url || '', true) - const connectionIdentifier = parsedUrl.query.connection_identifier as string - const requestId = parsedUrl.query.request_id as string - const wsUrl = parsedUrl.query.ws_url as string - const token = parsedUrl.query.token as string - const sessionId = parsedUrl.query.session as string - - const store = new SessionStore() - - if (!connectionIdentifier || !requestId || !wsUrl || !token || !sessionId) { - res.writeHead(400, { 'Content-Type': 'text/plain' }) - res.end( - `Missing required parameters:\n` + - ` connection_identifier: ${connectionIdentifier ?? 'undefined'}\n` + - ` request_id: ${requestId ?? 'undefined'}\n` + - ` url: ${wsUrl ?? 'undefined'}\n` + - ` token: ${token ?? 'undefined'}\n` + - ` sessionId: ${sessionId ?? 'undefined'}` - ) - return - } - - try { - await store.setSession(connectionIdentifier, requestId, { sessionId, token, url: wsUrl }) - } catch (err) { - console.error('Failed to save session token:', err) - res.writeHead(500, { 'Content-Type': 'text/plain' }) - res.end('Failed to save session token') - return - } - - res.writeHead(200) - res.end('Session token refreshed successfully') -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/server.ts b/packages/core/src/awsService/sagemaker/detached-server/server.ts deleted file mode 100644 index e785516146..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/server.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable aws-toolkits/no-console-log */ -/* eslint-disable no-restricted-imports */ -import http, { IncomingMessage, ServerResponse } from 'http' -import { handleGetSession } from './routes/getSession' -import { handleGetSessionAsync } from './routes/getSessionAsync' -import { handleRefreshToken } from './routes/refreshToken' -import url from 'url' -import * as os from 'os' -import fs from 'fs' -import { execFile } from 'child_process' - -const pollInterval = 30 * 60 * 100 // 30 minutes - -const server = http.createServer((req: IncomingMessage, res: ServerResponse) => { - const parsedUrl = url.parse(req.url || '', true) - - switch (parsedUrl.pathname) { - case '/get_session': - return handleGetSession(req, res) - case '/get_session_async': - return handleGetSessionAsync(req, res) - case '/refresh_token': - return handleRefreshToken(req, res) - default: - res.writeHead(404, { 'Content-Type': 'text/plain' }) - res.end(`Not Found: ${req.url}`) - } -}) - -server.listen(0, '127.0.0.1', async () => { - const address = server.address() - if (address && typeof address === 'object') { - const port = address.port - const pid = process.pid - - console.log(`Detached server listening on http://127.0.0.1:${port} (pid: ${pid})`) - - const filePath = process.env.SAGEMAKER_LOCAL_SERVER_FILE_PATH - if (!filePath) { - throw new Error('SAGEMAKER_LOCAL_SERVER_FILE_PATH environment variable is not set') - } - - const data = { pid, port } - console.log(`Writing local endpoint info to ${filePath}`) - - fs.writeFileSync(filePath, JSON.stringify(data, undefined, 2), 'utf-8') - } else { - console.error('Failed to retrieve assigned port') - process.exit(0) - } - await monitorVSCodeAndExit() -}) - -function checkVSCodeWindows(): Promise { - return new Promise((resolve) => { - const platform = os.platform() - - if (platform === 'win32') { - execFile('tasklist', ['/FI', 'IMAGENAME eq Code.exe'], (err, stdout) => { - if (err) { - resolve(false) - return - } - resolve(/Code\.exe/i.test(stdout)) - }) - } else if (platform === 'darwin') { - execFile('ps', ['aux'], (err, stdout) => { - if (err) { - resolve(false) - return - } - - const found = stdout - .split('\n') - .some((line) => /Visual Studio Code( - Insiders)?\.app\/Contents\/MacOS\/Electron/.test(line)) - resolve(found) - }) - } else { - execFile('ps', ['-A', '-o', 'comm'], (err, stdout) => { - if (err) { - resolve(false) - return - } - - const found = stdout.split('\n').some((line) => /^(code(-insiders)?|electron)$/i.test(line.trim())) - resolve(found) - }) - } - }) -} - -async function monitorVSCodeAndExit() { - while (true) { - const found = await checkVSCodeWindows() - if (!found) { - console.log('No VSCode windows found. Shutting down detached server.') - process.exit(0) - } - await new Promise((r) => setTimeout(r, pollInterval)) - } -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/sessionStore.ts b/packages/core/src/awsService/sagemaker/detached-server/sessionStore.ts deleted file mode 100644 index 9a09ad2418..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/sessionStore.ts +++ /dev/null @@ -1,146 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SsmConnectionInfo } from '../types' -import { readMapping, writeMapping } from './utils' - -export type SessionStatus = 'pending' | 'fresh' | 'consumed' | 'not-started' - -export class SessionStore { - async getRefreshUrl(connectionId: string): Promise { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - return entry.refreshUrl - } - - async getFreshEntry(connectionId: string, requestId: string) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - const requests = entry.requests - const initialEntry = requests['initial-connection'] - if (initialEntry?.status === 'fresh') { - await this.markConsumed(connectionId, 'initial-connection') - return initialEntry - } - - const asyncEntry = requests[requestId] - if (asyncEntry?.status === 'fresh') { - delete requests[requestId] - await writeMapping(mapping) - return asyncEntry - } - - return undefined - } - - async getStatus(connectionId: string, requestId: string) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - const status = entry.requests?.[requestId]?.status - return status ?? 'not-started' - } - - async markConsumed(connectionId: string, requestId: string) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - const requests = entry.requests - if (!requests[requestId]) { - throw new Error(`No request entry found for requestId: "${requestId}"`) - } - - requests[requestId].status = 'consumed' - await writeMapping(mapping) - } - - async markPending(connectionId: string, requestId: string) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - entry.requests[requestId] = { - sessionId: '', - token: '', - url: '', - status: 'pending', - } - - await writeMapping(mapping) - } - - async cleanupExpiredConnection(connectionId: string) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - - // Remove the entire connection entry for the expired space - if (mapping.deepLink[connectionId]) { - delete mapping.deepLink[connectionId] - await writeMapping(mapping) - } - } - - async setSession(connectionId: string, requestId: string, ssmConnectionInfo: SsmConnectionInfo) { - const mapping = await readMapping() - - if (!mapping.deepLink) { - throw new Error('No deepLink mapping found') - } - const entry = mapping.deepLink[connectionId] - if (!entry) { - throw new Error(`No mapping found for connectionId: "${connectionId}"`) - } - - entry.requests[requestId] = { - sessionId: ssmConnectionInfo.sessionId, - token: ssmConnectionInfo.token, - url: ssmConnectionInfo.url, - status: ssmConnectionInfo.status ?? 'fresh', - } - - await writeMapping(mapping) - } -} diff --git a/packages/core/src/awsService/sagemaker/detached-server/utils.ts b/packages/core/src/awsService/sagemaker/detached-server/utils.ts deleted file mode 100644 index fdbd1da1ab..0000000000 --- a/packages/core/src/awsService/sagemaker/detached-server/utils.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable aws-toolkits/no-console-log */ -/* eslint-disable no-restricted-imports */ -import { ServerInfo } from '../types' -import { promises as fs } from 'fs' -import { SageMakerClient, StartSessionCommand } from '@amzn/sagemaker-client' -import os from 'os' -import { join } from 'path' -import { SpaceMappings } from '../types' -import open from 'open' -import { ConfiguredRetryStrategy } from '@smithy/util-retry' -export { open } - -export const mappingFilePath = join(os.homedir(), '.aws', '.sagemaker-space-profiles') -const tempFilePath = `${mappingFilePath}.tmp` - -// Simple file lock to prevent concurrent writes -let isWriting = false -const writeQueue: Array<() => Promise> = [] - -// Currently SSM registration happens asynchronously with App launch, which can lead to -// StartSession Internal Failure when connecting to a fresly-started Space. -// To mitigate, spread out retries over multiple seconds instead of sending all retries within a second. -// Backoff sequence: 1500ms, 2250ms, 3375ms -// Retry timing: 1500ms, 3750ms, 7125ms -const startSessionRetryStrategy = new ConfiguredRetryStrategy(3, (attempt: number) => 1000 * 1.5 ** attempt) - -/** - * Reads the local endpoint info file (default or via env) and returns pid & port. - * @throws Error if the file is missing, invalid JSON, or missing fields - */ -export async function readServerInfo(): Promise { - const filePath = process.env.SAGEMAKER_LOCAL_SERVER_FILE_PATH - if (!filePath) { - throw new Error('Environment variable SAGEMAKER_LOCAL_SERVER_FILE_PATH is not set') - } - - try { - const content = await fs.readFile(filePath, 'utf-8') - const data = JSON.parse(content) - if (typeof data.pid !== 'number' || typeof data.port !== 'number') { - throw new TypeError(`Invalid server info format in ${filePath}`) - } - return { pid: data.pid, port: data.port } - } catch (err: any) { - if (err.code === 'ENOENT') { - throw new Error(`Server info file not found at ${filePath}`) - } - throw new Error(`Failed to read server info: ${err.message ?? String(err)}`) - } -} - -/** - * Parses a SageMaker ARN to extract region, account ID, and space name. - * Supports formats like: - * arn:aws:sagemaker:::space// - * or sm_lc_arn:aws:sagemaker:::space__d-xxxx__ - * - * If the input is prefixed with an identifier (e.g. "sagemaker-user@"), the function will strip it. - * - * @param arn - The full SageMaker ARN string - * @returns An object containing the region, accountId, and spaceName - * @throws If the ARN format is invalid - */ -export function parseArn(arn: string): { region: string; accountId: string; spaceName: string } { - const cleanedArn = arn.includes('@') ? arn.split('@')[1] : arn - const regex = /^arn:aws:sagemaker:(?[^:]+):(?\d+):space[/:].+$/i - const match = cleanedArn.match(regex) - - if (!match?.groups) { - throw new Error(`Invalid SageMaker ARN format: "${arn}"`) - } - - // Extract space name from the end of the ARN (after the last forward slash) - const spaceName = cleanedArn.split('/').pop() - if (!spaceName) { - throw new Error(`Could not extract space name from ARN: "${arn}"`) - } - - return { - region: match.groups.region, - accountId: match.groups.account_id, - spaceName: spaceName, - } -} - -export async function startSagemakerSession({ region, connectionIdentifier, credentials }: any) { - const endpoint = process.env.SAGEMAKER_ENDPOINT || `https://sagemaker.${region}.amazonaws.com` - const client = new SageMakerClient({ region, credentials, endpoint, retryStrategy: startSessionRetryStrategy }) - const command = new StartSessionCommand({ ResourceIdentifier: connectionIdentifier }) - return client.send(command) -} - -/** - * Reads the mapping file and parses it as JSON. - * Throws if the file doesn't exist or is malformed. - */ -export async function readMapping() { - try { - const content = await fs.readFile(mappingFilePath, 'utf-8') - console.log(`Mapping file path: ${mappingFilePath}`) - return JSON.parse(content) - } catch (err) { - throw new Error(`Failed to read mapping file: ${err instanceof Error ? err.message : String(err)}`) - } -} - -/** - * Processes the write queue to ensure only one write operation happens at a time. - */ -async function processWriteQueue() { - if (isWriting || writeQueue.length === 0) { - return - } - - isWriting = true - try { - while (writeQueue.length > 0) { - const writeOperation = writeQueue.shift()! - await writeOperation() - } - } finally { - isWriting = false - } -} - -/** - * Detects if the connection identifier is using SMUS credentials - * @param connectionIdentifier - The connection identifier to check - * @returns Promise - true if SMUS, false otherwise - */ -export async function isSmusConnection(connectionIdentifier: string): Promise { - try { - const mapping = await readMapping() - const profile = mapping.localCredential?.[connectionIdentifier] - - // Check if profile exists and has smusProjectId - return profile && 'smusProjectId' in profile - } catch (err) { - // If we can't read the mapping, assume not SMUS to avoid breaking existing functionality - return false - } -} - -/** - * Detects if the connection identifier is using SMUS IAM credentials - * @param connectionIdentifier - The connection identifier to check - * @returns Promise - true if SMUS IAM connection, false otherwise - */ -export async function isSmusIamConnection(connectionIdentifier: string): Promise { - try { - const mapping = await readMapping() - const profile = mapping.localCredential?.[connectionIdentifier] - - // Check if profile exists, has smusProjectId, and type is 'iam' - return profile && 'smusProjectId' in profile && profile.type === 'iam' - } catch (err) { - // If we can't detect it is iam connection, assume not SMUS IAM to avoid breaking existing functionality - return false - } -} - -/** - * Writes the mapping to a temp file and atomically renames it to the target path. - * Uses a queue to prevent race conditions when multiple requests try to write simultaneously. - */ -export async function writeMapping(mapping: SpaceMappings) { - return new Promise((resolve, reject) => { - const writeOperation = async () => { - try { - // Generate unique temp file name to avoid conflicts - const uniqueTempPath = `${tempFilePath}.${process.pid}.${Date.now()}` - - const json = JSON.stringify(mapping, undefined, 2) - await fs.writeFile(uniqueTempPath, json) - await fs.rename(uniqueTempPath, mappingFilePath) - resolve() - } catch (err) { - reject(new Error(`Failed to write mapping file: ${err instanceof Error ? err.message : String(err)}`)) - } - } - - writeQueue.push(writeOperation) - - // ProcessWriteQueue handles its own errors via individual operation callbacks - // eslint-disable-next-line @typescript-eslint/no-floating-promises - processWriteQueue() - }) -} diff --git a/packages/core/src/awsService/sagemaker/explorer/constants.ts b/packages/core/src/awsService/sagemaker/explorer/constants.ts deleted file mode 100644 index eb2464086f..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export abstract class SagemakerConstants { - static readonly HyperPodPlaceHolderMessage = '[No HyperPod Spaces Found]' - static readonly NoDevSpaceToFilter = 'No dev spaces to filter' - static readonly SelectedClusterNamespacesState = 'aws.hyperpod.selectedClusterNamespaces' - static readonly FilterHyperpodPlaceholderKey = 'aws.filterHyperpodSpacesPlaceholder' - static readonly FilterHyperpodPlaceholderMessage = 'Filter dev spaces by name spaces or cluster (unselect to hide)' - static readonly PlaceHolderMessage = '[No Sagemaker Spaces Found]' - static readonly EnableIdentityFilteringSetting = 'aws.sagemaker.studio.spaces.enableIdentityFiltering' - static readonly SelectedDomainUsersState = 'aws.sagemaker.selectedDomainUsers' - static readonly FilterPlaceholderKey = 'aws.filterSagemakerSpacesPlaceholder' - static readonly FilterPlaceholderMessage = 'Filter spaces by user profile or domain (unselect to hide)' - static readonly NoSpaceToFilter = 'No spaces to filter' - - static readonly IamUserArnRegex = /^arn:aws[a-z\-]*:iam::\d{12}:user\/?([a-zA-Z_0-9+=,.@\-_]+)$/ - static readonly IamSessionArnRegex = - /^arn:aws[a-z\-]*:sts::\d{12}:assumed-role\/?[a-zA-Z_0-9+=,.@\-_]+\/([a-zA-Z_0-9+=,.@\-_]+)$/ - static readonly IdentityCenterArnRegex = - /^arn:aws[a-z\-]*:sts::\d{12}:assumed-role\/?AWSReservedSSO[a-zA-Z_0-9+=,.@\-_]+\/([a-zA-Z_0-9+=,.@\-_]+)$/ - static readonly SpecialCharacterRegex = /[+=,.@\-_]/g -} diff --git a/packages/core/src/awsService/sagemaker/explorer/sagemakerDevSpaceNode.ts b/packages/core/src/awsService/sagemaker/explorer/sagemakerDevSpaceNode.ts deleted file mode 100644 index 92cd449c21..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/sagemakerDevSpaceNode.ts +++ /dev/null @@ -1,140 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { HyperpodCluster, HyperpodDevSpace } from '../../../shared/clients/kubectlClient' -import { SagemakerHyperpodNode } from './sagemakerHyperpodNode' -import { getIcon } from '../../../shared/icons' -import { getLogger } from '../../../shared/logger/logger' - -export const devSpaceContextValueStopped = 'awsSagemakerHyperpodDevSpaceStoppedNode' -export const devSpaceContextValueRunning = 'awsSagemakerHyperpodDevSpaceRunningNode' -export const devSpaceContextValueTransitional = 'awsSagemakerHyperpodDevSpaceTransitionalNode' -export const devSpaceContextValueError = 'awsSagemakerHyperpodDevSpaceErrorNode' - -export class SagemakerDevSpaceNode extends AWSTreeNodeBase { - public constructor( - public readonly parent: SagemakerHyperpodNode, - public readonly devSpace: HyperpodDevSpace, - public readonly hpCluster: HyperpodCluster, - public override readonly regionCode: string - ) { - super('') - this.updateWorkspace() - } - - public updateWorkspace() { - this.label = this.buildLabel() - this.description = this.buildDescription() - this.tooltip = new vscode.MarkdownString(this.buildTooltip()) - this.iconPath = this.buildIconPath() - this.contextValue = this.getContext() - if (this.isPending()) { - this.parent.trackPendingNode(this.getDevSpaceKey()) - } - } - - public buildLabel(): string { - return `${this.devSpace.name} (${this.devSpace.status})` - } - - public buildDescription(): string { - return `${this.devSpace.accessType ?? 'Public'} space` - } - - public buildTooltip(): string { - return `**Space:** ${this.devSpace.name} - \n**Namespace:** ${this.devSpace.namespace} - \n**Cluster:** ${this.devSpace.cluster} - \n**Creator:** ${this.devSpace.creator} - \n**Environment:** Hyperpod` - } - - public buildIconPath() { - switch (this.devSpace.appType) { - case 'jupyterlab': { - return getIcon('aws-sagemaker-jupyter-lab') - } - case 'code-editor': { - return getIcon('aws-sagemaker-code-editor') - } - default: { - break - } - } - } - - public getContext(): string { - if (this.status === 'Stopping' || this.status === 'Starting') { - return devSpaceContextValueTransitional - } else if (this.status === 'Stopped') { - return devSpaceContextValueStopped - } else if (this.status === 'Running') { - return devSpaceContextValueRunning - } else { - return devSpaceContextValueError - } - } - - public isPending(): boolean { - return ( - this.status !== 'Running' && - this.status !== 'Stopped' && - this.status !== 'Error' && - this.status !== 'Invalid' - ) - } - - public get status(): string { - return this.devSpace.status - } - - public get name(): string { - return this.devSpace.name - } - - public get namespace(): string { - return this.devSpace.namespace - } - - public get cluster(): string { - return this.devSpace.cluster - } - - public getParent(): SagemakerHyperpodNode { - return this.parent - } - - public getDevSpaceKey(): string { - return `${this.cluster}-${this.namespace}-${this.name}` - } - - public async updateWorkspaceStatus() { - try { - const kubectlClient = this.getParent().getKubectlClient(this.hpCluster.clusterName) - if (!kubectlClient) { - getLogger().info(`Failed to update workspace status due to unavailable kubectl client`) - return - } - this.devSpace.status = await kubectlClient.getHyperpodSpaceStatus(this.devSpace) - } catch (error) { - getLogger().warn( - '[Hyperpod] Failed to update status for %s: %s', - this.devSpace.name, - (error as Error).message - ) - } - this.updateWorkspace() - if (this.isPending()) { - this.parent.trackPendingNode(this.getDevSpaceKey()) - } - } - - public async refreshNode() { - await this.updateWorkspaceStatus() - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} diff --git a/packages/core/src/awsService/sagemaker/explorer/sagemakerHyperpodNode.ts b/packages/core/src/awsService/sagemaker/explorer/sagemakerHyperpodNode.ts deleted file mode 100644 index f2d6f9f8ff..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/sagemakerHyperpodNode.ts +++ /dev/null @@ -1,233 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { HyperpodCluster, HyperpodDevSpace, KubectlClient } from '../../../shared/clients/kubectlClient' -import { SagemakerDevSpaceNode } from './sagemakerDevSpaceNode' -import { PollingSet } from '../../../shared/utilities/pollingSet' -import { SagemakerConstants } from './constants' -import { SagemakerClient } from '../../../shared/clients/sagemaker' -import { DescribeClusterCommand, EKSClient } from '@aws-sdk/client-eks' -import { GetCallerIdentityResponse } from 'aws-sdk/clients/sts' -import { DefaultStsClient } from '../../../shared/clients/stsClient' -import { updateInPlace } from '../../../shared/utilities/collectionUtils' -import { getLogger } from '../../../shared/logger/logger' -import globals from '../../../shared/extensionGlobals' - -export const hyperpodContextValue = 'awsSagemakerHyperpodNode' -export type SelectedClusterNamespaces = [string, string[]][] -export type SelectedClusterNameSpacesByRegion = [string, SelectedClusterNamespaces][] - -export class SagemakerHyperpodNode extends AWSTreeNodeBase { - public readonly hyperpodDevSpaceNodes: Map - public allSpaces: Map = new Map() - public readonly kubectlClients: Map = new Map() - public readonly eksClient: EKSClient - protected stsClient: DefaultStsClient - callerIdentity: GetCallerIdentityResponse = {} - clusterNamespaces: Map = new Map() - public readonly pollingSet: PollingSet = new PollingSet(5000, this.updatePendingNodes.bind(this)) - - public constructor( - public override readonly regionCode: string, - protected readonly sagemakerClient: SagemakerClient - ) { - super('HyperPod', vscode.TreeItemCollapsibleState.Collapsed) - this.contextValue = hyperpodContextValue - this.eksClient = this.sagemakerClient.getEKSClient() - this.stsClient = new DefaultStsClient(regionCode) - this.hyperpodDevSpaceNodes = new Map() - } - - public override async getChildren(): Promise { - const result = await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - return [...this.hyperpodDevSpaceNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode(this, SagemakerConstants.HyperPodPlaceHolderMessage), - sort: (nodeA, nodeB) => nodeA.name.localeCompare(nodeB.name), - }) - - return result - } - - public async listSpaces(): Promise> { - try { - const clusters = await this.sagemakerClient.listHyperpodClusters() - if (!clusters) { - void vscode.window.showErrorMessage(`Error: No hyperpod clusters found`) - throw new Error(`Error: No hyperpod cluster found`) - } - const spaceMap: Map = new Map() - - for (const cluster of clusters) { - if (!cluster.eksClusterName) { - getLogger().warn(`HyperPod cluster ${cluster.clusterName} does not have an EKS cluster`) - continue - } - - const eksCommand = new DescribeClusterCommand({ - name: cluster.eksClusterName, - }) - const eksResponse = await this.eksClient.send(eksCommand) - if (!eksResponse) { - getLogger().warn(`Error: Invalid response`) - continue - } - - const eksCluster = eksResponse.cluster - if (!eksCluster) { - getLogger().warn( - `Error: EKS cluster ${cluster.eksClusterName} not found in region ${cluster.regionCode}` - ) - continue - } - - let kcClient = this.getKubectlClient(cluster.clusterName) - if (!kcClient) { - kcClient = new KubectlClient(eksCluster, cluster) - this.kubectlClients.set(cluster.clusterName, kcClient) - } - const spacesPerCluster = await kcClient.getSpacesForCluster(eksCluster) - if (!spacesPerCluster) { - getLogger().warn(`Error: No spaces found in eks cluster ${cluster.eksClusterName}`) - continue - } - - for (const devSpace of spacesPerCluster) { - const key = this.getWorkspaceKey(devSpace) - spaceMap.set(key, { cluster, devSpace }) - } - } - return spaceMap - } catch (error) { - void vscode.window.showErrorMessage(`Error: No workspaces listed`) - throw new Error(`Error: No workspaces listed`) - } - } - - public async updateChildren(): Promise { - this.allSpaces = await this.listSpaces() - this.clusterNamespaces.clear() - - for (const [_, { devSpace }] of this.allSpaces) { - const filterKey = this.getClusterNamespaceKey(devSpace) - this.clusterNamespaces.set(filterKey, devSpace) - } - - const filterSpaces = new Map(this.allSpaces) - this.callerIdentity = await this.stsClient.getCallerIdentity() - - const selectedClusterNamespaces = await this.getSelectedClusterNamespaces() - - for (const [key, { devSpace }] of this.allSpaces) { - const filterKey = this.getClusterNamespaceKey(devSpace) - if (!selectedClusterNamespaces.has(filterKey)) { - filterSpaces.delete(key) - } - } - - updateInPlace( - this.hyperpodDevSpaceNodes, - filterSpaces.keys(), - (key: string) => this.hyperpodDevSpaceNodes.get(key)!.updateWorkspace(), - (key: string) => - new SagemakerDevSpaceNode( - this, - filterSpaces.get(key)!.devSpace, - filterSpaces.get(key)!.cluster, - this.regionCode - ) - ) - } - - public trackPendingNode(devSpaceKey: string) { - this.pollingSet.add(devSpaceKey) - } - - private async updatePendingNodes() { - for (const key of this.pollingSet) { - const pendingDevSpaceNode = this.getHyperpodNode(key) - await this.updatePendingHyperpodSpaceNode(pendingDevSpaceNode) - } - } - - private async updatePendingHyperpodSpaceNode(devSpaceNode: SagemakerDevSpaceNode) { - await devSpaceNode.updateWorkspaceStatus() - if (!devSpaceNode.isPending()) { - this.pollingSet.delete(devSpaceNode.getDevSpaceKey()) - await devSpaceNode.refreshNode() - } - } - - public getKubectlClient(clusterName: string): KubectlClient | undefined { - return this.kubectlClients.get(clusterName) - } - - private getHyperpodNode(key: string): SagemakerDevSpaceNode { - const devSpaceNode = this.hyperpodDevSpaceNodes.get(key) - if (devSpaceNode) { - return devSpaceNode - } else { - throw new Error(`[Hyperpod] Devspace ${key} from polling set not found`) - } - } - - public getClusterNamespaceKey(space: HyperpodDevSpace): string { - return `${space.cluster}-${space.namespace}` - } - - public getWorkspaceKey(space: HyperpodDevSpace): string { - return `${space.cluster}-${space.namespace}-${space.name}` - } - - public async getDefaultSelectedClusterNamespaces(): Promise { - return [...this.clusterNamespaces.keys()] - } - - public async getSelectedClusterNamespaces(): Promise> { - const selectedClusterNamespacesByRegionMap = new Map( - globals.globalState.get( - SagemakerConstants.SelectedClusterNamespacesState, - [] - ) - ) - - const selectedClusterNamespacesMap = new Map(selectedClusterNamespacesByRegionMap.get(this.regionCode)) - const defaultSelectedClusterNamespaces = await this.getDefaultSelectedClusterNamespaces() - const cachedClusterNamespaces = selectedClusterNamespacesMap.get(this.callerIdentity.Arn || '') - - if (cachedClusterNamespaces && cachedClusterNamespaces.length > 0) { - return new Set(cachedClusterNamespaces) - } else { - return new Set(defaultSelectedClusterNamespaces) - } - } - - public saveSelectedClusterNamespaces(selectedClusterNamespaces: string[]) { - const selectedClusterNamespacesByRegionMap = new Map( - globals.globalState.get( - SagemakerConstants.SelectedClusterNamespacesState, - [] - ) - ) - - const selectedClusterNamespacesMap = new Map(selectedClusterNamespacesByRegionMap.get(this.regionCode)) - - if (this.callerIdentity.Arn) { - selectedClusterNamespacesMap?.set(this.callerIdentity.Arn, selectedClusterNamespaces) - selectedClusterNamespacesByRegionMap?.set(this.regionCode, [...selectedClusterNamespacesMap]) - - globals.globalState.tryUpdate(SagemakerConstants.SelectedClusterNamespacesState, [ - ...selectedClusterNamespacesByRegionMap, - ]) - } - } -} diff --git a/packages/core/src/awsService/sagemaker/explorer/sagemakerParentNode.ts b/packages/core/src/awsService/sagemaker/explorer/sagemakerParentNode.ts deleted file mode 100644 index e58f168aad..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/sagemakerParentNode.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SagemakerClient } from '../../../shared/clients/sagemaker' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { SagemakerStudioNode } from './sagemakerStudioNode' -import { SagemakerHyperpodNode } from './sagemakerHyperpodNode' - -export const parentContextValue = 'awsSagemakerParentNode' - -export class SagemakerParentNode extends AWSTreeNodeBase { - public override readonly contextValue: string = parentContextValue - private studioNode: SagemakerStudioNode - private hyperpodNode: SagemakerHyperpodNode - - public constructor( - public override readonly regionCode: string, - protected readonly sagemakerClient: SagemakerClient - ) { - super('SageMaker AI', vscode.TreeItemCollapsibleState.Collapsed) - this.studioNode = new SagemakerStudioNode(regionCode, sagemakerClient) - this.hyperpodNode = new SagemakerHyperpodNode(regionCode, sagemakerClient) - } - - public override async getChildren(): Promise { - return [this.studioNode, this.hyperpodNode] - } - - public getStudioNode(): SagemakerStudioNode { - return this.studioNode - } -} diff --git a/packages/core/src/awsService/sagemaker/explorer/sagemakerSpaceNode.ts b/packages/core/src/awsService/sagemaker/explorer/sagemakerSpaceNode.ts deleted file mode 100644 index 669b80497f..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/sagemakerSpaceNode.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SagemakerClient, SagemakerSpaceApp } from '../../../shared/clients/sagemaker' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { SagemakerStudioNode } from './sagemakerStudioNode' -import { getLogger } from '../../../shared/logger/logger' -import { SagemakerUnifiedStudioSpaceNode } from '../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SagemakerSpace } from '../sagemakerSpace' - -export class SagemakerSpaceNode extends AWSTreeNodeBase implements AWSResourceNode { - private smSpace: SagemakerSpace - public constructor( - public readonly parent: SagemakerStudioNode, - public readonly client: SagemakerClient, - public override readonly regionCode: string, - public readonly spaceApp: SagemakerSpaceApp - ) { - super('') - this.smSpace = new SagemakerSpace(this.client, this.regionCode, this.spaceApp) - this.updateSpace(spaceApp) - this.contextValue = this.smSpace.getContext() - } - - public updateSpace(spaceApp: SagemakerSpaceApp) { - this.smSpace.updateSpace(spaceApp) - this.updateFromSpace() - if (this.isPending()) { - this.parent.trackPendingNode(this.DomainSpaceKey) - } - } - - private updateFromSpace() { - this.label = this.smSpace.label - this.description = this.smSpace.description - this.tooltip = this.smSpace.tooltip - this.iconPath = this.smSpace.iconPath - this.contextValue = this.smSpace.contextValue - } - - public isPending(): boolean { - return this.smSpace.isPending() - } - - public getStatus(): string { - return this.smSpace.getStatus() - } - - public async getAppStatus() { - return this.smSpace.getAppStatus() - } - - public get name(): string { - return this.smSpace.name - } - - public get arn(): string { - return this.smSpace.arn - } - - public async getAppArn() { - return this.smSpace.getAppArn() - } - - public async getSpaceArn() { - return this.smSpace.getSpaceArn() - } - - public async updateSpaceAppStatus() { - await this.smSpace.updateSpaceAppStatus() - this.updateFromSpace() - if (this.isPending()) { - this.parent.trackPendingNode(this.DomainSpaceKey) - } - } - - public get DomainSpaceKey(): string { - return this.spaceApp.DomainSpaceKey! - } - - public async refreshNode(): Promise { - await this.updateSpaceAppStatus() - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} - -export async function tryRefreshNode(node?: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode) { - if (node) { - try { - // For SageMaker spaces, refresh just the individual space node to avoid expensive - // operation of refreshing all spaces in the domain - await node.updateSpaceAppStatus() - node instanceof SagemakerSpaceNode - ? await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) - : await node.refreshNode() - } catch (e) { - getLogger().error('refreshNode failed: %s', (e as Error).message) - } - } -} diff --git a/packages/core/src/awsService/sagemaker/explorer/sagemakerStudioNode.ts b/packages/core/src/awsService/sagemaker/explorer/sagemakerStudioNode.ts deleted file mode 100644 index be7b50238f..0000000000 --- a/packages/core/src/awsService/sagemaker/explorer/sagemakerStudioNode.ts +++ /dev/null @@ -1,206 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { GetCallerIdentityResponse } from 'aws-sdk/clients/sts' -import { DescribeDomainResponse } from '@amzn/sagemaker-client' -import { SagemakerClient, SagemakerSpaceApp } from '../../../shared/clients/sagemaker' -import { DefaultStsClient } from '../../../shared/clients/stsClient' -import globals from '../../../shared/extensionGlobals' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../../shared/treeview/utils' -import { updateInPlace } from '../../../shared/utilities/collectionUtils' -import { isRemoteWorkspace } from '../../../shared/vscode/env' -import { SagemakerConstants } from './constants' -import { SagemakerSpaceNode } from './sagemakerSpaceNode' -import { getDomainSpaceKey, getDomainUserProfileKey, getSpaceAppsForUserProfile } from '../utils' -import { PollingSet } from '../../../shared/utilities/pollingSet' -import { getRemoteAppMetadata } from '../remoteUtils' - -export type SelectedDomainUsers = [string, string[]][] -export type SelectedDomainUsersByRegion = [string, SelectedDomainUsers][] - -export interface UserProfileMetadata { - domain: DescribeDomainResponse -} - -export const studioContextValue = 'awsSagemakerStudioNode' - -export class SagemakerStudioNode extends AWSTreeNodeBase { - protected sagemakerSpaceNodes: Map - protected stsClient: DefaultStsClient - public override readonly contextValue: string = studioContextValue - domainUserProfiles: Map = new Map() - spaceApps: Map = new Map() - callerIdentity: GetCallerIdentityResponse = {} - public readonly pollingSet: PollingSet = new PollingSet(5000, this.updatePendingNodes.bind(this)) - - public constructor( - public override readonly regionCode: string, - protected readonly sagemakerClient: SagemakerClient - ) { - super('Studio', vscode.TreeItemCollapsibleState.Collapsed) - this.sagemakerSpaceNodes = new Map() - this.stsClient = new DefaultStsClient(regionCode) - } - - public override async getChildren(): Promise { - const result = await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - return [...this.sagemakerSpaceNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => new PlaceholderNode(this, SagemakerConstants.PlaceHolderMessage), - sort: (nodeA, nodeB) => nodeA.name.localeCompare(nodeB.name), - }) - - return result - } - - public trackPendingNode(domainSpaceKey: string) { - this.pollingSet.add(domainSpaceKey) - } - - private async updatePendingNodes() { - for (const spaceKey of this.pollingSet.values()) { - const childNode = this.getSpaceNodes(spaceKey) - await this.updatePendingSpaceNode(childNode) - } - } - - private async updatePendingSpaceNode(node: SagemakerSpaceNode) { - await node.updateSpaceAppStatus() - if (!node.isPending()) { - this.pollingSet.delete(node.DomainSpaceKey) - await node.refreshNode() - } - } - - public getSpaceNodes(spaceKey: string): SagemakerSpaceNode { - const childNode = this.sagemakerSpaceNodes.get(spaceKey) - if (childNode) { - return childNode - } else { - throw new Error(`Node with id ${spaceKey} from polling set not found`) - } - } - - public async getLocalSelectedDomainUsers(): Promise { - const iamMatches = - this.callerIdentity.Arn?.match(SagemakerConstants.IamUserArnRegex) || - this.callerIdentity.Arn?.match(SagemakerConstants.IamSessionArnRegex) - const idcMatches = this.callerIdentity.Arn?.match(SagemakerConstants.IdentityCenterArnRegex) - - const matches = - iamMatches && vscode.workspace.getConfiguration().get(SagemakerConstants.EnableIdentityFilteringSetting) - ? iamMatches - : idcMatches - ? idcMatches - : undefined - - const userProfilePrefix = - matches && matches.length >= 2 - ? `${matches[1].replaceAll(SagemakerConstants.SpecialCharacterRegex, '-')}-` - : '' - - return getSpaceAppsForUserProfile([...this.spaceApps.values()], userProfilePrefix) - } - - public async getRemoteSelectedDomainUsers(): Promise { - const remoteAppMetadata = await getRemoteAppMetadata() - return getSpaceAppsForUserProfile( - [...this.spaceApps.values()], - remoteAppMetadata.UserProfileName, - remoteAppMetadata.DomainId - ) - } - - public async getDefaultSelectedDomainUsers(): Promise { - if (isRemoteWorkspace()) { - return this.getRemoteSelectedDomainUsers() - } else { - return this.getLocalSelectedDomainUsers() - } - } - - public async getSelectedDomainUsers(): Promise> { - const selectedDomainUsersByRegionMap = new Map( - globals.globalState.get(SagemakerConstants.SelectedDomainUsersState, []) - ) - - const selectedDomainUsersMap = new Map(selectedDomainUsersByRegionMap.get(this.regionCode)) - const defaultSelectedDomainUsers = await this.getDefaultSelectedDomainUsers() - const cachedDomainUsers = selectedDomainUsersMap.get(this.callerIdentity.Arn || '') - - if (cachedDomainUsers && cachedDomainUsers.length > 0) { - return new Set(cachedDomainUsers) - } else { - return new Set(defaultSelectedDomainUsers) - } - } - - public saveSelectedDomainUsers(selectedDomainUsers: string[]) { - const selectedDomainUsersByRegionMap = new Map( - globals.globalState.get(SagemakerConstants.SelectedDomainUsersState, []) - ) - - const selectedDomainUsersMap = new Map(selectedDomainUsersByRegionMap.get(this.regionCode)) - - if (this.callerIdentity.Arn) { - selectedDomainUsersMap?.set(this.callerIdentity.Arn, selectedDomainUsers) - selectedDomainUsersByRegionMap?.set(this.regionCode, [...selectedDomainUsersMap]) - - globals.globalState.tryUpdate(SagemakerConstants.SelectedDomainUsersState, [ - ...selectedDomainUsersByRegionMap, - ]) - } - } - - public async updateChildren(): Promise { - const [spaceApps, domains] = await this.sagemakerClient.fetchSpaceAppsAndDomains() - this.spaceApps = spaceApps - - this.callerIdentity = await this.stsClient.getCallerIdentity() - const selectedDomainUsers = await this.getSelectedDomainUsers() - this.domainUserProfiles.clear() - - for (const app of spaceApps.values()) { - const domainId = app.DomainId - const userProfile = app.OwnershipSettingsSummary?.OwnerUserProfileName - if (!domainId || !userProfile) { - continue - } - - const domainUserProfileKey = getDomainUserProfileKey(domainId, userProfile) - const domainSpaceKey = getDomainSpaceKey(domainId, app.SpaceName || '') - - this.domainUserProfiles.set(domainUserProfileKey, { - domain: domains.get(domainId) as DescribeDomainResponse, - }) - - if (!selectedDomainUsers.has(domainUserProfileKey) && app.SpaceName) { - spaceApps.delete(domainSpaceKey) - continue - } - } - - updateInPlace( - this.sagemakerSpaceNodes, - spaceApps.keys(), - (key) => this.sagemakerSpaceNodes.get(key)!.updateSpace(spaceApps.get(key)!), - (key) => new SagemakerSpaceNode(this as any, this.sagemakerClient, this.regionCode, spaceApps.get(key)!) - ) - } - - public async clearChildren() { - this.sagemakerSpaceNodes = new Map() - } - - public async refreshNode(): Promise { - await this.clearChildren() - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', this) - } -} diff --git a/packages/core/src/awsService/sagemaker/hyperpodCommands.ts b/packages/core/src/awsService/sagemaker/hyperpodCommands.ts deleted file mode 100644 index 454bcc49c1..0000000000 --- a/packages/core/src/awsService/sagemaker/hyperpodCommands.ts +++ /dev/null @@ -1,182 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { getLogger } from '../../shared/logger/logger' -import { isRemoteWorkspace } from '../../shared/vscode/env' -import { SagemakerDevSpaceNode } from './explorer/sagemakerDevSpaceNode' -import { showConfirmationMessage } from '../../shared/utilities/messages' -import { SagemakerConstants } from './explorer/constants' -import { SagemakerHyperpodNode } from './explorer/sagemakerHyperpodNode' - -const localize = nls.loadMessageBundle() - -export async function openHyperPodRemoteConnection(node: SagemakerDevSpaceNode): Promise { - await startHyperpodSpaceCommand(node) - await waitForDevSpaceRunning(node) - await connectToHyperPodDevSpace(node) -} - -async function waitForDevSpaceRunning(node: SagemakerDevSpaceNode): Promise { - const kubectlClient = node.getParent().getKubectlClient(node.hpCluster.clusterName) - if (!kubectlClient) { - getLogger().error(`No kubectlClient available for cluster: ${node.hpCluster.clusterName}`) - return - } - const timeout = 5 * 60 * 1000 // 5 minutes - const startTime = Date.now() - - while (Date.now() - startTime < timeout) { - const status = await kubectlClient.getHyperpodSpaceStatus(node.devSpace) - if (status === 'Running') { - return - } - await new Promise((resolve) => setTimeout(resolve, 5000)) - } - - throw new Error('Timeout waiting for dev space to reach Running status') -} - -export async function connectToHyperPodDevSpace(node: SagemakerDevSpaceNode): Promise { - const logger = getLogger() - - if (isRemoteWorkspace()) { - void vscode.window.showErrorMessage( - 'Cannot connect to HyperPod from a remote workspace. Please use a local VS Code instance.' - ) - return - } - - try { - const kubectlClient = node.getParent().getKubectlClient(node.hpCluster.clusterName) - if (!kubectlClient) { - logger.error(`No kubectlClient available for cluster: ${node.hpCluster.clusterName}`) - return - } - const response = await kubectlClient.createWorkspaceConnection(node.devSpace) - getLogger().debug(`HyperPod connection response: &O`, response) - await vscode.env.openExternal(vscode.Uri.parse(response.url)) - void vscode.window.showInformationMessage(`Started connection to HyperPod dev space: ${node.devSpace.name}`) - } catch (error) { - logger.error(`Failed to connect to HyperPod dev space: ${error}`) - void vscode.window.showErrorMessage( - `Failed to connect to HyperPod dev space: ${error instanceof Error ? error.message : String(error)}` - ) - } -} - -export async function startHyperpodSpaceCommand(node: SagemakerDevSpaceNode): Promise { - if (node.devSpace.status === 'Invalid') { - void vscode.window.showErrorMessage(`Error: Cannot start an invalid space`) - throw new Error(`Error: Cannot start an invalid space`) - } - if (node.devSpace.status === 'Error') { - void vscode.window.showErrorMessage(`Error: Cannot start space until resolved`) - throw new Error(`Error: Cannot start space until resolved`) - } - if (node.devSpace.status === 'Running') { - return - } - // Set transitional state immediately - node.devSpace.status = 'Starting' - node.contextValue = 'awsSagemakerHyperpodDevSpaceTransitionalNode' - await node.refreshNode() - - const kc = node.getParent().getKubectlClient(node.hpCluster.clusterName) - if (!kc) { - getLogger().error(`Failed to start space (${node.devSpace.name}) due to unavailable kubectl client`) - return - } - await kc.startHyperpodDevSpace(node) -} - -export async function stopHyperPodSpaceCommand(node: SagemakerDevSpaceNode): Promise { - const confirmed = await showConfirmationMessage({ - prompt: `You are about to stop this space. Any active resource will also be stopped. Are you sure you want to stop the space?`, - confirm: 'Stop Space', - cancel: 'Cancel', - type: 'warning', - }) - - if (!confirmed) { - return - } - - if (node.devSpace.status === 'Error') { - void vscode.window.showErrorMessage(`Error: Cannot stop space until resolved`) - throw new Error(`Error: Cannot stop space until resolved`) - } - - // Set transitional state immediately - node.devSpace.status = 'Stopping' - node.contextValue = 'awsSagemakerHyperpodDevSpaceTransitionalNode' - await node.refreshNode() - - const kc = node.getParent().getKubectlClient(node.hpCluster.clusterName) - if (!kc) { - getLogger().error(`Failed to start space (${node.devSpace.name}) due to unavailable kubectl client`) - return - } - await kc.stopHyperpodDevSpace(node) -} - -export async function filterDevSpacesByNamespaceCluster(hpNode: SagemakerHyperpodNode): Promise { - if (hpNode.clusterNamespaces.size === 0) { - // if hyperpodNode has not been expanded, then devSpaceNodes will be empty - // if so, this will attempt to populate devSpaceNodes - await hpNode.updateChildren() - if (hpNode.clusterNamespaces.size === 0) { - getLogger().info(SagemakerConstants.NoDevSpaceToFilter) - void vscode.window.showInformationMessage(SagemakerConstants.NoDevSpaceToFilter) - return - } - } - - // Sort by EKS cluster name and namespace - const sortedClusterNamespaces = new Map( - [...hpNode.clusterNamespaces].sort((a, b) => { - const clusterA = a[1].cluster - const clusterB = b[1].cluster - const namespaceA = a[1].namespace - const namespaceB = b[1].namespace - - return clusterA.localeCompare(clusterB) || namespaceA.localeCompare(namespaceB) - }) - ) - - const previousSelection = await hpNode.getSelectedClusterNamespaces() - const items: (vscode.QuickPickItem & { key: string })[] = [] - - for (const [_, devSpace] of sortedClusterNamespaces) { - const filterKey = `${devSpace.cluster}-${devSpace.namespace}` - items.push({ - label: devSpace.namespace, - detail: `In cluster: ${devSpace.cluster}`, - picked: previousSelection.has(filterKey), - key: filterKey, - }) - } - - const placeholder = localize( - SagemakerConstants.FilterHyperpodPlaceholderKey, - SagemakerConstants.FilterHyperpodPlaceholderMessage - ) - const result = await vscode.window.showQuickPick(items, { - placeHolder: placeholder, - canPickMany: true, - matchOnDetail: true, - }) - - if (!result) { - return // User canceled - } - - const newSelection = result.map((r) => r.key) - if (newSelection.length !== previousSelection.size || newSelection.some((key) => !previousSelection.has(key))) { - hpNode.saveSelectedClusterNamespaces(newSelection) - await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', hpNode) - } -} diff --git a/packages/core/src/awsService/sagemaker/model.ts b/packages/core/src/awsService/sagemaker/model.ts deleted file mode 100644 index ffbc3cad9f..0000000000 --- a/packages/core/src/awsService/sagemaker/model.ts +++ /dev/null @@ -1,394 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -// Disabled: detached server files cannot import vscode. -/* eslint-disable no-restricted-imports */ -import * as vscode from 'vscode' -import { getSshConfigPath, sshAgentSocketVariable, startSshAgent, startVscodeRemote } from '../../shared/extensions/ssh' -import { createBoundProcess, ensureDependencies } from '../../shared/remoteSession' -import { SshConfig } from '../../shared/sshConfig' -import { Result } from '../../shared/utilities/result' -import * as path from 'path' -import { persistLocalCredentials, persistSmusProjectCreds, persistSSMConnection } from './credentialMapping' -import * as os from 'os' -import _ from 'lodash' -import { fs } from '../../shared/fs/fs' -import * as nodefs from 'fs' -import { getSmSsmEnv, spawnDetachedServer } from './utils' -import { getLogger } from '../../shared/logger/logger' -import { DevSettings } from '../../shared/settings' -import { ToolkitError } from '../../shared/errors' -import { SagemakerSpaceNode } from './explorer/sagemakerSpaceNode' -import { sleep } from '../../shared/utilities/timeoutUtils' -import { SagemakerUnifiedStudioSpaceNode } from '../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SshConfigError, SshConfigErrorMessage } from './constants' -import globals from '../../shared/extensionGlobals' - -const logger = getLogger('sagemaker') - -class HyperPodSshConfig extends SshConfig { - constructor( - sshPath: string, - private readonly hyperpodConnectPath: string - ) { - super(sshPath, 'hp_', 'hyperpod_connect') - } - - protected override createSSHConfigSection(proxyCommand: string): string { - return ` -# Created by Amazon Q for VSCode. https://github.com/aws/amazon-q-vscode -Host hp_* - ForwardAgent yes - AddKeysToAgent yes - StrictHostKeyChecking accept-new - ProxyCommand '${this.hyperpodConnectPath}' '%h' - IdentitiesOnly yes -` - } - - public override async ensureValid() { - const proxyCommand = `'${this.hyperpodConnectPath}' '%h'` - const verifyHost = await this.verifySSHHost(proxyCommand) - if (verifyHost.isErr()) { - return verifyHost - } - return Result.ok() - } -} - -export async function tryRemoteConnection( - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode, - ctx: vscode.ExtensionContext, - progress: vscode.Progress<{ message?: string; increment?: number }> -) { - const spaceArn = (await node.getSpaceArn()) as string - const isSMUS = node instanceof SagemakerUnifiedStudioSpaceNode - const remoteEnv = await prepareDevEnvConnection(spaceArn, ctx, 'sm_lc', isSMUS, node) - try { - progress.report({ message: 'Opening remote session' }) - await startVscodeRemote( - remoteEnv.SessionProcess, - remoteEnv.hostname, - '/home/sagemaker-user', - remoteEnv.vscPath, - 'sagemaker-user' - ) - } catch (err) { - getLogger().info( - `sm:OpenRemoteConnect: Unable to connect to target space with arn: ${await node.getAppArn()} error: ${err}` - ) - } -} - -export function extractRegionFromStreamUrl(streamUrl: string): string { - const url = new URL(streamUrl) - const match = url.hostname.match(/^[^.]+\.([^.]+)\.amazonaws\.com$/) - if (!match) { - throw new Error(`Unable to get region from stream url: ${streamUrl}`) - } - return match[1] -} - -export async function prepareDevEnvConnection( - spaceArn: string, - ctx: vscode.ExtensionContext, - connectionType: string, - isSMUS: boolean, - node: SagemakerSpaceNode | SagemakerUnifiedStudioSpaceNode | undefined, - session?: string, - wsUrl?: string, - token?: string, - domain?: string, - appType?: string -) { - const remoteLogger = configureRemoteConnectionLogger() - const { ssm, vsc, ssh } = (await ensureDependencies()).unwrap() - - // Check timeout setting for remote SSH connections - const remoteSshConfig = vscode.workspace.getConfiguration('remote.SSH') - const current = remoteSshConfig.get('connectTimeout') - if (typeof current === 'number' && current < 120) { - await remoteSshConfig.update('connectTimeout', 120, vscode.ConfigurationTarget.Global) - void vscode.window.showInformationMessage( - 'Updated "remote.SSH.connectTimeout" to 120 seconds to improve stability.' - ) - } - - const hostnamePrefix = connectionType - let hostname: string - if (connectionType === 'sm_hp') { - hostname = `hp_${session}` - } else { - hostname = `${hostnamePrefix}_${spaceArn.replace(/\//g, '__').replace(/:/g, '_._')}` - } - // save space credential mapping - if (connectionType === 'sm_lc') { - if (!isSMUS) { - await persistLocalCredentials(spaceArn) - } else { - await persistSmusProjectCreds(spaceArn, node as SagemakerUnifiedStudioSpaceNode) - } - } else if (connectionType === 'sm_dl') { - await persistSSMConnection(spaceArn, domain ?? '', session, wsUrl, token, appType, isSMUS) - } - - // HyperPod doesn't need the local server (only for SageMaker Studio) - if (connectionType !== 'sm_hp') { - await startLocalServer(ctx) - } - await removeKnownHost(hostname) - - const hyperpodConnectPath = path.join(ctx.globalStorageUri.fsPath, 'hyperpod_connect') - - // Copy hyperpod_connect script if needed - if (connectionType === 'sm_hp') { - const sourceScriptPath = ctx.asAbsolutePath('resources/hyperpod_connect') - if (!(await fs.existsFile(hyperpodConnectPath))) { - try { - await fs.copy(sourceScriptPath, hyperpodConnectPath) - await fs.chmod(hyperpodConnectPath, 0o755) - logger.info(`Copied hyperpod_connect script to ${hyperpodConnectPath}`) - } catch (err) { - logger.error(`Failed to copy hyperpod_connect script: ${err}`) - } - } - } - - const sshConfig = - connectionType === 'sm_hp' - ? new HyperPodSshConfig(ssh, hyperpodConnectPath) - : new SshConfig(ssh, 'sm_', 'sagemaker_connect') - const config = await sshConfig.ensureValid() - if (config.isErr()) { - const err = config.err() - logger.error(`sagemaker: failed to add ssh config section: ${err.message}`) - - if (err instanceof ToolkitError && err.code === 'SshCheckFailed') { - const sshConfigPath = getSshConfigPath() - const openConfigButton = 'Open SSH Config' - const resp = await vscode.window.showErrorMessage( - SshConfigErrorMessage(), - { modal: true, detail: err.message }, - openConfigButton - ) - - if (resp === openConfigButton) { - void vscode.window.showTextDocument(vscode.Uri.file(sshConfigPath)) - } - - // Throw error to stop the connection flow - // User is already notified via modal above, downstream handlers check the error code - throw new ToolkitError('Unable to connect: SSH configuration contains errors', { - code: SshConfigError, - }) - } - - const logPrefix = connectionType === 'sm_hp' ? 'hyperpod' : 'sagemaker' - logger.error(`${logPrefix}: failed to add ssh config section: ${err.message}`) - throw err - } - - // set envirionment variables - const vars: NodeJS.ProcessEnv = - connectionType === 'sm_hp' - ? await (async () => { - const logFileLocation = path.join(ctx.globalStorageUri.fsPath, 'hyperpod-connection.log') - const decodedWsUrl = - wsUrl - ?.replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/&/g, '&') || '' - const decodedToken = - token - ?.replace(/'/g, "'") - .replace(/"/g, '"') - .replace(/&/g, '&') || '' - const region = decodedWsUrl ? extractRegionFromStreamUrl(decodedWsUrl) : '' - - const hyperPodEnv: NodeJS.ProcessEnv = { - AWS_REGION: region, - SESSION_ID: session || '', - STREAM_URL: decodedWsUrl, - TOKEN: decodedToken, - AWS_SSM_CLI: ssm, - DEBUG_LOG: '1', - LOG_FILE_LOCATION: logFileLocation, - } - - // Add AWS credentials - try { - const creds = await globals.awsContext.getCredentials() - if (creds) { - hyperPodEnv.AWS_ACCESS_KEY_ID = creds.accessKeyId - hyperPodEnv.AWS_SECRET_ACCESS_KEY = creds.secretAccessKey - if (creds.sessionToken) { - hyperPodEnv.AWS_SESSION_TOKEN = creds.sessionToken - } - logger.info('Added AWS credentials to environment') - } else { - logger.warn('No AWS credentials available for HyperPod connection') - } - } catch (err) { - logger.warn(`Failed to get AWS credentials: ${err}`) - } - - return { ...process.env, ...hyperPodEnv } - })() - : getSmSsmEnv(ssm, path.join(ctx.globalStorageUri.fsPath, 'sagemaker-local-server-info.json')) - - logger.info(`connect script logs at ${vars.LOG_FILE_LOCATION}`) - - const envProvider = async () => { - return { [sshAgentSocketVariable]: await startSshAgent(), ...vars } - } - const SessionProcess = createBoundProcess(envProvider).extend({ - onStdout: (data: string) => { - remoteLogger(data) - if (connectionType === 'sm_hp') { - getLogger().info(`[ProxyCommand stdout] ${data}`) - } - }, - onStderr: (data: string) => { - remoteLogger(data) - if (connectionType === 'sm_hp') { - getLogger().error(`[ProxyCommand stderr] ${data}`) - } - }, - rejectOnErrorCode: true, - }) - - return { - hostname, - envProvider, - sshPath: ssh, - vscPath: vsc, - SessionProcess, - } -} - -export function configureRemoteConnectionLogger() { - const logPrefix = 'sagemaker:' - const logger = (data: string) => getLogger().info(`${logPrefix}: ${data}`) - return logger -} - -export async function startLocalServer(ctx: vscode.ExtensionContext) { - const storagePath = ctx.globalStorageUri.fsPath - const serverPath = ctx.asAbsolutePath(path.join('dist/src/awsService/sagemaker/detached-server/', 'server.js')) - const outLog = path.join(storagePath, 'sagemaker-local-server.out.log') - const errLog = path.join(storagePath, 'sagemaker-local-server.err.log') - const infoFilePath = path.join(storagePath, 'sagemaker-local-server-info.json') - - logger.info(`sagemaker-local-server.*.log at ${storagePath}`) - - const customEndpoint = DevSettings.instance.get('endpoints', {})['sagemaker'] - - await stopLocalServer(ctx) - - const child = spawnDetachedServer(process.execPath, [serverPath], { - cwd: path.dirname(serverPath), - detached: true, - stdio: ['ignore', nodefs.openSync(outLog, 'a'), nodefs.openSync(errLog, 'a')], - env: { - ...process.env, - SAGEMAKER_ENDPOINT: customEndpoint, - SAGEMAKER_LOCAL_SERVER_FILE_PATH: infoFilePath, - }, - }) - - child.unref() - - // Wait for the info file to appear (timeout after 10 seconds) - const maxRetries = 20 - const delayMs = 500 - for (let i = 0; i < maxRetries; i++) { - if (await fs.existsFile(infoFilePath)) { - logger.debug('Detected server info file.') - return - } - await sleep(delayMs) - } - - throw new ToolkitError(`Timed out waiting for local server info file: ${infoFilePath}`) -} - -interface LocalServerInfo { - pid: number - port: string -} - -export async function stopLocalServer(ctx: vscode.ExtensionContext): Promise { - const infoFilePath = path.join(ctx.globalStorageUri.fsPath, 'sagemaker-local-server-info.json') - - if (!(await fs.existsFile(infoFilePath))) { - logger.debug('no server info file found. nothing to stop.') - return - } - - let pid: number | undefined - try { - const content = await fs.readFileText(infoFilePath) - const infoJson = JSON.parse(content) as LocalServerInfo - pid = infoJson.pid - } catch (err: any) { - throw ToolkitError.chain(err, 'failed to parse server info file') - } - - if (typeof pid === 'number' && !isNaN(pid)) { - try { - process.kill(pid) - logger.debug(`stopped local server with PID ${pid}`) - } catch (err: any) { - if (err.code === 'ESRCH') { - logger.warn(`no process found with PID ${pid}. It may have already exited.`) - } else { - throw ToolkitError.chain(err, 'failed to stop local server') - } - } - } else { - logger.warn('no valid PID found in info file.') - } - - try { - await fs.delete(infoFilePath) - logger.debug('removed server info file.') - } catch (err: any) { - logger.warn(`could not delete info file: ${err.message ?? err}`) - } -} - -export async function removeKnownHost(hostname: string): Promise { - const knownHostsPath = path.join(os.homedir(), '.ssh', 'known_hosts') - - if (!(await fs.existsFile(knownHostsPath))) { - logger.warn(`known_hosts not found at ${knownHostsPath}`) - return - } - - let lines: string[] - try { - const content = await fs.readFileText(knownHostsPath) - lines = content.split('\n') - } catch (err: any) { - throw ToolkitError.chain(err, 'Failed to read known_hosts file') - } - - const updatedLines = lines.filter((line) => { - const entryHostname = line.split(' ')[0].split(',') - // Hostnames in the known_hosts file seem to be always lowercase, but keeping the case-sensitive check just in - // case. Originally we were only doing the case-sensitive check which caused users to get a host - // identification error when reconnecting to a Space after it was restarted. - return !entryHostname.includes(hostname) && !entryHostname.includes(hostname.toLowerCase()) - }) - - if (updatedLines.length !== lines.length) { - try { - await fs.writeFile(knownHostsPath, updatedLines.join('\n'), { atomic: true }) - logger.debug(`Removed '${hostname}' from known_hosts`) - } catch (err: any) { - throw ToolkitError.chain(err, 'Failed to write updated known_hosts file') - } - } -} diff --git a/packages/core/src/awsService/sagemaker/remoteUtils.ts b/packages/core/src/awsService/sagemaker/remoteUtils.ts deleted file mode 100644 index 9ff8d8ca17..0000000000 --- a/packages/core/src/awsService/sagemaker/remoteUtils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fs } from '../../shared/fs/fs' -import { SagemakerClient } from '../../shared/clients/sagemaker' -import { RemoteAppMetadata } from './utils' -import { getLogger } from '../../shared/logger/logger' -import { parseArn } from './detached-server/utils' - -export async function getRemoteAppMetadata(): Promise { - try { - const metadataPath = '/opt/ml/metadata/resource-metadata.json' - const metadataContent = await fs.readFileText(metadataPath) - const metadata = JSON.parse(metadataContent) - - const domainId = metadata.DomainId - const spaceName = metadata.SpaceName - - if (!domainId || !spaceName) { - throw new Error('DomainId or SpaceName not found in metadata file') - } - - const { region } = parseArn(metadata.ResourceArn) - - const client = new SagemakerClient(region) - const spaceDetails = await client.describeSpace({ DomainId: domainId, SpaceName: spaceName }) - - const userProfileName = spaceDetails.OwnershipSettings?.OwnerUserProfileName - - if (!userProfileName) { - throw new Error('OwnerUserProfileName not found in space details') - } - - return { - DomainId: domainId, - UserProfileName: userProfileName, - } - } catch (error) { - const logger = getLogger() - logger.error(`getRemoteAppMetadata: Failed to read metadata file, using fallback values: ${error}`) - return { - DomainId: '', - UserProfileName: '', - } - } -} diff --git a/packages/core/src/awsService/sagemaker/sagemakerSpace.ts b/packages/core/src/awsService/sagemaker/sagemakerSpace.ts deleted file mode 100644 index f4bcfdd952..0000000000 --- a/packages/core/src/awsService/sagemaker/sagemakerSpace.ts +++ /dev/null @@ -1,248 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import { AppType } from '@aws-sdk/client-sagemaker' -import { SagemakerClient, SagemakerSpaceApp } from '../../shared/clients/sagemaker' -import { getIcon, IconPath } from '../../shared/icons' -import { generateSpaceStatus, updateIdleFile, startMonitoringTerminalActivity, ActivityCheckInterval } from './utils' -import { UserActivity } from '../../shared/extensionUtilities' -import { getLogger } from '../../shared/logger/logger' -import { ToolkitError } from '../../shared/errors' -import { SpaceStatus, RemoteAccess } from './constants' - -const logger = getLogger('sagemaker') - -export class SagemakerSpace { - public label: string = '' - public contextValue: string = '' - public description?: string - private spaceApp: SagemakerSpaceApp - public tooltip?: vscode.MarkdownString - public iconPath?: IconPath - public refreshCallback?: () => Promise - - public constructor( - private readonly client: SagemakerClient, - public readonly regionCode: string, - spaceApp: SagemakerSpaceApp, - private readonly isSMUSSpace: boolean = false - ) { - this.spaceApp = spaceApp - this.updateSpace(spaceApp) - this.contextValue = this.getContext() - } - - public updateSpace(spaceApp: SagemakerSpaceApp) { - // Edge case when this.spaceApp.App is null, returned by ListApp API for a Space that is not connected to for over 24 hours - if (!this.spaceApp.App) { - this.spaceApp.App = spaceApp.App - } - this.setSpaceStatus(spaceApp.Status ?? '', spaceApp.App?.Status ?? '') - // Only update RemoteAccess property to minimize impact due to minor structural differences between variables - if (this.spaceApp.SpaceSettingsSummary && spaceApp.SpaceSettingsSummary?.RemoteAccess) { - this.spaceApp.SpaceSettingsSummary.RemoteAccess = spaceApp.SpaceSettingsSummary.RemoteAccess - } - this.label = this.buildLabel() - this.description = this.isSMUSSpace ? undefined : this.buildDescription() - this.tooltip = new vscode.MarkdownString(this.buildTooltip()) - this.iconPath = this.getAppIcon() - this.contextValue = this.getContext() - } - - public setSpaceStatus(spaceStatus: string, appStatus: string) { - this.spaceApp.Status = spaceStatus - if (this.spaceApp.App) { - this.spaceApp.App.Status = appStatus - } - } - - public isPending(): boolean { - return this.getStatus() !== SpaceStatus.RUNNING && this.getStatus() !== SpaceStatus.STOPPED - } - - public getStatus(): string { - return generateSpaceStatus(this.spaceApp.Status, this.spaceApp.App?.Status) - } - - public async getAppStatus() { - const app = await this.client.describeApp({ - DomainId: this.spaceApp.DomainId, - AppName: this.spaceApp.App?.AppName, - AppType: this.spaceApp.SpaceSettingsSummary?.AppType, - SpaceName: this.spaceApp.SpaceName, - }) - - return app.Status ?? 'Unknown' - } - - public get name(): string { - return this.spaceApp.SpaceName ?? `(no name)` - } - - public get arn(): string { - return 'placeholder-arn' - } - - // TODO: Verify this method is still needed to retrieve the app ARN or build based on provided details - public async getAppArn() { - const appDetails = await this.client.describeApp({ - DomainId: this.spaceApp.DomainId, - AppName: this.spaceApp.App?.AppName, - AppType: this.spaceApp?.SpaceSettingsSummary?.AppType, - SpaceName: this.spaceApp.SpaceName, - }) - - return appDetails.AppArn - } - - // TODO: Verify this method is still needed to retrieve the app ARN or build based on provided details - public async getSpaceArn() { - const spaceDetails = await this.client.describeSpace({ - DomainId: this.spaceApp.DomainId, - SpaceName: this.spaceApp.SpaceName, - }) - - return spaceDetails.SpaceArn - } - - public async updateSpaceAppStatus() { - const space = await this.client.describeSpace({ - DomainId: this.spaceApp.DomainId, - SpaceName: this.spaceApp.SpaceName, - }) - // get app using ListApps API, with given DomainId and SpaceName - const app = - this.spaceApp.DomainId && this.spaceApp.SpaceName - ? await this.client.listAppForSpace(this.spaceApp.DomainId, this.spaceApp.SpaceName) - : undefined - if (!app) { - logger.error( - `updateSpaceAppStatus: unable to get app, [DomainId: ${this.spaceApp.DomainId}], [SpaceName: ${this.spaceApp.SpaceName}]` - ) - throw new ToolkitError( - `Cannot update app status without [DomainId: ${this.spaceApp.DomainId} and SpaceName: ${this.spaceApp.SpaceName}]` - ) - } - - // AWS DescribeSpace API returns full details with property names like 'SpaceSettings' - // but our internal SagemakerSpaceApp type expects 'SpaceSettingsSummary' (from ListSpaces API) - // We destructure and rename properties to maintain type compatibility - const { - SpaceSettings: spaceSettingsSummary, - OwnershipSettings: ownershipSettingsSummary, - SpaceSharingSettings: spaceSharingSettingsSummary, - ...spaceDetails - } = space - this.updateSpace({ - SpaceSettingsSummary: spaceSettingsSummary, - OwnershipSettingsSummary: ownershipSettingsSummary, - SpaceSharingSettingsSummary: spaceSharingSettingsSummary, - ...spaceDetails, - App: app, - DomainSpaceKey: this.spaceApp.DomainSpaceKey, - }) - } - - public buildLabel(): string { - const status = generateSpaceStatus(this.spaceApp.Status, this.spaceApp.App?.Status) - return `${this.name} (${status})` - } - - public buildDescription(): string { - return `${this.spaceApp.SpaceSharingSettingsSummary?.SharingType ?? 'Unknown'} space` - } - - public buildTooltip() { - const spaceName = this.spaceApp?.SpaceName ?? '-' - const appType = this.spaceApp?.SpaceSettingsSummary?.AppType || '-' - const domainId = this.spaceApp?.DomainId ?? '-' - const owner = this.spaceApp?.OwnershipSettingsSummary?.OwnerUserProfileName || '-' - const instanceType = this.spaceApp?.App?.ResourceSpec?.InstanceType ?? '-' - const remoteAccess = this.spaceApp?.SpaceSettingsSummary?.RemoteAccess - - let baseTooltip = '' - if (this.isSMUSSpace) { - baseTooltip = `**Space:** ${spaceName} \n\n**Application:** ${appType} \n\n**Instance Type:** ${instanceType}` - if (remoteAccess === RemoteAccess.ENABLED) { - baseTooltip += `\n\n**Remote Access:** Enabled` - } else if (remoteAccess === RemoteAccess.DISABLED) { - baseTooltip += `\n\n**Remote Access:** Disabled` - } - - return baseTooltip - } - return `**Space:** ${spaceName} \n\n**Application:** ${appType} \n\n**Domain ID:** ${domainId} \n\n**User Profile:** ${owner}` - } - - public getAppIcon() { - const appType = this.spaceApp.SpaceSettingsSummary?.AppType - if (appType === AppType.JupyterLab) { - return getIcon('aws-sagemaker-jupyter-lab') - } - if (appType === AppType.CodeEditor) { - return getIcon('aws-sagemaker-code-editor') - } - } - - public getContext(): string { - const status = this.getStatus() - - if (status === SpaceStatus.RUNNING) { - return 'awsSagemakerSpaceRunningNode' - } - - if (status === SpaceStatus.STOPPED) { - return 'awsSagemakerSpaceStoppedNode' - } - - // For all other states (STARTING, STOPPING, etc.), return base context - return this.isSMUSSpace ? 'smusSpaceNode' : 'awsSagemakerSpaceNode' - } - - public get DomainSpaceKey(): string { - return this.spaceApp.DomainSpaceKey! - } -} - -/** - * Sets up user activity monitoring for SageMaker spaces - */ -export async function setupUserActivityMonitoring(extensionContext: vscode.ExtensionContext): Promise { - logger.info('setupUserActivityMonitoring: Starting user activity monitoring setup') - - const tmpDirectory = '/tmp/' - const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp') - logger.debug(`setupUserActivityMonitoring: Using idle file path: ${idleFilePath}`) - - try { - const userActivity = new UserActivity(ActivityCheckInterval) - userActivity.onUserActivity(() => { - logger.debug('setupUserActivityMonitoring: User activity detected, updating idle file') - void updateIdleFile(idleFilePath) - }) - - let terminalActivityInterval: NodeJS.Timeout | undefined = startMonitoringTerminalActivity(idleFilePath) - logger.debug('setupUserActivityMonitoring: Started terminal activity monitoring') - // Write initial timestamp - await updateIdleFile(idleFilePath) - logger.info('setupUserActivityMonitoring: Initial timestamp written successfully') - extensionContext.subscriptions.push(userActivity, { - dispose: () => { - logger.info('setupUserActivityMonitoring: Disposing user activity monitoring') - if (terminalActivityInterval) { - clearInterval(terminalActivityInterval) - terminalActivityInterval = undefined - } - }, - }) - - logger.info('setupUserActivityMonitoring: User activity monitoring setup completed successfully') - } catch (error) { - logger.error(`setupUserActivityMonitoring: Error during setup: ${error}`) - throw error - } -} diff --git a/packages/core/src/awsService/sagemaker/types.ts b/packages/core/src/awsService/sagemaker/types.ts deleted file mode 100644 index 76eb3c23ea..0000000000 --- a/packages/core/src/awsService/sagemaker/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export interface SpaceMappings { - localCredential?: { [spaceName: string]: LocalCredentialProfile } - deepLink?: { [spaceName: string]: DeeplinkSession } - smusProjects?: { [smusProjectId: string]: { accessKey: string; secret: string; token: string } } -} - -export type LocalCredentialProfile = - | { type: 'iam'; profileName: string } - | { type: 'sso'; accessKey: string; secret: string; token: string } - | { type: 'sso' | 'iam'; smusProjectId: string } - -export interface DeeplinkSession { - requests: Record - refreshUrl?: string -} - -export interface SsmConnectionInfo { - sessionId: string - url: string - token: string - status?: 'fresh' | 'consumed' | 'pending' -} - -export interface ServerInfo { - pid: number - port: number -} diff --git a/packages/core/src/awsService/sagemaker/uriHandlers.ts b/packages/core/src/awsService/sagemaker/uriHandlers.ts deleted file mode 100644 index 411fe5a789..0000000000 --- a/packages/core/src/awsService/sagemaker/uriHandlers.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SearchParams } from '../../shared/vscode/uriHandler' -import { deeplinkConnect } from './commands' -import { ExtContext } from '../../shared/extensions' -import { telemetry } from '../../shared/telemetry/telemetry' - -export function register(ctx: ExtContext) { - async function connectHandler(params: ReturnType) { - await telemetry.sagemaker_deeplinkConnect.run(async () => { - const wsUrl = `${params.ws_url}&cell-number=${params['cell-number']}` - await deeplinkConnect( - ctx, - params.connection_identifier, - params.session, - wsUrl, - params.token, - params.domain, - params.app_type - ) - }) - } - - async function hyperPodConnectHandler(params: ReturnType) { - await telemetry.sagemaker_deeplinkConnect.run(async () => { - const wsUrl = `${params.streamUrl}&cell-number=${params['cell-number']}` - await deeplinkConnect( - ctx, - '', - params.sessionId, - wsUrl, - params.sessionToken, - '', - undefined, - params.workspaceName, - params.namespace, - params.eksClusterArn - ) - }) - } - - return vscode.Disposable.from( - ctx.uriHandler.onPath('/connect/sagemaker', connectHandler, parseConnectParams), - ctx.uriHandler.onPath('/connect/workspace', hyperPodConnectHandler, parseHyperpodConnectParams) - ) -} - -export function parseHyperpodConnectParams(query: SearchParams) { - const requiredParams = query.getFromKeysOrThrow('sessionId', 'streamUrl', 'sessionToken', 'cell-number') - const optionalParams = query.getFromKeys('workspaceName', 'namespace', 'eksClusterArn') - return { ...requiredParams, ...optionalParams } -} -export function parseConnectParams(query: SearchParams) { - const requiredParams = query.getFromKeysOrThrow( - 'connection_identifier', - 'domain', - 'user_profile', - 'session', - 'ws_url', - 'cell-number', - 'token' - ) - const optionalParams = query.getFromKeys('app_type') - - return { ...requiredParams, ...optionalParams } -} diff --git a/packages/core/src/awsService/sagemaker/utils.ts b/packages/core/src/awsService/sagemaker/utils.ts deleted file mode 100644 index 33cc5880be..0000000000 --- a/packages/core/src/awsService/sagemaker/utils.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as cp from 'child_process' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import { AppStatus, SpaceStatus } from '@aws-sdk/client-sagemaker' -import { SagemakerSpaceApp } from '../../shared/clients/sagemaker' -import { sshLogFileLocation } from '../../shared/sshConfig' -import { fs } from '../../shared/fs/fs' -import { getLogger } from '../../shared/logger/logger' - -export const DomainKeyDelimiter = '__' - -export function getDomainSpaceKey(domainId: string, spaceName: string): string { - return `${domainId}${DomainKeyDelimiter}${spaceName}` -} - -export function getDomainUserProfileKey(domainId: string, userProfileName: string): string { - return `${domainId}${DomainKeyDelimiter}${userProfileName}` -} - -export function generateSpaceStatus(spaceStatus?: string, appStatus?: string) { - if ( - spaceStatus === SpaceStatus.Failed || - spaceStatus === SpaceStatus.Delete_Failed || - spaceStatus === SpaceStatus.Update_Failed || - (appStatus === AppStatus.Failed && spaceStatus !== SpaceStatus.Updating) - ) { - return 'Failed' - } - - if (spaceStatus === SpaceStatus.InService && appStatus === AppStatus.InService) { - return 'Running' - } - - if (spaceStatus === SpaceStatus.InService && appStatus === AppStatus.Pending) { - return 'Starting' - } - - if (spaceStatus === SpaceStatus.Updating) { - return 'Updating' - } - - if (spaceStatus === SpaceStatus.InService && appStatus === AppStatus.Deleting) { - return 'Stopping' - } - - if (spaceStatus === SpaceStatus.InService && (appStatus === AppStatus.Deleted || !appStatus)) { - return 'Stopped' - } - - if (spaceStatus === SpaceStatus.Deleting) { - return 'Deleting' - } - - return 'Unknown' -} - -export interface RemoteAppMetadata { - DomainId: string - UserProfileName: string -} - -export function getSpaceAppsForUserProfile( - spaceApps: SagemakerSpaceApp[], - userProfilePrefix: string, - domainId?: string -): string[] { - return spaceApps.reduce((result: string[], app: SagemakerSpaceApp) => { - if (app.OwnershipSettingsSummary?.OwnerUserProfileName?.startsWith(userProfilePrefix)) { - if (domainId && app.DomainId !== domainId) { - return result - } - result.push( - getDomainUserProfileKey(app.DomainId || '', app.OwnershipSettingsSummary?.OwnerUserProfileName || '') - ) - } - - return result - }, [] as string[]) -} - -export function getSmSsmEnv(ssmPath: string, sagemakerLocalServerPath: string): NodeJS.ProcessEnv { - return Object.assign( - { - AWS_SSM_CLI: ssmPath, - SAGEMAKER_LOCAL_SERVER_FILE_PATH: sagemakerLocalServerPath, - LOF_FILE_LOCATION: sshLogFileLocation('sagemaker', 'blah'), - }, - process.env - ) -} - -export function spawnDetachedServer(...args: Parameters) { - return cp.spawn(...args) -} - -export const ActivityCheckInterval = 60000 - -/** - * Updates the idle file with the current timestamp - */ -export async function updateIdleFile(idleFilePath: string): Promise { - try { - const timestamp = new Date().toISOString() - await fs.writeFile(idleFilePath, timestamp) - } catch (error) { - getLogger().error(`Failed to update SMAI idle file: ${error}`) - } -} - -/** - * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. - * - * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. - * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. - * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, - * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification - * times of the files in the /dev/pts directory, we can detect terminal activity. - * - * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function - * updates the last activity timestamp. - */ -export async function checkTerminalActivity(idleFilePath: string): Promise { - try { - const files = await fs.readdir('/dev/pts') - const now = Date.now() - - for (const [fileName] of files) { - const filePath = path.join('/dev/pts', fileName) - try { - const stats = await fs.stat(filePath) - const mtime = new Date(stats.mtime).getTime() - if (now - mtime < ActivityCheckInterval) { - await updateIdleFile(idleFilePath) - return - } - } catch (err) { - getLogger().error(`Error reading file stats:`, err) - } - } - } catch (err) { - getLogger().error(`Error reading /dev/pts directory:`, err) - } -} - -/** - * Starts monitoring terminal activity by setting an interval to check for activity in the /dev/pts directory. - */ -export function startMonitoringTerminalActivity(idleFilePath: string): NodeJS.Timeout { - return setInterval(async () => { - await checkTerminalActivity(idleFilePath) - }, ActivityCheckInterval) -} diff --git a/packages/core/src/awsexplorer/activation.ts b/packages/core/src/awsexplorer/activation.ts deleted file mode 100644 index ec4c23ccd7..0000000000 --- a/packages/core/src/awsexplorer/activation.ts +++ /dev/null @@ -1,217 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { deleteCloudFormation } from '../lambda/commands/deleteCloudFormation' -import { CloudFormationStackNode } from '../lambda/explorer/cloudFormationNodes' -import globals from '../shared/extensionGlobals' -import { isCloud9, isSageMaker } from '../shared/extensionUtilities' -import { ExtContext, VSCODE_EXTENSION_ID } from '../shared/extensions' -import { getLogger } from '../shared/logger/logger' -import { RegionProvider } from '../shared/regions/regionProvider' -import { AWSResourceNode } from '../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { Commands } from '../shared/vscode/commands2' -import { downloadStateMachineDefinition } from '../stepFunctions/commands/downloadStateMachineDefinition' -import { executeStateMachine } from '../stepFunctions/vue/executeStateMachine/executeStateMachine' -import { StateMachineNode } from '../stepFunctions/explorer/stepFunctionsNodes' -import { AwsExplorer } from './awsExplorer' -import { copyTextCommand } from './commands/copyText' -import { loadMoreChildrenCommand } from './commands/loadMoreChildren' -import { checkExplorerForDefaultRegion } from './defaultRegion' -import { ToolView } from './toolView' -import { telemetry } from '../shared/telemetry/telemetry' -import { CdkRootNode } from '../awsService/cdk/explorer/rootNode' -import { CodeCatalystRootNode } from '../codecatalyst/explorer' -import { CodeCatalystAuthenticationProvider } from '../codecatalyst/auth' -import { S3FolderNode } from '../awsService/s3/explorer/s3FolderNode' -import { AmazonQNode, refreshAmazonQ, refreshAmazonQRootNode } from '../amazonq/explorer/amazonQTreeNode' -import { activateViewsShared, registerToolView } from './activationShared' -import { isExtensionInstalled } from '../shared/utilities/vsCodeUtils' -import { CommonAuthViewProvider } from '../login/webview/commonAuthViewProvider' -import { setContext } from '../shared/vscode/setContext' -import { TreeNode } from '../shared/treeview/resourceTreeDataProvider' -import { getSourceNode } from '../shared/utilities/treeNodeUtils' -import { openAwsCFNConsoleCommand, openAwsConsoleCommand } from '../shared/awsConsole' -import { StackNameNode } from '../awsService/appBuilder/explorer/nodes/deployedStack' -import { LambdaFunctionNodeDecorationProvider } from '../lambda/explorer/lambdaFunctionNodeDecorationProvider' - -/** - * Activates the AWS Explorer UI and related functionality. - * - * IMPORTANT: Views that should work in all vscode environments (node or web) - * should be setup in {@link activateViewsShared}. - */ -export async function activate(args: { - context: ExtContext - regionProvider: RegionProvider - toolkitOutputChannel: vscode.OutputChannel -}): Promise { - const awsExplorer = new AwsExplorer(globals.context, args.regionProvider) - - const view = vscode.window.createTreeView(awsExplorer.viewProviderId, { - treeDataProvider: awsExplorer, - showCollapseAll: true, - }) - view.onDidExpandElement((element) => { - if (element.element instanceof S3FolderNode) { - globals.globalState.tryUpdate('aws.lastTouchedS3Folder', { - bucket: element.element.bucket, - folder: element.element.folder, - }) - } - if (element.element.serviceId) { - telemetry.aws_expandExplorerNode.emit({ serviceType: element.element.serviceId, result: 'Succeeded' }) - } - }) - globals.context.subscriptions.push( - view, - vscode.window.registerFileDecorationProvider(LambdaFunctionNodeDecorationProvider.getInstance()) - ) - - await registerAwsExplorerCommands(args.context, awsExplorer, args.toolkitOutputChannel) - - telemetry.vscode_activeRegions.emit({ value: args.regionProvider.getExplorerRegions().length }) - - args.context.extensionContext.subscriptions.push( - args.context.awsContext.onDidChangeContext(async (credentialsChangedEvent) => { - getLogger().verbose(`Credentials changed (${credentialsChangedEvent.profileName}), updating AWS Explorer`) - awsExplorer.refresh() - - if (credentialsChangedEvent.profileName) { - await checkExplorerForDefaultRegion(args.regionProvider, awsExplorer) - } - }) - ) - - const authProvider = CodeCatalystAuthenticationProvider.fromContext(args.context.extensionContext) - const codecatalystViewNode: ToolView[] = [] - let codecatalystNode: CodeCatalystRootNode | undefined - - const shouldShowCodeCatalyst = !(isCloud9('classic') || isSageMaker()) - if (shouldShowCodeCatalyst) { - codecatalystNode = new CodeCatalystRootNode(authProvider) - codecatalystViewNode.push({ - nodes: [codecatalystNode], - view: 'aws.codecatalyst', - refreshCommands: [ - (provider) => { - codecatalystNode!.addRefreshEmitter(() => provider.refresh()) - }, - ], - }) - } - // CodeCatalyst view may not be present. Wrap VS Code-owned command to avoid warning toasts if missing - args.context.extensionContext.subscriptions.push( - Commands.register(`aws.codecatalyst.maybeFocus`, async () => { - if (shouldShowCodeCatalyst) { - // vs code-owned command - await vscode.commands.executeCommand('aws.codecatalyst.focus') - } - }) - ) - - const amazonQViewNode: ToolView[] = [] - if ( - isExtensionInstalled(VSCODE_EXTENSION_ID.amazonq) || - globals.globalState.get('aws.toolkit.amazonq.dismissed') - ) { - await setContext('aws.toolkit.amazonq.dismissed', true) - } - - // We should create the tree even if it's dismissed, in case the user installs Amazon Q later. - amazonQViewNode.push({ - nodes: [AmazonQNode.instance], - view: 'aws.amazonq.codewhisperer', - refreshCommands: [refreshAmazonQ, refreshAmazonQRootNode], - }) - - const viewNodes: ToolView[] = [ - ...amazonQViewNode, - ...codecatalystViewNode, - { nodes: [CdkRootNode.instance], view: 'aws.cdk', refreshCommands: [CdkRootNode.instance.refreshCdkExplorer] }, - ] - for (const viewNode of viewNodes) { - registerToolView(viewNode, args.context.extensionContext) - } - - const toolkitAuthProvider = new CommonAuthViewProvider(args.context.extensionContext, 'toolkit') - args.context.extensionContext.subscriptions.push( - vscode.window.registerWebviewViewProvider(toolkitAuthProvider.viewType, toolkitAuthProvider, { - webviewOptions: { - retainContextWhenHidden: true, - }, - }), - // Hacky way for a webview to call setLoginService(). - vscode.commands.registerCommand('aws.explorer.setLoginService', (serviceToShow?: string) => { - if (toolkitAuthProvider.webView && 'setLoginService' in toolkitAuthProvider.webView.server) { - toolkitAuthProvider.webView.server.setLoginService(serviceToShow) - } - }) - ) -} - -async function registerAwsExplorerCommands( - context: ExtContext, - awsExplorer: AwsExplorer, - toolkitOutputChannel: vscode.OutputChannel -): Promise { - context.extensionContext.subscriptions.push( - Commands.register({ id: 'aws.showRegion', autoconnect: false }, async () => { - try { - await globals.awsContextCommands.onCommandShowRegion() - } finally { - telemetry.aws_setRegion.emit() - telemetry.vscode_activeRegions.emit({ value: awsExplorer.getRegionNodesSize() }) - } - }), - Commands.register({ id: 'aws.refreshAwsExplorer', autoconnect: true }, async (passive: boolean = false) => { - awsExplorer.refresh() - - if (!passive) { - telemetry.aws_refreshExplorer.emit() - } - }), - Commands.register( - { id: 'aws.deleteCloudFormation', autoconnect: true }, - async (node: CloudFormationStackNode) => - await deleteCloudFormation(() => awsExplorer.refresh(node.parent), node) - ), - Commands.register( - { id: 'aws.downloadStateMachineDefinition', autoconnect: true }, - async (node: StateMachineNode) => - await downloadStateMachineDefinition({ - stateMachineNode: node, - outputChannel: toolkitOutputChannel, - }) - ) - ) - - context.extensionContext.subscriptions.push( - Commands.register( - 'aws.executeStateMachine', - async (node: StateMachineNode) => await executeStateMachine(context, node) - ), - Commands.register('aws.copyArn', async (node: AWSResourceNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await copyTextCommand(sourceNode, 'ARN') - }), - Commands.register('aws.copyName', async (node: AWSResourceNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await copyTextCommand(sourceNode, 'name') - }), - Commands.register('aws.openAwsConsole', async (node: AWSResourceNode | TreeNode) => { - const sourceNode = getSourceNode(node) - await openAwsConsoleCommand(sourceNode) - }), - Commands.register('aws.openAwsCFNConsole', async (node: StackNameNode) => { - await openAwsCFNConsoleCommand(node) - }), - Commands.register('aws.refreshAwsExplorerNode', async (element: AWSTreeNodeBase | undefined) => { - awsExplorer.refresh(element) - }), - loadMoreChildrenCommand.register(awsExplorer) - ) -} diff --git a/packages/core/src/awsexplorer/activationShared.ts b/packages/core/src/awsexplorer/activationShared.ts deleted file mode 100644 index 9f488a35c3..0000000000 --- a/packages/core/src/awsexplorer/activationShared.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { createToolView, ToolView } from './toolView' -import { telemetry } from '../shared/telemetry/telemetry' -import { CdkRootNode } from '../awsService/cdk/explorer/rootNode' - -/** - * Activates vscode Views (eg tree view) that work in any vscode environment (nodejs or browser). - */ -export async function activateViewsShared(context: vscode.ExtensionContext): Promise {} - -export function registerToolView(viewNode: ToolView, context: vscode.ExtensionContext) { - const toolView = createToolView(viewNode) - context.subscriptions.push(toolView) - if (viewNode.view === 'aws.cdk') { - // Legacy CDK behavior. Mostly useful for C9 as they do not have inline buttons. - toolView.onDidChangeVisibility(({ visible }) => visible && CdkRootNode.instance.refresh()) - } - - toolView.onDidExpandElement((e) => { - if (e.element.resource instanceof CdkRootNode) { - // Legacy CDK metric, remove this when we add something generic - telemetry.cdk_appExpanded.emit() - } - }) - - return toolView -} diff --git a/packages/core/src/awsexplorer/awsExplorer.ts b/packages/core/src/awsexplorer/awsExplorer.ts deleted file mode 100644 index 6370752717..0000000000 --- a/packages/core/src/awsexplorer/awsExplorer.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { Auth } from '../auth/auth' -import { getIdeProperties } from '../shared/extensionUtilities' -import { getIcon } from '../shared/icons' -import { getLogger, Logger } from '../shared/logger/logger' -import { RegionProvider } from '../shared/regions/regionProvider' -import { RefreshableAwsTreeProvider } from '../shared/treeview/awsTreeProvider' -import { AWSCommandTreeNode } from '../shared/treeview/nodes/awsCommandTreeNode' -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { makeChildrenNodes, TreeShim } from '../shared/treeview/utils' -import { intersection, toMap, updateInPlace } from '../shared/utilities/collectionUtils' -import { once } from '../shared/utilities/functionUtils' -import { localize } from '../shared/utilities/vsCodeUtils' -import { RegionNode } from './regionNode' -import { AuthNode } from '../auth/utils' -import { Commands } from '../shared/vscode/commands2' - -export class AwsExplorer implements vscode.TreeDataProvider, RefreshableAwsTreeProvider { - public viewProviderId: string = 'aws.explorer' - public readonly onDidChangeTreeData: vscode.Event - private readonly logger: Logger = getLogger() - private readonly _onDidChangeTreeData: vscode.EventEmitter - private readonly regionNodes: Map - - private readonly rootNodeAddRegion = new AWSCommandTreeNode( - undefined, - localize('AWS.explorerNode.addRegion', 'Add regions to {0} Explorer...', getIdeProperties().company), - 'aws.showRegion', - undefined, - localize( - 'AWS.explorerNode.addRegion.tooltip', - 'Click here to add a region to {0} Explorer.', - getIdeProperties().company - ) - ) - - public constructor( - private readonly extContext: vscode.ExtensionContext, - private readonly regionProvider: RegionProvider, - private readonly auth = Auth.instance - ) { - this._onDidChangeTreeData = new vscode.EventEmitter() - this.onDidChangeTreeData = this._onDidChangeTreeData.event - this.regionNodes = new Map() - - this.extContext.subscriptions.push( - this.regionProvider.onDidChange(() => { - this.logger.verbose('Refreshing AWS Explorer due to Region Provider updates') - this.refresh() - }), - this.auth.onDidChangeActiveConnection(() => this.refresh()) - ) - } - - public getTreeItem(element: AWSTreeNodeBase): vscode.TreeItem { - return element - } - - public async getChildren(element?: AWSTreeNodeBase): Promise { - if (element) { - this.regionProvider.setLastTouchedRegion(element.regionCode) - } - - let childNodes: AWSTreeNodeBase[] = [] - - try { - if (element) { - childNodes = childNodes.concat(await element.getChildren()) - } else { - childNodes = childNodes.concat(await this.getRootNodes()) - } - } catch (err) { - const error = err as Error - this.logger.error(`Error getting children for node %O: %s`, element?.label ?? 'Root Node', error) - - childNodes.splice( - 0, - childNodes.length, - new AWSCommandTreeNode( - element, - localize('AWS.explorerNode.error.retry', 'Unable to get child nodes, click here to retry'), - 'aws.refreshAwsExplorerNode', - [element], - error.message - ) - ) - } - - return childNodes - } - - public getRegionNodesSize(): number { - return this.regionNodes.size - } - - public refresh(node?: AWSTreeNodeBase): void { - this._onDidChangeTreeData.fire(node) - } - - private readonly getAuthNode = once(() => new TreeShim(new AuthNode(this.auth))) - private async getRootNodes(): Promise { - const conn = this.auth.activeConnection - if (conn !== undefined && conn.type !== 'iam') { - // TODO: this should show up as a child node? - const selectIamNode = (await Commands.getOrThrow('_aws.toolkit.auth.useIamCredentials')) - .build(this.auth) - .asTreeNode({ - // label: `No IAM credentials linked to ${conn.label}`, - // iconPath: getIcon('vscode-circle-slash'), - label: 'Select IAM Credentials to View Resources', - iconPath: getIcon('vscode-sync'), - }) - - return [this.getAuthNode(), new TreeShim(selectIamNode)] - } else if (conn === undefined || conn.state !== 'valid') { - return [this.getAuthNode()] - } - - const partitionRegions = this.regionProvider.getRegions() - const userVisibleRegionCodes = this.regionProvider.getExplorerRegions() - const regionMap = toMap(partitionRegions, (r) => r.id) - - updateInPlace( - this.regionNodes, - intersection(regionMap.keys(), userVisibleRegionCodes), - (key) => this.regionNodes.get(key)!.update(regionMap.get(key)!), - (key) => new RegionNode(regionMap.get(key)!, this.regionProvider) - ) - - if (this.regionNodes.size === 0) { - return [this.getAuthNode(), this.rootNodeAddRegion] - } - - return await makeChildrenNodes({ - getChildNodes: async () => [this.getAuthNode(), ...this.regionNodes.values()], - sort: (a, b) => - a instanceof TreeShim ? -1 : b instanceof TreeShim ? 1 : a.regionName.localeCompare(b.regionName), - }) - } -} diff --git a/packages/core/src/awsexplorer/childNodeCache.ts b/packages/core/src/awsexplorer/childNodeCache.ts deleted file mode 100644 index da4bbbd851..0000000000 --- a/packages/core/src/awsexplorer/childNodeCache.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { ChildNodePage } from './childNodeLoader' - -/** - * The state for a Node with paginated children. - * - * Allows for easier appending to the node's children and less error-prone (resetting) of the node's internal state. - */ -export class ChildNodeCache { - private _children: T[] = [] - private _continuationToken: string | undefined = undefined - private _isPristine: boolean = true - - /** - * Appends a new page to the cache. - * - * Once this has been called, the cache is no longer considered pristine ({@link isPristine} will return false). - * This is true even if the original state of the cache remains unchanged (all items appended are empty/undefined). - */ - public appendPage(page: ChildNodePage): void { - this._children = [...this._children, ...page.newChildren] - this._continuationToken = page.newContinuationToken - this._isPristine = false - } - - /** - * The list of children nodes previously appended to the cache. - */ - public get children(): T[] { - return this._children - } - - /** - * The continuation token that was last written to the cache. - */ - public get continuationToken(): string | undefined { - return this._continuationToken - } - - /** - * Returns true if the cache is completely new and untouched (apart from construction). - * - * Once {@link appendPage} has been called, the cache is no longer considered pristine. - * This is true even if the original state of the cache remains unchanged (all items appended are empty/undefined). - */ - public get isPristine(): boolean { - return this._isPristine - } -} diff --git a/packages/core/src/awsexplorer/childNodeLoader.ts b/packages/core/src/awsexplorer/childNodeLoader.ts deleted file mode 100644 index bb3997a558..0000000000 --- a/packages/core/src/awsexplorer/childNodeLoader.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../shared/treeview/nodes/loadMoreNode' -import { MoreResultsNode } from './moreResultsNode' -import { ChildNodeCache } from './childNodeCache' -import AsyncLock from 'async-lock' - -const lockKey = 'ChildNodeLoader' - -export interface ChildNodePage { - newChildren: T[] - newContinuationToken: string | undefined -} - -/** - * Controls loading paginated children for LoadMore nodes. - */ -export class ChildNodeLoader { - private readonly loadPage: (continuationToken: string | undefined) => Promise> - private readonly moreResults: MoreResultsNode - private readonly loadChildrenLock: AsyncLock - private cache: ChildNodeCache - - public constructor( - parent: AWSTreeNodeBase & LoadMoreNode, - loadPage: (continuationToken: string | undefined) => Promise> - ) { - this.loadPage = loadPage - this.moreResults = new MoreResultsNode(parent) - this.loadChildrenLock = new AsyncLock() - this.cache = new ChildNodeCache() - } - - /** - * Gets the initial or previously-loaded children. - */ - public async getChildren(): Promise<(T | MoreResultsNode)[]> { - await this.loadMoreChildrenIf(() => !this.initialChildrenLoaded()) - return this.getExistingChildren() - } - - /** - * Loads and appends a new page of children. - * - * If there is no new page of children, has no effect. - */ - public async loadMoreChildren(): Promise { - return this.loadMoreChildrenIf(() => !this.allChildrenLoaded()) - } - - /** - * Returns true if a {@link loadMoreChildren} call is in progress. - */ - public isLoadingMoreChildren(): boolean { - return this.loadChildrenLock.isBusy(lockKey) - } - - /** - * Clears all previously-loaded children. - */ - public clearChildren(): void { - this.cache = new ChildNodeCache() - } - - private initialChildrenLoaded(): boolean { - return !this.cache.isPristine - } - - private allChildrenLoaded(): boolean { - return this.initialChildrenLoaded() && this.cache.continuationToken === undefined - } - - private getExistingChildren(): (T | MoreResultsNode)[] { - if (this.cache.continuationToken !== undefined) { - return [...this.cache.children, this.moreResults] - } - - return this.cache.children - } - - /** - * Prevents multiple concurrent attempts to load next page. - * - * This can happen if the user double clicks a node that executes a command before the node is hidden. - * In this case, the attempts are queued up. - * - * @param condition a double checked condition that must evaluate to true for the page load to take place. - */ - private async loadMoreChildrenIf(condition: () => boolean): Promise { - if (condition()) { - return this.loadChildrenLock.acquire(lockKey, async () => { - if (condition()) { - const newPage = await this.loadPage(this.cache.continuationToken) - this.cache.appendPage(newPage) - } - }) - } - } -} diff --git a/packages/core/src/awsexplorer/commands/copyText.ts b/packages/core/src/awsexplorer/commands/copyText.ts deleted file mode 100644 index 4fbb0244bb..0000000000 --- a/packages/core/src/awsexplorer/commands/copyText.ts +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { copyToClipboard } from '../../shared/utilities/messages' -import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode' -import { TreeShim } from '../../shared/treeview/utils' - -export type copyableText = 'ARN' | 'name' | 'id' - -export async function copyTextCommand( - node: AWSResourceNode | TreeShim, - text: copyableText -): Promise { - node = node instanceof TreeShim ? node.node.resource : node - switch (text) { - case 'ARN': - await copyToClipboard(node.arn, text) - break - case 'name': - await copyToClipboard(node.name, text) - break - case 'id': - await copyToClipboard(node.id!, text) - break - } -} diff --git a/packages/core/src/awsexplorer/commands/loadMoreChildren.ts b/packages/core/src/awsexplorer/commands/loadMoreChildren.ts deleted file mode 100644 index 6bc37b770e..0000000000 --- a/packages/core/src/awsexplorer/commands/loadMoreChildren.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { localize } from '../../shared/utilities/vsCodeUtils' -import { getLogger } from '../../shared/logger/logger' -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../shared/treeview/nodes/loadMoreNode' -import { AwsExplorer } from '../awsExplorer' -import { ToolkitError } from '../../shared/errors' -import { Commands } from '../../shared/vscode/commands2' - -/** - * Loads more children for the given node. - */ -export async function loadMoreChildren(awsExplorer: AwsExplorer, node: AWSTreeNodeBase & LoadMoreNode): Promise { - // This can happen if the user double clicks a node that executes this command before the node is hidden - if (node.isLoadingMoreChildren()) { - getLogger().debug('LoadMoreChildren already in progress. Ignoring.') - return - } - - try { - await node.loadMoreChildren() - } catch (e) { - const message = localize('AWS.explorerNode.loadMoreChildren.error', 'Error loading more resources') - throw ToolkitError.chain(e, message) - } finally { - awsExplorer.refresh(node) - } -} - -export const loadMoreChildrenCommand = Commands.declare('aws.loadMoreChildren', (explorer: AwsExplorer) => { - return loadMoreChildren.bind(undefined, explorer) -}) diff --git a/packages/core/src/awsexplorer/defaultRegion.ts b/packages/core/src/awsexplorer/defaultRegion.ts deleted file mode 100644 index be28e4ec6d..0000000000 --- a/packages/core/src/awsexplorer/defaultRegion.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AwsExplorer } from './awsExplorer' -import { RegionProvider } from '../shared/regions/regionProvider' - -export async function checkExplorerForDefaultRegion( - regionProvider: RegionProvider, - awsExplorer: AwsExplorer -): Promise { - const profileRegion = regionProvider.defaultRegionId - - const explorerRegions = new Set(regionProvider.getExplorerRegions()) - if (explorerRegions.has(profileRegion)) { - return - } - - await regionProvider.updateExplorerRegions([...explorerRegions, profileRegion]) - awsExplorer.refresh() -} diff --git a/packages/core/src/awsexplorer/moreResultsNode.ts b/packages/core/src/awsexplorer/moreResultsNode.ts deleted file mode 100644 index de09541aed..0000000000 --- a/packages/core/src/awsexplorer/moreResultsNode.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../shared/treeview/nodes/loadMoreNode' -import { localize } from '../shared/utilities/vsCodeUtils' -import { inspect } from 'util' -import { loadMoreChildrenCommand } from './commands/loadMoreChildren' - -/** - * Represents the a "Load More..." node that appears as the last child of nodes more results. - * - * Clicking the node executes the Load More command for the parent Node. - */ -export class MoreResultsNode extends AWSTreeNodeBase { - // Parent is a get method to prevent circular referencing - public constructor(public parent: AWSTreeNodeBase & LoadMoreNode) { - super(localize('AWS.explorerNode.loadMoreChildren', 'Load More...')) - - this.command = loadMoreChildrenCommand.build(parent).asCommand({ - title: localize('AWS.explorerNode.loadMoreChildren', 'Load More...'), - }) - this.contextValue = 'awsMoreResultsNode' - } - - public [inspect.custom](): string { - return `MoreResultsNode (parent=${this.parent})` - } -} diff --git a/packages/core/src/awsexplorer/regionNode.ts b/packages/core/src/awsexplorer/regionNode.ts deleted file mode 100644 index d78bcbec2a..0000000000 --- a/packages/core/src/awsexplorer/regionNode.ts +++ /dev/null @@ -1,182 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { TreeItemCollapsibleState } from 'vscode' -import { ApiGatewayNode } from '../awsService/apigateway/explorer/apiGatewayNodes' -import { SchemasNode } from '../eventSchemas/explorer/schemasNode' -import { CloudFormationNode } from '../lambda/explorer/cloudFormationNodes' -import { CloudWatchLogsNode } from '../awsService/cloudWatchLogs/explorer/cloudWatchLogsNode' -import { LambdaNode } from '../lambda/explorer/lambdaNodes' -import { S3Node } from '../awsService/s3/explorer/s3Nodes' -import { EcrNode } from '../awsService/ecr/explorer/ecrNode' -import { RedshiftNode } from '../awsService/redshift/explorer/redshiftNode' -import { IotNode } from '../awsService/iot/explorer/iotNodes' -import { Region } from '../shared/regions/endpoints' -import { defaultPartition, RegionProvider } from '../shared/regions/regionProvider' -import { AWSTreeNodeBase } from '../shared/treeview/nodes/awsTreeNodeBase' -import { StepFunctionsNode } from '../stepFunctions/explorer/stepFunctionsNodes' -import { SsmDocumentNode } from '../ssmDocument/explorer/ssmDocumentNode' -import { ResourcesNode } from '../dynamicResources/explorer/nodes/resourcesNode' -import { AppRunnerNode } from '../awsService/apprunner/explorer/apprunnerNode' -import { DocumentDBNode } from '../docdb/explorer/docdbNode' -import { DefaultDocumentDBClient } from '../shared/clients/docdbClient' -import { AppRunnerClient } from '../shared/clients/apprunner' -import { DefaultEcrClient } from '../shared/clients/ecrClient' -import { DefaultRedshiftClient } from '../shared/clients/redshiftClient' -import { DefaultIotClient } from '../shared/clients/iotClient' -import { S3Client } from '../shared/clients/s3' -import { DefaultSchemaClient } from '../shared/clients/schemaClient' -import { getEcsRootNode } from '../awsService/ecs/model' -import { compareTreeItems, TreeShim } from '../shared/treeview/utils' -import { Ec2ParentNode } from '../awsService/ec2/explorer/ec2ParentNode' -import { Ec2Client } from '../shared/clients/ec2' -import { SagemakerParentNode } from '../awsService/sagemaker/explorer/sagemakerParentNode' -import { SagemakerClient } from '../shared/clients/sagemaker' - -interface ServiceNode { - allRegions?: boolean - serviceId: string - /** - * Decides if the node should be shown. Example: - * ``` - * when: () => DevSettings.instance.isDevMode() - * ``` - */ - when?: () => boolean - createFn: (regionCode: string, partitionId: string) => any -} - -const serviceCandidates: ServiceNode[] = [ - { - serviceId: 'apigateway', - createFn: (regionCode: string, partitionId: string) => new ApiGatewayNode(partitionId, regionCode), - }, - { - serviceId: 'apprunner', - createFn: (regionCode: string) => new AppRunnerNode(regionCode, new AppRunnerClient(regionCode)), - }, - { - serviceId: 'cloudformation', - createFn: (regionCode: string) => new CloudFormationNode(regionCode), - }, - { - serviceId: 'docdb', - createFn: (regionCode: string) => new DocumentDBNode(DefaultDocumentDBClient.create(regionCode)), - }, - { - serviceId: 'logs', - createFn: (regionCode: string) => new CloudWatchLogsNode(regionCode), - }, - { - serviceId: 'ec2', - createFn: (regionCode: string, partitionId: string) => - new Ec2ParentNode(regionCode, partitionId, new Ec2Client(regionCode)), - }, - { - serviceId: 'ecr', - createFn: (regionCode: string) => new EcrNode(new DefaultEcrClient(regionCode)), - }, - { - serviceId: 'redshift', - createFn: (regionCode: string) => new RedshiftNode(new DefaultRedshiftClient(regionCode)), - }, - { - serviceId: 'ecs', - createFn: (regionCode: string) => new TreeShim(getEcsRootNode(regionCode)), - }, - { - serviceId: 'iot', - createFn: (regionCode: string) => new IotNode(new DefaultIotClient(regionCode)), - }, - { - serviceId: 'lambda', - createFn: (regionCode: string) => new LambdaNode(regionCode), - }, - { - serviceId: 's3', - createFn: (regionCode: string) => new S3Node(new S3Client(regionCode)), - }, - { - serviceId: 'api.sagemaker', - createFn: (regionCode: string) => new SagemakerParentNode(regionCode, new SagemakerClient(regionCode)), - }, - { - serviceId: 'schemas', - createFn: (regionCode: string) => new SchemasNode(new DefaultSchemaClient(regionCode)), - }, - { - serviceId: 'states', - createFn: (regionCode: string) => new StepFunctionsNode(regionCode), - }, - { - serviceId: 'ssm', - createFn: (regionCode: string) => new SsmDocumentNode(regionCode), - }, - { - allRegions: true, - serviceId: 'cloudcontrol', - createFn: (regionCode: string) => new ResourcesNode(regionCode), - }, -] - -/** - * An AWS Explorer node representing a region. - * Contains resource types as child nodes (for example, nodes representing - * an account's Lambda Functions and CloudFormation stacks for this region) - */ -export class RegionNode extends AWSTreeNodeBase { - private region: Region - public override readonly regionCode: string - - public get regionName(): string { - return this.region.name - } - - public constructor( - region: Region, - private readonly regionProvider: RegionProvider - ) { - super(region.name, TreeItemCollapsibleState.Expanded) - this.contextValue = 'awsRegionNode' - this.region = region - this.regionCode = region.id - this.update(region) - } - - public override async getChildren(): Promise { - // Services that are candidates to add to the region explorer. - // `serviceId`s are checked against ~/resources/endpoints.json to see whether or not the service is available in the given region. - // If the service is available, we use the `createFn` to generate the node for the region. - // This interface exists so we can add additional nodes to the array (otherwise Typescript types the array to what's already in the array at creation) - const partitionId = this.regionProvider.getPartitionId(this.regionCode) ?? defaultPartition - const childNodes: AWSTreeNodeBase[] = [] - for (const service of serviceCandidates) { - if (service.when !== undefined && !service.when()) { - continue - } - if (service.allRegions || this.regionProvider.isServiceInRegion(service.serviceId, this.regionCode)) { - const node = service.createFn(this.regionCode, partitionId) - if (node !== undefined) { - node.serviceId = service.serviceId - childNodes.push(node) - } - } - } - - return this.sortNodes(childNodes) - } - - private sortNodes(nodes: AWSTreeNodeBase[]) { - return nodes.sort((a, b) => { - // Always sort `ResourcesNode` at the bottom - return a instanceof ResourcesNode ? 1 : b instanceof ResourcesNode ? -1 : compareTreeItems(a, b) - }) - } - public update(region: Region): void { - this.region = region - this.label = this.regionName - this.tooltip = `${this.regionName} [${this.regionCode}]` - } -} diff --git a/packages/core/src/awsexplorer/toolView.ts b/packages/core/src/awsexplorer/toolView.ts deleted file mode 100644 index 99d20fbdb3..0000000000 --- a/packages/core/src/awsexplorer/toolView.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ResourceTreeDataProvider, TreeNode } from '../shared/treeview/resourceTreeDataProvider' - -export interface ToolView { - nodes: TreeNode[] - view: string - refreshCommands: ((provider: ResourceTreeDataProvider) => void)[] -} - -/** - * The 'local' explorer is represented as 'Developer Tools' in the UI. We use a different name within - * source code to differentiate between _Toolkit developers_ and _Toolkit users_. - * - * Components placed under this view do not strictly need to be 'local'. They just need to place greater - * emphasis on the developer's local development environment. - */ -export function createToolView(viewNode: ToolView): vscode.TreeView { - const treeDataProvider = new ResourceTreeDataProvider({ getChildren: () => getChildren(viewNode.nodes) }) - for (const refreshCommand of viewNode.refreshCommands ?? []) { - refreshCommand(treeDataProvider) - } - - return vscode.window.createTreeView(viewNode.view, { treeDataProvider }) -} - -async function getChildren(roots: TreeNode[]) { - const nodes: TreeNode[] = [] - - for (const node of roots) { - if (node.getChildren) { - nodes.push(...(await node.getChildren())) - } - } - - return nodes -} diff --git a/packages/core/src/codecatalyst/activation.ts b/packages/core/src/codecatalyst/activation.ts deleted file mode 100644 index db5ff9e12f..0000000000 --- a/packages/core/src/codecatalyst/activation.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as uriHandlers from './uriHandlers' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { ExtContext } from '../shared/extensions' -import { CodeCatalystRemoteSourceProvider } from './repos/remoteSourceProvider' -import { CodeCatalystCommands, codecatalystConnectionsCmd } from './commands' -import { GitExtension } from '../shared/extensions/git' -import { CodeCatalystAuthenticationProvider } from './auth' -import { registerDevfileWatcher, updateDevfileCommand } from './devfile' -import { DevEnvClient } from '../shared/clients/devenvClient' -import { watchRestartingDevEnvs } from './reconnect' -import { ToolkitPromptSettings } from '../shared/settings' -import { dontShow } from '../shared/localizedText' -import { getIdeProperties } from '../shared/extensionUtilities' -import { Commands } from '../shared/vscode/commands2' -import { getCodeCatalystConfig } from '../shared/clients/codecatalystClient' -import { isDevenvVscode } from './utils' -import { codeCatalystConnectCommand, getThisDevEnv } from './model' -import { getLogger } from '../shared/logger/logger' -import { DevEnvActivityStarter } from './devEnv' -import { learnMoreCommand, onboardCommand, reauth } from './explorer' -import { isInDevEnv } from '../shared/vscode/env' -import { hasScopes, scopesCodeWhispererCore, getTelemetryMetadataForConn } from '../auth/connection' -import { telemetry } from '../shared/telemetry/telemetry' -import { asStringifiedStack } from '../shared/telemetry/spans' - -const localize = nls.loadMessageBundle() - -/** - * Activate CodeCatalyst functionality. - */ -export async function activate(ctx: ExtContext): Promise { - const authProvider = CodeCatalystAuthenticationProvider.fromContext(ctx.extensionContext) - const commands = new CodeCatalystCommands(authProvider) - const remoteSourceProvider = new CodeCatalystRemoteSourceProvider(commands, authProvider) - - codeCatalystConnectCommand.register() - reauth.register() - onboardCommand.register() - updateDevfileCommand.register() - learnMoreCommand.register() - - await authProvider.restore() - - // Forget Amazon Q connections while we transition to separate auth sessions per extension. - // Note: credentials on disk in the dev env cannot have Q scopes, so it will never be forgotten. - // TODO: Remove after some time? - if (authProvider.isConnected() && hasScopes(authProvider.activeConnection!, scopesCodeWhispererCore)) { - await telemetry.function_call.run( - async () => { - await telemetry.auth_modifyConnection.run(async () => { - const conn = authProvider.activeConnection - telemetry.record({ - action: 'forget', - source: asStringifiedStack(telemetry.getFunctionStack()), - connectionState: conn ? authProvider.auth.getConnectionState(conn) : undefined, - ...(await getTelemetryMetadataForConn(conn)), - }) - - await authProvider.secondaryAuth.forgetConnection() - }) - }, - { emit: false, functionId: { name: 'activate', class: 'CodeCatalyst' } } - ) - } - - ctx.extensionContext.subscriptions.push( - uriHandlers.register(ctx.uriHandler, CodeCatalystCommands.declared), - ...Object.values(CodeCatalystCommands.declared).map((c) => c.register(commands)), - codecatalystConnectionsCmd.register(), - Commands.register('aws.codecatalyst.signout', () => { - return authProvider.secondaryAuth.deleteConnection() - }) - ) - - await GitExtension.instance.registerRemoteSourceProvider(remoteSourceProvider).then((disposable) => { - ctx.extensionContext.subscriptions.push(disposable) - }) - - await GitExtension.instance - .registerCredentialsProvider({ - getCredentials(uri: vscode.Uri) { - if (uri.authority.endsWith(getCodeCatalystConfig().gitHostname)) { - return commands.withClient((client) => authProvider.getCredentialsForGit(client)) - } - }, - }) - .then((disposable) => ctx.extensionContext.subscriptions.push(disposable)) - - watchRestartingDevEnvs(ctx, authProvider) - - const thisDevenv = (await getThisDevEnv(authProvider))?.unwrapOrElse((err) => { - getLogger().error('codecatalyst: failed to get current Dev Enviroment: %s', err) - return undefined - }) - - if (!thisDevenv) { - if (isInDevEnv()) { - getLogger().info('codecatalyst: Dev Environment timeout=unknown') - } else { - getLogger().verbose('codecatalyst: not a Dev Environment ($__DEV_ENVIRONMENT_ID is undefined)') - } - } else { - ctx.extensionContext.subscriptions.push(DevEnvClient.instance) - if (DevEnvClient.instance.id) { - ctx.extensionContext.subscriptions.push(registerDevfileWatcher(DevEnvClient.instance)) - } - - const timeoutMin = thisDevenv.summary.inactivityTimeoutMinutes - const timeout = timeoutMin === 0 ? 'never' : `${timeoutMin} min` - getLogger().info('codecatalyst: Dev Environment timeout=%s, ides=%O', timeout, thisDevenv.summary.ides) - if (thisDevenv && !isDevenvVscode(thisDevenv.summary.ides)) { - // Prevent Toolkit from reconnecting to a "non-vscode" devenv by actively closing it. - // Can happen if devenv is switched to ides="cloud9", etc. - // TODO: Is this needed without cloud9 check? - void vscode.commands.executeCommand('workbench.action.remote.close') - return - } - - await showReadmeFileOnFirstLoad(ctx.extensionContext.workspaceState) - - const settings = ToolkitPromptSettings.instance - if (settings.isPromptEnabled('remoteConnected')) { - const message = localize( - 'AWS.codecatalyst.connectedMessage', - 'Welcome to your Amazon CodeCatalyst Dev Environment. For more options and information, view Dev Environment settings ({0} Extension > CodeCatalyst).', - getIdeProperties().company - ) - const openDevEnvSettings = localize('AWS.codecatalyst.openDevEnvSettings', 'Open Dev Environment Settings') - void vscode.window.showInformationMessage(message, dontShow, openDevEnvSettings).then(async (selection) => { - if (selection === dontShow) { - await settings.disablePrompt('remoteConnected') - } else if (selection === openDevEnvSettings) { - await CodeCatalystCommands.declared.openDevEnvSettings.execute() - } - }) - } - } - - // This must always be called on activation - DevEnvActivityStarter.init(authProvider) -} - -async function showReadmeFileOnFirstLoad(workspaceState: vscode.ExtensionContext['workspaceState']): Promise { - getLogger().debug('codecatalyst: showReadmeFileOnFirstLoad()') - // Check dev env state to see if this is the first time the user has connected to a dev env - const isFirstLoad = workspaceState.get('aws.codecatalyst.devEnv.isFirstLoad', true) - - if (!isFirstLoad) { - getLogger().info('codecatalyst: is not first load, skipping showing README.md') - return - } - - // Determine expected readme file location - const readmePath = `README.md` - - // Find readme file in workspace - const readmeUri = await vscode.workspace.findFiles(readmePath).then((files) => { - if (files.length === 0) { - return undefined - } - return files[0] - }) - - if (readmeUri === undefined) { - getLogger().debug(`codecatalyst: README.md not found in path '${readmePath}'`) - return - } - - // Show rendered readme file to user - await vscode.commands.executeCommand('markdown.showPreview', readmeUri) - - await workspaceState.update('aws.codecatalyst.devEnv.isFirstLoad', false) -} diff --git a/packages/core/src/codecatalyst/auth.ts b/packages/core/src/codecatalyst/auth.ts deleted file mode 100644 index f1d3ea8295..0000000000 --- a/packages/core/src/codecatalyst/auth.ts +++ /dev/null @@ -1,430 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { onAccessDeniedException, CodeCatalystClient, createClient } from '../shared/clients/codecatalystClient' -import { Auth } from '../auth/auth' -import * as localizedText from '../shared/localizedText' -import { getSecondaryAuth, setScopes } from '../auth/secondaryAuth' -import { getLogger } from '../shared/logger/logger' -import globals from '../shared/extensionGlobals' -import { ToolkitError, isAwsError } from '../shared/errors' -import { MetricName, MetricShapes, telemetry } from '../shared/telemetry/telemetry' -import { openUrl } from '../shared/utilities/vsCodeUtils' -import { - scopesSsoAccountAccess, - scopesCodeCatalyst, - SsoConnection, - Connection, - isBuilderIdConnection, - isSsoConnection, - createSsoProfile, - isValidCodeCatalystConnection, - isIdcSsoConnection, - hasExactScopes, -} from '../auth/connection' -import { createBuilderIdConnection } from '../auth/utils' -import { showReauthenticateMessage } from '../shared/utilities/messages' -import { ToolkitPromptSettings } from '../shared/settings' -import { setContext } from '../shared/vscode/setContext' -import { withTelemetryContext } from '../shared/telemetry/util' -import { builderIdStartUrl } from '../auth/sso/constants' - -// Secrets stored on the macOS keychain appear as individual entries for each key -// This is fine so long as the user has only a few accounts. Otherwise this should -// store secrets as a map. -export class CodeCatalystAuthStorage { - public constructor(private readonly secrets: vscode.SecretStorage) {} - - public async getPat(username: string): Promise { - return this.secrets.get(`codecatalyst.pat.${username}`) - } - - public async storePat(username: string, pat: string): Promise { - await this.secrets.store(`codecatalyst.pat.${username}`, pat) - } -} - -export const onboardingUrl = vscode.Uri.parse('https://codecatalyst.aws/onboarding/view') - -/** - * AWS account scopes are intended to be included. Some codepaths that use defaultScopes may depend on these scopes. - */ -export const defaultScopes = [...scopesSsoAccountAccess, ...scopesCodeCatalyst] - -export const isUpgradeableConnection = (conn: Connection): conn is SsoConnection => - isSsoConnection(conn) && !isValidCodeCatalystConnection(conn) - -export function setCodeCatalystConnectedContext(isConnected: boolean) { - return setContext('aws.codecatalyst.connected', isConnected) -} - -type ConnectionState = { - onboarded: boolean - scopeExpired: boolean -} - -const authClassName = 'AuthCodeCatalyst' - -export class CodeCatalystAuthenticationProvider { - public readonly onDidChangeActiveConnection = this.secondaryAuth.onDidChangeActiveConnection - public readonly onAccessDeniedException = onAccessDeniedException - private readonly onDidChangeEmitter = new vscode.EventEmitter() - public readonly onDidChange = this.onDidChangeEmitter.event - - public constructor( - protected readonly storage: CodeCatalystAuthStorage, - public readonly auth = Auth.instance, - public readonly secondaryAuth = getSecondaryAuth( - auth, - 'codecatalyst', - 'CodeCatalyst', - isValidCodeCatalystConnection - ) - ) { - this.onDidChangeActiveConnection(async () => { - if (this.activeConnection) { - await this.setScopeExpired(this.activeConnection, false) - } - await setCodeCatalystConnectedContext(this.isConnectionValid()) - this.onDidChangeEmitter.fire() - }) - - this.onAccessDeniedException(async (showReauthPrompt: boolean) => { - await this.accessDeniedExceptionHandler(showReauthPrompt) - this.onDidChangeEmitter.fire() - }) - - // set initial context in case event does not trigger - void setCodeCatalystConnectedContext(this.isConnectionValid()) - } - - public get activeConnection() { - return this.secondaryAuth.activeConnection - } - - public get isUsingSavedConnection() { - return this.secondaryAuth.hasSavedConnection - } - - public async setScopeExpired(conn: SsoConnection, isExpired: boolean) { - await this.updateConnectionState(conn, { scopeExpired: isExpired }) - } - - public isScopeExpired(conn: SsoConnection): boolean { - return this.getConnectionState(conn).scopeExpired - } - - public isConnectionValid(): boolean { - return ( - this.activeConnection !== undefined && - !this.secondaryAuth.isConnectionExpired && - !this.isScopeExpired(this.activeConnection) - ) - } - - // Get rid of this? Not sure where to put PAT code. - public async getPat(client: CodeCatalystClient, username = client.identity.name): Promise { - const stored = await this.storage.getPat(username) - - if (stored) { - return stored - } - - const resp = await client.createAccessToken({ name: 'aws-toolkits-vscode-token' }) - await this.storage.storePat(username, resp.secret) - - return resp.secret - } - - public async getCredentialsForGit(client: CodeCatalystClient) { - getLogger().verbose(`codecatalyst (git): attempting to provide credentials`) - - const username = client.identity.name - - try { - return { - username, - password: await this.getPat(client, username), - } - } catch (err) { - getLogger().verbose(`codecatalyst (git): failed to get credentials for user "${username}": %s`, err) - } - } - - public async restore() { - await this.secondaryAuth.restoreConnection() - } - - private async accessDeniedExceptionHandler(showReauthPrompt: boolean = true) { - if (!this.isConnectionValid()) { - return - } - - await this.setScopeExpired(this.activeConnection!, true) - await setCodeCatalystConnectedContext(this.isConnectionValid()) - - // showReauthPrompt is true primarily when a user interaction triggered the ADE - if (showReauthPrompt) { - void this.showReauthenticationPrompt(this.activeConnection!) - } - } - - public async showReauthenticationPrompt(conn: SsoConnection): Promise { - await showReauthenticateMessage({ - message: localizedText.connectionExpired('CodeCatalyst'), - connect: localizedText.connect, - suppressId: 'codeCatalystConnectionExpired', - settings: ToolkitPromptSettings.instance, - reauthFunc: async () => { - await this.reauthenticate(conn) - }, - }) - } - - public async promptOnboarding(): Promise { - const message = `Using CodeCatalyst requires onboarding with a Space. Sign up with CodeCatalyst to get started.` - const openBrowser = 'Open Browser' - const resp = await vscode.window.showInformationMessage(message, { modal: true }, openBrowser) - if (resp === openBrowser) { - await openUrl(onboardingUrl) - } - - // Mark the current execution as cancelled regardless of the user response. We could poll here instead, waiting - // for the user to onboard. But that might take a while. - throw new ToolkitError('Not onboarded with CodeCatalyst', { code: 'NotOnboarded', cancelled: true }) - } - - /** - * Return a Builder ID connection that works with CodeCatalyst. - * - * This cannot create a Builder ID, but will return an existing Builder ID, - * upgrading the scopes if necessary. - */ - public async tryGetBuilderIdConnection(): Promise { - if (this.activeConnection && isBuilderIdConnection(this.activeConnection)) { - return this.activeConnection - } - - type ConnectionFlowEvent = Partial & { - readonly codecatalyst_connectionFlow: 'Create' | 'Switch' | 'Upgrade' // eslint-disable-line @typescript-eslint/naming-convention - } - - const existingBuilderId = (await this.auth.listConnections()).find(isBuilderIdConnection) - if (isValidCodeCatalystConnection(existingBuilderId)) { - // A Builder ID with the correct scopes already exists so we can use this immediately - await this.secondaryAuth.useNewConnection(existingBuilderId) - return this.activeConnection! - } - - const conn = (await this.auth.listConnections()).find(isBuilderIdConnection) - - if (conn === undefined) { - telemetry.record({ - codecatalyst_connectionFlow: 'Create', - } satisfies ConnectionFlowEvent as MetricShapes[MetricName]) - - const newConn = await createBuilderIdConnection(this.auth, defaultScopes) - if (this.auth.activeConnection?.id !== newConn.id) { - await this.secondaryAuth.useNewConnection(newConn) - } - - return newConn - } - - const upgrade = async () => { - telemetry.record({ - codecatalyst_connectionFlow: 'Upgrade', - } satisfies ConnectionFlowEvent as MetricShapes[MetricName]) - - return this.secondaryAuth.addScopes(conn, defaultScopes) - } - - if (this.auth.activeConnection?.id !== conn.id) { - telemetry.record({ - codecatalyst_connectionFlow: 'Switch', - } satisfies ConnectionFlowEvent as MetricShapes[MetricName]) - - if (isUpgradeableConnection(conn)) { - await upgrade() - } - - return (await this.secondaryAuth.useNewConnection(conn)) as SsoConnection - } - - if (isUpgradeableConnection(conn)) { - return upgrade() - } - - throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnectionBadState' }) - } - - public isConnected(): boolean { - return this.activeConnection !== undefined - } - - public isBuilderIdInUse(): boolean { - return this.isConnected() && isBuilderIdConnection(this.activeConnection) - } - - public isEnterpriseSsoInUse(): boolean { - return this.isConnected() && isIdcSsoConnection(this.activeConnection) - } - - @withTelemetryContext({ name: 'connectToAwsBuilderId', class: authClassName }) - public async connectToAwsBuilderId(): Promise { - let conn: SsoConnection - let isConnectionOnboarded: boolean - - try { - conn = await this.tryGetBuilderIdConnection() - - if (this.auth.getConnectionState(conn) === 'invalid') { - conn = await this.reauthenticate(conn) - } - - isConnectionOnboarded = await this.isConnectionOnboarded(conn, true) - } catch (e) { - throw ToolkitError.chain(e, 'Failed to connect to Builder ID', { - code: 'FailedToConnect', - }) - } - - if (!isConnectionOnboarded) { - await this.promptOnboarding() - } - - return (await this.secondaryAuth.useNewConnection(conn)) as SsoConnection - } - - @withTelemetryContext({ name: 'connectToEnterpriseSso', class: authClassName }) - public async connectToEnterpriseSso(startUrl: string, region: string): Promise { - let conn: SsoConnection | undefined - let isConnectionOnboarded: boolean - - try { - conn = (await this.auth.listConnections()).find( - (c): c is SsoConnection => isSsoConnection(c) && c.startUrl.toLowerCase() === startUrl.toLowerCase() - ) - - if (!conn) { - conn = await this.auth.createConnection(createSsoProfile(startUrl, region, defaultScopes)) - } else if (!isValidCodeCatalystConnection(conn)) { - conn = await this.secondaryAuth.addScopes(conn, defaultScopes) - } - - if (this.auth.getConnectionState(conn) === 'invalid') { - conn = await this.reauthenticate(conn) - } - - isConnectionOnboarded = await this.isConnectionOnboarded(conn, true) - } catch (e) { - throw ToolkitError.chain(e, 'Failed to connect to IAM Identity Center', { - code: 'FailedToConnect', - }) - } - - if (!isConnectionOnboarded) { - await this.promptOnboarding() - } - - return (await this.secondaryAuth.useNewConnection(conn)) as SsoConnection - } - - /** - * Try to ensure a specific connection is active. - */ - @withTelemetryContext({ name: 'tryConnectTo', class: authClassName }) - public async tryConnectTo(connection: { startUrl: string; region: string }) { - if (!this.isConnectionValid() || connection.startUrl !== this.activeConnection!.startUrl) { - if (connection.startUrl === builderIdStartUrl) { - await this.connectToAwsBuilderId() - } else { - await this.connectToEnterpriseSso(connection.startUrl, connection.region) - } - } - } - - @withTelemetryContext({ name: 'reauthenticate', class: authClassName }) - public async reauthenticate(conn: SsoConnection) { - try { - let connToReauth = conn - // Sanity check - connections with other scopes should have been forced out at this point. - if (!hasExactScopes(conn, defaultScopes)) { - const newConn = await setScopes(conn, defaultScopes) - connToReauth = await this.secondaryAuth.useNewConnection(newConn) - } - - return await this.auth.reauthenticate(connToReauth) - } catch (err) { - throw ToolkitError.chain(err, 'Unable to reauthenticate CodeCatalyst connection.') - } - } - - private getStates(): Record { - return globals.globalState.tryGet>('codecatalyst.connections', Object, {}) - } - - public tryGetConnectionState(conn: SsoConnection): ConnectionState | undefined { - return this.getStates()[conn.id] - } - public getConnectionState(conn: SsoConnection): ConnectionState { - return ( - this.tryGetConnectionState(conn) ?? { - onboarded: false, - scopeExpired: false, - } - ) - } - - private async setConnectionState(conn: SsoConnection, state: ConnectionState) { - await globals.globalState.update('codecatalyst.connections', { - ...this.getStates(), - [conn.id]: state, - }) - } - - private async updateConnectionState(conn: SsoConnection, state: Partial) { - const initial = this.getConnectionState(conn) - await this.setConnectionState(conn, { ...initial, ...state }) - } - - public async isConnectionOnboarded(conn: SsoConnection, recheck = false) { - const state = this.tryGetConnectionState(conn) - if (state !== undefined && !recheck) { - return state.onboarded - } - - try { - await createClient(conn) - await this.updateConnectionState(conn, { onboarded: true }) - - return true - } catch (e) { - if (isOnboardingException(e) && this.auth.getConnectionState(conn) === 'valid') { - await this.updateConnectionState(conn, { onboarded: false }) - - return false - } - - throw e - } - - function isOnboardingException(e: unknown) { - // `GetUserDetails` returns `AccessDeniedException` if the user has not onboarded - return isAwsError(e) && e.code === 'AccessDeniedException' && e.message.includes('GetUserDetails') - } - } - - static #instance: CodeCatalystAuthenticationProvider | undefined - - public static get instance(): CodeCatalystAuthenticationProvider | undefined { - return CodeCatalystAuthenticationProvider.#instance - } - - public static fromContext(ctx: Pick) { - return (this.#instance ??= new this(new CodeCatalystAuthStorage(ctx.secrets))) - } -} diff --git a/packages/core/src/codecatalyst/commands.ts b/packages/core/src/codecatalyst/commands.ts deleted file mode 100644 index 509d9b40d0..0000000000 --- a/packages/core/src/codecatalyst/commands.ts +++ /dev/null @@ -1,371 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../shared/extensionGlobals' - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { selectCodeCatalystRepository, selectCodeCatalystResource } from './wizards/selectResource' -import { openCodeCatalystUrl } from './utils' -import { CodeCatalystAuthenticationProvider } from './auth' -import { Commands, VsCodeCommandArg, placeholder } from '../shared/vscode/commands2' -import { CodeCatalystClient, CodeCatalystResource, createClient } from '../shared/clients/codecatalystClient' -import { DevEnvironmentId, getConnectedDevEnv, openDevEnv } from './model' -import { showConfigureDevEnv } from './vue/configure/backend' -import { showCreateDevEnv } from './vue/create/backend' -import { CancellationError } from '../shared/utilities/timeoutUtils' -import { ToolkitError, errorCode } from '../shared/errors' -import { telemetry } from '../shared/telemetry/telemetry' -import { showConfirmationMessage } from '../shared/utilities/messages' -import { AccountStatus } from '../shared/telemetry/telemetryClient' -import { SsoConnection } from '../auth/connection' -import { isInDevEnv, isRemoteWorkspace } from '../shared/vscode/env' -import { commandPalette } from '../codewhisperer/commands/types' -import { CreateDevEnvironmentRequest, UpdateDevEnvironmentRequest } from '@aws-sdk/client-codecatalyst' - -/** "List CodeCatalyst Commands" command. */ -export async function listCommands(): Promise { - await vscode.commands.executeCommand('workbench.action.quickOpen', '> CodeCatalyst') -} - -/** "Clone CodeCatalyst Repository" command. */ -export async function cloneCodeCatalystRepo(client: CodeCatalystClient, url?: vscode.Uri): Promise { - let resource: { name: string; project: string; org: string } - if (!url) { - const r = await selectCodeCatalystRepository(client, false) - if (!r) { - throw new CancellationError('user') - } - resource = { name: r.name, project: r.project.name, org: r.org.name } - } else { - const [_, org, project, repo] = url.path.slice(1).split('/') - if (!org || !project || !repo) { - throw new Error(`Invalid CodeCatalyst URL: unable to parse repository`) - } - resource = { name: repo, project, org } - } - - const uri = await client.getRepoCloneUrl({ - spaceName: resource.org, - projectName: resource.project, - sourceRepositoryName: resource.name, - }) - await vscode.commands.executeCommand('git.clone', uri) -} - -/** - * Implements commands: - * - "Open CodeCatalyst Space" - * - "Open CodeCatalyst Project" - * - "Open CodeCatalyst Repository" - */ -export async function openCodeCatalystResource( - client: CodeCatalystClient, - kind: CodeCatalystResource['type'] -): Promise { - const resource = await selectCodeCatalystResource(client, kind) - - if (!resource) { - throw new CancellationError('user') - } - - openCodeCatalystUrl(resource) -} - -export async function stopDevEnv( - client: CodeCatalystClient, - devenv: DevEnvironmentId, - opts?: { readonly showPrompt?: boolean } -): Promise { - if (opts?.showPrompt) { - const confirmed = await showConfirmationMessage({ - prompt: localize( - 'aws.codecatalyst.stopDevEnv.confirm', - 'Stopping the Dev Environment will end all processes. Continue?' - ), - }) - - if (!confirmed) { - throw new CancellationError('user') - } - } - - await client.stopDevEnvironment({ - id: devenv.id, - projectName: devenv.project.name, - spaceName: devenv.org.name, - }) -} - -export async function deleteDevEnv(client: CodeCatalystClient, devenv: DevEnvironmentId): Promise { - await client.deleteDevEnvironment({ - id: devenv.id, - projectName: devenv.project.name, - spaceName: devenv.org.name, - }) -} - -export type DevEnvironmentSettings = Pick< - CreateDevEnvironmentRequest, - 'alias' | 'instanceType' | 'inactivityTimeoutMinutes' | 'persistentStorage' -> - -export type UpdateDevEnvironmentSettings = Pick< - UpdateDevEnvironmentRequest, - 'alias' | 'instanceType' | 'inactivityTimeoutMinutes' -> - -export async function updateDevEnv( - client: CodeCatalystClient, - devenv: DevEnvironmentId, - settings: UpdateDevEnvironmentSettings -) { - return client.updateDevEnvironment({ - ...settings, - id: devenv.id, - projectName: devenv.project.name, - spaceName: devenv.org.name, - }) -} - -function createClientInjector(authProvider: CodeCatalystAuthenticationProvider): ClientInjector { - return async (command, ...args) => { - telemetry.record({ userId: AccountStatus.NotSet }) - - await authProvider.restore() - const conn = authProvider.activeConnection - if (!conn) { - // TODO: In the future, it would be very nice to open a connection picker here. - throw new ToolkitError('Not connected to CodeCatalyst', { code: 'NoConnectionBadState' }) - } - const validatedConn = await validateConnection(conn, authProvider) - const client = await createClient(validatedConn) - telemetry.record({ userId: client.identity.id }) - - return command(client, ...args) - } -} - -/** - * Returns a connection that is ensured to be authenticated. - * - * Provides the user the ability to re-authenticate if needed, - * otherwise throwing an error. - */ -async function validateConnection( - conn: SsoConnection, - authProvider: CodeCatalystAuthenticationProvider -): Promise { - if (authProvider.auth.getConnectionState(conn) === 'valid') { - return conn - } - - // Have user try to log in - const loginMessage = localize('aws.auth.invalidConnection', 'Connection is invalid or expired, login again?') - const result = await vscode.window.showErrorMessage(loginMessage, 'Login') - - if (result !== 'Login') { - throw new ToolkitError('User cancelled login.', { cancelled: true, code: errorCode.invalidConnection }) - } - - conn = await authProvider.reauthenticate(conn) - - // Log in attempt failed - if (authProvider.auth.getConnectionState(conn) !== 'valid') { - throw new ToolkitError('Login failed.', { code: errorCode.invalidConnection }) - } - - return conn -} - -function createCommandDecorator(commands: CodeCatalystCommands): CommandDecorator { - return (command) => - (...args) => - commands.withClient(command, ...args) -} - -interface CodeCatalystCommand { - (client: CodeCatalystClient, ...args: T): U | Promise -} - -interface ClientInjector { - (command: CodeCatalystCommand, ...args: T): Promise -} - -interface CommandDecorator { - (command: CodeCatalystCommand): (...args: T) => Promise -} - -type Inject = T extends (...args: infer P) => infer R - ? P extends [U, ...infer L] - ? (...args: L) => R - : never - : never - -type WithClient = Parameters> - -class RemoteContextError extends ToolkitError { - constructor() { - super('Cannot connect from a remote context. Try again from a local VS Code instance.', { - code: 'ConnectedToRemote', - }) - } -} - -export const codecatalystConnectionsCmd = Commands.declare( - 'aws.codecatalyst.manageConnections', - () => () => - void vscode.commands.executeCommand( - 'aws.toolkit.auth.manageConnections', - placeholder, - 'codecatalystDeveloperTools', - 'codecatalyst' - ) -) - -export class CodeCatalystCommands { - public readonly withClient: ClientInjector - public readonly bindClient = createCommandDecorator(this) - - public constructor(private authProvider: CodeCatalystAuthenticationProvider) { - this.withClient = createClientInjector(authProvider) - } - - public listCommands() { - return listCommands() - } - - public cloneRepo(_?: VsCodeCommandArg, ...args: WithClient) { - return this.withClient(cloneCodeCatalystRepo, ...args) - } - - public createDevEnv(_?: VsCodeCommandArg): Promise { - if (isRemoteWorkspace() && isInDevEnv()) { - throw new RemoteContextError() - } - return this.withClient(showCreateDevEnv, globals.context, CodeCatalystCommands.declared) - } - - public openResource(...args: WithClient) { - return this.withClient(openCodeCatalystResource, ...args) - } - - public stopDevEnv(...args: WithClient) { - return this.withClient(stopDevEnv, ...args).then(() => { - void vscode.commands.executeCommand('workbench.action.remote.close') - }) - } - - public deleteDevEnv(...args: WithClient) { - return this.withClient(deleteDevEnv, ...args) - } - - public updateDevEnv(...args: WithClient) { - telemetry.record({ - codecatalyst_updateDevEnvironmentLocationType: 'remote', - }) - - return this.withClient(updateDevEnv, ...args) - } - - public openSpace() { - return this.openResource('org') - } - - public openProject() { - return this.openResource('project') - } - - public openRepository() { - return this.openResource('repo') - } - - public async openDevfile(uri: vscode.Uri) { - await vscode.window.showTextDocument(uri) - } - - public async openDevEnv( - _?: VsCodeCommandArg, - id?: DevEnvironmentId, - targetPath?: string, - connection?: { startUrl: string; region: string } - ): Promise { - if (isRemoteWorkspace() && isInDevEnv()) { - throw new RemoteContextError() - } - - const devenv = id ?? (await this.selectDevEnv()) - - // TODO(sijaden): add named timestamp markers for granular duration info - // - // right now this command may prompt the user if they came from the explorer or command palette - // need to be careful of mapping explosion so this granular data would either need - // to be flattened or we restrict the names to a pre-determined set - if (id === undefined) { - telemetry.record({ source: commandPalette }) - } - - if (connection !== undefined) { - await this.authProvider.tryConnectTo(connection) - } else if (!this.authProvider.isConnectionValid()) { - void codecatalystConnectionsCmd.execute() - return - } - - return this.withClient(openDevEnv, devenv, targetPath) - } - - public async openDevEnvSettings(): Promise { - const devenv = await this.withClient(getConnectedDevEnv) - - return this.withClient(showConfigureDevEnv, globals.context, devenv, CodeCatalystCommands.declared) - } - - private async selectDevEnv(): Promise { - const devenv = await this.withClient(selectCodeCatalystResource, 'devEnvironment' as const) - - if (!devenv) { - throw new CancellationError('user') - } - - return devenv - } - - public static fromContext(ctx: Pick) { - const auth = CodeCatalystAuthenticationProvider.fromContext(ctx) - - return new this(auth) - } - - public static readonly declared = { - openResource: Commands.from(this).declareOpenResource('aws.codecatalyst.openResource'), - listCommands: Commands.from(this).declareListCommands('aws.codecatalyst.listCommands'), - openSpace: Commands.from(this).declareOpenSpace('aws.codecatalyst.openOrg'), - openProject: Commands.from(this).declareOpenProject('aws.codecatalyst.openProject'), - openRepository: Commands.from(this).declareOpenRepository('aws.codecatalyst.openRepo'), - stopDevEnv: Commands.from(this).declareStopDevEnv('aws.codecatalyst.stopDevEnv'), - deleteDevEnv: Commands.from(this).declareDeleteDevEnv('aws.codecatalyst.deleteDevEnv'), - openDevEnvSettings: Commands.from(this).declareOpenDevEnvSettings('aws.codecatalyst.openDevEnvSettings'), - openDevfile: Commands.from(this).declareOpenDevfile('aws.codecatalyst.openDevfile'), - cloneRepo: Commands.from(this).declareCloneRepo({ - id: 'aws.codecatalyst.cloneRepo', - telemetryName: 'codecatalyst_localClone', - }), - createDevEnv: Commands.from(this).declareCreateDevEnv({ - id: 'aws.codecatalyst.createDevEnv', - telemetryName: 'codecatalyst_createDevEnvironment', - }), - updateDevEnv: Commands.from(this).declareUpdateDevEnv({ - id: 'aws.codecatalyst.updateDevEnv', - telemetryName: 'codecatalyst_updateDevEnvironmentSettings', - }), - openDevEnv: Commands.from(this).declareOpenDevEnv({ - id: 'aws.codecatalyst.openDevEnv', - telemetryName: 'codecatalyst_connect', - }), - } as const -} diff --git a/packages/core/src/codecatalyst/devEnv.ts b/packages/core/src/codecatalyst/devEnv.ts deleted file mode 100644 index 962c772eb2..0000000000 --- a/packages/core/src/codecatalyst/devEnv.ts +++ /dev/null @@ -1,286 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DevEnvironment } from '../shared/clients/codecatalystClient' -import { DevEnvActivity, DevEnvClient } from '../shared/clients/devenvClient' -import globals from '../shared/extensionGlobals' -import * as vscode from 'vscode' -import { waitUntil } from '../shared/utilities/timeoutUtils' -import { getLogger } from '../shared/logger/logger' -import { CodeCatalystAuthenticationProvider } from './auth' -import { getThisDevEnv } from './model' -import { isInDevEnv } from '../shared/vscode/env' -import { shared } from '../shared/utilities/functionUtils' -import { DevSettings } from '../shared/settings' - -/** Starts the {@link DevEnvActivity} Hearbeat mechanism. */ -export class DevEnvActivityStarter { - private static _instance: DevEnvActivityStarter | undefined = undefined - - /** - * Trys to start the {@link DevEnvActivity} heartbeat. - * This mechanism keeps the Dev Env from timing out. - */ - public static init(authProvider: CodeCatalystAuthenticationProvider) { - if (!isInDevEnv()) { - getLogger().debug('codecatalyst: not in a devenv, skipping DevEnvActivity setup') - return - } - - DevEnvActivityStarter._instance ??= new DevEnvActivityStarter(authProvider) - DevEnvActivityStarter._instance.retryStartActivityHeartbeat().catch((e) => { - getLogger().error('retryStartActivityHeartbeat failed: %s', (e as Error).message) - }) - } - - private devEnvActivity: DevEnvActivity | undefined = undefined - private onDidChangeAuth: ReturnType - - protected constructor(private readonly authProvider: CodeCatalystAuthenticationProvider) { - this.onDidChangeAuth = authProvider.onDidChangeActiveConnection(() => { - this.tryStartActivityHeartbeat(true).catch((e) => { - getLogger().error('tryStartActivityHeartbeat failed: %s', (e as Error).message) - }) - }) - globals.context.subscriptions.push(this.onDidChangeAuth) - } - - /** - * Keeps executing {@link DevEnvActivityStarter.tryStartActivityHeartbeat} on an interval until it succeeds. - * After a certain amount of time this will stop retrying. - */ - private async retryStartActivityHeartbeat() { - return waitUntil( - async () => { - await this.tryStartActivityHeartbeat(false) - return !!this.devEnvActivity - }, - { interval: 20_000, timeout: 60_000 * 5 } - ) - } - - /** Trys to start the Activity Heartbeat mechanism */ - private tryStartActivityHeartbeat = shared(async (reauth: boolean) => { - if (!!this.devEnvActivity && !reauth) { - return - } - - const thisDevenv = (await getThisDevEnv(this.authProvider))?.unwrapOrElse((err) => { - getLogger().warn('codecatalyst: failed to get current devenv: %s', err) - return undefined - }) - - if (!thisDevenv && reauth) { - getLogger().warn('codecatalyst: failed to get devenv after reauthenticate attempt') - return - } else if (!thisDevenv) { - const connection = this.authProvider.activeConnection - if (connection) { - void vscode.window - .showErrorMessage('CodeCatalyst: Reauthenticate your connection.', 'Reauthenticate') - .then(async (res) => { - if (res !== 'Reauthenticate') { - return - } - await this.authProvider.reauthenticate(connection) - }) - } - getLogger().warn( - 'codecatalyst: starting DevEnvActivity heartbeat without auth (unknown inactivityTimeoutMinutes)' - ) - } - - const devenvTimeoutMs = DevSettings.instance.get('devenvTimeoutMs', 0) - if (devenvTimeoutMs) { - getLogger().warn('codecatalyst: using devenvTimeoutMs=%d', devenvTimeoutMs) - } - // If user is not authenticated, assume 15 minutes. - const inactivityTimeoutMin = - devenvTimeoutMs > 0 ? devenvTimeoutMs / 60000 : (thisDevenv?.summary.inactivityTimeoutMinutes ?? 15) - if (!shouldSendActivity(inactivityTimeoutMin)) { - getLogger().info( - `codecatalyst: disabling DevEnvActivity heartbeat: configured to never timeout (inactivityTimeoutMinutes=${inactivityTimeoutMin})` - ) - return - } - - const devEnvActivity = await DevEnvActivity.create(thisDevenv?.devenvClient ?? DevEnvClient.instance) - if (!devEnvActivity) { - getLogger().error('codecatalyst: failed to start DevEnvActivity heartbeat, devenv may timeout') - return - } - - if (this.devEnvActivity) { - // Special case: user authenticated, so reinitialize with the correct `inactivityTimeoutMinutes`. - this.devEnvActivity.dispose() - } - - // Everything is good, we can start the activity heartbeat now - devEnvActivity.setUpdateActivityOnIdeActivity(true) - - // Setup the "shutdown imminent" message. Skip this if we aren't authenticated, because we - // don't know the actual `inactivityTimeoutMinutes`. - if (thisDevenv) { - const inactivityMessage = new InactivityMessage() - await inactivityMessage.init(inactivityTimeoutMin, devEnvActivity) - globals.context.subscriptions.push(inactivityMessage) - this.onDidChangeAuth.dispose() // Don't need to wait for reauth now. - getLogger().debug('codecatalyst: setup InactivityMessage') - } - - globals.context.subscriptions.push(devEnvActivity) - this.devEnvActivity = devEnvActivity - }) -} - -/** - * Should we send activity heartbeat? - * - * If inactivityTimeoutMinutes=0 then it never expires, so Toolkit doesn't need to send heartbeat. - */ -export function shouldSendActivity(inactivityTimeoutMin: DevEnvironment['inactivityTimeoutMinutes']): boolean { - // This value is initialized when the dev env is first created. If it is updated, MDE restarts - // the dev env and this extension will grab the new value on startup. - // https://docs.aws.amazon.com/codecatalyst/latest/APIReference/API_UpdateDevEnvironment.html#codecatalyst-UpdateDevEnvironment-request-inactivityTimeoutMinutes - return inactivityTimeoutMin > 0 -} - -/** Shows a "Dev env will shutdown in x minutes due to inactivity" warning. */ -export class InactivityMessage implements vscode.Disposable { - #showMessageTimer: NodeJS.Timeout | undefined - /** Show a message this many minutes before auto-shutdown. */ - public readonly shutdownWarningThreshold = 5 - - /** - * Creates a timer which will show warning messsage(s) when the dev env is nearing auto-shutdown because of inactivity. - * - * @param maxInactivityMinutes Maximum inactivity allowed before auto-shutdown. - * @param devEnvActivity DevEnvActivity client - * @param oneMin Milliseconds in a "minute". Used in tests. - */ - async init( - maxInactivityMinutes: DevEnvironment['inactivityTimeoutMinutes'], - devEnvActivity: DevEnvActivity, - oneMin: number = 60_000 - ) { - // Send an initial update to the dev env on startup - await devEnvActivity.sendActivityUpdate() - - // Reset (redefine) the timer whenever user is active. - devEnvActivity.onActivityUpdate(async (lastActivity) => { - this.clear() - - const { millisToWait, minutesSinceActivity } = this.millisUntilNextWholeMinute(lastActivity, oneMin) - const minutesUntilShutdown = Math.round(maxInactivityMinutes - minutesSinceActivity) - const minutesUntilMessage = Math.round(Math.max(0, minutesUntilShutdown - this.shutdownWarningThreshold)) - const timerInterval = millisToWait + minutesUntilMessage * oneMin - - if (timerInterval <= 10 * oneMin || DevSettings.instance.get('devenvTimeoutMs', 0)) { - getLogger().debug( - 'InactivityMessage: minutesUntilMessage=%d timerInterval=%d', - minutesUntilMessage, - timerInterval - ) - } - - /** Wait until we are {@link InactivityMessage.shutdownWarningThreshold} minutes before shutdown. */ - this.#showMessageTimer = globals.clock.setTimeout(() => { - const userIsActive = () => { - devEnvActivity.sendActivityUpdate().catch((e) => { - getLogger().error('DevEnvActivity.sendActivityUpdate failed: %s', (e as Error).message) - }) - } - - const willRefreshOnStaleTimestamp = async () => await devEnvActivity.isLocalActivityStale() - - this.clear() - this.show( - Math.round(Math.max(0, minutesSinceActivity + minutesUntilMessage)), - Math.round(Math.max(0, minutesUntilShutdown - minutesUntilMessage)), - userIsActive, - willRefreshOnStaleTimestamp - ).catch((e) => { - getLogger().error('InactivityMessage.show failed: %s', (e as Error).message) - }) - }, timerInterval) - }) - } - - /** - * The latest activity timestamp may not always be the current time, it may be from the past. - * So the amount of time that has passed since that timestamp may not be a whole minute. - * - * This returns the amount of time we need to wait until the next whole minute, along with how many - * minutes would have passed assuming the caller waitied until the next minute. - * - * Eg: - * - 1 minute and 29 seconds have passed since the given timestamp. - * - returns { millisToWait: 31_000, minutesSinceActivity: 2} - */ - private millisUntilNextWholeMinute( - latestTimestamp: number, - oneMin: number - ): { millisToWait: number; minutesSinceActivity: number } { - const millisSinceLastActivity = Date.now() - latestTimestamp - const millisToNextMinute = millisSinceLastActivity % oneMin - - const millisToWait = millisToNextMinute !== 0 ? oneMin - millisToNextMinute : 0 - const minutesSinceActivity = (millisSinceLastActivity + millisToWait) / oneMin - - return { millisToWait, minutesSinceActivity } - } - - /** - * Show the warning message - * - * @param minutesUserWasInactive total minutes user was inactive. - * @param minutesUntilShutdown remaining minutes until shutdown. - * @param userIsActive Call this to signal that user is active. - * @param willRefreshOnStaleTimestamp sanity checks with the dev env api that the latest activity timestamp - * is the same as what this client has locally. If stale, the warning message - * will be refreshed asynchronously. Returns true if the message will be refreshed. - */ - async show( - minutesUserWasInactive: number, - minutesUntilShutdown: number, - userIsActive: () => void, - willRefreshOnStaleTimestamp: () => Promise - ) { - getLogger().debug( - 'InactivityMessage.show: minutesUserWasInactive=%d minutesUntilShutdown=%d', - minutesUserWasInactive, - minutesUntilShutdown - ) - - if (await willRefreshOnStaleTimestamp()) { - return - } - - const imHere = "I'm here!" - return vscode.window - .showWarningMessage( - `Your CodeCatalyst Dev Environment has been inactive for ${minutesUserWasInactive} minutes, and will stop soon.`, - { modal: true }, - imHere - ) - .then((res) => { - if (res === imHere) { - userIsActive() - } - }) - } - - /** Cancels or disposes the existing message and timer. */ - clear() { - if (this.#showMessageTimer) { - clearTimeout(this.#showMessageTimer) - this.#showMessageTimer = undefined - } - } - - dispose() { - this.clear() - } -} diff --git a/packages/core/src/codecatalyst/devfile.ts b/packages/core/src/codecatalyst/devfile.ts deleted file mode 100644 index f8a857e255..0000000000 --- a/packages/core/src/codecatalyst/devfile.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import * as path from 'path' -import { DevEnvClient } from '../shared/clients/devenvClient' -import { DevfileRegistry, devfileGlobPattern } from '../shared/fs/devfileRegistry' -import { getLogger } from '../shared/logger/logger' -import { Commands } from '../shared/vscode/commands2' -import { checkUnsavedChanges } from '../shared/utilities/workspaceUtils' -import { ToolkitError } from '../shared/errors' - -async function updateDevfile(uri: vscode.Uri): Promise { - const client = DevEnvClient.instance - if (!client.isCodeCatalystDevEnv()) { - throw new Error('Cannot update devfile outside a Dev Environment') - } - - // XXX: hard-coded `projects` path, waiting for CodeCatalyst to provide an environment variable - // could also just parse the devfile... - const location = path.relative('/projects', uri.fsPath) - - const title = localize('AWS.codecatalyst.container.restart', 'Restarting Dev Environment container...') - await vscode.window.withProgress({ title, location: vscode.ProgressLocation.Notification }, async () => { - if (checkUnsavedChanges()) { - // TODO: show confirmation prompt instead - throw new ToolkitError('Cannot update devfile with unsaved changes in the Dev Environment') - } - - try { - await client.startDevfile({ location }) - } catch (err) { - throw ToolkitError.chain(err, 'Failed to update devfile') - } - }) - // if we get here, no restart happened :( - // TODO: accurate telemetry is hard to capture here -} - -export const updateDevfileCommand = Commands.declare( - { - id: 'aws.codecatalyst.updateDevfile', - telemetryName: 'codecatalyst_updateDevfile', - }, - () => (uri) => updateDevfile(uri) -) - -type Workspace = Pick - -export class DevfileCodeLensProvider implements vscode.CodeLensProvider { - private canUpdate = false - private readonly disposables: vscode.Disposable[] = [] - private readonly _onDidChangeCodeLenses = new vscode.EventEmitter() - public readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event - - public constructor( - registry: DevfileRegistry, - private readonly client = DevEnvClient.instance, - workspace: Workspace = vscode.workspace - ) { - this.disposables.push(this._onDidChangeCodeLenses) - this.disposables.push( - workspace.onDidSaveTextDocument(async (document) => { - if (!registry.getItem(document.fileName)) { - return - } - - await this.handleUpdate(document).catch((err) => { - getLogger().debug(`codecatalyst: devfile codelens failure: ${err?.message}`) - }) - }) - ) - } - - public async handleUpdate(document: vscode.TextDocument) { - const response = await this.client.getStatus() - - this.canUpdate = response.status === 'CHANGED' - this._onDidChangeCodeLenses.fire() - } - - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.ProviderResult { - if (!this.canUpdate || document.uri.scheme !== 'file') { - return - } - - // TODO: handle both create and update case based on current context - // also make the positions better - const range = new vscode.Range(0, 0, 0, 0) - const lens = updateDevfileCommand.build(document.uri).asCodeLens(range, { - title: localize('AWS.codecatalyst.codelens.updateDevEnv', 'Update Dev Environment'), - }) - - return [lens] - } - - public dispose() { - vscode.Disposable.from(...this.disposables).dispose() - } -} - -export function registerDevfileWatcher(devenvClient: DevEnvClient): vscode.Disposable { - const registry = new DevfileRegistry() - const codelensProvider = new DevfileCodeLensProvider(registry, devenvClient) - registry.addWatchPatterns([devfileGlobPattern]) - registry.rebuild().catch((e) => { - getLogger().error('WatchedFiles.rebuild failed: %s', (e as Error).message) - }) - - const codelensDisposable = vscode.languages.registerCodeLensProvider( - { - language: 'yaml', - scheme: 'file', - pattern: devfileGlobPattern, - }, - codelensProvider - ) - - return vscode.Disposable.from(codelensDisposable, codelensProvider, registry) -} diff --git a/packages/core/src/codecatalyst/explorer.ts b/packages/core/src/codecatalyst/explorer.ts deleted file mode 100644 index af3d6a2047..0000000000 --- a/packages/core/src/codecatalyst/explorer.ts +++ /dev/null @@ -1,228 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { DevEnvironment } from '../shared/clients/codecatalystClient' -import { addColor, getIcon } from '../shared/icons' -import { TreeNode } from '../shared/treeview/resourceTreeDataProvider' -import { Commands } from '../shared/vscode/commands2' -import { CodeCatalystAuthenticationProvider } from './auth' -import { CodeCatalystCommands, codecatalystConnectionsCmd } from './commands' -import { ConnectedDevEnv, getDevfileLocation, getThisDevEnv } from './model' -import * as codecatalyst from './model' -import { getLogger } from '../shared/logger/logger' -import { SsoConnection } from '../auth/connection' -import { openUrl } from '../shared/utilities/vsCodeUtils' - -export const learnMoreCommand = Commands.declare('aws.learnMore', () => async (docsUrl: vscode.Uri) => { - return openUrl(docsUrl) -}) - -export const reauth = Commands.declare( - '_aws.codecatalyst.reauthenticate', - () => async (conn: SsoConnection, authProvider: CodeCatalystAuthenticationProvider) => { - await authProvider.reauthenticate(conn) - } -) - -export const onboardCommand = Commands.declare( - '_aws.codecatalyst.onboard', - () => async (authProvider: CodeCatalystAuthenticationProvider) => { - void authProvider.promptOnboarding() - } -) - -async function getLocalCommands(auth: CodeCatalystAuthenticationProvider) { - const docsUrl = codecatalyst.docs.overview - const learnMoreNode = learnMoreCommand.build(docsUrl).asTreeNode({ - label: 'Learn more about CodeCatalyst', - iconPath: getIcon('vscode-question'), - }) - - // There is a connection, but it is expired, or CodeCatalyst scopes are expired. - if (auth.activeConnection && !auth.isConnectionValid()) { - return [ - reauth.build(auth.activeConnection, auth).asTreeNode({ - label: 'Re-authenticate to connect', - iconPath: addColor(getIcon('vscode-debug-disconnect'), 'notificationsErrorIcon.foreground'), - }), - learnMoreNode, - ] - } - - if (!auth.activeConnection) { - return [ - codecatalystConnectionsCmd.build().asTreeNode({ - label: 'Sign in to get started', - iconPath: getIcon('vscode-account'), - }), - learnMoreNode, - ] - } - - // We are connected but not onboarded, so show them button to onboard - if (!(await auth.isConnectionOnboarded(auth.activeConnection))) { - return [ - onboardCommand.build(auth).asTreeNode({ - label: 'Onboard CodeCatalyst to get started', - iconPath: getIcon('vscode-account'), - }), - learnMoreNode, - ] - } - - return [ - CodeCatalystCommands.declared.cloneRepo.build().asTreeNode({ - label: 'Clone Repository', - iconPath: getIcon('vscode-symbol-namespace'), - }), - CodeCatalystCommands.declared.openDevEnv.build().asTreeNode({ - label: 'Open Dev Environment', - iconPath: getIcon('vscode-vm-connect'), - }), - CodeCatalystCommands.declared.createDevEnv.build().asTreeNode({ - label: 'Create Dev Environment', - iconPath: getIcon('vscode-add'), - }), - CodeCatalystCommands.declared.listCommands.build().asTreeNode({ - label: 'Show CodeCatalyst Commands', - iconPath: getIcon('vscode-list-flat'), // TODO(sijaden): use better icon - }), - ] -} - -function getRemoteCommands(currentDevEnv: DevEnvironment, devfileLocation: vscode.Uri) { - return [ - CodeCatalystCommands.declared.stopDevEnv.build(currentDevEnv, { showPrompt: true }).asTreeNode({ - label: 'Stop Dev Environment', - iconPath: getIcon('vscode-stop-circle'), - }), - CodeCatalystCommands.declared.openDevEnvSettings.build().asTreeNode({ - label: 'Open Settings', - iconPath: getIcon('vscode-settings-gear'), - }), - CodeCatalystCommands.declared.openDevfile.build(devfileLocation).asTreeNode({ - label: 'Open Devfile', - iconPath: getIcon('vscode-symbol-namespace'), - description: vscode.workspace.asRelativePath(devfileLocation), - }), - ] -} - -export class CodeCatalystRootNode implements TreeNode { - public readonly id = 'codecatalyst' - public readonly resource = this.devenv - - private readonly onDidChangeEmitter = new vscode.EventEmitter() - - public readonly onDidChangeVisibility = this.onDidChangeEmitter.event - public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event - public readonly onDidChangeChildren = this.onDidChangeEmitter.event - - private refreshEmitters: (() => void)[] = [] - - private devenv?: ConnectedDevEnv - private resolveDevEnv: Promise | undefined = undefined - - public constructor(private readonly authProvider: CodeCatalystAuthenticationProvider) { - this.addRefreshEmitter(() => this.onDidChangeEmitter.fire()) - - this.authProvider.onDidChange(() => { - for (const fire of this.refreshEmitters) { - fire() - } - }) - } - - public async getChildren(): Promise { - await this.initDevEnvLoad() - - if (!this.devenv) { - return getLocalCommands(this.authProvider) - } - - const devfileLocation = await getDevfileLocation(this.devenv.devenvClient) - - return getRemoteCommands(this.devenv.summary, devfileLocation) - } - - /** - * HACK: Since this is assumed to be an immediate child of the - * root, we return undefined. - * - * TODO: Look to have a base root class to extend so we do not - * need to implement this here. - * @returns - */ - getParent(): TreeNode | undefined { - return undefined - } - - public async getTreeItem() { - await this.initDevEnvLoad() - - await this.authProvider.restore() - - const item = new vscode.TreeItem('CodeCatalyst', vscode.TreeItemCollapsibleState.Collapsed) - item.contextValue = this.authProvider.isUsingSavedConnection - ? 'awsCodeCatalystNodeSaved' - : 'awsCodeCatalystNode' - - if (this.devenv !== undefined) { - item.description = 'Connected to Dev Environment' - item.iconPath = addColor(getIcon('vscode-pass'), 'testing.iconPassed') - } else { - item.description = this.getDescription() - } - - return item - } - - private getDescription(): string { - if (this.authProvider.activeConnection) { - if (this.authProvider.secondaryAuth.isConnectionExpired) { - return 'Expired Connection' - } - if (this.authProvider.isBuilderIdInUse()) { - return 'AWS Builder ID Connected' - } - if (this.authProvider.isEnterpriseSsoInUse()) { - return 'IAM Identity Center Connected' - } - } - return '' - } - - public addRefreshEmitter(emitter: () => void) { - this.refreshEmitters.push(emitter) - } - - /** - * CALL THIS BEFORE USING `this.devenv` - * Ensures that the node has attempted to load the `devenv` field by - * creating a promise that can be awaited if init has already begun and is being called elsewhere. - */ - private async initDevEnvLoad() { - if (this.resolveDevEnv) { - await this.resolveDevEnv - return - } - let resolve: ((val: boolean) => void) | undefined - if (this.resolveDevEnv === undefined) { - this.resolveDevEnv = new Promise((res) => { - resolve = res - }) - } - - this.devenv = (await getThisDevEnv(this.authProvider))?.unwrapOrElse((e) => { - const err = e as Error - getLogger().warn('codecatalyst: failed to get current Dev Enviroment: %s', err.message) - return undefined - }) - if (resolve !== undefined) { - resolve(true) - } - } -} diff --git a/packages/core/src/codecatalyst/model.ts b/packages/core/src/codecatalyst/model.ts deleted file mode 100644 index 0451f9be95..0000000000 --- a/packages/core/src/codecatalyst/model.ts +++ /dev/null @@ -1,309 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../shared/extensionGlobals' - -import * as vscode from 'vscode' -import * as path from 'path' -import { - createClient, - CodeCatalystClient, - DevEnvironment, - CodeCatalystRepo, - getCodeCatalystConfig, -} from '../shared/clients/codecatalystClient' -import { DevEnvClient } from '../shared/clients/devenvClient' -import { getLogger } from '../shared/logger/logger' -import { AsyncCollection, toCollection } from '../shared/utilities/asyncCollection' -import { getCodeCatalystSpaceName, getCodeCatalystProjectName, getCodeCatalystDevEnvId } from '../shared/vscode/env' -import { sshAgentSocketVariable, startSshAgent, startVscodeRemote } from '../shared/extensions/ssh' -import { isDevenvVscode } from './utils' -import { Timeout } from '../shared/utilities/timeoutUtils' -import { Commands } from '../shared/vscode/commands2' -import { areEqual } from '../shared/utilities/pathUtils' -import { fileExists } from '../shared/filesystemUtilities' -import { CodeCatalystAuthenticationProvider } from './auth' -import { ToolkitError } from '../shared/errors' -import { Result } from '../shared/utilities/result' -import { EnvProvider, VscodeRemoteConnection, createBoundProcess, ensureDependencies } from '../shared/remoteSession' -import { SshConfig, sshLogFileLocation } from '../shared/sshConfig' -import { fs } from '../shared/fs/fs' - -export type DevEnvironmentId = Pick -export const connectScriptPrefix = 'codecatalyst_connect' - -export const docs = { - main: vscode.Uri.parse('https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/codecatalyst-service'), - overview: vscode.Uri.parse( - 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/codecatalyst-overview.html' - ), - devenv: vscode.Uri.parse( - 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/codecatalyst-devenvironment.html' - ), - setup: vscode.Uri.parse('https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/codecatalyst-setup.html'), - troubleshoot: vscode.Uri.parse( - 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/codecatalyst-troubleshoot.html' - ), -} as const - -export function getCodeCatalystSsmEnv(region: string, ssmPath: string, devenv: DevEnvironmentId): NodeJS.ProcessEnv { - return Object.assign( - { - AWS_REGION: region, - AWS_SSM_CLI: ssmPath, - CODECATALYST_ENDPOINT: getCodeCatalystConfig().endpoint, - BEARER_TOKEN_LOCATION: bearerTokenCacheLocation(devenv.id), - LOG_FILE_LOCATION: sshLogFileLocation('codecatalyst', devenv.id), - SPACE_NAME: devenv.org.name, - PROJECT_NAME: devenv.project.name, - DEVENV_ID: devenv.id, - }, - process.env - ) -} - -export function createCodeCatalystEnvProvider( - client: CodeCatalystClient, - ssmPath: string, - devenv: DevEnvironment, - useSshAgent: boolean = true -): EnvProvider { - return async () => { - await cacheBearerToken(await client.getBearerToken(), devenv.id) - const vars = getCodeCatalystSsmEnv(client.regionCode, ssmPath, devenv) - - return useSshAgent ? { [sshAgentSocketVariable]: await startSshAgent(), ...vars } : vars - } -} - -export async function cacheBearerToken(bearerToken: string, devenvId: string): Promise { - await fs.writeFile(bearerTokenCacheLocation(devenvId), `${bearerToken}`, 'utf8') -} - -export function bearerTokenCacheLocation(devenvId: string): string { - return path.join(globals.context.globalStorageUri.fsPath, `codecatalyst.${devenvId}.token`) -} - -export interface ConnectedDevEnv { - readonly summary: DevEnvironment - readonly devenvClient: DevEnvClient -} - -export async function getConnectedDevEnv( - codeCatalystClient: CodeCatalystClient, - devenvClient = DevEnvClient.instance -): Promise { - const devEnvId = devenvClient.id - if (!devEnvId) { - throw new ToolkitError('Not connected to a dev environment', { code: 'NotConnectedToDevEnv' }) - } - - const projectName = getCodeCatalystProjectName() - const spaceName = getCodeCatalystSpaceName() - - if (!projectName || !spaceName) { - throw new Error('No project or space name found') - } - - const summary = await codeCatalystClient.getDevEnvironment({ - projectName: projectName, - spaceName: spaceName, - id: devEnvId, - }) - - return { summary, devenvClient: devenvClient } -} - -/** - * Gets the current devenv that Toolkit is running in, if any. - */ -export async function getThisDevEnv(authProvider: CodeCatalystAuthenticationProvider) { - if (!getCodeCatalystDevEnvId()) { - return - } - - try { - await authProvider.restore() - const conn = authProvider.activeConnection - if (conn !== undefined && authProvider.auth.getConnectionState(conn) === 'valid') { - const client = await createClient(conn) - return Result.ok(await getConnectedDevEnv(client)) - } - } catch (err) { - return Result.err(err) - } -} - -/** - * Everything needed to connect to a dev environment via VS Code or `ssh` - */ -interface DevEnvConnection extends VscodeRemoteConnection { - readonly devenv: DevEnvironment -} - -export async function prepareDevEnvConnection( - client: CodeCatalystClient, - { id, org, project }: DevEnvironmentId, - { topic, timeout }: { topic?: string; timeout?: Timeout } = {} -): Promise { - const { ssm, vsc, ssh } = (await ensureDependencies()).unwrap() - const hostNamePrefix = 'aws-devenv-' - const sshConfig = new SshConfig(ssh, hostNamePrefix, connectScriptPrefix) - const config = await sshConfig.ensureValid() - - if (config.isErr()) { - const err = config.err() - getLogger().error(`codecatalyst: failed to add ssh config section: ${err.message}`) - - throw err - } - - const runningDevEnv = await client.startDevEnvironmentWithProgress({ - id, - spaceName: org.name, - projectName: project.name, - }) - - const hostname = `${hostNamePrefix}${id}` - const logPrefix = topic ? `codecatalyst ${topic} (${id})` : `codecatalyst (${id})` - const logger = (data: string) => getLogger().verbose(`${logPrefix}: ${data}`) - const envProvider = createCodeCatalystEnvProvider(client, ssm, runningDevEnv) - const SessionProcess = createBoundProcess(envProvider).extend({ - timeout, - onStdout: logger, - onStderr: logger, - rejectOnErrorCode: true, - }) - - return { - hostname, - devenv: runningDevEnv, - envProvider, - sshPath: ssh, - vscPath: vsc, - SessionProcess, - } -} - -/** - * Starts the given devenv, waits, and connects a new vscode instance when the devenv is ready. - * - * @param client CodeCatalyst service client - * @param devenv DevEnvironment to open - * @param targetPath vscode workspace (default: "/projects/[repo]") - */ -export async function openDevEnv( - client: CodeCatalystClient, - devenv: DevEnvironmentId, - targetPath?: string -): Promise { - const env = await prepareDevEnvConnection(client, devenv, { topic: 'connect' }) - - if (!targetPath) { - const repo = env.devenv.repositories.length === 1 ? env.devenv.repositories[0].repositoryName : undefined - targetPath = repo ? `/projects/${repo}` : '/projects' - } - await startVscodeRemote(env.SessionProcess, env.hostname, targetPath, env.vscPath) -} - -// The "codecatalyst_connect" metric should really be splt into two parts: -// 1. the setup/launch from the local machine -// 2. toolkit initialization on the remote -// -// Recording metrics like this is a lot more involved so for now we'll -// assume that if the first step succeeds, the user probably succeeded -// in connecting to the devenv -export const codeCatalystConnectCommand = Commands.declare( - { - id: '_aws.codecatalyst.connect', - telemetryName: 'codecatalyst_connect', - }, - () => (client, devenv, targetPath) => openDevEnv(client, devenv, targetPath) -) - -export async function getDevfileLocation(client: DevEnvClient, root?: vscode.Uri) { - const rootDirectory = root ?? vscode.workspace.workspaceFolders?.[0].uri - if (!rootDirectory) { - throw new Error('No root directory or Dev Environment folder found') - } - - async function checkDefaultLocations(rootDirectory: vscode.Uri): Promise { - // Check the projects root location - const devfileRoot = vscode.Uri.joinPath(vscode.Uri.parse('/projects'), 'devfile.yaml') - if (await fileExists(devfileRoot.fsPath)) { - return devfileRoot - } - - // Check the location relative to the current directory - const projectRoot = vscode.Uri.joinPath(rootDirectory, 'devfile.yaml') - if (await fileExists(projectRoot.fsPath)) { - return projectRoot - } - - throw new Error('Devfile location was not found') - } - - // TODO(sijaden): should make this load greedily and continously poll - // latency is very high for some reason - const devfileLocation = await client.getStatus().then((r) => r.location) - if (!devfileLocation) { - return checkDefaultLocations(rootDirectory) - } - - if (areEqual(undefined, rootDirectory.fsPath, '/projects')) { - return vscode.Uri.joinPath(rootDirectory, devfileLocation) - } - - // we have /projects/repo, where MDE may or may not return [repo]/devfile.yaml - const repo = path.basename(rootDirectory.fsPath) - const splitDevfilePath = devfileLocation.split('/') - const devfilePath = vscode.Uri.joinPath(rootDirectory, 'devfile.yaml') - if (repo === splitDevfilePath[0] && (await fileExists(devfilePath.fsPath))) { - return devfilePath - } - - const baseLocation = vscode.Uri.joinPath(rootDirectory, devfileLocation) - if (await fileExists(baseLocation.fsPath)) { - return baseLocation - } - - return checkDefaultLocations(rootDirectory) -} - -/** - * Given a collection of CodeCatalyst repos, try to find a corresponding devenv, if any - */ -export function associateDevEnv( - client: CodeCatalystClient, - repos: AsyncCollection -): AsyncCollection { - return toCollection(async function* () { - const devenvs = await client - .listResources('devEnvironment') - .flatten() - .filter((env) => env.repositories.length > 0 && isDevenvVscode(env.ides)) - .toMap((env) => `${env.org.name}.${env.project.name}.${env.repositories[0].repositoryName}`) - - yield* repos.map((repo) => ({ - ...repo, - devEnv: devenvs.get(`${repo.org.name}.${repo.project.name}.${repo.name}`), - })) - }) -} - -export interface DevEnvMemento { - /** True if the extension is watching the status of the devenv to try and reconnect. */ - attemptingReconnect?: boolean - /** Unix time of the most recent connection. */ - previousConnectionTimestamp: number - /** Previous open vscode workspace directory. */ - previousVscodeWorkspace: string - /** CodeCatalyst Space (Org) Name */ - spaceName: string - /** CodeCatalyst Project name */ - projectName: string - /** CodeCatalyst Alias */ - alias: string | undefined -} diff --git a/packages/core/src/codecatalyst/reconnect.ts b/packages/core/src/codecatalyst/reconnect.ts deleted file mode 100644 index 9e0ab491f3..0000000000 --- a/packages/core/src/codecatalyst/reconnect.ts +++ /dev/null @@ -1,296 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import * as vscode from 'vscode' -import { CodeCatalystClient, createClient, DevEnvironment } from '../shared/clients/codecatalystClient' -import { ExtContext } from '../shared/extensions' -import { getLogger } from '../shared/logger/logger' -import { sleep } from '../shared/utilities/timeoutUtils' -import { DevEnvironmentSettings } from './commands' -import { codeCatalystConnectCommand, DevEnvironmentId, DevEnvMemento } from './model' -import { showViewLogsMessage } from '../shared/utilities/messages' -import { CodeCatalystAuthenticationProvider } from './auth' -import { getCodeCatalystDevEnvId } from '../shared/vscode/env' -import globals from '../shared/extensionGlobals' -import { isDevenvVscode } from './utils' -import { telemetry } from '../shared/telemetry/telemetry' -import { SsoConnection } from '../auth/connection' - -const localize = nls.loadMessageBundle() - -const reconnectTimer = 5000 -const maxReconnectTime = 10 * 60 * 1000 - -export function watchRestartingDevEnvs(ctx: ExtContext, authProvider: CodeCatalystAuthenticationProvider) { - let restartHandled = false - authProvider.onDidChangeActiveConnection(async (conn) => { - if (restartHandled || conn === undefined || authProvider.auth.getConnectionState(conn) !== 'valid') { - return - } - getLogger().info(`codecatalyst: reconnect: onDidChangeActiveConnection: startUrl=${conn.startUrl}`) - - const envId = getCodeCatalystDevEnvId() - handleRestart(conn, ctx, envId) - restartHandled = true - }) -} - -function handleRestart(conn: SsoConnection, ctx: ExtContext, envId: string | undefined) { - if (envId !== undefined) { - const pendingReconnects = globals.globalState.get>('CODECATALYST_RECONNECT') ?? {} - if (envId in pendingReconnects) { - const devenv = pendingReconnects[envId] - const devenvName = getDevEnvName(devenv.alias, envId) - getLogger().info(`codecatalyst: ssh session reconnected to devenv: ${devenvName}`) - void vscode.window.showInformationMessage( - localize('AWS.codecatalyst.reconnect.success', 'Reconnected to Dev Environment: {0}', devenvName) - ) - delete pendingReconnects[envId] - globals.globalState.tryUpdate('CODECATALYST_RECONNECT', pendingReconnects) - } - } else { - getLogger().info('codecatalyst: attempting to poll dev environments') - - // Reconnect devenvs (if coming from a restart) - reconnectDevEnvs(conn).catch((err) => { - getLogger().error(`codecatalyst: error while resuming devenvs: ${err}`) - }) - } -} - -/** - * Attempt to poll for connection in all valid devenvs - * @param conn a connection that may be used for CodeCatalyst - * @param ctx the extension context - */ -async function reconnectDevEnvs(conn: SsoConnection): Promise { - const pendingDevEnvs = globals.globalState.tryGet>( - 'CODECATALYST_RECONNECT', - Object, - {} - ) - const validDevEnvs = filterInvalidDevEnvs(pendingDevEnvs) - if (Object.keys(validDevEnvs).length === 0) { - return - } - - const devenvNames = [] - for (const [id, devenv] of Object.entries(validDevEnvs)) { - devenvNames.push(getDevEnvName(devenv.alias, id)) - } - - const polledDevEnvs = devenvNames.join(', ') - const progressTitle = localize( - 'AWS.codecatalyst.reconnect.restarting', - 'Dev Environments restarting: {0}', - polledDevEnvs - ) - await vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - }, - async (progress, token) => { - progress.report({ message: progressTitle }) - const client = await createClient(conn) - - return pollDevEnvs(client, progress, token, validDevEnvs) - } - ) -} - -/** - * Filter out devenvs who are expired OR are already attempting to reconnect. - * @param devenvs All of the possible in devenvs to check - */ -function filterInvalidDevEnvs(devenvs: Record) { - for (const reconnectDevEnvId in devenvs) { - const devenvDetail = devenvs[reconnectDevEnvId] - if (isExpired(devenvDetail.previousConnectionTimestamp) || devenvDetail.attemptingReconnect) { - delete devenvs[reconnectDevEnvId] - } - } - return devenvs -} - -/** - * Ensure that all devenvs that are currently being looked at set to attempting to reconnect so that they are not looked at - * by any other instance of VSCode. - * @param devenvs - */ -function setWatchedDevEnvStatus(devenvs: Record, watchStatus: boolean) { - for (const [id, detail] of Object.entries(devenvs)) { - devenvs[id] = { ...detail, attemptingReconnect: watchStatus } - } - return globals.globalState.update('CODECATALYST_RECONNECT', devenvs) -} - -/** - * Continuously poll all the devenvs until they are either: - * 1. Available for re-opening, in which case re-open them automatically - * 2. In a terminating state or expired, in which case no longer watch the devenv - * 3. Failed to start, in which case notify the user - * @param client A connected client - * @param devenvs All VALID devenvs that are not being watched by any other VSCode instance - */ -async function pollDevEnvs( - client: CodeCatalystClient, - progress: vscode.Progress<{ message: string }>, - token: vscode.CancellationToken, - devenvs: Record -) { - // Ensure that all devenvs that you want to look at are attempting reconnection - // and won't be watched by any other VSCode instance - await setWatchedDevEnvStatus(devenvs, true) - - const shouldCloseRootInstance = Object.keys(devenvs).length === 1 - getLogger().info(`codecatalyst: reconnect: pollDevEnvs: ${Object.keys(devenvs).length}`) - - while (Object.keys(devenvs).length > 0) { - if (token.isCancellationRequested) { - await setWatchedDevEnvStatus(devenvs, false) - return - } - - for (const id in devenvs) { - const details = devenvs[id] - const devenvName = getDevEnvName(details.alias, id) - - try { - const metadata = await client.getDevEnvironment({ - id: id, - spaceName: details.spaceName, - projectName: details.projectName, - }) - - const ide = metadata.ides?.[0]?.name - getLogger().info(`codecatalyst: reconnect: ides=${ide} statusReason=${metadata.statusReason}`) - - if (metadata?.status === 'RUNNING') { - progress.report({ - message: `Dev Environment ${devenvName} is now running. Attempting to reconnect.`, - }) - - openReconnectedDevEnv(client, id, details, shouldCloseRootInstance).catch((e) => { - getLogger().error('openReconnectedDevEnv failed: %s', (e as Error).message) - }) - - // Don't watch this devenv, it is already being re-opened in SSH. - delete devenvs[id] - } else if (!isDevenvVscode(metadata.ides)) { - // Technically vscode _can_ connect to a ideRuntime=jetbrains devenv, but - // we refuse to anyway so that the experience is consistent since that devenv - // is not capable of connecting to a devenv that lacks their runtime/bootstrap files. - const ide = metadata.ides?.[0] - const toIde = ide ? ` to "${ide.name}"` : '' - progress.report({ message: `Dev Environment ${devenvName} was switched${toIde}` }) - // Don't watch devenv that is no longer connectable. - delete devenvs[id] - } else if (isTerminating(metadata)) { - progress.report({ message: `Dev Environment ${devenvName} is terminating` }) - // Don't watch devenv that is terminating. - delete devenvs[id] - } else if (isExpired(details.previousConnectionTimestamp)) { - progress.report({ message: `Dev Environment ${devenvName} has expired` }) - } - } catch { - await failDevEnv(id) - delete devenvs[id] - void showViewLogsMessage( - localize('AWS.codecatalyst.reconnect', 'Unable to reconnect to ${0}', devenvName) - ) - } - } - await sleep(reconnectTimer) - } -} - -function isTerminating(devenv: Pick): boolean { - if (!devenv.status) { - return false - } - return devenv.status === 'FAILED' || devenv.status === 'DELETING' || devenv.status === 'DELETED' -} - -function isExpired(previousConnectionTime: number): boolean { - return Date.now() - previousConnectionTime > maxReconnectTime -} - -/** - * When a devenv fails, remove it from globalState so we no longer watch it in the future - * @param devenvId the id of the deveng to fail - */ -function failDevEnv(devenvId: string) { - const curr = globals.globalState.tryGet>('CODECATALYST_RECONNECT', Object, {}) - delete curr[devenvId] - return globals.globalState.update('CODECATALYST_RECONNECT', curr) -} - -async function openReconnectedDevEnv( - client: CodeCatalystClient, - id: string, - devenv: DevEnvMemento, - closeRootInstance: boolean -): Promise { - const identifier: DevEnvironmentId = { - id, - org: { name: devenv.spaceName }, - project: { name: devenv.projectName }, - } - - await telemetry.runRoot(async () => { - telemetry.record({ source: 'Reconnect' }) - await codeCatalystConnectCommand.execute(client, identifier, devenv.previousVscodeWorkspace) - - // When we only have 1 devenv to watch we might as well close the local vscode instance - if (closeRootInstance) { - // A brief delay ensures that metrics are saved from the connect command - await sleep(5000).then(() => vscode.commands.executeCommand('workbench.action.closeWindow')) - } - }) -} - -function getDevEnvName(alias: string | undefined, id: string) { - return alias && alias !== '' ? alias : id -} - -export function isLongReconnect(oldSettings: DevEnvironmentSettings, newSettings: DevEnvironmentSettings): boolean { - return ( - newSettings.inactivityTimeoutMinutes !== undefined && - newSettings.instanceType !== undefined && - (oldSettings.inactivityTimeoutMinutes !== newSettings.inactivityTimeoutMinutes || - oldSettings.instanceType !== newSettings.instanceType) - ) -} - -export function saveReconnectionInformation(devenv: DevEnvironmentId & Pick): Thenable { - const pendingReconnects = globals.globalState.tryGet>( - 'CODECATALYST_RECONNECT', - Object, - {} - ) - const workspaceFolders = vscode.workspace.workspaceFolders - const currentWorkspace = - workspaceFolders !== undefined && workspaceFolders.length > 0 ? workspaceFolders[0].uri.fsPath : '/projects' - pendingReconnects[devenv.id] = { - previousVscodeWorkspace: currentWorkspace, - spaceName: devenv.org.name, - projectName: devenv.project.name, - attemptingReconnect: false, - previousConnectionTimestamp: Date.now(), - alias: devenv.alias, - } - return globals.globalState.update('CODECATALYST_RECONNECT', pendingReconnects) -} - -export function removeReconnectionInformation(devenv: DevEnvironmentId): Thenable { - const pendingReconnects = globals.globalState.tryGet>( - 'CODECATALYST_RECONNECT', - Object, - {} - ) - delete pendingReconnects[devenv.id] - return globals.globalState.update('CODECATALYST_RECONNECT', pendingReconnects) -} diff --git a/packages/core/src/codecatalyst/repos/remoteSourceProvider.ts b/packages/core/src/codecatalyst/repos/remoteSourceProvider.ts deleted file mode 100644 index 55be5dac22..0000000000 --- a/packages/core/src/codecatalyst/repos/remoteSourceProvider.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { RemoteSource, RemoteSourceProvider } from '../../../types/git' -import { CodeCatalystAuthenticationProvider } from '../auth' -import { createRepoLabel } from '../wizards/selectResource' -import { CodeCatalystCommands } from '../commands' -import { CodeCatalystRepo } from '../../shared/clients/codecatalystClient' -import { getIcon, Icon } from '../../shared/icons' - -function showQuickPickLoadingBar(title: string, task: () => Promise): Promise { - const picker = vscode.window.createQuickPick() - picker.show() // `show` must happen first on C9 before assigning fields - picker.placeholder = title - picker.busy = true - picker.enabled = false - picker.ignoreFocusOut = true - - return task().finally(() => picker.hide()) -} - -export class CodeCatalystRemoteSourceProvider implements RemoteSourceProvider { - public readonly supportsQuery = false // TODO(sijaden): implement query - - public constructor( - private readonly commands: Pick, - private readonly authProvider: Pick - ) {} - - // Must be a `codicon` id - public get icon() { - const icon = getIcon('aws-codecatalyst-logo') - - return icon instanceof Icon ? icon.id : undefined - } - - public get name(): string { - const username = this.authProvider.activeConnection?.label - - return localize( - 'AWS.codecatalyst.cloneRepo.git', - 'Amazon CodeCatalyst {0}', - username - ? localize('AWS.codecatalyst.cloneRepo.connected', '(connected with {0})', username) - : localize('AWS.credentials.statusbar.no.credentials', '(not connected)') - ) - } - - public async getRemoteSources(query?: string): Promise { - return this.commands.withClient(async (client) => { - const intoRemote = async (repo: CodeCatalystRepo): Promise => { - const resource = { name: repo.name, project: repo.project.name, org: repo.org.name } - const url = await client.getRepoCloneUrl({ - spaceName: resource.org, - projectName: resource.project, - sourceRepositoryName: resource.name, - }) - - return { - name: createRepoLabel(repo), - url: url, - description: repo.description, - } - } - - return showQuickPickLoadingBar('Repository name', () => - client.listResources('repo').flatten().map(intoRemote).promise() - ) - }) - } -} diff --git a/packages/core/src/codecatalyst/uriHandlers.ts b/packages/core/src/codecatalyst/uriHandlers.ts deleted file mode 100644 index 7e4f908051..0000000000 --- a/packages/core/src/codecatalyst/uriHandlers.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SearchParams, UriHandler } from '../shared/vscode/uriHandler' -import { getCodeCatalystConfig } from '../shared/clients/codecatalystClient' -import { CodeCatalystCommands } from './commands' -import { defaultSsoRegion } from '../auth/connection' -import { getLogger } from '../shared/logger/logger' -import { builderIdStartUrl } from '../auth/sso/constants' - -type ConnectParams = { - devEnvironmentId: string - spaceName: string - projectName: string - sso?: { - startUrl: string - region: string - } -} - -export function register( - handler: UriHandler, - commands: Pick -) { - async function cloneHandler(params: ReturnType) { - if (params.url.authority.endsWith(getCodeCatalystConfig().gitHostname)) { - await commands.cloneRepo.execute(undefined, params.url) - } else { - await vscode.commands.executeCommand('git.clone', params.url.toString()) - } - } - - async function connectHandler(params: ConnectParams) { - await commands.openDevEnv.execute( - undefined, - { - id: params.devEnvironmentId, - org: { name: params.spaceName }, - project: { name: params.projectName }, - }, - undefined, - params.sso - ) - } - - return vscode.Disposable.from( - handler.onPath('/clone', cloneHandler, parseCloneParams), - handler.onPath('/connect/codecatalyst', connectHandler, parseConnectParams), - handler.onPath('/connect/caws', connectHandler, parseConnectParamsOld) // FIXME: remove this before GA - ) -} - -function parseCloneParams(query: SearchParams) { - return { url: query.getAsOrThrow('url', 'A URL must be provided', (v) => vscode.Uri.parse(v, true)) } -} - -export function parseConnectParams(query: SearchParams): ConnectParams { - const params = query.getFromKeysOrThrow('devEnvironmentId', 'spaceName', 'projectName') - - try { - const ssoParams = query.getFromKeysOrThrow('sso_start_url', 'sso_region') - return { ...params, sso: { startUrl: ssoParams.sso_start_url, region: ssoParams.sso_region } } - } catch { - getLogger().debug(`No IdC SSO params provided in CodeCatalyst URI, defaulting to Builder ID.`) - return { ...params, sso: { startUrl: builderIdStartUrl, region: defaultSsoRegion } } - } -} - -function parseConnectParamsOld(query: SearchParams): ConnectParams { - const params = query.getFromKeysOrThrow('devEnvironmentId', 'organizationName', 'projectName') - return { - devEnvironmentId: params.devEnvironmentId, - spaceName: params.organizationName, - projectName: params.projectName, - } -} diff --git a/packages/core/src/codecatalyst/utils.ts b/packages/core/src/codecatalyst/utils.ts deleted file mode 100644 index 3f9cf6fe0b..0000000000 --- a/packages/core/src/codecatalyst/utils.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Ide } from '@aws-sdk/client-codecatalyst' -import * as vscode from 'vscode' -import { CodeCatalystResource, getCodeCatalystConfig } from '../shared/clients/codecatalystClient' -import { pushIf } from '../shared/utilities/collectionUtils' -import { getLogger } from '../shared/logger/logger' - -/** - * Builds a web URL from the given CodeCatalyst object. - */ -export function toCodeCatalystUrl(resource: CodeCatalystResource): string { - const prefix = `https://${getCodeCatalystConfig().hostname}/spaces` - - const format = (org: string, proj?: string, repo?: string, branch?: string) => { - const parts = [prefix, org] - pushIf(parts, !!proj, 'projects', proj) - pushIf(parts, !!repo, 'source-repositories', repo) - pushIf(parts, !!branch, 'branch', 'refs', 'heads', branch) - - return parts.concat('view').join('/') - } - - switch (resource.type) { - case 'org': - return format(resource.name) - case 'project': - return format(resource.org.name, resource.name) - case 'repo': - return format(resource.org.name, resource.project.name, resource.name) - case 'branch': - return format(resource.org.name, resource.project.name, resource.repo.name, resource.name) - case 'devEnvironment': - // There's currently no page to view an individual devenv. - // This may be changed to direct to the underlying repository instead - return [prefix, resource.org.name, 'projects', resource.project.name, 'dev-environments'].join('/') - } -} - -export function getHelpUrl(): string { - return `https://${getCodeCatalystConfig().hostname}/help` -} - -/** - * Builds a web URL from the given CodeCatalyst object. - */ -export function openCodeCatalystUrl(o: CodeCatalystResource) { - const url = toCodeCatalystUrl(o) - vscode.env.openExternal(vscode.Uri.parse(url)).then(undefined, (e) => { - getLogger().error('openExternal failed: %s', (e as Error).message) - }) -} - -/** Returns true if the dev env has a "vscode" IDE runtime. */ -export function isDevenvVscode(ides: Ide[] | undefined): boolean { - return ides !== undefined && ides.some((ide) => ide.name === 'VSCode') -} diff --git a/packages/core/src/codecatalyst/vue/compute.vue b/packages/core/src/codecatalyst/vue/compute.vue deleted file mode 100644 index 99c28e637c..0000000000 --- a/packages/core/src/codecatalyst/vue/compute.vue +++ /dev/null @@ -1,167 +0,0 @@ - - - - - diff --git a/packages/core/src/codecatalyst/vue/configure/backend.ts b/packages/core/src/codecatalyst/vue/configure/backend.ts deleted file mode 100644 index e5dd360cac..0000000000 --- a/packages/core/src/codecatalyst/vue/configure/backend.ts +++ /dev/null @@ -1,210 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { ConnectedDevEnv, DevEnvironmentId, getDevfileLocation } from '../../model' -import { CodeCatalystCommands, DevEnvironmentSettings } from '../../commands' -import { VueWebview } from '../../../webviews/main' -import { Prompter } from '../../../shared/ui/prompter' -import { isValidResponse } from '../../../shared/wizards/wizard' -import { sleep } from '../../../shared/utilities/timeoutUtils' -import { GetStatusResponse } from '../../../shared/clients/devenvClient' -import { openCodeCatalystUrl } from '../../utils' -import { assertHasProps } from '../../../shared/utilities/tsUtils' -import { - createAliasPrompter, - createInstancePrompter, - createTimeoutPrompter, - getAllInstanceDescriptions, - isValidSubscriptionType, -} from '../../wizards/devenvSettings' -import { updateDevfileCommand } from '../../devfile' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { isLongReconnect, removeReconnectionInformation, saveReconnectionInformation } from '../../reconnect' -import { CodeCatalystClient, DevEnvironment } from '../../../shared/clients/codecatalystClient' - -const localize = nls.loadMessageBundle() - -export class CodeCatalystConfigureWebview extends VueWebview { - public static readonly sourcePath: string = 'src/codecatalyst/vue/configure/index.js' - public readonly id = 'configureCodeCatalyst' - - public readonly onDidChangeDevfile = new vscode.EventEmitter() - - public constructor( - private readonly client: CodeCatalystClient, - private readonly devenv: ConnectedDevEnv, - private readonly commands: typeof CodeCatalystCommands.declared - ) { - super(CodeCatalystConfigureWebview.sourcePath) - } - - public init() { - return this.devenv.summary - } - - public getAllInstanceDescriptions() { - return getAllInstanceDescriptions() - } - - public async getDevfileLocation() { - const location = await getDevfileLocation(this.devenv.devenvClient) - return vscode.workspace.asRelativePath(location) - } - - public async openDevfile() { - const location = await getDevfileLocation(this.devenv.devenvClient) - return this.commands.openDevfile.execute(location) - } - - public async updateDevfile(location: string) { - // TODO(sijaden): we should be able to store the absolute URI somewhere? - const rootDirectory = vscode.workspace.workspaceFolders?.[0].uri - if (!rootDirectory) { - throw new Error('No workspace folder found') - } - - await updateDevfileCommand.execute(vscode.Uri.joinPath(rootDirectory, location)) - } - - public async stopDevEnv(id: DevEnvironmentId) { - return this.commands.stopDevEnv.execute(id) - } - - public async deleteDevEnv(id: DevEnvironmentId) { - return this.commands.deleteDevEnv.execute(id) - } - - public async updateDevEnv( - id: Pick, - settings: DevEnvironmentSettings - ) { - if (isLongReconnect(this.devenv.summary, settings)) { - try { - await saveReconnectionInformation(id) - const resp = await this.commands.updateDevEnv.execute(id, settings) - if (resp === undefined) { - await removeReconnectionInformation(id) - } else { - reportClosingMessage() - } - - return resp - } catch (err) { - await removeReconnectionInformation(id) - throw err - } - } else { - return this.commands.updateDevEnv.execute(id, settings) - } - } - - public async showLogsMessage(title: string): Promise { - return showViewLogsMessage(title) - } - - public async editSetting( - settings: DevEnvironmentSettings, - key: keyof DevEnvironmentSettings - ): Promise { - const spaceName = this.devenv.summary.org.name - const subscriptionType = await this.client - .getSubscription({ spaceName }) - .then((resp) => (isValidSubscriptionType(resp.subscriptionType) ? resp.subscriptionType : 'FREE')) - - async function prompt(prompter: Prompter) { - prompter.recentItem = settings[key] - const response = await prompter.prompt() - - if (isValidResponse(response)) { - return { ...settings, [key]: response } - } else { - return settings - } - } - - switch (key) { - case 'alias': - return prompt(createAliasPrompter()) - case 'instanceType': - return prompt(createInstancePrompter(subscriptionType)) - case 'inactivityTimeoutMinutes': - return prompt(createTimeoutPrompter()) - case 'persistentStorage': - throw new Error('Persistent storage cannot be changed after creation') - } - } - - public openBranch(): void { - const repo = this.devenv.summary.repositories?.[0] - assertHasProps(repo, 'branchName') - openCodeCatalystUrl({ - type: 'branch', - name: repo.branchName, - repo: { name: repo.repositoryName }, - org: { name: this.devenv.summary.org.name }, - project: { name: this.devenv.summary.project.name }, - }) - } -} - -const Panel = VueWebview.compilePanel(CodeCatalystConfigureWebview) -let activePanel: InstanceType | undefined -let subscriptions: vscode.Disposable[] | undefined - -export async function showConfigureDevEnv( - client: CodeCatalystClient, - ctx: vscode.ExtensionContext, - devenv: ConnectedDevEnv, - commands: typeof CodeCatalystCommands.declared -): Promise { - activePanel ??= new Panel(ctx, client, devenv, commands) - const webview = await activePanel.show({ - title: localize('AWS.view.configureDevEnv.title', 'Dev Environment Settings'), - viewColumn: vscode.ViewColumn.Active, - }) - - if (!subscriptions) { - const poller = pollDevfile(devenv, activePanel.server) - subscriptions = [ - poller, - webview.onDidDispose(() => { - vscode.Disposable.from(...(subscriptions ?? [])).dispose() - activePanel = undefined - subscriptions = undefined - }), - ] - } -} - -function pollDevfile(devenv: ConnectedDevEnv, server: CodeCatalystConfigureWebview): vscode.Disposable { - let done = false - - void (async () => { - while (!done) { - const resp = await devenv.devenvClient.getStatus() - if (resp.status === 'CHANGED') { - server.onDidChangeDevfile.fire({ ...resp, actionId: 'devfile' }) - } - await sleep(2500) - } - })() - - return { dispose: () => (done = true) } -} - -function reportClosingMessage(): void { - void vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: 'Session ended. Session will restore when the Dev Environment is available again.', - }, - async (progress, token) => { - await sleep(2500) - await vscode.commands.executeCommand('workbench.action.remote.close') - } - ) -} diff --git a/packages/core/src/codecatalyst/vue/configure/index.ts b/packages/core/src/codecatalyst/vue/configure/index.ts deleted file mode 100644 index fbd8dd1fb1..0000000000 --- a/packages/core/src/codecatalyst/vue/configure/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createApp } from 'vue' -import component from './root.vue' - -const create = () => createApp(component) -const app = create() -app.mount('#vue-app') - -window.addEventListener('remount', () => { - app.unmount() - create().mount('#vue-app') -}) diff --git a/packages/core/src/codecatalyst/vue/configure/root.vue b/packages/core/src/codecatalyst/vue/configure/root.vue deleted file mode 100644 index 998c780d15..0000000000 --- a/packages/core/src/codecatalyst/vue/configure/root.vue +++ /dev/null @@ -1,203 +0,0 @@ - - - - - diff --git a/packages/core/src/codecatalyst/vue/create/backend.ts b/packages/core/src/codecatalyst/vue/create/backend.ts deleted file mode 100644 index 102c632965..0000000000 --- a/packages/core/src/codecatalyst/vue/create/backend.ts +++ /dev/null @@ -1,284 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { CodeCatalystCommands, DevEnvironmentSettings } from '../../commands' -import { VueWebview } from '../../../webviews/main' -import { Prompter } from '../../../shared/ui/prompter' -import { isValidResponse } from '../../../shared/wizards/wizard' -import { - createAliasPrompter, - createInstancePrompter, - createStoragePrompter, - createTimeoutPrompter, - getAllInstanceDescriptions, - isValidSubscriptionType, -} from '../../wizards/devenvSettings' -import { showViewLogsMessage } from '../../../shared/utilities/messages' -import { - CodeCatalystBranch, - CodeCatalystOrg, - CodeCatalystProject, - CodeCatalystClient, - DevEnvironment, - isThirdPartyRepo, -} from '../../../shared/clients/codecatalystClient' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { isNonNullable } from '../../../shared/utilities/tsUtils' -import { createOrgPrompter, createProjectPrompter } from '../../wizards/selectResource' -import { GetSourceRepositoryCloneUrlsRequest } from '@aws-sdk/client-codecatalyst' -import { QuickPickPrompter } from '../../../shared/ui/pickerPrompter' - -interface LinkedResponse { - readonly type: 'linked' - readonly selectedSpace: Pick - readonly selectedProject: CodeCatalystProject - readonly selectedBranch: CodeCatalystBranch - readonly newBranch: string -} - -interface EmptyResponse { - readonly type: 'none' - readonly selectedSpace: Pick - readonly selectedProject: CodeCatalystProject -} - -export type SourceResponse = LinkedResponse | EmptyResponse - -export class CodeCatalystCreateWebview extends VueWebview { - public static readonly sourcePath: string = 'src/codecatalyst/vue/create/index.js' - public readonly id = 'createCodeCatalyst' - - private projectPrompter?: QuickPickPrompter - private spacePrompter?: QuickPickPrompter - - public constructor( - private readonly client: CodeCatalystClient, - private readonly commands: typeof CodeCatalystCommands.declared, - private readonly onComplete: (devenv?: DevEnvironment) => void - ) { - super(CodeCatalystCreateWebview.sourcePath) - - // triggers pre-loading of Spaces - this.spacePrompter = createOrgPrompter(client) - } - - public close() { - this.dispose() - this.onComplete() - } - - /** - * Opens a quick pick that lists all Spaces of the user. - * - * @returns Space if it was selected, otherwise undefined due to user cancellation. - */ - public async quickPickSpace(): Promise { - this.spacePrompter = this.spacePrompter ?? createOrgPrompter(this.client) - const selectedSpace = await this.spacePrompter.prompt() - this.spacePrompter = undefined // We want the prompter to be re-created on subsequent calls - - if (!isValidResponse(selectedSpace)) { - return undefined - } - - // This will initiate preloading of the projects - this.projectPrompter = createProjectPrompter(this.client, selectedSpace?.name) - - return selectedSpace - } - - /** - * Opens a quick pick that lists all Projects from a given Space. - * - * @param spaceName Space to show Projects from - * @returns Project if it was selected, otherwise undefined due to user cancellation. - */ - public async quickPickProject(spaceName: CodeCatalystOrg['name']): Promise { - this.projectPrompter = this.projectPrompter ?? createProjectPrompter(this.client, spaceName) - const selectedProject = await this.projectPrompter.prompt() - this.projectPrompter = undefined // We want the prompter to be re-created on subsequent calls - - if (!isValidResponse(selectedProject)) { - return undefined - } - - return selectedProject - } - - public async getBranches(project: CodeCatalystProject) { - const repos = this.client - .listSourceRepositories({ - spaceName: project.org.name, - projectName: project.name, - }) - .flatten() - - const branches = repos.map((r) => - this.client - .listBranches({ - spaceName: r.org.name, - projectName: r.project.name, - sourceRepositoryName: r.name, - }) - .flatten() - .promise() - ) - - return branches.flatten().promise() - } - - public isThirdPartyRepo(codeCatalystRepo: GetSourceRepositoryCloneUrlsRequest): Promise { - return isThirdPartyRepo(this.client, codeCatalystRepo) - } - - public getAllInstanceDescriptions() { - return getAllInstanceDescriptions() - } - - public async showLogsMessage(title: string): Promise { - return showViewLogsMessage(title) - } - - public async editSetting( - settings: DevEnvironmentSettings, - key: keyof DevEnvironmentSettings, - org?: Pick - ): Promise { - const subscriptionType = isNonNullable(org) - ? await this.client - .getSubscription({ spaceName: org.name }) - .then((resp) => (isValidSubscriptionType(resp.subscriptionType) ? resp.subscriptionType : 'FREE')) - : 'FREE' - - async function prompt(prompter: Prompter) { - prompter.recentItem = settings[key] - const response = await prompter.prompt() - - if (isValidResponse(response)) { - return { ...settings, [key]: response } - } else { - return settings - } - } - - switch (key) { - case 'alias': - return prompt(createAliasPrompter()) - case 'instanceType': - return prompt(createInstancePrompter(subscriptionType)) - case 'inactivityTimeoutMinutes': - return prompt(createTimeoutPrompter()) - case 'persistentStorage': - return prompt(createStoragePrompter(subscriptionType)) - } - } - - public async submit(settings: DevEnvironmentSettings, source: SourceResponse) { - const devenv = await this.createDevEnvOfType(settings, source) - void this.commands.openDevEnv.execute(undefined, devenv) - } - - public async createDevEnvOfType(settings: DevEnvironmentSettings, source: SourceResponse) { - const devenv: DevEnvironment = await (() => { - switch (source.type) { - case 'none': - return this.createEmptyDevEnv(settings, source) - case 'linked': - return this.createLinkedDevEnv(settings, source) - } - })() - - telemetry.record({ - source: 'Webview', - codecatalyst_createDevEnvironmentRepoType: source.type, - }) - - this.onComplete(devenv) - return devenv - } - - private async createEmptyDevEnv(settings: DevEnvironmentSettings, source: EmptyResponse) { - return this.client.createDevEnvironment({ - ides: [{ name: 'VSCode' }], - projectName: source.selectedProject.name, - spaceName: source.selectedProject.org.name, - ...settings, - }) - } - - private async createLinkedDevEnv(settings: DevEnvironmentSettings, source: LinkedResponse) { - const isNewBranch = !!source.newBranch - if (isNewBranch) { - await this.client.createSourceBranch({ - name: source.newBranch, - spaceName: source.selectedProject.org.name, - projectName: source.selectedProject.name, - sourceRepositoryName: source.selectedBranch.repo.name, - headCommitId: source.selectedBranch.headCommitId, - }) - } - - const branchName = isNewBranch ? source.newBranch : source.selectedBranch.name.replace('refs/heads/', '') - return this.client.createDevEnvironment({ - ides: [{ name: 'VSCode' }], - projectName: source.selectedProject.name, - spaceName: source.selectedProject.org.name, - repositories: [ - { - repositoryName: source.selectedBranch.repo.name, - branchName, - }, - ], - ...settings, - }) - } -} - -// TODO(sijaden): de-dupe this basic init pattern for webviews -// the logic here is mainly to preserve the same panel in case the -// user re-runs a command, which is fairly common -const Panel = VueWebview.compilePanel(CodeCatalystCreateWebview) -let activePanel: InstanceType | undefined -let subscriptions: vscode.Disposable[] | undefined -let submitPromise: Promise | undefined - -export async function showCreateDevEnv( - client: CodeCatalystClient, - ctx: vscode.ExtensionContext, - commands: typeof CodeCatalystCommands.declared -): Promise { - submitPromise ??= new Promise((resolve, reject) => { - activePanel ??= new Panel(ctx, client, commands, (devenv) => { - if (devenv === undefined) { - reject(new CancellationError('user')) - } else { - resolve() - } - }) - }) - - const webview = await activePanel!.show({ - title: localize('AWS.view.createDevEnv.title', 'Create a CodeCatalyst Dev Environment'), - viewColumn: vscode.ViewColumn.Active, - }) - - if (!subscriptions) { - subscriptions = [ - webview.onDidDispose(() => { - vscode.Disposable.from(...(subscriptions ?? [])).dispose() - activePanel = undefined - subscriptions = undefined - submitPromise = undefined - }), - ] - } - - return submitPromise -} diff --git a/packages/core/src/codecatalyst/vue/create/index.ts b/packages/core/src/codecatalyst/vue/create/index.ts deleted file mode 100644 index fbd8dd1fb1..0000000000 --- a/packages/core/src/codecatalyst/vue/create/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createApp } from 'vue' -import component from './root.vue' - -const create = () => createApp(component) -const app = create() -app.mount('#vue-app') - -window.addEventListener('remount', () => { - app.unmount() - create().mount('#vue-app') -}) diff --git a/packages/core/src/codecatalyst/vue/create/root.vue b/packages/core/src/codecatalyst/vue/create/root.vue deleted file mode 100644 index f794acc2d9..0000000000 --- a/packages/core/src/codecatalyst/vue/create/root.vue +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - diff --git a/packages/core/src/codecatalyst/vue/create/source.vue b/packages/core/src/codecatalyst/vue/create/source.vue deleted file mode 100644 index c3e8e60d29..0000000000 --- a/packages/core/src/codecatalyst/vue/create/source.vue +++ /dev/null @@ -1,349 +0,0 @@ - - - - - diff --git a/packages/core/src/codecatalyst/vue/devfile.vue b/packages/core/src/codecatalyst/vue/devfile.vue deleted file mode 100644 index c788ff94f1..0000000000 --- a/packages/core/src/codecatalyst/vue/devfile.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/packages/core/src/codecatalyst/vue/summary.vue b/packages/core/src/codecatalyst/vue/summary.vue deleted file mode 100644 index 38ac2e1d18..0000000000 --- a/packages/core/src/codecatalyst/vue/summary.vue +++ /dev/null @@ -1,163 +0,0 @@ - - - - - diff --git a/packages/core/src/codecatalyst/wizards/devenvSettings.ts b/packages/core/src/codecatalyst/wizards/devenvSettings.ts deleted file mode 100644 index 676b65fb74..0000000000 --- a/packages/core/src/codecatalyst/wizards/devenvSettings.ts +++ /dev/null @@ -1,104 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as settings from './parameterDescriptions.json' -import { createQuickPick, QuickPickPrompter } from '../../shared/ui/pickerPrompter' -import { createInputBox, InputBoxPrompter } from '../../shared/ui/inputPrompter' -import { Prompter } from '../../shared/ui/prompter' -import { toTitleCase } from '../../shared/utilities/textUtilities' - -const devenvOptions = settings['environment'] -export type InstanceType = keyof (typeof devenvOptions)['instanceType'] -export type SubscriptionType = (typeof subscriptionTypes)[number] - -const subscriptionTypes = ['FREE', 'STANDARD'] as const - -export function isValidSubscriptionType(type = ''): type is SubscriptionType { - return (subscriptionTypes as readonly string[]).includes(type) -} - -export interface InstanceDescription { - name: string - specs: string -} - -function entries, K extends keyof T = keyof T & string>(obj: T): [K, T[K]][] { - return Object.entries(obj) as any -} - -function abbreviateUnit(unit: string): string { - switch (unit) { - case 'gigabyte': - return 'GB' - case 'megabyte': - return 'MB' - default: - return '' - } -} - -export function getInstanceDescription(type: InstanceType): InstanceDescription { - const desc = devenvOptions.instanceType[type] - - return { - name: toTitleCase(type.split('.').pop()!), - specs: `${desc.vcpus} vCPUs, ${desc.ram.value}${abbreviateUnit(desc.ram.unit)} RAM`, - } -} - -export function getAllInstanceDescriptions(): { [key: string]: InstanceDescription } { - const desc: { [key: string]: InstanceDescription } = {} - for (const [k] of entries(devenvOptions.instanceType)) { - desc[k] = getInstanceDescription(k) - } - return desc -} - -export function createInstancePrompter(subscriptionType: SubscriptionType): QuickPickPrompter { - const isSupported = (name: string) => subscriptionType !== 'FREE' || name === 'dev.standard1.small' - const items = entries(devenvOptions.instanceType).map(([name, desc]) => ({ - data: name, - label: `${getInstanceDescription(name).name} (${getInstanceDescription(name).specs})`, - description: isSupported(name) ? '' : 'unavailable in current billing tier', - invalidSelection: !isSupported(name), - })) - - return createQuickPick(items, { - title: 'Compute Size', - }) -} - -export function createTimeoutPrompter(): Prompter { - return createInputBox({ - title: 'Timeout Length', - placeholder: 'Timeout length in minutes', - validateInput: (resp) => (Number.isNaN(Number(resp)) ? 'Timeout must be a number' : undefined), - }).transform(Number) -} - -export function createAliasPrompter(): InputBoxPrompter { - return createInputBox({ - title: 'Edit Alias', - validateInput: (value) => { - if (value?.length > 128) { - return 'Dev Environment alias cannot be longer than 128 characters' - } - }, - }) -} - -export function createStoragePrompter(subscriptionType: SubscriptionType): QuickPickPrompter<{ sizeInGiB: number }> { - const isSupported = (v: number) => subscriptionType !== 'FREE' || v === 16 - const items = settings.environment.persistentStorageSize.map((v) => ({ - data: { sizeInGiB: v }, - label: `${v} GB`, - description: isSupported(v) ? '' : 'unavailable in current organization billing tier', - invalidSelection: !isSupported(v), - })) - - return createQuickPick(items, { - title: 'Storage Size', - }) -} diff --git a/packages/core/src/codecatalyst/wizards/parameterDescriptions.json b/packages/core/src/codecatalyst/wizards/parameterDescriptions.json deleted file mode 100644 index de8983461a..0000000000 --- a/packages/core/src/codecatalyst/wizards/parameterDescriptions.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "environment": { - "instanceType": { - "dev.standard1.small": { - "vcpus": 2, - "ram": { "value": 4, "unit": "gigabyte" }, - "arch": "x86_64" - }, - "dev.standard1.medium": { - "vcpus": 4, - "ram": { "value": 8, "unit": "gigabyte" }, - "arch": "x86_64" - }, - "dev.standard1.large": { - "vcpus": 8, - "ram": { "value": 16, "unit": "gigabyte" }, - "arch": "x86_64" - }, - "dev.standard1.xlarge": { - "vcpus": 16, - "ram": { "value": 32, "unit": "gigabyte" }, - "arch": "x86_64" - } - }, - "persistentStorageSize": [16, 32, 64], - "defaultDevfileLocation": "https://registry.devfile.io/devfiles/nodejs" - } -} diff --git a/packages/core/src/codecatalyst/wizards/selectResource.ts b/packages/core/src/codecatalyst/wizards/selectResource.ts deleted file mode 100644 index 54e3bb2095..0000000000 --- a/packages/core/src/codecatalyst/wizards/selectResource.ts +++ /dev/null @@ -1,238 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as codecatalyst from '../../shared/clients/codecatalystClient' -import { createCommonButtons, createRefreshButton } from '../../shared/ui/buttons' -import { - createQuickPick, - DataQuickPickItem, - ExtendedQuickPickOptions, - QuickPickPrompter, -} from '../../shared/ui/pickerPrompter' -import { AsyncCollection } from '../../shared/utilities/asyncCollection' -import { isValidResponse } from '../../shared/wizards/wizard' -import { associateDevEnv, docs } from '../model' -import { getHelpUrl, isDevenvVscode } from '../utils' -import { getLogger } from '../../shared/logger/logger' -import { getRelativeDate } from '../../shared/datetime' - -export function createRepoLabel(r: codecatalyst.CodeCatalystRepo): string { - return `${r.org.name} / ${r.project.name} / ${r.name}` -} - -/** - * Maps CodeCatalystFoo objects to `vscode.QuickPickItem` objects. - */ -export function asQuickpickItem(resource: T): DataQuickPickItem { - switch (resource.type) { - case 'project': - return { - label: `${resource.org.name} / ${resource.name}`, - description: resource.description, - data: resource, - } - case 'repo': - return { - label: createRepoLabel(resource), - description: resource.description, - data: resource, - } - case 'devEnvironment': - return { ...fromDevEnv(resource), data: resource } - case 'org': - return { label: resource.name, detail: resource.description, data: resource } - default: - return { label: resource.name, data: resource } - } -} - -function fromDevEnv(env: codecatalyst.DevEnvironment): Omit, 'data'> { - const labelParts = [] as string[] - - if (env.status === 'RUNNING') { - labelParts.push('$(pass)') - } else { - labelParts.push('$(circle-slash)') // TODO(sijaden): get actual 'stopped' icon - } - - labelParts.push(env.alias ? env.alias : env.id) - - const repo = env.repositories[0] - const branchName = repo?.branchName?.replace('refs/heads/', '') - const repoLabel = repo - ? branchName - ? `${repo.repositoryName}/${branchName}` - : repo.repositoryName - : '(no repository)' - - const statusLabel = env.status === 'RUNNING' ? 'RUNNING - IN USE' : env.status - const desc = `${statusLabel} ${getRelativeDate(env.lastUpdatedTime)}` - - return { - label: labelParts.join(' '), - description: desc, - detail: `${env.org.name}/${env.project.name}/${repoLabel}`, - } -} - -function createResourcePrompter( - resources: AsyncCollection, - helpUri: vscode.Uri, - presentation: Omit, 'buttons'> -): QuickPickPrompter { - const refresh = createRefreshButton() - const items = resources.map((p) => p.map(asQuickpickItem)) - const prompter = createQuickPick(items, { - buttons: [refresh, ...createCommonButtons(helpUri)], - ...presentation, - matchOnDetail: true, - }) - - refresh.onClick = () => { - prompter.clearAndLoadItems(items).catch((e) => { - getLogger().error('clearAndLoadItems failed: %s', (e as Error).message) - }) - } - - return prompter -} - -export function createOrgPrompter( - client: codecatalyst.CodeCatalystClient -): QuickPickPrompter { - const helpUri = docs.main - return createResourcePrompter(client.listSpaces(), helpUri, { - title: 'Select a CodeCatalyst Organization', - placeholder: 'Search for an Organization', - }) -} - -export function createProjectPrompter( - client: codecatalyst.CodeCatalystClient, - spaceName?: codecatalyst.CodeCatalystOrg['name'] -): QuickPickPrompter { - const helpUri = docs.main - const projects = spaceName ? client.listProjects({ spaceName }) : client.listResources('project') - - return createResourcePrompter(projects, helpUri, { - title: 'Select a CodeCatalyst Project', - placeholder: 'Search for a Project', - }) -} - -export function createRepoPrompter( - client: codecatalyst.CodeCatalystClient, - proj?: codecatalyst.CodeCatalystProject, - thirdParty?: boolean -): QuickPickPrompter { - const helpUri = docs.main - const repos = proj - ? client.listSourceRepositories({ spaceName: proj.org.name, projectName: proj.name }, thirdParty) - : client.listResources('repo', thirdParty) - - return createResourcePrompter(repos, helpUri, { - title: 'Select a CodeCatalyst Repository', - placeholder: 'Search for a CodeCatalyst Repository', - }) -} - -export function createDevEnvPrompter( - client: codecatalyst.CodeCatalystClient, - proj?: codecatalyst.CodeCatalystProject -): QuickPickPrompter { - const helpUri = docs.devenv - const envs = proj ? client.listDevEnvironments(proj) : client.listResources('devEnvironment') - const filtered = envs.map((arr) => arr.filter((env) => isDevenvVscode(env.ides))) - const isData = (obj: T | DataQuickPickItem['data']): obj is T => { - return typeof obj !== 'function' && isValidResponse(obj) - } - - return createResourcePrompter(filtered, helpUri, { - title: 'Select a CodeCatalyst Dev Environment', - placeholder: 'Search for a Dev Environment', - compare: (a, b) => { - if (isData(a.data) && isData(b.data)) { - return b.data.lastUpdatedTime.getTime() - a.data.lastUpdatedTime.getTime() - } - - return 0 - }, - }) -} - -type ResourceType = codecatalyst.CodeCatalystResource['type'] - -export async function selectCodeCatalystResource( - client: codecatalyst.CodeCatalystClient, - type: T & ResourceType -): Promise<(codecatalyst.CodeCatalystResource & { type: typeof type }) | undefined> { - const prompter = (() => { - switch (type as ResourceType) { - case 'org': - return createOrgPrompter(client) - case 'project': - return createProjectPrompter(client) - case 'repo': - return createRepoPrompter(client) - case 'branch': - throw new Error('Picking a branch is not supported') - case 'devEnvironment': - return createDevEnvPrompter(client) - } - })() - - const response = await prompter.prompt() - return isValidResponse(response) ? (response as codecatalyst.CodeCatalystResource & { type: T }) : undefined -} - -export async function selectCodeCatalystRepository( - client: codecatalyst.CodeCatalystClient, - includeThirdPartyRepos?: boolean -): Promise { - const prompter = createRepoPrompter(client, undefined, includeThirdPartyRepos) - const response = await prompter.prompt() - return isValidResponse(response) ? response : undefined -} - -/** - * Special-case of {@link createRepoPrompter} for creating a new devenv - */ -export async function selectRepoForDevEnv( - client: codecatalyst.CodeCatalystClient -): Promise { - const repos = associateDevEnv(client, client.listResources('repo').flatten()) - - const refresh = createRefreshButton() - const items = repos.map((repo) => [ - { - ...asQuickpickItem(repo), - invalidSelection: repo.devEnv !== undefined, - description: repo.devEnv ? `Repository already has a Dev Environment` : '', - }, - ]) - - const prompter = createQuickPick(items, { - buttons: [refresh, ...createCommonButtons(getHelpUrl())], - title: 'Select a CodeCatalyst Repository', - placeholder: 'Search for a Repository', - compare: (a, b) => { - if (a.invalidSelection === b.invalidSelection) { - return 0 - } - - return a.invalidSelection ? 1 : b.invalidSelection ? -1 : 0 - }, - }) - - refresh.onClick = () => { - prompter.clearAndLoadItems(items).catch((e) => { - getLogger().error('clearAndLoadItems failed: %s', (e as Error).message) - }) - } - - const response = await prompter.prompt() - return isValidResponse(response) ? response : undefined -} diff --git a/packages/core/src/codewhispererChat/app.ts b/packages/core/src/codewhispererChat/app.ts deleted file mode 100644 index 3916e57195..0000000000 --- a/packages/core/src/codewhispererChat/app.ts +++ /dev/null @@ -1,240 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EventEmitter } from 'vscode' -import { ChatController as CwChatController } from '../codewhispererChat/controllers/chat/controller' -import { UIMessageListener } from './view/messages/messageListener' -import { AmazonQAppInitContext } from '../amazonq/apps/initContext' -import { MessageListener } from '../amazonq/messages/messageListener' -import { MessagePublisher } from '../amazonq/messages/messagePublisher' -import { - ViewDiff, - ChatItemFeedbackMessage, - ChatItemVotedMessage, - CopyCodeToClipboard, - FooterInfoLinkClick, - InsertCodeAtCursorPosition, - PromptMessage, - ResponseBodyLinkClickMessage, - SourceLinkClickMessage, - StopResponseMessage, - TabChangedMessage, - TabClosedMessage, - TabCreatedMessage, - TriggerTabIDReceived, - UIFocusMessage, - AcceptDiff, - QuickCommandGroupActionClick, - FileClick, - TabBarButtonClick, - SaveChatMessage, -} from './controllers/chat/model' -import { EditorContextCommand, registerCommands } from './commands/registerCommands' -import { - ContextSelectedMessage, - CustomFormActionMessage, - DetailedListActionClickMessage, - DetailedListFilterChangeMessage, - DetailedListItemSelectMessage, -} from './view/connector/connector' - -export function init(appContext: AmazonQAppInitContext) { - const cwChatControllerEventEmitters = { - processPromptChatMessage: new EventEmitter(), - processTabCreatedMessage: new EventEmitter(), - processTabClosedMessage: new EventEmitter(), - processTabChangedMessage: new EventEmitter(), - processInsertCodeAtCursorPosition: new EventEmitter(), - processAcceptDiff: new EventEmitter(), - processViewDiff: new EventEmitter(), - processCopyCodeToClipboard: new EventEmitter(), - processContextMenuCommand: new EventEmitter(), - processTriggerTabIDReceived: new EventEmitter(), - processStopResponseMessage: new EventEmitter(), - processChatItemVotedMessage: new EventEmitter(), - processChatItemFeedbackMessage: new EventEmitter(), - processUIFocusMessage: new EventEmitter(), - processSourceLinkClick: new EventEmitter(), - processResponseBodyLinkClick: new EventEmitter(), - processFooterInfoLinkClick: new EventEmitter(), - processContextCommandUpdateMessage: new EventEmitter(), - processQuickCommandGroupActionClicked: new EventEmitter(), - processCustomFormAction: new EventEmitter(), - processContextSelected: new EventEmitter(), - processFileClick: new EventEmitter(), - processTabBarButtonClick: new EventEmitter(), - processSaveChat: new EventEmitter(), - processDetailedListFilterChangeMessage: new EventEmitter(), - processDetailedListItemSelectMessage: new EventEmitter(), - processDetailedListActionClickMessage: new EventEmitter(), - } - - const cwChatControllerMessageListeners = { - processPromptChatMessage: new MessageListener( - cwChatControllerEventEmitters.processPromptChatMessage - ), - processTabCreatedMessage: new MessageListener( - cwChatControllerEventEmitters.processTabCreatedMessage - ), - processTabClosedMessage: new MessageListener( - cwChatControllerEventEmitters.processTabClosedMessage - ), - processTabChangedMessage: new MessageListener( - cwChatControllerEventEmitters.processTabChangedMessage - ), - processInsertCodeAtCursorPosition: new MessageListener( - cwChatControllerEventEmitters.processInsertCodeAtCursorPosition - ), - processAcceptDiff: new MessageListener(cwChatControllerEventEmitters.processAcceptDiff), - processViewDiff: new MessageListener(cwChatControllerEventEmitters.processViewDiff), - processCopyCodeToClipboard: new MessageListener( - cwChatControllerEventEmitters.processCopyCodeToClipboard - ), - processContextMenuCommand: new MessageListener( - cwChatControllerEventEmitters.processContextMenuCommand - ), - processTriggerTabIDReceived: new MessageListener( - cwChatControllerEventEmitters.processTriggerTabIDReceived - ), - processStopResponseMessage: new MessageListener( - cwChatControllerEventEmitters.processStopResponseMessage - ), - processChatItemVotedMessage: new MessageListener( - cwChatControllerEventEmitters.processChatItemVotedMessage - ), - processChatItemFeedbackMessage: new MessageListener( - cwChatControllerEventEmitters.processChatItemFeedbackMessage - ), - processUIFocusMessage: new MessageListener(cwChatControllerEventEmitters.processUIFocusMessage), - processSourceLinkClick: new MessageListener( - cwChatControllerEventEmitters.processSourceLinkClick - ), - processResponseBodyLinkClick: new MessageListener( - cwChatControllerEventEmitters.processResponseBodyLinkClick - ), - processFooterInfoLinkClick: new MessageListener( - cwChatControllerEventEmitters.processFooterInfoLinkClick - ), - processContextCommandUpdateMessage: new MessageListener( - cwChatControllerEventEmitters.processContextCommandUpdateMessage - ), - processQuickCommandGroupActionClicked: new MessageListener( - cwChatControllerEventEmitters.processQuickCommandGroupActionClicked - ), - processCustomFormAction: new MessageListener( - cwChatControllerEventEmitters.processCustomFormAction - ), - processContextSelected: new MessageListener( - cwChatControllerEventEmitters.processContextSelected - ), - processFileClick: new MessageListener(cwChatControllerEventEmitters.processFileClick), - processTabBarButtonClick: new MessageListener( - cwChatControllerEventEmitters.processTabBarButtonClick - ), - processSaveChat: new MessageListener(cwChatControllerEventEmitters.processSaveChat), - processDetailedListFilterChangeMessage: new MessageListener( - cwChatControllerEventEmitters.processDetailedListFilterChangeMessage - ), - processDetailedListItemSelectMessage: new MessageListener( - cwChatControllerEventEmitters.processDetailedListItemSelectMessage - ), - processDetailedListActionClickMessage: new MessageListener( - cwChatControllerEventEmitters.processDetailedListActionClickMessage - ), - } - - const cwChatControllerMessagePublishers = { - processPromptChatMessage: new MessagePublisher( - cwChatControllerEventEmitters.processPromptChatMessage - ), - processTabCreatedMessage: new MessagePublisher( - cwChatControllerEventEmitters.processTabCreatedMessage - ), - processTabClosedMessage: new MessagePublisher( - cwChatControllerEventEmitters.processTabClosedMessage - ), - processTabChangedMessage: new MessagePublisher( - cwChatControllerEventEmitters.processTabChangedMessage - ), - processInsertCodeAtCursorPosition: new MessagePublisher( - cwChatControllerEventEmitters.processInsertCodeAtCursorPosition - ), - processAcceptDiff: new MessagePublisher(cwChatControllerEventEmitters.processAcceptDiff), - processViewDiff: new MessagePublisher(cwChatControllerEventEmitters.processViewDiff), - processCopyCodeToClipboard: new MessagePublisher( - cwChatControllerEventEmitters.processCopyCodeToClipboard - ), - processContextMenuCommand: new MessagePublisher( - cwChatControllerEventEmitters.processContextMenuCommand - ), - processTriggerTabIDReceived: new MessagePublisher( - cwChatControllerEventEmitters.processTriggerTabIDReceived - ), - processStopResponseMessage: new MessagePublisher( - cwChatControllerEventEmitters.processStopResponseMessage - ), - processChatItemVotedMessage: new MessagePublisher( - cwChatControllerEventEmitters.processChatItemVotedMessage - ), - processChatItemFeedbackMessage: new MessagePublisher( - cwChatControllerEventEmitters.processChatItemFeedbackMessage - ), - processUIFocusMessage: new MessagePublisher( - cwChatControllerEventEmitters.processUIFocusMessage - ), - processSourceLinkClick: new MessagePublisher( - cwChatControllerEventEmitters.processSourceLinkClick - ), - processResponseBodyLinkClick: new MessagePublisher( - cwChatControllerEventEmitters.processResponseBodyLinkClick - ), - processFooterInfoLinkClick: new MessagePublisher( - cwChatControllerEventEmitters.processFooterInfoLinkClick - ), - processContextCommandUpdateMessage: new MessagePublisher( - cwChatControllerEventEmitters.processContextCommandUpdateMessage - ), - processQuickCommandGroupActionClicked: new MessagePublisher( - cwChatControllerEventEmitters.processQuickCommandGroupActionClicked - ), - processCustomFormAction: new MessagePublisher( - cwChatControllerEventEmitters.processCustomFormAction - ), - processContextSelected: new MessagePublisher( - cwChatControllerEventEmitters.processContextSelected - ), - processFileClick: new MessagePublisher(cwChatControllerEventEmitters.processFileClick), - processTabBarButtonClick: new MessagePublisher( - cwChatControllerEventEmitters.processTabBarButtonClick - ), - processSaveChat: new MessagePublisher(cwChatControllerEventEmitters.processSaveChat), - processDetailedListActionClickMessage: new MessagePublisher( - cwChatControllerEventEmitters.processDetailedListActionClickMessage - ), - processDetailedListFilterChangeMessage: new MessagePublisher( - cwChatControllerEventEmitters.processDetailedListFilterChangeMessage - ), - processDetailedListItemSelectMessage: new MessagePublisher( - cwChatControllerEventEmitters.processDetailedListItemSelectMessage - ), - } - - new CwChatController( - cwChatControllerMessageListeners, - appContext.getAppsToWebViewMessagePublisher(), - appContext.onDidChangeAmazonQVisibility.event - ) - - const cwChatUIInputEventEmitter = new EventEmitter() - - new UIMessageListener({ - chatControllerMessagePublishers: cwChatControllerMessagePublishers, - webViewMessageListener: new MessageListener(cwChatUIInputEventEmitter), - }) - - appContext.registerWebViewToAppMessagePublisher(new MessagePublisher(cwChatUIInputEventEmitter), 'cwc') - - registerCommands() -} diff --git a/packages/core/src/codewhispererChat/clients/chat/v0/model.ts b/packages/core/src/codewhispererChat/clients/chat/v0/model.ts deleted file mode 100644 index 60a0cc6436..0000000000 --- a/packages/core/src/codewhispererChat/clients/chat/v0/model.ts +++ /dev/null @@ -1,155 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -export type ContextKey = string -export type ContextKeys = ContextKey[] - -export interface MatchPolicy { - readonly should?: ContextKeys - readonly must?: ContextKeys - readonly mustNot?: ContextKeys -} - -export interface Context { - readonly matchPolicy?: MatchPolicy -} - -export interface FullyQualifiedName { - readonly source?: SourceIdentifier - readonly symbol?: SymbolIdentifier -} - -export type FullyQualifiedNamesUsages = FullyQualifiedName[] - -export type Name = string - -export type SymbolIdentifier = Name[] - -export type SourceIdentifier = Name[] -export type FullyQualifiedNames = FullyQualifiedName[] -export interface FullyQualifiedNamesDeclarationsUsages { - readonly used?: FullyQualifiedNamesUsages -} - -export type SimpleName = string -export type SimpleNames = SimpleName[] - -export interface CodeQuery { - readonly simpleNames?: SimpleNames - readonly fullyQualifiedNames?: FullyQualifiedNamesDeclarationsUsages -} - -export interface EditorContext { - readonly fileContent?: string - readonly language?: string - readonly query?: string - readonly code?: string - readonly context?: Context - readonly codeQuery?: CodeQuery -} - -export enum ApiDocsType { - RequestMatchedFqn, - RelatedFrequentlyUsedFqn, -} - -class ApiDocsMetadata { - constructor(public canonicalExample?: CanonicalExample) {} -} - -class CanonicalExample { - constructor( - public url: string, - public body: string - ) {} -} - -export interface ChatApiDocsSuggestion { - readonly url?: string - readonly title?: string - readonly body?: string - readonly ancestor?: string | null - readonly type: ApiDocsType - readonly metadata?: ApiDocsMetadata -} - -export interface ChatSuggestion { - readonly url: string - readonly title: string - readonly body: string - readonly context: string[] - readonly metadata: SuggestionMetadata - readonly type: string -} - -interface StackExchangeMetadata { - readonly answerCount: number - readonly isAccepted: boolean - readonly score: number - readonly lastActivityDate: number -} - -interface SuggestionMetadata { - readonly StackOverflow?: StackExchangeMetadata - readonly StackExchange?: StackExchangeMetadata -} - -export interface FollowUp { - readonly type: FollowUpType - readonly message?: string - readonly attachedSuggestions?: ChatSuggestion[] - readonly attachedApiDocsSuggestions?: ChatApiDocsSuggestion[] -} - -export interface ChatRequest { - readonly message: string - readonly editorContext: EditorContext - readonly attachedSuggestions: ChatSuggestion[] - readonly attachedApiDocsSuggestions: ChatApiDocsSuggestion[] -} -export interface FollowUpRequest { - readonly followUp: FollowUp - readonly editorContext?: EditorContext -} - -export interface IdeTriggerRequest { - readonly trigger: string - readonly editorContext?: EditorContext -} - -// @ts-ignore -export interface ChatEvent { - readonly messageId: string - readonly header?: Header - readonly token?: string - readonly followUps?: SuggestedFollowUp[] - readonly suggestions?: ChatSuggestion[] - readonly query?: string -} - -interface Header { - readonly sender: string - readonly responseTo: string - readonly sequenceId: string -} - -export enum FollowUpType { - Alternatives, - CommonPractices, - Improvements, - MoreExamples, - CiteSources, - LineByLine, - ExplainInDetail, - Generated, -} - -interface SuggestedFollowUp { - readonly type: FollowUpType - readonly pillText?: string - readonly prompt?: string - readonly message?: string - readonly attachedSuggestions?: ChatSuggestion[] - readonly attachedApiDocsSuggestions?: ChatApiDocsSuggestion[] -} diff --git a/packages/core/src/codewhispererChat/constants.ts b/packages/core/src/codewhispererChat/constants.ts deleted file mode 100644 index 84dd2dae29..0000000000 --- a/packages/core/src/codewhispererChat/constants.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as path from 'path' -import fs from '../shared/fs/fs' -import { ContextLengths } from './controllers/chat/model' - -export const promptFileExtension = '.md' - -// limit for each entry of @prompt, @rules, @files and @folder -export const additionalContentInnerContextLimit = 8192 - -export const aditionalContentNameLimit = 1024 - -// limit for each chunk of @workspace -export const workspaceChunkMaxSize = 40_960 - -export const getUserPromptsDirectory = () => { - return path.join(fs.getUserHomeDir(), '.aws', 'amazonq', 'prompts') -} - -export const createSavedPromptCommandId = 'create-saved-prompt' - -export const defaultContextLengths: ContextLengths = { - additionalContextLengths: { - fileContextLength: 0, - promptContextLength: 0, - ruleContextLength: 0, - }, - truncatedAdditionalContextLengths: { - fileContextLength: 0, - promptContextLength: 0, - ruleContextLength: 0, - }, - workspaceContextLength: 0, - truncatedWorkspaceContextLength: 0, - userInputContextLength: 0, - truncatedUserInputContextLength: 0, - focusFileContextLength: 0, - truncatedFocusFileContextLength: 0, -} diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts deleted file mode 100644 index 0786853ac8..0000000000 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ /dev/null @@ -1,1160 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as path from 'path' -import * as vscode from 'vscode' -import { Event as VSCodeEvent, Uri, workspace, window, ViewColumn, Position, Selection } from 'vscode' -import { EditorContextExtractor } from '../../editor/context/extractor' -import { ChatSessionStorage } from '../../storages/chatSession' -import { Messenger, MessengerResponseType, StaticTextResponseType } from './messenger/messenger' -import { - PromptMessage, - ChatTriggerType, - TriggerPayload, - TabClosedMessage, - InsertCodeAtCursorPosition, - TriggerTabIDReceived, - StopResponseMessage, - CopyCodeToClipboard, - ChatItemVotedMessage, - ChatItemFeedbackMessage, - TabCreatedMessage, - TabChangedMessage, - UIFocusMessage, - SourceLinkClickMessage, - ResponseBodyLinkClickMessage, - ChatPromptCommandType, - FooterInfoLinkClick, - ViewDiff, - AcceptDiff, - QuickCommandGroupActionClick, - DocumentReference, - FileClick, - RelevantTextDocumentAddition, - TabBarButtonClick, - SaveChatMessage, -} from './model' -import { - AppToWebViewMessageDispatcher, - ContextSelectedMessage, - CustomFormActionMessage, - DetailedListActionClickMessage, - DetailedListFilterChangeMessage, - DetailedListItemSelectMessage, -} from '../../view/connector/connector' -import { MessagePublisher } from '../../../amazonq/messages/messagePublisher' -import { MessageListener } from '../../../amazonq/messages/messageListener' -import { EditorContentController } from '../../../amazonq/commons/controllers/contentController' -import { EditorContextCommand } from '../../commands/registerCommands' -import { PromptsGenerator } from './prompts/promptsGenerator' -import { TriggerEventsStorage } from '../../storages/triggerEvents' -import { SendMessageRequest } from '@amzn/amazon-q-developer-streaming-client' -import { CodeWhispererStreamingServiceException } from '@amzn/codewhisperer-streaming' -import { UserIntentRecognizer } from './userIntent/userIntentRecognizer' -import { CWCTelemetryHelper, recordTelemetryChatRunCommand } from './telemetryHelper' -import { CodeWhispererTracker } from '../../../codewhisperer/tracker/codewhispererTracker' -import { getLogger } from '../../../shared/logger/logger' -import { triggerPayloadToChatRequest } from './chatRequest/converter' -import { AuthUtil } from '../../../codewhisperer/util/authUtil' -import { openUrl } from '../../../shared/utilities/vsCodeUtils' -import { randomUUID } from '../../../shared/crypto' -import { getSelectedCustomization } from '../../../codewhisperer/util/customizationUtil' -import { getHttpStatusCode, AwsClientResponseError } from '../../../shared/errors' -import { uiEventRecorder } from '../../../amazonq/util/eventRecorder' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { isSsoConnection } from '../../../auth/connection' -import { inspect } from '../../../shared/utilities/collectionUtils' -import { DefaultAmazonQAppInitContext } from '../../../amazonq/apps/initContext' -import globals from '../../../shared/extensionGlobals' -import { MynahIconsType, MynahUIDataModel, QuickActionCommand } from '@aws/mynah-ui' -import { workspaceCommand } from '../../../amazonq/webview/ui/tabs/constants' -import fs from '../../../shared/fs/fs' -import { FeatureConfigProvider, Features } from '../../../shared/featureConfig' -import { i18n } from '../../../shared/i18n-helper' -import { - getUserPromptsDirectory, - promptFileExtension, - createSavedPromptCommandId, - defaultContextLengths, -} from '../../constants' -import { ChatSession } from '../../clients/chat/v0/chat' -import { Database } from '../../../shared/db/chatDb/chatDb' -import { TabBarController } from './tabBarController' - -export interface ChatControllerMessagePublishers { - readonly processPromptChatMessage: MessagePublisher - readonly processTabCreatedMessage: MessagePublisher - readonly processTabClosedMessage: MessagePublisher - readonly processTabChangedMessage: MessagePublisher - readonly processInsertCodeAtCursorPosition: MessagePublisher - readonly processAcceptDiff: MessagePublisher - readonly processViewDiff: MessagePublisher - readonly processCopyCodeToClipboard: MessagePublisher - readonly processContextMenuCommand: MessagePublisher - readonly processTriggerTabIDReceived: MessagePublisher - readonly processStopResponseMessage: MessagePublisher - readonly processChatItemVotedMessage: MessagePublisher - readonly processChatItemFeedbackMessage: MessagePublisher - readonly processUIFocusMessage: MessagePublisher - readonly processSourceLinkClick: MessagePublisher - readonly processResponseBodyLinkClick: MessagePublisher - readonly processFooterInfoLinkClick: MessagePublisher - readonly processContextCommandUpdateMessage: MessagePublisher - readonly processQuickCommandGroupActionClicked: MessagePublisher - readonly processCustomFormAction: MessagePublisher - readonly processContextSelected: MessagePublisher - readonly processFileClick: MessagePublisher - readonly processTabBarButtonClick: MessagePublisher - readonly processSaveChat: MessagePublisher - readonly processDetailedListFilterChangeMessage: MessagePublisher - readonly processDetailedListItemSelectMessage: MessagePublisher - readonly processDetailedListActionClickMessage: MessagePublisher -} - -export interface ChatControllerMessageListeners { - readonly processPromptChatMessage: MessageListener - readonly processTabCreatedMessage: MessageListener - readonly processTabClosedMessage: MessageListener - readonly processTabChangedMessage: MessageListener - readonly processInsertCodeAtCursorPosition: MessageListener - readonly processAcceptDiff: MessageListener - readonly processViewDiff: MessageListener - readonly processCopyCodeToClipboard: MessageListener - readonly processContextMenuCommand: MessageListener - readonly processTriggerTabIDReceived: MessageListener - readonly processStopResponseMessage: MessageListener - readonly processChatItemVotedMessage: MessageListener - readonly processChatItemFeedbackMessage: MessageListener - readonly processUIFocusMessage: MessageListener - readonly processSourceLinkClick: MessageListener - readonly processResponseBodyLinkClick: MessageListener - readonly processFooterInfoLinkClick: MessageListener - readonly processContextCommandUpdateMessage: MessageListener - readonly processQuickCommandGroupActionClicked: MessageListener - readonly processCustomFormAction: MessageListener - readonly processContextSelected: MessageListener - readonly processFileClick: MessageListener - readonly processTabBarButtonClick: MessageListener - readonly processSaveChat: MessageListener - readonly processDetailedListFilterChangeMessage: MessageListener - readonly processDetailedListItemSelectMessage: MessageListener - readonly processDetailedListActionClickMessage: MessageListener -} - -export class ChatController { - private readonly sessionStorage: ChatSessionStorage - private readonly triggerEventsStorage: TriggerEventsStorage - private readonly messenger: Messenger - private readonly editorContextExtractor: EditorContextExtractor - private readonly editorContentController: EditorContentController - private readonly tabBarController: TabBarController - private readonly promptGenerator: PromptsGenerator - private readonly userIntentRecognizer: UserIntentRecognizer - private readonly telemetryHelper: CWCTelemetryHelper - private userPromptsWatcher: vscode.FileSystemWatcher | undefined - private chatHistoryDb = Database.getInstance() - private cancelTokenSource: vscode.CancellationTokenSource = new vscode.CancellationTokenSource() - - public constructor( - private readonly chatControllerMessageListeners: ChatControllerMessageListeners, - appsToWebViewMessagePublisher: MessagePublisher, - onDidChangeAmazonQVisibility: VSCodeEvent - ) { - this.sessionStorage = new ChatSessionStorage() - this.triggerEventsStorage = new TriggerEventsStorage() - this.telemetryHelper = CWCTelemetryHelper.init(this.sessionStorage, this.triggerEventsStorage) - this.messenger = new Messenger( - new AppToWebViewMessageDispatcher(appsToWebViewMessagePublisher), - this.telemetryHelper - ) - this.editorContextExtractor = new EditorContextExtractor() - this.editorContentController = new EditorContentController() - this.promptGenerator = new PromptsGenerator() - this.userIntentRecognizer = new UserIntentRecognizer() - this.tabBarController = new TabBarController(this.messenger) - - onDidChangeAmazonQVisibility((visible) => { - if (visible) { - this.telemetryHelper.recordOpenChat() - } else { - this.telemetryHelper.recordCloseChat() - } - }) - - AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => { - this.cancelTokenSource.cancel() - }) - - this.chatControllerMessageListeners.processPromptChatMessage.onMessage((data) => { - const uiEvents = uiEventRecorder.get(data.tabID) - if (uiEvents) { - uiEventRecorder.set(data.tabID, { - events: { - featureReceivedMessage: globals.clock.Date.now(), - }, - }) - } - /** - * traceId is only instrumented for chat-prompt but not for things - * like follow-up-was-clicked. In those cases we fallback to a different - * uuid - **/ - return telemetry.withTraceId(() => { - return this.processPromptChatMessage(data) - }, uiEvents?.traceId ?? randomUUID()) - }) - - this.chatControllerMessageListeners.processTabCreatedMessage.onMessage((data) => { - return this.processTabCreateMessage(data) - }) - - this.chatControllerMessageListeners.processTabClosedMessage.onMessage((data) => { - return this.processTabCloseMessage(data) - }) - - this.chatControllerMessageListeners.processTabChangedMessage.onMessage((data) => { - return this.processTabChangedMessage(data) - }) - - this.chatControllerMessageListeners.processInsertCodeAtCursorPosition.onMessage((data) => { - return this.processInsertCodeAtCursorPosition(data) - }) - - this.chatControllerMessageListeners.processAcceptDiff.onMessage((data) => { - return this.processAcceptDiff(data) - }) - - this.chatControllerMessageListeners.processViewDiff.onMessage((data) => { - return this.processViewDiff(data) - }) - - this.chatControllerMessageListeners.processCopyCodeToClipboard.onMessage((data) => { - return this.processCopyCodeToClipboard(data) - }) - - this.chatControllerMessageListeners.processContextMenuCommand.onMessage((data) => { - return this.processContextMenuCommand(data) - }) - - this.chatControllerMessageListeners.processTriggerTabIDReceived.onMessage((data) => { - return this.processTriggerTabIDReceived(data) - }) - - this.chatControllerMessageListeners.processStopResponseMessage.onMessage((data) => { - return this.processStopResponseMessage(data) - }) - - this.chatControllerMessageListeners.processChatItemVotedMessage.onMessage((data) => { - return this.processChatItemVotedMessage(data) - }) - - this.chatControllerMessageListeners.processChatItemFeedbackMessage.onMessage((data) => { - return this.processChatItemFeedbackMessage(data) - }) - - this.chatControllerMessageListeners.processUIFocusMessage.onMessage((data) => { - return this.processUIFocusMessage(data) - }) - - this.chatControllerMessageListeners.processSourceLinkClick.onMessage((data) => { - return this.processSourceLinkClick(data) - }) - this.chatControllerMessageListeners.processResponseBodyLinkClick.onMessage((data) => { - return this.processResponseBodyLinkClick(data) - }) - this.chatControllerMessageListeners.processFooterInfoLinkClick.onMessage((data) => { - return this.processFooterInfoLinkClick(data) - }) - this.chatControllerMessageListeners.processContextCommandUpdateMessage.onMessage(() => { - return this.processContextCommandUpdateMessage() - }) - this.chatControllerMessageListeners.processQuickCommandGroupActionClicked.onMessage((data) => { - return this.processQuickCommandGroupActionClicked(data) - }) - this.chatControllerMessageListeners.processCustomFormAction.onMessage((data) => { - return this.processCustomFormAction(data) - }) - this.chatControllerMessageListeners.processContextSelected.onMessage((data) => { - return this.processContextSelected(data) - }) - this.chatControllerMessageListeners.processFileClick.onMessage((data) => { - return this.processFileClickMessage(data) - }) - this.chatControllerMessageListeners.processTabBarButtonClick.onMessage((data) => { - return this.tabBarController.processTabBarButtonClick(data) - }) - this.chatControllerMessageListeners.processSaveChat.onMessage((data) => { - return this.tabBarController.processSaveChat(data) - }) - this.chatControllerMessageListeners.processDetailedListActionClickMessage.onMessage((data) => { - return this.tabBarController.processActionClickMessage(data) - }) - this.chatControllerMessageListeners.processDetailedListFilterChangeMessage.onMessage((data) => { - return this.tabBarController.processFilterChangeMessage(data) - }) - this.chatControllerMessageListeners.processDetailedListItemSelectMessage.onMessage((data) => { - return this.tabBarController.processItemSelectMessage(data) - }) - AuthUtil.instance.regionProfileManager.onDidChangeRegionProfile(() => { - this.sessionStorage.deleteAllSessions() - }) - } - - private registerUserPromptsWatcher() { - if (this.userPromptsWatcher) { - return - } - this.userPromptsWatcher = vscode.workspace.createFileSystemWatcher( - new vscode.RelativePattern(vscode.Uri.file(getUserPromptsDirectory()), `*${promptFileExtension}`), - false, - true, - false - ) - this.userPromptsWatcher.onDidCreate(() => this.processContextCommandUpdateMessage()) - this.userPromptsWatcher.onDidDelete(() => this.processContextCommandUpdateMessage()) - globals.context.subscriptions.push(this.userPromptsWatcher) - } - - private processFooterInfoLinkClick(click: FooterInfoLinkClick) { - this.openLinkInExternalBrowser(click) - } - - private openLinkInExternalBrowser( - click: ResponseBodyLinkClickMessage | SourceLinkClickMessage | FooterInfoLinkClick - ) { - this.telemetryHelper.recordInteractWithMessage(click) - void openUrl(Uri.parse(click.link)) - } - - private processResponseBodyLinkClick(click: ResponseBodyLinkClickMessage) { - this.openLinkInExternalBrowser(click) - } - - private processSourceLinkClick(click: SourceLinkClickMessage) { - this.openLinkInExternalBrowser(click) - } - - private processQuickActionCommand(message: PromptMessage) { - this.editorContextExtractor - .extractContextForTrigger('QuickAction') - .then((context) => { - const triggerID = randomUUID() - - const quickActionCommand = message.command as ChatPromptCommandType - - this.messenger.sendQuickActionMessage(quickActionCommand, triggerID) - - this.triggerEventsStorage.addTriggerEvent({ - id: triggerID, - tabID: message.tabID, - message: undefined, - type: 'quick_action', - quickAction: quickActionCommand, - context, - }) - - if (quickActionCommand === 'help') { - void this.generateStaticTextResponse('quick-action-help', triggerID) - recordTelemetryChatRunCommand('help') - return - } - }) - .catch((e) => { - this.processException(e, '') - }) - } - - private async processChatItemFeedbackMessage(message: ChatItemFeedbackMessage) { - await this.telemetryHelper.recordFeedback(message) - } - - private async processChatItemVotedMessage(message: ChatItemVotedMessage) { - this.telemetryHelper.recordInteractWithMessage(message) - } - - private async processStopResponseMessage(message: StopResponseMessage) { - const session = this.sessionStorage.getSession(message.tabID) - session.tokenSource.cancel() - } - - private async processTriggerTabIDReceived(message: TriggerTabIDReceived) { - this.triggerEventsStorage.updateTriggerEventTabIDFromUnknown(message.triggerID, message.tabID) - } - - private async processInsertCodeAtCursorPosition(message: InsertCodeAtCursorPosition) { - this.editorContentController.insertTextAtCursorPosition(message.code, (editor, cursorStart) => { - CodeWhispererTracker.getTracker().enqueue({ - conversationID: this.telemetryHelper.getConversationId(message.tabID) ?? '', - messageID: message.messageId, - userIntent: message.userIntent, - time: new Date(), - fileUrl: editor.document.uri, - startPosition: cursorStart, - endPosition: editor.selection.active, - originalString: message.code, - }) - }) - this.telemetryHelper.recordInteractWithMessage(message) - } - - private async processAcceptDiff(message: AcceptDiff) { - const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || '' - this.editorContentController - .acceptDiff({ ...message, ...context }) - .then(() => { - this.telemetryHelper.recordInteractWithMessage(message) - }) - .catch((error) => { - this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' }) - }) - } - - private async processViewDiff(message: ViewDiff) { - const context = this.triggerEventsStorage.getTriggerEvent((message.data as any)?.triggerID) || '' - this.editorContentController - .viewDiff({ ...message, ...context }) - .then(() => { - this.telemetryHelper.recordInteractWithMessage(message) - }) - .catch((error) => { - this.telemetryHelper.recordInteractWithMessage(message, { result: 'Failed' }) - }) - } - - private async processCopyCodeToClipboard(message: CopyCodeToClipboard) { - this.telemetryHelper.recordInteractWithMessage(message) - } - - private async processTabCreateMessage(message: TabCreatedMessage) { - // this.telemetryHelper.recordOpenChat(message.tabOpenInteractionType) - } - - private async processTabCloseMessage(message: TabClosedMessage) { - this.sessionStorage.deleteSession(message.tabID) - this.triggerEventsStorage.removeTabEvents(message.tabID) - // this.telemetryHelper.recordCloseChat(message.tabID) - this.chatHistoryDb.updateTabOpenState(message.tabID, false) - } - - private async processTabChangedMessage(message: TabChangedMessage) { - if (message.prevTabID) { - this.telemetryHelper.recordExitFocusConversation(message.prevTabID) - } - this.telemetryHelper.recordEnterFocusConversation(message.tabID) - } - - private async processUIFocusMessage(message: UIFocusMessage) { - switch (message.type) { - case 'focus': - this.telemetryHelper.recordEnterFocusChat() - break - case 'blur': - this.telemetryHelper.recordExitFocusChat() - break - } - } - - private async processContextCommandUpdateMessage() { - // when UI is ready, refresh the context commands - this.tabBarController.loadChats() - this.registerUserPromptsWatcher() - const contextCommand: MynahUIDataModel['contextCommands'] = [ - { - commands: [ - ...workspaceCommand.commands, - { - command: i18n('AWS.amazonq.context.folders.title'), - children: [ - { - groupName: i18n('AWS.amazonq.context.folders.title'), - commands: [], - }, - ], - description: i18n('AWS.amazonq.context.folders.description'), - icon: 'folder' as MynahIconsType, - }, - { - command: i18n('AWS.amazonq.context.files.title'), - children: [ - { - groupName: i18n('AWS.amazonq.context.files.title'), - commands: [], - }, - ], - description: i18n('AWS.amazonq.context.files.description'), - icon: 'file' as MynahIconsType, - }, - { - command: i18n('AWS.amazonq.context.code.title'), - children: [ - { - groupName: i18n('AWS.amazonq.context.code.title'), - commands: [], - }, - ], - description: i18n('AWS.amazonq.context.code.description'), - icon: 'code-block' as MynahIconsType, - }, - { - command: i18n('AWS.amazonq.context.prompts.title'), - children: [ - { - groupName: i18n('AWS.amazonq.context.prompts.title'), - commands: [], - }, - ], - description: i18n('AWS.amazonq.context.prompts.description'), - icon: 'magic' as MynahIconsType, - }, - ], - }, - ] - - const feature = FeatureConfigProvider.getFeature(Features.highlightCommand) - const commandName = feature?.value.stringValue - if (commandName) { - const commandDescription = feature.variation - contextCommand.push({ - groupName: 'Additional Commands', - commands: [{ command: commandName, description: commandDescription }], - }) - } - const promptsCmd: QuickActionCommand = contextCommand[0].commands?.[4] - - // Check for user prompts - try { - const userPromptsDirectory = getUserPromptsDirectory() - const directoryExists = await fs.exists(userPromptsDirectory) - if (directoryExists) { - const systemPromptFiles = await fs.readdir(userPromptsDirectory) - promptsCmd.children?.[0].commands.push( - ...systemPromptFiles - .filter(([name]) => name.endsWith(promptFileExtension)) - .map(([name]) => ({ - command: path.basename(name, promptFileExtension), - icon: 'magic' as MynahIconsType, - id: 'prompt', - // label: 'file' as ContextCommandItemType, - route: [userPromptsDirectory, name], - })) - ) - } - } catch (e) { - getLogger().verbose(`Could not read prompts from ~/.aws/prompts: ${e}`) - } - - // Add create prompt button to the bottom of the prompts list - promptsCmd.children?.[0].commands.push({ - command: i18n('AWS.amazonq.savedPrompts.action'), - id: createSavedPromptCommandId, - icon: 'list-add' as MynahIconsType, - }) - - this.messenger.sendContextCommandData(contextCommand) - } - - private handlePromptCreate(tabID: string) { - this.messenger.showCustomForm( - tabID, - [ - { - id: 'prompt-name', - type: 'textinput', - mandatory: true, - autoFocus: true, - title: i18n('AWS.amazonq.savedPrompts.title'), - placeholder: i18n('AWS.amazonq.savedPrompts.placeholder'), - description: i18n('AWS.amazonq.savedPrompts.description'), - }, - ], - [ - { id: 'cancel-create-prompt', text: i18n('AWS.generic.cancel'), status: 'clear' }, - { id: 'submit-create-prompt', text: i18n('AWS.amazonq.savedPrompts.create'), status: 'main' }, - ], - `Create a saved prompt` - ) - } - - private processQuickCommandGroupActionClicked(message: QuickCommandGroupActionClick) { - if (message.actionId === createSavedPromptCommandId) { - this.handlePromptCreate(message.tabID) - } - } - - private async processCustomFormAction(message: CustomFormActionMessage) { - if (message.action.id === 'submit-create-prompt') { - const userPromptsDirectory = getUserPromptsDirectory() - - const title = message.action.formItemValues?.['prompt-name'] - const newFilePath = path.join( - userPromptsDirectory, - title ? `${title}${promptFileExtension}` : `default${promptFileExtension}` - ) - const newFileContent = new Uint8Array(Buffer.from('')) - await fs.writeFile(newFilePath, newFileContent, { mode: 0o600 }) - const newFileDoc = await vscode.workspace.openTextDocument(newFilePath) - await vscode.window.showTextDocument(newFileDoc) - telemetry.ui_click.emit({ elementId: 'amazonq_createSavedPrompt' }) - } - } - - private async processContextSelected(message: ContextSelectedMessage) { - if (message.tabID && message.contextItem.id === createSavedPromptCommandId) { - this.handlePromptCreate(message.tabID) - } - } - private async processFileClickMessage(message: FileClick) { - const session = this.sessionStorage.getSession(message.tabID) - const lineRanges = session.contexts.get(message.filePath) - - if (!lineRanges) { - return - } - - // Check if clicked file is in a different workspace root - const projectRoot = - session.relativePathToWorkspaceRoot.get(message.filePath) || workspace.workspaceFolders?.[0]?.uri.fsPath - if (!projectRoot) { - return - } - let absoluteFilePath = path.join(projectRoot, message.filePath) - - // Handle clicking on a user prompt outside the workspace - if (message.filePath.endsWith(promptFileExtension)) { - try { - await vscode.workspace.fs.stat(vscode.Uri.file(absoluteFilePath)) - } catch { - absoluteFilePath = path.join(getUserPromptsDirectory(), message.filePath) - } - } - - try { - // Open the file in VSCode - const document = await workspace.openTextDocument(absoluteFilePath) - const editor = await window.showTextDocument(document, ViewColumn.Active) - - // Create multiple selections based on line ranges - const selections: Selection[] = lineRanges - .filter(({ first, second }) => first !== -1 && second !== -1) - .map(({ first, second }) => { - const startPosition = new Position(first - 1, 0) // Convert 1-based to 0-based - const endPosition = new Position(second - 1, document.lineAt(second - 1).range.end.character) - return new Selection( - startPosition.line, - startPosition.character, - endPosition.line, - endPosition.character - ) - }) - - // Apply multiple selections to the editor - if (selections.length > 0) { - editor.selection = selections[0] // Set the first selection as active - editor.selections = selections // Apply multiple selections - editor.revealRange(selections[0], vscode.TextEditorRevealType.InCenter) - } - } catch (error) {} - } - - private processException(e: any, tabID: string) { - let errorMessage = '' - let requestID = undefined - const defaultMessage = 'Failed to get response' - if (typeof e === 'string') { - errorMessage = e.toUpperCase() - } else if (e instanceof SyntaxError) { - // Workaround to handle case when LB returns web-page with error and our client doesn't return proper exception - errorMessage = AwsClientResponseError.tryExtractReasonFromSyntaxError(e) ?? defaultMessage - } else if (e instanceof CodeWhispererStreamingServiceException) { - errorMessage = e.message - requestID = e.$metadata.requestId - } else if (e instanceof Error) { - errorMessage = e.message - } - - this.messenger.sendErrorMessage(errorMessage, tabID, requestID) - getLogger().error(`error: ${errorMessage} tabID: ${tabID} requestID: ${requestID}`) - - this.sessionStorage.deleteSession(tabID) - } - - private async processContextMenuCommand(command: EditorContextCommand) { - // Just open the chat panel in this case - if (!this.editorContextExtractor.isCodeBlockSelected() && command.type === 'aws.amazonq.sendToPrompt') { - return - } - - this.editorContextExtractor - .extractContextForTrigger('ContextMenu') - .then(async (context) => { - const triggerID = randomUUID() - if (command.type === 'aws.amazonq.generateUnitTests') { - DefaultAmazonQAppInitContext.instance.getAppsToWebViewMessagePublisher().publish({ - sender: 'testChat', - command: 'test', - type: 'chatMessage', - }) - // For non-supported languages, we'll just open the standard chat. - return - } - - if (context?.focusAreaContext?.codeBlock === undefined) { - throw 'Sorry, I cannot help with the selected language code snippet' - } - - const prompt = this.promptGenerator.generateForContextMenuCommand(command) - - if (command.type === 'aws.amazonq.explainIssue') { - this.messenger.sendEditorContextCommandMessage( - command.type, - context.activeFileContext?.fileText - ?.split('\n') - .slice(command.issue.startLine, command.issue.endLine) - .join('') ?? '', - triggerID, - command.issue - ) - } else { - this.messenger.sendEditorContextCommandMessage( - command.type, - context?.focusAreaContext?.codeBlock ?? '', - triggerID - ) - } - - if (command.type === 'aws.amazonq.sendToPrompt') { - // No need for response if send the code to prompt - return - } - - this.triggerEventsStorage.addTriggerEvent({ - id: triggerID, - tabID: undefined, - message: prompt, - type: 'editor_context_command', - context, - command, - }) - - return this.generateResponse( - { - message: prompt, - trigger: ChatTriggerType.ChatMessage, - query: undefined, - codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: context?.focusAreaContext?.extendedCodeBlock ?? '', - fileLanguage: context?.activeFileContext?.fileLanguage, - filePath: context?.activeFileContext?.filePath, - matchPolicy: context?.activeFileContext?.matchPolicy, - codeQuery: context?.focusAreaContext?.names, - userIntent: this.userIntentRecognizer.getFromContextMenuCommand(command), - customization: getSelectedCustomization(), - profile: AuthUtil.instance.regionProfileManager.activeRegionProfile, - additionalContents: [], - relevantTextDocuments: [], - documentReferences: [], - useRelevantDocuments: false, - contextLengths: { - ...defaultContextLengths, - }, - context: [], - }, - triggerID - ) - }) - .catch((e) => { - this.processException(e, '') - }) - } - - private async processPromptChatMessage(message: PromptMessage) { - if (message.message === undefined) { - this.messenger.sendErrorMessage('chatMessage should be set', message.tabID, undefined) - return - } - try { - switch (message.command) { - case 'follow-up-was-clicked': - await this.processFollowUp(message) - this.telemetryHelper.recordInteractWithMessage(message) - break - case 'onboarding-page-cwc-button-clicked': - case 'chat-prompt': - await this.processPromptMessageAsNewThread(message) - break - default: - await this.processCommandMessage(message) - } - } catch (e) { - this.processException(e, message.tabID) - } - } - - private async processCommandMessage(message: PromptMessage) { - if (message.command === undefined) { - return - } - switch (message.command) { - case 'clear': - this.sessionStorage.deleteSession(message.tabID) - this.triggerEventsStorage.removeTabEvents(message.tabID) - recordTelemetryChatRunCommand('clear') - this.chatHistoryDb.clearTab(message.tabID) - return - default: - this.processQuickActionCommand(message) - } - } - - private async processFollowUp(message: PromptMessage) { - try { - const lastTriggerEvent = this.triggerEventsStorage.getLastTriggerEventByTabID(message.tabID) - - if (lastTriggerEvent === undefined) { - throw "It's impossible to ask follow-ups on empty tabs" - } - - const triggerID = randomUUID() - this.triggerEventsStorage.addTriggerEvent({ - id: triggerID, - tabID: message.tabID, - message: message.message, - type: 'follow_up', - context: lastTriggerEvent.context, - }) - - return this.generateResponse( - { - message: message.message ?? '', - trigger: ChatTriggerType.ChatMessage, - query: message.message, - codeSelection: lastTriggerEvent.context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: lastTriggerEvent.context?.focusAreaContext?.extendedCodeBlock ?? '', - fileLanguage: lastTriggerEvent.context?.activeFileContext?.fileLanguage, - filePath: lastTriggerEvent.context?.activeFileContext?.filePath, - matchPolicy: lastTriggerEvent.context?.activeFileContext?.matchPolicy, - codeQuery: lastTriggerEvent.context?.focusAreaContext?.names, - userIntent: message.userIntent, - customization: getSelectedCustomization(), - profile: AuthUtil.instance.regionProfileManager.activeRegionProfile, - contextLengths: { - ...defaultContextLengths, - }, - relevantTextDocuments: [], - additionalContents: [], - documentReferences: [], - useRelevantDocuments: false, - context: [], - }, - triggerID - ) - } catch (e) { - this.processException(e, message.tabID) - } - } - - private async processPromptMessageAsNewThread(message: PromptMessage) { - this.editorContextExtractor - .extractContextForTrigger('ChatMessage') - .then((context) => { - const triggerID = randomUUID() - this.triggerEventsStorage.addTriggerEvent({ - id: triggerID, - tabID: message.tabID, - message: message.message, - type: 'chat_message', - context, - }) - return this.generateResponse( - { - message: message.message ?? '', - trigger: ChatTriggerType.ChatMessage, - query: message.message, - codeSelection: context?.focusAreaContext?.selectionInsideExtendedCodeBlock, - fileText: context?.focusAreaContext?.extendedCodeBlock ?? '', - fileLanguage: context?.activeFileContext?.fileLanguage, - filePath: context?.activeFileContext?.filePath, - matchPolicy: context?.activeFileContext?.matchPolicy, - codeQuery: context?.focusAreaContext?.names, - userIntent: this.userIntentRecognizer.getFromPromptChatMessage(message), - customization: getSelectedCustomization(), - profile: AuthUtil.instance.regionProfileManager.activeRegionProfile, - context: message.context ?? [], - relevantTextDocuments: [], - additionalContents: [], - documentReferences: [], - useRelevantDocuments: false, - contextLengths: { - ...defaultContextLengths, - }, - }, - triggerID - ) - }) - .catch((e) => { - this.processException(e, message.tabID) - }) - } - - private async generateStaticTextResponse(responseType: StaticTextResponseType, triggerID: string) { - // Loop while we waiting for tabID to be set - const triggerEvent = this.triggerEventsStorage.getTriggerEvent(triggerID) - if (triggerEvent === undefined) { - return - } - - if (triggerEvent.tabID === 'no-available-tabs') { - return - } - - if (triggerEvent.tabID === undefined) { - setTimeout(() => { - this.generateStaticTextResponse(responseType, triggerID).catch((e) => { - getLogger().error('generateStaticTextResponse failed: %s', (e as Error).message) - }) - }, 20) - return - } - - const tabID = triggerEvent.tabID - - const credentialsState = await AuthUtil.instance.getChatAuthState() - - if (credentialsState.codewhispererChat !== 'connected' && credentialsState.codewhispererCore !== 'connected') { - await this.messenger.sendAuthNeededExceptionMessage(credentialsState, tabID, triggerID) - return - } - - this.messenger.sendStaticTextResponse(responseType, triggerID, tabID) - } - - /** - * @returns A Uri array of prompt files in each workspace root's .amazonq/rules directory - */ - private async collectWorkspaceRules(): Promise { - const rulesFiles: string[] = [] - - if (!vscode.workspace.workspaceFolders) { - return rulesFiles - } - - for (const folder of vscode.workspace.workspaceFolders) { - const rulesPath = path.join(folder.uri.fsPath, '.amazonq', 'rules') - const folderExists = await fs.exists(rulesPath) - - if (folderExists) { - const entries = await fs.readdir(rulesPath) - - for (const [name, type] of entries) { - if (type === vscode.FileType.File && name.endsWith(promptFileExtension)) { - rulesFiles.push(path.join(rulesPath, name)) - } - } - } - } - - return rulesFiles - } - - private async resolveContextCommandPayload(triggerPayload: TriggerPayload, session: ChatSession) { - const contextCommands: any[] = [] - - // Check for workspace rules to add to context - const workspaceRules = await this.collectWorkspaceRules() - if (workspaceRules.length > 0) { - contextCommands.push( - ...workspaceRules.map((rule) => { - const workspaceFolderPath = - vscode.workspace.getWorkspaceFolder(vscode.Uri.parse(rule))?.uri?.path || '' - return { - workspaceFolder: workspaceFolderPath, - type: 'file' as any, - relativePath: path.relative(workspaceFolderPath, rule), - } - }) - ) - } - triggerPayload.workspaceRulesCount = workspaceRules.length - - for (const context of triggerPayload.context) { - if (typeof context !== 'string' && context.route && context.route.length === 2) { - contextCommands.push({ - workspaceFolder: context.route[0] || '', - type: (context.label || '') as any, - relativePath: context.route[1] || '', - id: context.id, - }) - } - } - - if (contextCommands.length === 0) { - return [] - } - const workspaceFolders = (vscode.workspace.workspaceFolders ?? []).map((folder) => folder.uri.fsPath) - if (!workspaceFolders) { - return [] - } - workspaceFolders.sort() - } - - private async generateResponse( - triggerPayload: TriggerPayload & { projectContextQueryLatencyMs?: number }, - triggerID: string - ) { - const triggerEvent = this.triggerEventsStorage.getTriggerEvent(triggerID) - if (triggerEvent === undefined) { - return - } - - if (triggerEvent.tabID === 'no-available-tabs') { - return - } - - if (triggerEvent.tabID === undefined) { - setTimeout(() => { - this.generateResponse(triggerPayload, triggerID).catch((e) => { - getLogger().error('generateResponse failed: %s', (e as Error).message) - }) - }, 20) - return - } - - const tabID = triggerEvent.tabID - - const credentialsState = await AuthUtil.instance.getChatAuthState() - - if ( - !(credentialsState.codewhispererChat === 'connected' && credentialsState.codewhispererCore === 'connected') - ) { - await this.messenger.sendAuthNeededExceptionMessage(credentialsState, tabID, triggerID) - return - } - - const session = this.sessionStorage.getSession(tabID) - if (!session.localHistoryHydrated) { - triggerPayload.history = this.chatHistoryDb.getMessages(triggerEvent.tabID, 10) - session.localHistoryHydrated = true - } - await this.resolveContextCommandPayload(triggerPayload, session) - triggerPayload.useRelevantDocuments = false - - triggerPayload.contextLengths.userInputContextLength = triggerPayload.message.length - triggerPayload.contextLengths.focusFileContextLength = triggerPayload.fileText.length - const request = triggerPayloadToChatRequest(triggerPayload) - triggerPayload.documentReferences = this.mergeRelevantTextDocuments(triggerPayload.relevantTextDocuments) - - // Update context transparency after it's truncated dynamically to show users only the context sent. - const relativePathsOfMergedRelevantDocuments = triggerPayload.documentReferences.map( - (doc) => doc.relativeFilePath - ) - const seen: string[] = [] - for (const additionalContent of triggerPayload.additionalContents) { - const relativePath = additionalContent.relativePath - if (!relativePathsOfMergedRelevantDocuments.includes(relativePath) && !seen.includes(relativePath)) { - triggerPayload.documentReferences.push({ - relativeFilePath: relativePath, - lineRanges: - additionalContent.name === 'symbol' - ? [{ first: additionalContent.startLine, second: additionalContent.endLine }] - : [{ first: -1, second: -1 }], - }) - seen.push(relativePath) - } - } - for (const doc of triggerPayload.documentReferences) { - session.contexts.set(doc.relativeFilePath, doc.lineRanges) - } - - getLogger().debug( - `request from tab: ${tabID} conversationID: ${session.sessionIdentifier} request: ${inspect(request, { - depth: 12, - })}` - ) - let response: MessengerResponseType | undefined = undefined - session.createNewTokenSource() - // TODO: onProfileChanged, abort previous response? - try { - this.messenger.sendInitalStream(tabID, triggerID, triggerPayload.documentReferences) - this.telemetryHelper.setConversationStreamStartTime(tabID) - if (isSsoConnection(AuthUtil.instance.conn)) { - const { $metadata, generateAssistantResponseResponse } = await session.chatSso(request) - response = { - $metadata: $metadata, - message: generateAssistantResponseResponse, - } - } else { - const { $metadata, sendMessageResponse } = await session.chatIam(request as SendMessageRequest) - response = { - $metadata: $metadata, - message: sendMessageResponse, - } - } - this.telemetryHelper.recordEnterFocusConversation(triggerEvent.tabID) - this.telemetryHelper.recordStartConversation(triggerEvent, triggerPayload) - if (session.sessionIdentifier) { - this.chatHistoryDb.addMessage(tabID, 'cwc', session.sessionIdentifier, { - body: triggerPayload.message, - type: 'prompt' as any, - }) - } - - getLogger().info( - `response to tab: ${tabID} conversationID: ${session.sessionIdentifier} requestID: ${ - response.$metadata.requestId - } metadata: ${inspect(response.$metadata, { depth: 12 })}` - ) - this.cancelTokenSource = new vscode.CancellationTokenSource() - await this.messenger.sendAIResponse( - response, - session, - tabID, - triggerID, - triggerPayload, - this.cancelTokenSource.token - ) - } catch (e: any) { - this.telemetryHelper.recordMessageResponseError(triggerPayload, tabID, getHttpStatusCode(e) ?? 0) - // clears session, record telemetry before this call - this.processException(e, tabID) - } - } - - private mergeRelevantTextDocuments(documents: RelevantTextDocumentAddition[]): DocumentReference[] { - if (documents.length === 0) { - return [] - } - return Object.entries( - documents.reduce>((acc, doc) => { - if (!doc.relativeFilePath || doc.startLine === undefined || doc.endLine === undefined) { - return acc // Skip invalid documents - } - - if (!acc[doc.relativeFilePath]) { - acc[doc.relativeFilePath] = [] - } - acc[doc.relativeFilePath].push({ first: doc.startLine, second: doc.endLine }) - return acc - }, {}) - ).map(([filePath, ranges]) => { - // Sort by startLine - const sortedRanges = ranges.sort((a, b) => a.first - b.first) - - const mergedRanges: { first: number; second: number }[] = [] - for (const { first, second } of sortedRanges) { - if (mergedRanges.length === 0 || mergedRanges[mergedRanges.length - 1].second < first - 1) { - // If no overlap, add new range - mergedRanges.push({ first, second }) - } else { - // Merge overlapping or consecutive ranges - mergedRanges[mergedRanges.length - 1].second = Math.max( - mergedRanges[mergedRanges.length - 1].second, - second - ) - } - } - - return { relativeFilePath: filePath, lineRanges: mergedRanges } - }) - } -} diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts deleted file mode 100644 index ab059ecb22..0000000000 --- a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts +++ /dev/null @@ -1,567 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { waitUntil } from '../../../../shared/utilities/timeoutUtils' -import { - AppToWebViewMessageDispatcher, - AuthNeededException, - CodeReference, - ContextCommandData, - EditorContextCommandMessage, - ExportChatMessage, - OpenSettingsMessage, - OpenDetailedListMessage, - QuickActionMessage, - RestoreTabMessage, - ShowCustomFormMessage, - UpdateDetailedListMessage, - CloseDetailedListMessage, - SelectTabMessage, -} from '../../../view/connector/connector' -import { EditorContextCommandType } from '../../../commands/registerCommands' -import { ChatResponseStream as qdevChatResponseStream } from '@amzn/amazon-q-developer-streaming-client' -import { - ChatResponseStream as cwChatResponseStream, - CodeWhispererStreamingServiceException, - SupplementaryWebLink, -} from '@amzn/codewhisperer-streaming' -import { ChatMessage, ErrorMessage, FollowUp, Suggestion } from '../../../view/connector/connector' -import { ChatSession } from '../../../clients/chat/v0/chat' -import { ChatException } from './model' -import { CWCTelemetryHelper } from '../telemetryHelper' -import { ChatPromptCommandType, DocumentReference, TriggerPayload } from '../model' -import { getHttpStatusCode, getRequestId, ToolkitError } from '../../../../shared/errors' -import { keys } from '../../../../shared/utilities/tsUtils' -import { getLogger } from '../../../../shared/logger/logger' -import { FeatureAuthState } from '../../../../codewhisperer/util/authUtil' -import { CodeScanIssue } from '../../../../codewhisperer/models/model' -import { marked } from 'marked' -import { JSDOM } from 'jsdom' -import { extractCodeBlockLanguage } from '../../../../shared/markdown' -import { extractAuthFollowUp } from '../../../../amazonq/util/authUtils' -import { helpMessage } from '../../../../amazonq/webview/ui/texts/constants' -import { ChatItem, ChatItemButton, ChatItemFormItem, DetailedList, MynahUIDataModel } from '@aws/mynah-ui' -import { Database } from '../../../../shared/db/chatDb/chatDb' -import { TabType } from '../../../../amazonq/webview/ui/storages/tabsStorage' - -export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help' - -export type MessengerResponseType = { - $metadata: { requestId?: string; httpStatusCode?: number } - message?: AsyncIterable -} - -export class Messenger { - chatHistoryDb = Database.getInstance() - - public constructor( - private readonly dispatcher: AppToWebViewMessageDispatcher, - private readonly telemetryHelper: CWCTelemetryHelper - ) {} - - public async sendAuthNeededExceptionMessage(credentialState: FeatureAuthState, tabID: string, triggerID: string) { - const { message, authType } = extractAuthFollowUp(credentialState) - this.dispatcher.sendAuthNeededExceptionMessage( - new AuthNeededException( - { - message, - authType, - triggerID, - }, - tabID - ) - ) - } - - public sendInitalStream( - tabID: string, - triggerID: string, - mergedRelevantDocuments: DocumentReference[] | undefined - ) { - this.dispatcher.sendChatMessage( - new ChatMessage( - { - message: '', - messageType: 'answer-stream', - followUps: undefined, - followUpsHeader: undefined, - relatedSuggestions: undefined, - triggerID, - messageID: triggerID, - userIntent: undefined, - codeBlockLanguage: undefined, - contextList: mergedRelevantDocuments, - }, - tabID - ) - ) - } - /** - * Tries to calculate the total number of code blocks. - * NOTES: - * - Not correct on all examples. Some may cause it to return 0 unexpectedly. - * - Plans in place (as of 4/22/2024) to move this server side. - * - See original pr: https://github.com/aws/aws-toolkit-vscode/pull/4761 for more details. - * @param message raw message response from codewhisperer client. - * @returns count of multi-line code blocks in response. - */ - public async countTotalNumberOfCodeBlocks(message: string): Promise { - // TODO: remove this when moved to server-side. - if (message === undefined) { - return 0 - } - - // To Convert Markdown text to HTML using marked library - const html = await marked(message) - - const dom = new JSDOM(html) - const document = dom.window.document - - // Search for
 elements containing  elements
-        const codeBlocks = document.querySelectorAll('pre > code')
-
-        return codeBlocks.length
-    }
-
-    public async sendAIResponse(
-        response: MessengerResponseType,
-        session: ChatSession,
-        tabID: string,
-        triggerID: string,
-        triggerPayload: TriggerPayload,
-        cancelToken: vscode.CancellationToken
-    ) {
-        let message = ''
-        const messageID = response.$metadata.requestId ?? ''
-        let codeReference: CodeReference[] = []
-        let followUps: FollowUp[] = []
-        let relatedSuggestions: Suggestion[] = []
-        let codeBlockLanguage: string = 'plaintext'
-
-        if (response.message === undefined) {
-            throw new ToolkitError(
-                `Empty response from CodeWhisperer Streaming service. Request ID: ${response.$metadata.requestId}`
-            )
-        }
-        this.telemetryHelper.setResponseStreamStartTime(tabID)
-
-        let cwsprChatHasProjectContext = false
-        if (
-            triggerPayload.relevantTextDocuments &&
-            triggerPayload.relevantTextDocuments.length > 0 &&
-            triggerPayload.useRelevantDocuments === true
-        ) {
-            cwsprChatHasProjectContext = true
-        }
-        const additionalCounts = this.telemetryHelper.getAdditionalContextCounts(triggerPayload)
-
-        this.telemetryHelper.setResponseFromAdditionalContext(messageID, {
-            cwsprChatHasProjectContext,
-            cwsprChatRuleContextCount: triggerPayload.workspaceRulesCount,
-            cwsprChatFileContextCount: additionalCounts.fileContextCount,
-            cwsprChatFolderContextCount: additionalCounts.folderContextCount,
-            cwsprChatPromptContextCount: additionalCounts.promptContextCount,
-        })
-
-        const eventCounts = new Map()
-        waitUntil(
-            async () => {
-                for await (const chatEvent of response.message!) {
-                    if (cancelToken.isCancellationRequested) {
-                        return
-                    }
-                    for (const key of keys(chatEvent)) {
-                        if ((chatEvent[key] as any) !== undefined) {
-                            eventCounts.set(key, (eventCounts.get(key) ?? 0) + 1)
-                        }
-                    }
-
-                    if (session.tokenSource.token.isCancellationRequested) {
-                        return true
-                    }
-
-                    if (
-                        chatEvent.codeReferenceEvent?.references !== undefined &&
-                        chatEvent.codeReferenceEvent.references.length > 0
-                    ) {
-                        codeReference = [
-                            ...codeReference,
-                            ...chatEvent.codeReferenceEvent.references.map((reference) => ({
-                                ...reference,
-                                recommendationContentSpan: {
-                                    start: reference.recommendationContentSpan?.start ?? 0,
-                                    end: reference.recommendationContentSpan?.end ?? 0,
-                                },
-                                information: `Reference code under **${reference.licenseName}** license from repository \`${reference.repository}\``,
-                            })),
-                        ]
-                    }
-
-                    if (
-                        chatEvent.assistantResponseEvent?.content !== undefined &&
-                        chatEvent.assistantResponseEvent.content.length > 0
-                    ) {
-                        message += chatEvent.assistantResponseEvent.content
-                        if (codeBlockLanguage === 'plaintext') {
-                            codeBlockLanguage = extractCodeBlockLanguage(message)
-                        }
-                        this.dispatcher.sendChatMessage(
-                            new ChatMessage(
-                                {
-                                    message: message,
-                                    messageType: 'answer-part',
-                                    followUps: undefined,
-                                    followUpsHeader: undefined,
-                                    relatedSuggestions: undefined,
-                                    codeReference,
-                                    triggerID,
-                                    messageID,
-                                    userIntent: triggerPayload.userIntent,
-                                    codeBlockLanguage: codeBlockLanguage,
-                                    contextList: undefined,
-                                },
-                                tabID
-                            )
-                        )
-                        this.telemetryHelper.setResponseStreamTimeForChunks(tabID)
-                    }
-
-                    if (chatEvent.supplementaryWebLinksEvent?.supplementaryWebLinks !== undefined) {
-                        let suggestionIndex = 0
-                        const newSuggestions: Suggestion[] =
-                            chatEvent.supplementaryWebLinksEvent.supplementaryWebLinks.map(
-                                (s: SupplementaryWebLink) =>
-                                    new Suggestion({
-                                        title: s.title ?? '',
-                                        url: s.url ?? '',
-                                        body: s.snippet ?? '',
-                                        id: suggestionIndex++,
-                                        context: [],
-                                    })
-                            )
-                        relatedSuggestions.push(...newSuggestions)
-                    }
-
-                    if (chatEvent.followupPromptEvent?.followupPrompt !== undefined) {
-                        const followUp = chatEvent.followupPromptEvent.followupPrompt
-                        followUps.push({
-                            type: followUp.userIntent ?? '',
-                            pillText: followUp.content ?? '',
-                            prompt: followUp.content ?? '',
-                        })
-                    }
-                }
-                return true
-            },
-            { timeout: 60000, truthy: true }
-        )
-            .catch((error: any) => {
-                let errorMessage = 'Error reading chat stream.'
-                let statusCode = undefined
-                let requestID = undefined
-
-                if (error instanceof CodeWhispererStreamingServiceException) {
-                    errorMessage = error.message
-                    statusCode = getHttpStatusCode(error) ?? 0
-                    requestID = getRequestId(error)
-                }
-
-                this.showChatExceptionMessage(
-                    { errorMessage, statusCode: statusCode?.toString(), sessionID: undefined },
-                    tabID,
-                    requestID
-                )
-                getLogger().error(`error: ${errorMessage} tabID: ${tabID} requestID: ${requestID}`)
-
-                followUps = []
-                relatedSuggestions = []
-                this.telemetryHelper.recordMessageResponseError(triggerPayload, tabID, statusCode ?? 0)
-            })
-            .finally(async () => {
-                if (session.sessionIdentifier) {
-                    this.chatHistoryDb.addMessage(tabID, 'cwc', session.sessionIdentifier, {
-                        body: message,
-                        type: 'answer' as any,
-                        codeReference: codeReference as any,
-                        relatedContent: { title: 'Sources', content: relatedSuggestions as any },
-                    })
-                }
-                if (triggerPayload.relevantTextDocuments && triggerPayload.relevantTextDocuments.length > 0) {
-                    this.dispatcher.sendChatMessage(
-                        new ChatMessage(
-                            {
-                                message:
-                                    message +
-                                    ` \n\nBy the way, I'm still indexing this project for full context from your workspace. I may have a better response in a few minutes when it's complete if you'd like to try again then.`,
-                                messageType: 'answer-part',
-                                followUps: undefined,
-                                followUpsHeader: undefined,
-                                relatedSuggestions: undefined,
-                                triggerID,
-                                messageID,
-                                userIntent: triggerPayload.userIntent,
-                                codeBlockLanguage: codeBlockLanguage,
-                                contextList: undefined,
-                            },
-                            tabID
-                        )
-                    )
-                }
-
-                if (relatedSuggestions.length !== 0) {
-                    this.dispatcher.sendChatMessage(
-                        new ChatMessage(
-                            {
-                                message: undefined,
-                                messageType: 'answer-part',
-                                followUpsHeader: undefined,
-                                followUps: undefined,
-                                relatedSuggestions,
-                                triggerID,
-                                messageID,
-                                userIntent: triggerPayload.userIntent,
-                                codeBlockLanguage: undefined,
-                                contextList: undefined,
-                            },
-                            tabID
-                        )
-                    )
-                }
-
-                this.dispatcher.sendChatMessage(
-                    new ChatMessage(
-                        {
-                            message: undefined,
-                            messageType: 'answer',
-                            followUps: followUps,
-                            followUpsHeader: undefined,
-                            relatedSuggestions: undefined,
-                            triggerID,
-                            messageID,
-                            userIntent: triggerPayload.userIntent,
-                            codeBlockLanguage: undefined,
-                            contextList: undefined,
-                        },
-                        tabID
-                    )
-                )
-
-                getLogger().info(
-                    `All events received. requestId=%s counts=%s`,
-                    response.$metadata.requestId,
-                    Object.fromEntries(eventCounts)
-                )
-
-                this.telemetryHelper.setResponseStreamTotalTime(tabID)
-
-                const responseCode = response?.$metadata.httpStatusCode ?? 0
-                this.telemetryHelper.recordAddMessage(triggerPayload, {
-                    followUpCount: followUps.length,
-                    suggestionCount: relatedSuggestions.length,
-                    tabID: tabID,
-                    messageLength: message.length,
-                    messageID,
-                    responseCode,
-                    codeReferenceCount: codeReference.length,
-                    totalNumberOfCodeBlocksInResponse: await this.countTotalNumberOfCodeBlocks(message),
-                })
-            })
-    }
-
-    public sendErrorMessage(errorMessage: string | undefined, tabID: string, requestID: string | undefined) {
-        this.showChatExceptionMessage(
-            {
-                errorMessage: errorMessage,
-                sessionID: undefined,
-                statusCode: undefined,
-            },
-            tabID,
-            requestID
-        )
-    }
-
-    private editorContextMenuCommandVerbs: Map = new Map([
-        ['aws.amazonq.explainCode', 'Explain'],
-        ['aws.amazonq.explainIssue', 'Explain'],
-        ['aws.amazonq.refactorCode', 'Refactor'],
-        ['aws.amazonq.fixCode', 'Fix'],
-        ['aws.amazonq.optimizeCode', 'Optimize'],
-        ['aws.amazonq.sendToPrompt', 'Send to prompt'],
-        ['aws.amazonq.generateUnitTests', 'Generate unit tests for'],
-    ])
-
-    public sendStaticTextResponse(type: StaticTextResponseType, triggerID: string, tabID: string) {
-        let message
-        let followUps
-        let followUpsHeader
-        switch (type) {
-            case 'quick-action-help':
-                message = helpMessage
-                break
-            case 'onboarding-help':
-                message = `### What I can do:
-                \n\n- Answer questions about AWS
-                \n\n- Answer questions about general programming concepts
-                \n\n- Explain what a line of code or code function does
-                \n\n- Write unit tests and code
-                \n\n- Debug and fix code
-                \n\n- Refactor code`
-                followUps = [
-                    {
-                        type: '',
-                        pillText: 'Should I use AWS Lambda or EC2 for a scalable web application backend?',
-                        prompt: 'Should I use AWS Lambda or EC2 for a scalable web application backend?',
-                    },
-                    {
-                        type: '',
-                        pillText: 'What is the syntax of declaring a variable in TypeScript?',
-                        prompt: 'What is the syntax of declaring a variable in TypeScript?',
-                    },
-                    {
-                        type: '',
-                        pillText: 'Write code for uploading a file to an s3 bucket in typescript',
-                        prompt: 'Write code for uploading a file to an s3 bucket in typescript',
-                    },
-                ]
-                followUpsHeader = 'Try Examples:'
-                break
-        }
-
-        this.dispatcher.sendChatMessage(
-            new ChatMessage(
-                {
-                    message,
-                    messageType: 'answer',
-                    followUpsHeader,
-                    followUps,
-                    relatedSuggestions: undefined,
-                    triggerID,
-                    messageID: 'static_message_' + triggerID,
-                    userIntent: undefined,
-                    codeBlockLanguage: undefined,
-                    contextList: undefined,
-                },
-                tabID
-            )
-        )
-    }
-
-    public sendQuickActionMessage(quickAction: ChatPromptCommandType, triggerID: string) {
-        let message = ''
-        switch (quickAction) {
-            case 'help':
-                message = 'How can Amazon Q help me?'
-                break
-        }
-
-        this.dispatcher.sendQuickActionMessage(
-            new QuickActionMessage({
-                message,
-                triggerID,
-            })
-        )
-    }
-
-    public sendEditorContextCommandMessage(
-        command: EditorContextCommandType,
-        selectedCode: string,
-        triggerID: string,
-        issue?: CodeScanIssue
-    ) {
-        // Remove newlines and spaces before and after the code
-        const trimmedCode = selectedCode.trimStart().trimEnd()
-
-        let message
-        if (command === 'aws.amazonq.sendToPrompt') {
-            message = ['\n```\n', trimmedCode, '\n```'].join('')
-        } else if (command === 'aws.amazonq.explainIssue' && issue) {
-            message = [
-                this.editorContextMenuCommandVerbs.get(command),
-                ` the "${issue.title}" issue in the following code:`,
-                '\n```\n',
-                trimmedCode,
-                '\n```',
-            ].join('')
-        } else {
-            message = [
-                this.editorContextMenuCommandVerbs.get(command),
-                ' the following part of my code:',
-                '\n```\n',
-                trimmedCode,
-                '\n```',
-            ].join('')
-        }
-
-        this.dispatcher.sendEditorContextCommandMessage(
-            new EditorContextCommandMessage({ message, triggerID, command })
-        )
-    }
-
-    private showChatExceptionMessage(e: ChatException, tabID: string, requestID: string | undefined) {
-        let message = 'This error is reported to the team automatically. We will attempt to fix it as soon as possible.'
-        if (e.errorMessage !== undefined) {
-            message += `\n\nDetails: ${e.errorMessage}`
-        }
-
-        if (e.statusCode !== undefined) {
-            message += `\n\nStatus Code: ${e.statusCode}`
-        }
-        if (e.sessionID !== undefined) {
-            message += `\n\nSession ID: ${e.sessionID}`
-        }
-        if (requestID !== undefined) {
-            message += `\n\nRequest ID: ${requestID}`
-        }
-
-        this.dispatcher.sendErrorMessage(
-            new ErrorMessage('An error occurred while processing your request.', message.trimEnd().trimStart(), tabID)
-        )
-    }
-
-    public sendOpenSettingsMessage(triggerId: string, tabID: string) {
-        this.dispatcher.sendOpenSettingsMessage(new OpenSettingsMessage(tabID))
-    }
-
-    public sendRestoreTabMessage(historyId: string, tabType: TabType, chats: ChatItem[], exportTab?: boolean) {
-        this.dispatcher.sendRestoreTabMessage(new RestoreTabMessage(historyId, tabType, chats, exportTab))
-    }
-
-    public sendOpenDetailedListMessage(tabId: string, listType: string, data: DetailedList) {
-        this.dispatcher.sendOpenDetailedListMessage(new OpenDetailedListMessage(tabId, listType, data))
-    }
-
-    public sendUpdateDetailedListMessage(listType: string, data: DetailedList) {
-        this.dispatcher.sendUpdateDetailedListMessage(new UpdateDetailedListMessage(listType, data))
-    }
-
-    public sendCloseDetailedListMessage(listType: string) {
-        this.dispatcher.sendCloseDetailedListMessage(new CloseDetailedListMessage(listType))
-    }
-
-    public sendSerializeTabMessage(tabId: string, uri: string, format: 'html' | 'markdown') {
-        this.dispatcher.sendSerializeTabMessage(new ExportChatMessage(tabId, format, uri))
-    }
-
-    public sendSelectTabMessage(tabId: string, eventID?: string) {
-        this.dispatcher.sendSelectTabMessage(new SelectTabMessage(tabId, eventID))
-    }
-
-    public sendContextCommandData(contextCommands: MynahUIDataModel['contextCommands']) {
-        this.dispatcher.sendContextCommandData(new ContextCommandData(contextCommands))
-    }
-
-    public showCustomForm(
-        tabID: string,
-        formItems?: ChatItemFormItem[],
-        buttons?: ChatItemButton[],
-        title?: string,
-        description?: string
-    ) {
-        this.dispatcher.sendShowCustomFormMessage(
-            new ShowCustomFormMessage(tabID, formItems, buttons, title, description)
-        )
-    }
-}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/model.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/model.ts
deleted file mode 100644
index c5d5eb92dd..0000000000
--- a/packages/core/src/codewhispererChat/controllers/chat/messenger/model.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export interface ChatException {
-    readonly errorMessage: string | undefined
-    readonly sessionID: string | undefined
-    readonly statusCode: string | undefined
-}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/model.ts b/packages/core/src/codewhispererChat/controllers/chat/model.ts
index 04da124aec..1ae86f4ea8 100644
--- a/packages/core/src/codewhispererChat/controllers/chat/model.ts
+++ b/packages/core/src/codewhispererChat/controllers/chat/model.ts
@@ -5,10 +5,9 @@
 
 import * as vscode from 'vscode'
 import { AdditionalContentEntry, RelevantTextDocument, UserIntent } from '@amzn/codewhisperer-streaming'
-import { MatchPolicy, CodeQuery } from '../../clients/chat/v0/model'
 import { Selection } from 'vscode'
 import { TabOpenType } from '../../../amazonq/webview/ui/storages/tabsStorage'
-import { CodeReference } from '../../view/connector/connector'
+import { CodeReference } from '../../../amazonq/commons/model'
 import { Customization } from '../../../codewhisperer/client/codewhispereruserclient'
 import { QuickActionCommand } from '@aws/mynah-ui'
 import { Message } from '../../../shared/db/chatDb/util'
@@ -259,3 +258,35 @@ export interface InsertedCode {
     readonly endPosition: vscode.Position
     readonly originalString: string
 }
+
+export type ContextKey = string
+export type ContextKeys = ContextKey[]
+
+export interface MatchPolicy {
+    readonly should?: ContextKeys
+    readonly must?: ContextKeys
+    readonly mustNot?: ContextKeys
+}
+
+export type SimpleName = string
+export type SimpleNames = SimpleName[]
+
+export type Name = string
+export type SymbolIdentifier = Name[]
+export type SourceIdentifier = Name[]
+
+export interface FullyQualifiedName {
+    readonly source?: SourceIdentifier
+    readonly symbol?: SymbolIdentifier
+}
+
+export type FullyQualifiedNamesUsages = FullyQualifiedName[]
+
+export interface FullyQualifiedNamesDeclarationsUsages {
+    readonly used?: FullyQualifiedNamesUsages
+}
+
+export interface CodeQuery {
+    readonly simpleNames?: SimpleNames
+    readonly fullyQualifiedNames?: FullyQualifiedNamesDeclarationsUsages
+}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts b/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts
deleted file mode 100644
index 6abecda2f8..0000000000
--- a/packages/core/src/codewhispererChat/controllers/chat/prompts/promptsGenerator.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { EditorContextBaseCommandType, EditorContextCommand } from '../../../commands/registerCommands'
-
-// TODO: It's a workaround for the demo, we need to remove it after backend will be ready
-
-export class PromptsGenerator {
-    private editorContextMenuCommandVerbs: Map = new Map([
-        ['aws.amazonq.explainCode', 'Explain'],
-        ['aws.amazonq.refactorCode', 'Refactor'],
-        ['aws.amazonq.fixCode', 'Fix'],
-        ['aws.amazonq.optimizeCode', 'Optimize'],
-        ['aws.amazonq.sendToPrompt', 'Send to prompt'],
-    ])
-
-    public generateForContextMenuCommand(command: EditorContextCommand): string {
-        if (command.type === 'aws.amazonq.explainIssue') {
-            return `Explain the issue "${command.issue.title}" (${JSON.stringify(
-                command.issue
-            )}) and generate code demonstrating the fix`
-        }
-        return [this.editorContextMenuCommandVerbs.get(command.type), ' the selected codeblock'].join('')
-    }
-}
diff --git a/packages/core/src/codewhispererChat/controllers/chat/tabBarController.ts b/packages/core/src/codewhispererChat/controllers/chat/tabBarController.ts
deleted file mode 100644
index 13088e096f..0000000000
--- a/packages/core/src/codewhispererChat/controllers/chat/tabBarController.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import fs from '../../../shared/fs/fs'
-import {
-    DetailedListActionClickMessage,
-    DetailedListFilterChangeMessage,
-    DetailedListItemSelectMessage,
-} from '../../view/connector/connector'
-import * as vscode from 'vscode'
-import { Messenger } from './messenger/messenger'
-import { Database } from '../../../shared/db/chatDb/chatDb'
-import { TabBarButtonClick, SaveChatMessage } from './model'
-import { Conversation, messageToChatItem, Tab } from '../../../shared/db/chatDb/util'
-import { DetailedListItemGroup, MynahIconsType } from '@aws/mynah-ui'
-
-export class TabBarController {
-    private readonly messenger: Messenger
-    private chatHistoryDb = Database.getInstance()
-    private loadedChats: boolean = false
-    private searchTimeout: NodeJS.Timeout | undefined = undefined
-    private readonly DebounceTime = 300 // milliseconds
-
-    constructor(messenger: Messenger) {
-        this.messenger = messenger
-    }
-    async processActionClickMessage(msg: DetailedListActionClickMessage) {
-        if (msg.listType === 'history') {
-            if (msg.action.text === 'Delete') {
-                this.chatHistoryDb.deleteHistory(msg.action.id)
-                this.messenger.sendUpdateDetailedListMessage('history', { list: this.generateHistoryList() })
-            } else if (msg.action.text === 'Export') {
-                // If conversation is already open, export it
-                const openTabId = this.chatHistoryDb.getOpenTabId(msg.action.id)
-                if (openTabId) {
-                    await this.exportChatButtonClicked({ tabID: openTabId, buttonId: 'export_chat' })
-                } // If conversation is not open, restore it before exporting
-                else {
-                    const selectedTab = this.chatHistoryDb.getTab(msg.action.id)
-                    this.restoreTab(selectedTab, true)
-                }
-            }
-        }
-    }
-
-    async processFilterChangeMessage(msg: DetailedListFilterChangeMessage) {
-        if (msg.listType === 'history') {
-            const searchFilter = msg.filterValues['search']
-            if (typeof searchFilter !== 'string') {
-                return
-            }
-
-            // Clear any pending search
-            if (this.searchTimeout) {
-                clearTimeout(this.searchTimeout)
-            }
-
-            // Set new timeout for this search
-            this.searchTimeout = setTimeout(() => {
-                const searchResults = this.chatHistoryDb.searchMessages(searchFilter)
-                this.messenger.sendUpdateDetailedListMessage('history', { list: searchResults })
-            }, this.DebounceTime)
-        }
-    }
-
-    // If selected is conversation is already open, select that tab. Else, open new tab with conversation.
-    processItemSelectMessage(msg: DetailedListItemSelectMessage) {
-        if (msg.listType === 'history') {
-            const historyID = msg.item.id
-            if (historyID) {
-                const openTabID = this.chatHistoryDb.getOpenTabId(historyID)
-                if (!openTabID) {
-                    const selectedTab = this.chatHistoryDb.getTab(historyID)
-                    this.restoreTab(selectedTab)
-                } else {
-                    this.messenger.sendSelectTabMessage(openTabID, historyID)
-                }
-                this.messenger.sendCloseDetailedListMessage('history')
-            }
-        }
-    }
-
-    restoreTab(selectedTab?: Tab | null, exportTab?: boolean) {
-        if (selectedTab) {
-            this.messenger.sendRestoreTabMessage(
-                selectedTab.historyId,
-                selectedTab.tabType,
-                selectedTab.conversations.flatMap((conv: Conversation) =>
-                    conv.messages.map((message) => messageToChatItem(message))
-                ),
-                exportTab
-            )
-        }
-    }
-
-    loadChats() {
-        if (this.loadedChats) {
-            return
-        }
-        this.loadedChats = true
-        const openConversations = this.chatHistoryDb.getOpenTabs()
-        if (openConversations) {
-            for (const conversation of openConversations) {
-                if (conversation.conversations && conversation.conversations.length > 0) {
-                    this.restoreTab(conversation)
-                }
-            }
-        }
-    }
-
-    async historyButtonClicked(message: TabBarButtonClick) {
-        this.messenger.sendOpenDetailedListMessage(message.tabID, 'history', {
-            header: { title: 'Chat history' },
-            filterOptions: [
-                {
-                    type: 'textinput',
-                    icon: 'search' as MynahIconsType,
-                    id: 'search',
-                    placeholder: 'Search...',
-                    autoFocus: true,
-                },
-            ],
-            list: this.generateHistoryList(),
-        })
-    }
-
-    generateHistoryList(): DetailedListItemGroup[] {
-        const historyItems = this.chatHistoryDb.getHistory()
-        return historyItems.length > 0 ? historyItems : [{ children: [{ description: 'No chat history found' }] }]
-    }
-
-    async processSaveChat(message: SaveChatMessage) {
-        try {
-            await fs.writeFile(message.uri, message.serializedChat)
-        } catch (error) {
-            void vscode.window.showErrorMessage('An error occurred while exporting your chat.')
-        }
-    }
-
-    async processTabBarButtonClick(message: TabBarButtonClick) {
-        switch (message.buttonId) {
-            case 'history_sheet':
-                await this.historyButtonClicked(message)
-                break
-            case 'export_chat':
-                await this.exportChatButtonClicked(message)
-                break
-        }
-    }
-
-    private async exportChatButtonClicked(message: TabBarButtonClick) {
-        const defaultFileName = `q-dev-chat-${new Date().toISOString().split('T')[0]}.md`
-        const workspaceFolders = vscode.workspace.workspaceFolders
-        let defaultUri
-
-        if (workspaceFolders && workspaceFolders.length > 0) {
-            // Use the first workspace folder as root
-            defaultUri = vscode.Uri.joinPath(workspaceFolders[0].uri, defaultFileName)
-        } else {
-            // Fallback if no workspace is open
-            defaultUri = vscode.Uri.file(defaultFileName)
-        }
-
-        const saveUri = await vscode.window.showSaveDialog({
-            filters: {
-                Markdown: ['md'],
-                HTML: ['html'],
-            },
-            defaultUri,
-            title: 'Export chat',
-        })
-
-        if (saveUri) {
-            // Determine format from file extension
-            const format = saveUri.fsPath.endsWith('.md') ? 'markdown' : 'html'
-            this.messenger.sendSerializeTabMessage(message.tabID, saveUri.fsPath, format)
-        }
-    }
-}
diff --git a/packages/core/src/codewhispererChat/editor/context/file/fileExtractor.ts b/packages/core/src/codewhispererChat/editor/context/file/fileExtractor.ts
index 7606fe0708..a9cced748b 100644
--- a/packages/core/src/codewhispererChat/editor/context/file/fileExtractor.ts
+++ b/packages/core/src/codewhispererChat/editor/context/file/fileExtractor.ts
@@ -5,7 +5,7 @@
 
 import { TextDocument } from 'vscode'
 import { extractLanguageNameFromFile, extractAdditionalLanguageMatchPoliciesFromFile } from './languages'
-import { MatchPolicy } from '../../../clients/chat/v0/model'
+import { MatchPolicy } from '../../../controllers/chat/model'
 import { readImports } from './importReader'
 import { FileContext } from './model'
 
diff --git a/packages/core/src/codewhispererChat/editor/context/file/model.ts b/packages/core/src/codewhispererChat/editor/context/file/model.ts
index 42b0846b0c..377c17253b 100644
--- a/packages/core/src/codewhispererChat/editor/context/file/model.ts
+++ b/packages/core/src/codewhispererChat/editor/context/file/model.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-import { MatchPolicy } from '../../../clients/chat/v0/model'
+import { MatchPolicy } from '../../../controllers/chat/model'
 
 export interface FileContext {
     readonly fileText: string | undefined
diff --git a/packages/core/src/codewhispererChat/index.ts b/packages/core/src/codewhispererChat/index.ts
index b47115fbc4..1ebadbdf49 100644
--- a/packages/core/src/codewhispererChat/index.ts
+++ b/packages/core/src/codewhispererChat/index.ts
@@ -3,10 +3,10 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 
-export { FocusAreaContextExtractor } from './editor/context/focusArea/focusAreaExtractor'
 export { TryChatCodeLensProvider, resolveModifierKey, tryChatCodeLensCommand } from './editor/codelens'
 export { focusAmazonQPanel } from './commands/registerCommands'
 export { ChatSession } from './clients/chat/v0/chat'
+export { FocusAreaContextExtractor } from './editor/context/focusArea/focusAreaExtractor'
 export { triggerPayloadToChatRequest, filePathSizeLimit } from './controllers/chat/chatRequest/converter'
 export { ChatTriggerType, PromptMessage, TriggerPayload } from './controllers/chat/model'
 export { UserIntentRecognizer } from './controllers/chat/userIntent/userIntentRecognizer'
diff --git a/packages/core/src/codewhispererChat/view/connector/connector.ts b/packages/core/src/codewhispererChat/view/connector/connector.ts
deleted file mode 100644
index 6632819688..0000000000
--- a/packages/core/src/codewhispererChat/view/connector/connector.ts
+++ /dev/null
@@ -1,463 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { MessagePublisher } from '../../../amazonq/messages/messagePublisher'
-import { EditorContextCommandType } from '../../commands/registerCommands'
-import { AuthFollowUpType } from '../../../amazonq/auth/model'
-import {
-    ChatItem,
-    ChatItemButton,
-    ChatItemFormItem,
-    DetailedList,
-    DetailedListItem,
-    MynahUIDataModel,
-    QuickActionCommand,
-} from '@aws/mynah-ui'
-import { DocumentReference } from '../../controllers/chat/model'
-import { TabType } from '../../../amazonq/webview/ui/storages/tabsStorage'
-
-class UiMessage {
-    readonly time: number = Date.now()
-    readonly sender: string = 'CWChat'
-    readonly type: string = ''
-
-    public constructor(public tabID: string | undefined) {}
-}
-
-export class ErrorMessage extends UiMessage {
-    readonly title!: string
-    readonly message!: string
-    override type = 'errorMessage'
-
-    constructor(title: string, message: string, tabID: string) {
-        super(tabID)
-        this.title = title
-        this.message = message
-    }
-}
-
-interface SearchSuggestionCommonProps {
-    readonly title: string
-    readonly url: string
-    readonly body: string
-    readonly id: number
-}
-
-class SearchSuggestionCommon {
-    readonly title!: string
-    readonly url!: string
-    readonly body!: string
-    readonly id!: number
-
-    constructor(props: SearchSuggestionCommonProps) {
-        this.title = props.title
-        this.url = props.url
-        this.body = props.body
-        this.id = props.id
-    }
-}
-
-export class APIDocsSuggestion extends SearchSuggestionCommon {
-    readonly metadata!: APIDocsSuggestionMetadata
-}
-
-interface APIDocsSuggestionMetadata {
-    readonly canonicalExample: CodeExample
-}
-
-interface CodeExample {
-    readonly url: string
-    readonly body: string
-}
-
-export interface SuggestionProps extends SearchSuggestionCommonProps {
-    readonly metadata?: SuggestionMetadata
-    readonly context: string[]
-}
-
-export class Suggestion extends SearchSuggestionCommon {
-    readonly metadata?: SuggestionMetadata
-    readonly context: string[]
-
-    constructor(props: SuggestionProps) {
-        super(props)
-        this.metadata = props.metadata
-        this.context = props.context
-    }
-}
-
-interface SuggestionMetadata {
-    readonly stackOverflow: StackOverflowMetadata
-}
-
-interface StackOverflowMetadata {
-    readonly answerCount: number
-    readonly isAccepted: boolean
-    readonly score: number
-    readonly lastActivityDate: Date
-}
-
-export class SearchView extends UiMessage {
-    readonly suggestions: Suggestion[] | undefined
-    readonly apiDocsSuggestions: APIDocsSuggestion[] | undefined
-    readonly enableAPIDocsTab: boolean = false
-    override type = 'drawNewSearchViewState'
-}
-
-export type ChatMessageType = 'answer-stream' | 'answer-part' | 'answer'
-
-export interface CodeReference {
-    licenseName?: string
-    repository?: string
-    url?: string
-    recommendationContentSpan?: {
-        start?: number
-        end?: number
-    }
-}
-
-export interface AuthNeededExceptionProps {
-    readonly message: string
-    readonly authType: AuthFollowUpType
-    readonly triggerID: string
-}
-
-export class AuthNeededException extends UiMessage {
-    readonly message: string
-    readonly authType: AuthFollowUpType
-    readonly triggerID: string
-    override type = 'authNeededException'
-
-    constructor(props: AuthNeededExceptionProps, tabID: string) {
-        super(tabID)
-        this.message = props.message
-        this.triggerID = props.triggerID
-        this.authType = props.authType
-    }
-}
-
-export class OpenSettingsMessage extends UiMessage {
-    override type = 'openSettingsMessage'
-}
-
-export class RestoreTabMessage extends UiMessage {
-    override type = 'restoreTabMessage'
-    readonly tabType: TabType
-    readonly chats: ChatItem[]
-    readonly historyId: string
-    readonly exportTab?: boolean
-
-    constructor(historyId: string, tabType: TabType, chats: ChatItem[], exportTab?: boolean) {
-        super(undefined)
-        this.chats = chats
-        this.tabType = tabType
-        this.historyId = historyId
-        this.exportTab = exportTab
-    }
-}
-
-export class OpenDetailedListMessage extends UiMessage {
-    override type = 'openDetailedListMessage'
-    readonly detailedList: DetailedList
-    listType: string
-
-    constructor(tabID: string, listType: string, data: DetailedList) {
-        super(tabID)
-        this.listType = listType
-        this.detailedList = data
-    }
-}
-
-export class UpdateDetailedListMessage extends UiMessage {
-    override type = 'updateDetailedListMessage'
-    readonly detailedList: DetailedList
-    listType: string
-
-    constructor(listType: string, data: DetailedList) {
-        super(undefined)
-        this.listType = listType
-        this.detailedList = data
-    }
-}
-
-export class CloseDetailedListMessage extends UiMessage {
-    override type = 'closeDetailedListMessage'
-    listType: string
-
-    constructor(listType: string) {
-        super(undefined)
-        this.listType = listType
-    }
-}
-
-export class ExportChatMessage extends UiMessage {
-    override type = 'exportChatMessage'
-    readonly format: 'markdown' | 'html'
-    readonly uri: string
-
-    constructor(tabID: string, format: 'markdown' | 'html', uri: string) {
-        super(tabID)
-        this.format = format
-        this.uri = uri
-    }
-}
-
-export class SelectTabMessage extends UiMessage {
-    override type = 'selectTabMessage'
-    readonly eventID?: string
-
-    constructor(tabID: string, eventID?: string) {
-        super(tabID)
-        this.eventID = eventID
-    }
-}
-
-export class DetailedListFilterChangeMessage extends UiMessage {
-    override type = 'detailedListFilterChangeMessage'
-    readonly listType: string
-    readonly filterValues: Record
-    readonly isValid: boolean
-
-    constructor(listType: string, filterValues: Record, isValid: boolean) {
-        super(undefined)
-        this.listType = listType
-        this.filterValues = filterValues
-        this.isValid = isValid
-    }
-}
-
-export class DetailedListItemSelectMessage extends UiMessage {
-    override type = 'detailedListItemSelectMessage'
-    readonly listType: string
-    readonly item: DetailedListItem
-
-    constructor(listType: string, item: DetailedListItem) {
-        super(undefined)
-        this.listType = listType
-        this.item = item
-    }
-}
-
-export class DetailedListActionClickMessage extends UiMessage {
-    override type = 'detailedListActionClickMessage'
-    readonly listType: string
-    action: ChatItemButton
-
-    constructor(listType: string, action: ChatItemButton) {
-        super(undefined)
-        this.listType = listType
-        this.action = action
-    }
-}
-
-export class ContextCommandData extends UiMessage {
-    readonly data: MynahUIDataModel['contextCommands']
-    override type = 'contextCommandData'
-    constructor(data: MynahUIDataModel['contextCommands']) {
-        super('tab-1')
-        this.data = data
-    }
-}
-
-export class CustomFormActionMessage extends UiMessage {
-    override type = 'customFormActionMessage'
-    readonly action: {
-        id: string
-        text?: string | undefined
-        formItemValues?: Record | undefined
-    }
-
-    constructor(
-        tabID: string,
-        action: {
-            id: string
-            text?: string | undefined
-            formItemValues?: Record | undefined
-        }
-    ) {
-        super(tabID)
-        this.action = action
-    }
-}
-
-export class ShowCustomFormMessage extends UiMessage {
-    override type = 'showCustomFormMessage'
-    readonly formItems?: ChatItemFormItem[]
-    readonly buttons?: ChatItemButton[]
-    readonly title?: string
-    readonly description?: string
-
-    constructor(
-        tabID: string,
-        formItems?: ChatItemFormItem[],
-        buttons?: ChatItemButton[],
-        title?: string,
-        description?: string
-    ) {
-        super(tabID)
-        this.formItems = formItems
-        this.buttons = buttons
-        this.title = title
-        this.description = description
-    }
-}
-
-export class ContextSelectedMessage extends UiMessage {
-    override type = 'contextSelectedMessage'
-    readonly contextItem: QuickActionCommand
-
-    constructor(tabID: string, contextItem: QuickActionCommand) {
-        super(tabID)
-        this.contextItem = contextItem
-    }
-}
-
-export interface ChatMessageProps {
-    readonly message: string | undefined
-    readonly messageType: ChatMessageType
-    readonly followUps: FollowUp[] | undefined
-    readonly followUpsHeader: string | undefined
-    readonly relatedSuggestions: Suggestion[] | undefined
-    readonly codeReference?: CodeReference[]
-    readonly triggerID: string
-    readonly messageID: string
-    readonly userIntent: string | undefined
-    readonly codeBlockLanguage: string | undefined
-    readonly contextList: DocumentReference[] | undefined
-}
-
-export class ChatMessage extends UiMessage {
-    readonly message: string | undefined
-    readonly messageType: ChatMessageType
-    readonly followUps: FollowUp[] | undefined
-    readonly codeReference: CodeReference[] | undefined
-    readonly relatedSuggestions: Suggestion[] | undefined
-    readonly searchResults: Suggestion[] | undefined
-    readonly followUpsHeader: string | undefined
-    readonly triggerID: string
-    readonly messageID: string | undefined
-    readonly userIntent: string | undefined
-    readonly codeBlockLanguage: string | undefined
-    readonly contextList: DocumentReference[] | undefined
-    override type = 'chatMessage'
-
-    constructor(props: ChatMessageProps, tabID: string) {
-        super(tabID)
-        this.message = props.message
-        this.messageType = props.messageType
-        this.followUps = props.followUps
-        this.followUpsHeader = props.followUpsHeader
-        this.relatedSuggestions = props.relatedSuggestions
-        this.codeReference = props.codeReference
-        this.triggerID = props.triggerID
-        this.messageID = props.messageID
-        this.userIntent = props.userIntent
-        this.codeBlockLanguage = props.codeBlockLanguage
-        this.contextList = props.contextList
-    }
-}
-
-export interface FollowUp {
-    readonly type: string
-    readonly pillText: string
-    readonly prompt: string
-}
-
-export interface EditorContextCommandMessageProps {
-    readonly message: string
-    readonly triggerID: string
-    readonly command?: EditorContextCommandType
-}
-
-export class EditorContextCommandMessage extends UiMessage {
-    readonly message: string
-    readonly triggerID: string
-    readonly command?: EditorContextCommandType
-    override type = 'editorContextCommandMessage'
-
-    constructor(props: EditorContextCommandMessageProps) {
-        super(undefined)
-        this.message = props.message
-        this.triggerID = props.triggerID
-        this.command = props.command
-    }
-}
-
-export interface QuickActionMessageProps {
-    readonly message: string
-    readonly triggerID: string
-}
-
-export class QuickActionMessage extends UiMessage {
-    readonly message: string
-    readonly triggerID: string
-    override type = 'editorContextCommandMessage'
-
-    constructor(props: QuickActionMessageProps) {
-        super(undefined)
-        this.message = props.message
-        this.triggerID = props.triggerID
-    }
-}
-
-export class AppToWebViewMessageDispatcher {
-    constructor(private readonly appsToWebViewMessagePublisher: MessagePublisher) {}
-
-    public sendErrorMessage(message: ErrorMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendChatMessage(message: ChatMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendEditorContextCommandMessage(message: EditorContextCommandMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendQuickActionMessage(message: QuickActionMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendAuthNeededExceptionMessage(message: AuthNeededException) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendOpenSettingsMessage(message: OpenSettingsMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendRestoreTabMessage(message: RestoreTabMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendOpenDetailedListMessage(message: OpenDetailedListMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendUpdateDetailedListMessage(message: UpdateDetailedListMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendCloseDetailedListMessage(message: CloseDetailedListMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendSerializeTabMessage(message: ExportChatMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendSelectTabMessage(message: SelectTabMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendContextCommandData(message: ContextCommandData) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-
-    public sendShowCustomFormMessage(message: ShowCustomFormMessage) {
-        this.appsToWebViewMessagePublisher.publish(message)
-    }
-}
diff --git a/packages/core/src/codewhispererChat/view/messages/messageListener.ts b/packages/core/src/codewhispererChat/view/messages/messageListener.ts
deleted file mode 100644
index 53709e125e..0000000000
--- a/packages/core/src/codewhispererChat/view/messages/messageListener.ts
+++ /dev/null
@@ -1,359 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import { MessageListener } from '../../../amazonq/messages/messageListener'
-import { ExtensionMessage } from '../../../amazonq/webview/ui/commands'
-import { AuthController } from '../../../amazonq/auth/controller'
-import { ChatControllerMessagePublishers } from '../../controllers/chat/controller'
-import { ReferenceLogController } from './referenceLogController'
-import { getLogger } from '../../../shared/logger/logger'
-import { openSettingsId } from '../../../shared/settings'
-import { Database } from '../../../shared/db/chatDb/chatDb'
-
-export interface UIMessageListenerProps {
-    readonly chatControllerMessagePublishers: ChatControllerMessagePublishers
-    readonly webViewMessageListener: MessageListener
-}
-
-export class UIMessageListener {
-    private chatControllerMessagePublishers: ChatControllerMessagePublishers
-    private webViewMessageListener: MessageListener
-    private referenceLogController: ReferenceLogController
-    private authController: AuthController
-
-    constructor(props: UIMessageListenerProps) {
-        this.chatControllerMessagePublishers = props.chatControllerMessagePublishers
-        this.webViewMessageListener = props.webViewMessageListener
-        this.referenceLogController = new ReferenceLogController()
-        this.authController = new AuthController()
-
-        this.webViewMessageListener.onMessage((msg) => {
-            this.handleMessage(msg)
-        })
-    }
-
-    private handleMessage(msg: ExtensionMessage) {
-        switch (msg.command) {
-            case 'help':
-            case 'clear':
-            case 'transform':
-            case 'chat-prompt':
-                this.processChatMessage(msg)
-                break
-            case 'new-tab-was-created':
-                this.processNewTabWasCreated(msg)
-                break
-            case 'tab-was-removed':
-                this.processTabWasRemoved(msg)
-                break
-            case 'tab-was-changed':
-                this.processTabWasChanged(msg)
-                break
-            case 'auth-follow-up-was-clicked':
-                this.processAuthFollowUpWasClicked(msg)
-                break
-            case 'follow-up-was-clicked':
-                if (msg.followUp?.prompt !== undefined) {
-                    this.processChatMessage({
-                        chatMessage: msg.followUp.prompt,
-                        tabID: msg.tabID,
-                        command: msg.command,
-                        messageId: msg.messageId,
-                        userIntent: msg.followUp.type,
-                    })
-                }
-                break
-            case 'accept_diff':
-                this.processAcceptDiff(msg)
-                break
-            case 'view_diff':
-                this.processViewDiff(msg)
-                break
-            case 'code_was_copied_to_clipboard':
-                this.processCodeWasCopiedToClipboard(msg)
-                break
-            case 'insert_code_at_cursor_position':
-                this.processInsertCodeAtCursorPosition(msg)
-                break
-            case 'trigger-tabID-received':
-                this.processTriggerTabIDReceived(msg)
-                break
-            case 'stop-response':
-                this.stopResponse(msg)
-                break
-            case 'chat-item-voted':
-                this.chatItemVoted(msg)
-                break
-            case 'chat-item-feedback':
-                this.chatItemFeedback(msg).catch((e) => {
-                    getLogger().error('chatItemFeedback failed: %s', (e as Error).message)
-                })
-                break
-            case 'ui-focus':
-                this.processUIFocus(msg)
-                break
-            case 'source-link-click':
-                this.processSourceLinkClick(msg)
-                break
-            case 'response-body-link-click':
-                this.processResponseBodyLinkClick(msg)
-                break
-            case 'footer-info-link-click':
-                this.processFooterInfoLinkClick(msg)
-                break
-            case 'open-settings':
-                this.processOpenSettings(msg)
-                break
-            case 'ui-is-ready':
-                this.processUIIsReady()
-                break
-            case 'quick-command-group-action-click':
-                this.quickCommandGroupActionClicked(msg)
-                break
-            case 'form-action-click':
-                this.processCustomFormAction(msg)
-                break
-            case 'context-selected':
-                this.processContextSelected(msg)
-                break
-            case 'file-click':
-                this.fileClick(msg)
-                break
-            case 'tab-restored':
-                this.tabRestored(msg)
-                break
-            case 'tab-bar-button-clicked':
-                this.tabBarButtonClicked(msg)
-                break
-            case 'save-chat':
-                this.saveChat(msg)
-                break
-            case 'detailed-list-filter-change':
-                this.processDetailedListFilterChange(msg)
-                break
-            case 'detailed-list-item-select':
-                this.processDetailedListItemSelect(msg)
-                break
-            case 'detailed-list-action-click':
-                this.processDetailedListActionClick(msg)
-                break
-        }
-    }
-
-    private processDetailedListFilterChange(msg: any) {
-        this.chatControllerMessagePublishers.processDetailedListFilterChangeMessage.publish(msg)
-    }
-    private processDetailedListItemSelect(msg: any) {
-        this.chatControllerMessagePublishers.processDetailedListItemSelectMessage.publish(msg)
-    }
-    private processDetailedListActionClick(msg: any) {
-        this.chatControllerMessagePublishers.processDetailedListActionClickMessage.publish(msg)
-    }
-
-    private tabRestored(msg: any) {
-        const chatHistoryDb = Database.getInstance()
-        chatHistoryDb.setHistoryIdMapping(msg.newTabId, msg.historyId)
-        if (msg.exportTab) {
-            this.chatControllerMessagePublishers.processTabBarButtonClick.publish({
-                tabID: msg.newTabId,
-                buttonId: 'export_chat',
-            })
-        }
-        chatHistoryDb.updateTabOpenState(msg.newTabId, true)
-    }
-
-    private saveChat(msg: any) {
-        this.chatControllerMessagePublishers.processSaveChat.publish({
-            uri: msg.uri,
-            serializedChat: msg.serializedChat,
-        })
-    }
-
-    private tabBarButtonClicked(msg: any) {
-        this.chatControllerMessagePublishers.processTabBarButtonClick.publish({
-            tabID: msg.tabID,
-            buttonId: msg.buttonId,
-        })
-    }
-
-    private processUIIsReady() {
-        this.chatControllerMessagePublishers.processContextCommandUpdateMessage.publish()
-    }
-
-    private processCustomFormAction(msg: any) {
-        this.chatControllerMessagePublishers.processCustomFormAction.publish({ tabID: msg.tabID, ...msg })
-    }
-
-    private processContextSelected(msg: any) {
-        this.chatControllerMessagePublishers.processContextSelected.publish({ tabID: msg.tabID, ...msg })
-    }
-
-    private quickCommandGroupActionClicked(msg: any) {
-        this.chatControllerMessagePublishers.processQuickCommandGroupActionClicked.publish({
-            tabID: msg.tabID,
-            actionId: msg.actionId,
-            command: 'quick-command-group-action-click',
-        })
-    }
-
-    private processOpenSettings(msg: any) {
-        void openSettingsId(`amazonQ.workspaceIndex` as any)
-    }
-
-    private processAuthFollowUpWasClicked(msg: any) {
-        this.authController.handleAuth(msg.authType)
-    }
-    private processFooterInfoLinkClick(msg: any) {
-        this.chatControllerMessagePublishers.processFooterInfoLinkClick.publish({
-            tabID: msg.tabID,
-            link: msg.link,
-            command: 'footer-info-link-click',
-        })
-    }
-    private processResponseBodyLinkClick(msg: any) {
-        this.chatControllerMessagePublishers.processResponseBodyLinkClick.publish({
-            command: msg.command,
-            messageId: msg.messageId,
-            tabID: msg.tabID,
-            link: msg.link,
-        })
-    }
-
-    private processSourceLinkClick(msg: any) {
-        this.chatControllerMessagePublishers.processSourceLinkClick.publish({
-            command: msg.command,
-            messageId: msg.messageId,
-            tabID: msg.tabID,
-            link: msg.link,
-        })
-    }
-
-    private processUIFocus(msg: any) {
-        this.chatControllerMessagePublishers.processUIFocusMessage.publish({
-            command: msg.command,
-            type: msg.type,
-        })
-    }
-
-    private processTriggerTabIDReceived(msg: any) {
-        this.chatControllerMessagePublishers.processTriggerTabIDReceived.publish({
-            tabID: msg.tabID,
-            triggerID: msg.triggerID,
-        })
-    }
-
-    private processInsertCodeAtCursorPosition(msg: any) {
-        this.referenceLogController.addReferenceLog(msg.codeReference, (msg.code as string) ?? '')
-        this.chatControllerMessagePublishers.processInsertCodeAtCursorPosition.publish({
-            command: msg.command,
-            tabID: msg.tabID,
-            messageId: msg.messageId,
-            userIntent: msg.userIntent,
-            code: msg.code,
-            insertionTargetType: msg.insertionTargetType,
-            codeReference: msg.codeReference,
-            eventId: msg.eventId,
-            codeBlockIndex: msg.codeBlockIndex,
-            totalCodeBlocks: msg.totalCodeBlocks,
-            codeBlockLanguage: msg.codeBlockLanguage,
-        })
-    }
-
-    private processAcceptDiff(msg: any) {
-        this.chatControllerMessagePublishers.processAcceptDiff.publish({
-            command: msg.command,
-            tabID: msg.tabID || msg.tabId,
-            ...msg,
-        })
-    }
-
-    private processViewDiff(msg: any) {
-        this.chatControllerMessagePublishers.processViewDiff.publish({
-            command: msg.command,
-            tabID: msg.tabID || msg.tabId,
-            ...msg,
-        })
-    }
-
-    private processCodeWasCopiedToClipboard(msg: any) {
-        this.chatControllerMessagePublishers.processCopyCodeToClipboard.publish({
-            command: msg.command,
-            tabID: msg.tabID,
-            messageId: msg.messageId,
-            userIntent: msg.userIntent,
-            code: msg.code,
-            insertionTargetType: msg.insertionTargetType,
-            codeReference: msg.codeReference,
-            eventId: msg.eventId,
-            codeBlockIndex: msg.codeBlockIndex,
-            totalCodeBlocks: msg.totalCodeBlocks,
-            codeBlockLanguage: msg.codeBlockLanguage,
-        })
-    }
-
-    private processTabWasRemoved(msg: any) {
-        this.chatControllerMessagePublishers.processTabClosedMessage.publish({
-            tabID: msg.tabID,
-        })
-    }
-
-    private processNewTabWasCreated(msg: any) {
-        this.chatControllerMessagePublishers.processTabCreatedMessage.publish({
-            tabID: msg.tabID,
-            tabOpenInteractionType: msg.tabOpenInteractionType,
-        })
-    }
-
-    private processTabWasChanged(msg: any) {
-        this.chatControllerMessagePublishers.processTabChangedMessage.publish({
-            tabID: msg.tabID,
-            prevTabID: msg.prevTabID,
-        })
-    }
-
-    private processChatMessage(msg: any) {
-        this.chatControllerMessagePublishers.processPromptChatMessage.publish({
-            message: msg.chatMessage,
-            command: msg.command,
-            tabID: msg.tabID,
-            messageId: msg.messageId,
-            userIntent: msg.userIntent !== '' ? msg.userIntent : undefined,
-            context: msg.chatContext,
-        })
-    }
-
-    private stopResponse(msg: any) {
-        this.chatControllerMessagePublishers.processStopResponseMessage.publish({
-            tabID: msg.tabID,
-        })
-    }
-
-    private chatItemVoted(msg: any) {
-        this.chatControllerMessagePublishers.processChatItemVotedMessage.publish({
-            tabID: msg.tabID,
-            command: msg.command,
-            vote: msg.vote,
-            messageId: msg.messageId,
-        })
-    }
-
-    private async chatItemFeedback(msg: any) {
-        this.chatControllerMessagePublishers.processChatItemFeedbackMessage.publish({
-            messageId: msg.messageId,
-            tabID: msg.tabID,
-            command: msg.command,
-            selectedOption: msg.selectedOption,
-            comment: msg.comment,
-        })
-    }
-
-    private fileClick(msg: any) {
-        this.chatControllerMessagePublishers.processFileClick.publish({
-            messageId: msg.messageId,
-            tabID: msg.tabID,
-            command: msg.command,
-            filePath: msg.filePath,
-        })
-    }
-}
diff --git a/packages/core/src/codewhispererChat/view/messages/referenceLogController.ts b/packages/core/src/codewhispererChat/view/messages/referenceLogController.ts
index c48ca18805..72d5e927e6 100644
--- a/packages/core/src/codewhispererChat/view/messages/referenceLogController.ts
+++ b/packages/core/src/codewhispererChat/view/messages/referenceLogController.ts
@@ -4,7 +4,7 @@
  */
 
 import { window } from 'vscode'
-import { CodeReference } from '../connector/connector'
+import { CodeReference } from '../../../amazonq/commons/model'
 import { ReferenceLogViewProvider } from '../../../codewhisperer/service/referenceLogViewProvider'
 
 export class ReferenceLogController {
diff --git a/packages/core/src/dev/activation.ts b/packages/core/src/dev/activation.ts
index 16b5d7e53a..7ccc9a8566 100644
--- a/packages/core/src/dev/activation.ts
+++ b/packages/core/src/dev/activation.ts
@@ -12,7 +12,6 @@ import { FileProvider, VirtualFileSystem } from '../shared/virtualFilesystem'
 import { Commands } from '../shared/vscode/commands2'
 import { createInputBox } from '../shared/ui/inputPrompter'
 import { Wizard } from '../shared/wizards/wizard'
-import { deleteDevEnvCommand, installVsixCommand, openTerminalCommand } from './codecatalyst'
 import { isAnySsoConnection } from '../auth/connection'
 import { Auth } from '../auth/auth'
 import { getLogger } from '../shared/logger/logger'
@@ -34,9 +33,6 @@ interface MenuOption {
 }
 
 export type DevFunction =
-    | 'installVsix'
-    | 'openTerminal'
-    | 'deleteDevEnv'
     | 'editStorage'
     | 'resetState'
     | 'showEnvVars'
@@ -68,24 +64,6 @@ let targetNotificationsController: NotificationsController
  */
 const menuOptions: () => Record = () => {
     return {
-        installVsix: {
-            label: 'Install VSIX on Remote Environment',
-            description: 'CodeCatalyst',
-            detail: 'Automatically upload/install a VSIX to a remote host',
-            executor: installVsixCommand,
-        },
-        openTerminal: {
-            label: 'Open Remote Terminal',
-            description: 'CodeCatalyst',
-            detail: 'Opens a new terminal connected to the remote environment',
-            executor: openTerminalCommand,
-        },
-        deleteDevEnv: {
-            label: 'Delete Workspace',
-            description: 'CodeCatalyst',
-            detail: 'Deletes the selected Dev Environment',
-            executor: deleteDevEnvCommand,
-        },
         editStorage: {
             label: 'Show or Edit globalState',
             description: 'VS Code',
diff --git a/packages/core/src/dev/codecatalyst.ts b/packages/core/src/dev/codecatalyst.ts
deleted file mode 100644
index d9dd4e9e5a..0000000000
--- a/packages/core/src/dev/codecatalyst.ts
+++ /dev/null
@@ -1,280 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { glob } from 'glob'
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as manifest from '../../package.json'
-import { getLogger } from '../shared/logger/logger'
-import { selectCodeCatalystResource } from '../codecatalyst/wizards/selectResource'
-import { VSCODE_EXTENSION_ID } from '../shared/extensions'
-import { DevEnvironment, CodeCatalystClient } from '../shared/clients/codecatalystClient'
-import { prepareDevEnvConnection } from '../codecatalyst/model'
-import { ChildProcess } from '../shared/utilities/processUtils'
-import { Timeout } from '../shared/utilities/timeoutUtils'
-import { CodeCatalystCommands } from '../codecatalyst/commands'
-import { showViewLogsMessage } from '../shared/utilities/messages'
-import { startVscodeRemote } from '../shared/extensions/ssh'
-import { isValidResponse } from '../shared/wizards/wizard'
-import { createQuickPick } from '../shared/ui/pickerPrompter'
-import { createCommonButtons } from '../shared/ui/buttons'
-import { fs } from '../shared/fs/fs'
-
-type LazyProgress = vscode.Progress & vscode.Disposable & { getToken(): Timeout }
-
-/**
- * Progress dialog that does not show until `report()` is called.
- */
-function lazyProgress>(timeout: Timeout): LazyProgress {
-    let dispose!: () => void
-    let progress: vscode.Progress
-    const location = vscode.ProgressLocation.Notification
-    const thenable = new Promise((resolve) => {
-        dispose = resolve
-        timeout.token.onCancellationRequested(() => resolve)
-    })
-
-    return {
-        dispose,
-        getToken: () => timeout,
-        report: (value) => {
-            if (!progress) {
-                void vscode.window.withProgress({ location, cancellable: true }, (p, t) => {
-                    progress = p
-                    t.onCancellationRequested((e) => timeout.cancel())
-                    return thenable
-                })
-            }
-            progress.report(value)
-        },
-    }
-}
-
-export async function openTerminalCommand(ctx: vscode.ExtensionContext) {
-    const commands = CodeCatalystCommands.fromContext(ctx)
-    const progress = lazyProgress<{ message: string }>(new Timeout(900000))
-
-    await commands.withClient(openTerminal, progress).finally(() => progress.dispose())
-}
-
-async function openTerminal(client: CodeCatalystClient, progress: LazyProgress<{ message: string }>) {
-    const devenv = await selectCodeCatalystResource(client, 'devEnvironment')
-    if (!devenv) {
-        return
-    }
-
-    const connection = await prepareDevEnvConnection(client, devenv, {
-        topic: 'terminal',
-        timeout: progress.getToken(),
-    })
-
-    progress.report({ message: 'Opening terminal...' })
-
-    const options: vscode.TerminalOptions = {
-        name: `Remote Connection (${devenv.id})`,
-        shellPath: connection.sshPath,
-        shellArgs: [connection.hostname],
-        env: (await connection.envProvider()) as Record,
-    }
-
-    vscode.window.createTerminal(options).show()
-}
-
-export async function installVsixCommand(ctx: vscode.ExtensionContext) {
-    const commands = CodeCatalystCommands.fromContext(ctx)
-
-    await commands.withClient(async (client) => {
-        const env = await selectCodeCatalystResource(client, 'devEnvironment')
-        if (!env) {
-            return
-        }
-        const progress = lazyProgress<{ message: string }>(new Timeout(900000))
-
-        try {
-            await installVsix(ctx, client, progress, env).finally(() => progress.dispose())
-        } catch (err) {
-            getLogger().error(`installVsixCommand: installation failed: %O`, err)
-            void showViewLogsMessage('VSIX installation failed')
-        }
-    })
-}
-
-async function promptVsix(
-    ctx: vscode.ExtensionContext,
-    progress?: LazyProgress<{ message: string }>
-): Promise {
-    const folders = (vscode.workspace.workspaceFolders ?? [])
-        .map((f) => f.uri)
-        .concat(vscode.Uri.file(ctx.extensionPath))
-
-    const isDevelopmentWindow = ctx.extensionMode === vscode.ExtensionMode.Development
-    const extPath = isDevelopmentWindow ? ctx.extensionPath : folders[0].fsPath
-
-    const packageNew = {
-        label: 'Create new VSIX',
-        detail: extPath,
-        description: 'Important: this currently breaks any running `watch` tasks',
-        skipEstimate: true,
-        data: async () => {
-            progress?.report({ message: 'Running package script...' })
-            const process = new ChildProcess('npm', ['run', 'package', '--', '--no-clean'], {
-                spawnOptions: { cwd: extPath },
-            })
-            const vsixUri = new Promise(async (resolve, reject) => {
-                await process
-                    .run({
-                        timeout: progress?.getToken(),
-                        rejectOnErrorCode: true,
-                        onStdout(text) {
-                            getLogger().info(text, { raw: true })
-                            const match = text.match(/VSIX Version: ([\w\-\.]+)/)
-                            if (match?.[1]) {
-                                try {
-                                    resolve(vscode.Uri.file(path.join(extPath, `${manifest.name}-${match[1]}.vsix`)))
-                                } catch (e) {
-                                    reject(e)
-                                }
-                            }
-                        },
-                        onStderr(text) {
-                            getLogger().info(text, { raw: true })
-                        },
-                    })
-                    .catch(reject)
-
-                reject(new Error('Did not get VSIX version from "npm run package"'))
-            })
-
-            return vsixUri
-        },
-    }
-
-    const localInstall = {
-        label: 'Use local install (experimental)',
-        detail: extPath,
-        data: vscode.Uri.file(extPath),
-    }
-
-    const seps = [
-        { label: 'Scripts', kind: -1, data: {} as any },
-        { label: 'Packages', kind: -1, data: {} as any },
-    ]
-    const items = (async function* () {
-        yield [seps.shift()!, packageNew, localInstall]
-
-        for (const f of folders) {
-            const paths = await glob('*.vsix', { cwd: f.fsPath })
-            const uris = paths.map((v) => vscode.Uri.file(path.join(f.fsPath, v)))
-
-            if (uris.length > 0 && seps.length > 0) {
-                yield [seps.shift()!]
-            }
-
-            yield uris.map((v) => ({
-                label: path.basename(v.fsPath),
-                detail: v.fsPath,
-                data: v,
-            }))
-        }
-    })()
-
-    const prompter = createQuickPick(items, {
-        title: 'Choose a script or VSIX',
-        buttons: createCommonButtons(),
-    })
-    const resp = await prompter.prompt()
-
-    return isValidResponse(resp) ? resp : undefined
-}
-
-/**
- * Bootstrap an environment for remote development/debugging
- */
-async function installVsix(
-    ctx: vscode.ExtensionContext,
-    client: CodeCatalystClient,
-    progress: LazyProgress<{ message: string }>,
-    env: DevEnvironment
-): Promise {
-    const resp = await promptVsix(ctx, progress).then((r) => r?.fsPath)
-
-    if (!resp) {
-        return
-    }
-
-    const connection = await prepareDevEnvConnection(client, env, {
-        topic: 'install',
-        timeout: progress.getToken(),
-    })
-    const { hostname, vscPath, sshPath, SessionProcess } = connection
-
-    const extId = VSCODE_EXTENSION_ID.awstoolkit
-    const extPath = `/home/mde-user/.vscode-server/extensions`
-    const userWithHost = `mde-user@${hostname}`
-
-    if (path.extname(resp) !== '.vsix') {
-        progress.report({ message: 'Copying extension...' })
-
-        const packageData = await fs.readFileText(path.join(resp, 'package.json'))
-        const targetManfiest: typeof manifest = JSON.parse(packageData)
-        const destName = `${extPath}/${extId}-${targetManfiest.version}`
-        const source = `${resp}${path.sep}`
-
-        // Using `.vscodeignore` would be nice here but `rsync` doesn't understand glob patterns
-        const excludes = ['.git/', 'node_modules/', '/src/', '/scripts/', '/dist/src/test/']
-            .map((p) => ['--exclude', p])
-            .reduce((a, b) => a.concat(b))
-
-        const installCommand = [`cd ${destName}`, 'npm i --ignore-scripts'].join(' && ')
-
-        await new SessionProcess('ssh', [hostname, '-v', `mkdir -p ${destName}`]).run()
-        await new SessionProcess('rsync', ['-vr', ...excludes, source, `${userWithHost}:${destName}`]).run()
-        await new SessionProcess('ssh', [hostname, '-v', installCommand]).run()
-    } else {
-        progress.report({ message: 'Copying VSIX...' })
-        const remoteVsix = `/projects/${path.basename(resp)}`
-
-        await new SessionProcess('scp', ['-v', resp, `${userWithHost}:${remoteVsix}`]).run()
-
-        const suffixParts = path
-            .basename(resp)
-            .split('-')
-            .reverse()
-            .slice(0, 2)
-            .map((s) => s.replace('.vsix', ''))
-        const destName = [extId, ...suffixParts.reverse()].join('-')
-
-        const installCmd = [
-            `rm ${extPath}/.obsolete || true`,
-            `find ${extPath} -type d -name '${extId}*' -exec rm -rf {} +`,
-            `unzip ${remoteVsix} "extension/*" "extension.vsixmanifest" -d ${extPath}`,
-            `mv ${extPath}/extension ${extPath}/${destName}`,
-            `mv ${extPath}/extension.vsixmanifest ${extPath}/${destName}/.vsixmanifest`,
-        ].join(' && ')
-
-        progress.report({ message: 'Installing VSIX...' })
-        await new SessionProcess(sshPath, [`${hostname}`, '-v', installCmd]).run()
-    }
-
-    progress.report({ message: 'Launching instance...' })
-    await startVscodeRemote(SessionProcess, hostname, '/projects', vscPath)
-}
-
-export async function deleteDevEnvCommand(ctx: vscode.ExtensionContext) {
-    const commands = CodeCatalystCommands.fromContext(ctx)
-
-    await commands.withClient(async (client) => {
-        const devenv = await selectCodeCatalystResource(client, 'devEnvironment')
-        if (!devenv) {
-            return
-        }
-
-        await client.deleteDevEnvironment({
-            id: devenv.id,
-            projectName: devenv.project.name,
-            spaceName: devenv.org.name,
-        })
-    })
-}
diff --git a/packages/core/src/docdb/activation.ts b/packages/core/src/docdb/activation.ts
deleted file mode 100644
index 62b61d5e49..0000000000
--- a/packages/core/src/docdb/activation.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { Commands } from '../shared/vscode/commands2'
-import { ExtContext } from '../shared/extensions'
-import { DBResourceNode } from './explorer/dbResourceNode'
-import { DocumentDBNode } from './explorer/docdbNode'
-import { DBClusterNode } from './explorer/dbClusterNode'
-import { DBInstanceNode } from './explorer/dbInstanceNode'
-import { addRegion } from './commands/addRegion'
-import { createCluster } from './commands/createCluster'
-import { deleteCluster } from './commands/deleteCluster'
-import { renameCluster } from './commands/renameCluster'
-import { startCluster } from './commands/startCluster'
-import { stopCluster } from './commands/stopCluster'
-import { createInstance } from './commands/createInstance'
-import { deleteInstance } from './commands/deleteInstance'
-import { modifyInstance } from './commands/modifyInstance'
-import { rebootInstance } from './commands/rebootInstance'
-import { renameInstance } from './commands/renameInstance'
-import { addTag, listTags, removeTag } from './commands/tagCommands'
-import { Uri } from 'vscode'
-import { openUrl } from '../shared/utilities/vsCodeUtils'
-import { getLogger } from '../shared/logger/logger'
-
-/**
- * A utility function to automatically invoke trackChanges after a command.
- */
-
-function withTrackChanges(
-    command: (node: T) => Promise,
-    commandName: string = 'UnnamedCommand'
-): (node: T) => Promise {
-    return async (node: T) => {
-        const arn = node.arn || 'UnknownARN'
-        const startTime = new Date().toISOString()
-
-        getLogger().info(
-            `[${startTime}] Executing command "${commandName}" for resource with ARN: ${arn}. Tracking changes will be invoked post-execution.`
-        )
-
-        await command(node)
-
-        const endTime = new Date().toISOString()
-        getLogger().info(
-            `[${endTime}] Successfully executed command "${commandName}" for resource with ARN: ${arn}. Invoking trackChanges now.`
-        )
-
-        await node.trackChangesWithWaitProcessingStatus()
-    }
-}
-
-/**
- * Activates DocumentDB components.
- */
-export async function activate(ctx: ExtContext): Promise {
-    ctx.extensionContext.subscriptions.push(
-        Commands.register('aws.docdb.createCluster', async (node?: DocumentDBNode) => {
-            await createCluster(node)
-        }),
-
-        Commands.register('aws.docdb.deleteCluster', withTrackChanges(deleteCluster, 'deleteCluster')),
-
-        Commands.register('aws.docdb.renameCluster', withTrackChanges(renameCluster, 'renameCluster')),
-
-        Commands.register('aws.docdb.startCluster', withTrackChanges(startCluster, 'startCluster')),
-
-        Commands.register('aws.docdb.stopCluster', withTrackChanges(stopCluster, 'stopCluster')),
-
-        Commands.register('aws.docdb.addRegion', withTrackChanges(addRegion, 'addRegion')),
-
-        Commands.register(
-            'aws.docdb.createInstance',
-            withTrackChanges(createInstance, 'createInstance')
-        ),
-
-        Commands.register(
-            'aws.docdb.deleteInstance',
-            withTrackChanges(deleteInstance, 'deleteInstance')
-        ),
-
-        Commands.register(
-            'aws.docdb.modifyInstance',
-            withTrackChanges(modifyInstance, 'modifyInstance')
-        ),
-
-        Commands.register(
-            'aws.docdb.rebootInstance',
-            withTrackChanges(rebootInstance, 'rebootInstance')
-        ),
-
-        Commands.register(
-            'aws.docdb.renameInstance',
-            withTrackChanges(renameInstance, 'renameInstance')
-        ),
-
-        Commands.register('aws.docdb.listTags', async (node: DBResourceNode) => {
-            await listTags(node)
-        }),
-
-        Commands.register('aws.docdb.addTag', async (node: DBResourceNode) => {
-            await addTag(node)
-        }),
-
-        Commands.register('aws.docdb.removeTag', async (node: DBResourceNode) => {
-            await removeTag(node)
-        }),
-
-        Commands.register('aws.docdb.viewConsole', async (node?: DBResourceNode) => {
-            await node?.openInBrowser()
-        }),
-
-        Commands.register('aws.docdb.viewDocs', async () => {
-            await openUrl(
-                Uri.parse('https://docs.aws.amazon.com/documentdb/latest/developerguide/get-started-guide.html')
-            )
-        }),
-
-        Commands.register('aws.docdb.copyEndpoint', async (node?: DBResourceNode) => {
-            await node?.copyEndpoint()
-        })
-    )
-}
diff --git a/packages/core/src/docdb/commands/addRegion.ts b/packages/core/src/docdb/commands/addRegion.ts
deleted file mode 100644
index ab3e6d1b72..0000000000
--- a/packages/core/src/docdb/commands/addRegion.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-import { DBGlobalClusterNode } from '../explorer/dbGlobalClusterNode'
-import { DefaultDocumentDBClient } from '../../shared/clients/docdbClient'
-import { ToolkitError } from '../../shared/errors'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { isValidResponse } from '../../shared/wizards/wizard'
-import { CancellationError } from '../../shared/utilities/timeoutUtils'
-import { CreateGlobalClusterWizard } from '../wizards/createGlobalClusterWizard'
-import { CreateDBClusterMessage } from '@aws-sdk/client-docdb'
-import { createInstancesForCluster } from './createCluster'
-import { isSupportedGlobalInstanceClass } from '../utils'
-
-export async function addRegion(node: DBClusterNode | DBGlobalClusterNode): Promise {
-    if (!node) {
-        throw new ToolkitError('No node specified for AddRegion')
-    }
-
-    return telemetry.docdb_addRegion.run(async () => {
-        let globalClusterName = undefined
-
-        if (node.cluster.StorageEncrypted) {
-            void vscode.window.showErrorMessage('Encrypted clusters are not supported')
-            return
-        }
-
-        if (node instanceof DBClusterNode) {
-            if (node.clusterRole !== 'regional') {
-                void vscode.window.showErrorMessage('Only regional clusters are supported')
-                return
-            }
-
-            if (node.cluster.DBClusterMembers?.length === 0) {
-                void vscode.window.showErrorMessage(
-                    localize(
-                        'AWS.docdb.addRegion.noInstances',
-                        'Cluster must have at least one instance to add a region'
-                    )
-                )
-                throw new ToolkitError('Cluster must have at least one instance to add a region', { cancelled: true })
-            }
-
-            const unsupportedInstanceFound = node.instances.find(
-                (instance) => !isSupportedGlobalInstanceClass(instance.DBInstanceClass!)
-            )
-
-            if (unsupportedInstanceFound) {
-                void vscode.window.showErrorMessage(
-                    localize(
-                        'AWS.docdb.addRegion.unsupportedInstanceClass',
-                        'Instance class {0} not supported for global cluster.  Upgrade the instances then try again.',
-                        unsupportedInstanceFound.DBInstanceClass
-                    )
-                )
-                throw new ToolkitError('Instance class not supported for global cluster', {
-                    cancelled: true,
-                    code: 'docdbInstanceClassNotSupported',
-                })
-            }
-        } else {
-            globalClusterName = node.cluster.GlobalClusterIdentifier
-
-            if (node.cluster.GlobalClusterMembers!.length > 4) {
-                void vscode.window.showErrorMessage(
-                    localize('AWS.docdb.addRegion.maxRegions', 'Global clusters can have a maximum of 5 regions')
-                )
-                throw new ToolkitError('Global clusters can have a maximum of 5 regions', {
-                    cancelled: true,
-                    code: 'docdbMaxRegionsInUse',
-                })
-            }
-        }
-
-        if (!node.isAvailable) {
-            void vscode.window.showErrorMessage(localize('AWS.docdb.clusterStopped', 'Cluster must be running'))
-            throw new ToolkitError('Cluster not available', { cancelled: true, code: 'docdbClusterStopped' })
-        }
-
-        const wizard = new CreateGlobalClusterWizard(node.regionCode, node.cluster.EngineVersion, node.client, {
-            initState: { GlobalClusterName: globalClusterName },
-        })
-        const response = await wizard.run()
-
-        if (!isValidResponse(response)) {
-            throw new CancellationError('user')
-        }
-
-        const regionCode = response.RegionCode
-        let input: CreateDBClusterMessage
-        let clusterName = response.GlobalClusterName
-
-        try {
-            if (node instanceof DBClusterNode) {
-                // Create new global cluster from regional cluster
-                const primaryCluster = node.cluster
-
-                getLogger().info(`docdb: Creating global cluster: ${clusterName}`)
-                const globalCluster = await node.client.createGlobalCluster({
-                    GlobalClusterIdentifier: response.GlobalClusterName,
-                    SourceDBClusterIdentifier: primaryCluster.DBClusterArn,
-                })
-
-                input = {
-                    GlobalClusterIdentifier: globalCluster?.GlobalClusterIdentifier,
-                    DBClusterIdentifier: response.Cluster.DBClusterIdentifier,
-                    DeletionProtection: primaryCluster.DeletionProtection,
-                    Engine: primaryCluster.Engine,
-                    EngineVersion: primaryCluster.EngineVersion,
-                    StorageType: primaryCluster.StorageType,
-                    StorageEncrypted: globalCluster?.StorageEncrypted,
-                }
-            } else {
-                // Add secondary cluster to global cluster
-                const globalCluster = node.cluster
-
-                input = {
-                    GlobalClusterIdentifier: globalClusterName,
-                    DBClusterIdentifier: response.Cluster.DBClusterIdentifier,
-                    DeletionProtection: globalCluster.DeletionProtection,
-                    Engine: globalCluster.Engine,
-                    EngineVersion: globalCluster.EngineVersion,
-                    StorageEncrypted: globalCluster.StorageEncrypted,
-                }
-            }
-
-            clusterName = response.Cluster.DBClusterIdentifier
-            getLogger().info(`docdb: Creating secondary cluster: ${clusterName} in region ${regionCode}`)
-
-            const client = DefaultDocumentDBClient.create(regionCode)
-            const newCluster = await client.createCluster(input)
-
-            if (response.Cluster.DBInstanceCount) {
-                await createInstancesForCluster(
-                    client,
-                    clusterName,
-                    response.Cluster.DBInstanceClass,
-                    response.Cluster.DBInstanceCount
-                )
-            }
-
-            getLogger().info('docdb: Created cluster: %O', newCluster)
-            void vscode.window.showInformationMessage(localize('AWS.docdb.addRegion.success', 'Region added'))
-
-            if (node instanceof DBClusterNode) {
-                node?.parent.refresh()
-            } else {
-                node?.refresh()
-            }
-        } catch (e) {
-            getLogger().error(`docdb: Failed to create cluster ${clusterName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.createCluster.error', 'Failed to create cluster: {0}', clusterName)
-            )
-            throw ToolkitError.chain(e, `Failed to create cluster ${clusterName}`)
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/createCluster.ts b/packages/core/src/docdb/commands/createCluster.ts
deleted file mode 100644
index 6268931915..0000000000
--- a/packages/core/src/docdb/commands/createCluster.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { DocumentDBNode } from '../explorer/docdbNode'
-import { CreateClusterWizard } from '../wizards/createClusterWizard'
-import { CreateClusterInput } from '@aws-sdk/client-docdb-elastic'
-import { DocDBEngine, DocumentDBClient } from '../../shared/clients/docdbClient'
-
-/**
- * Creates a DocumentDB cluster.
- *
- * Prompts the user for the cluster name.
- * Creates the cluster.
- * Refreshes the node.
- */
-export async function createCluster(node?: DocumentDBNode) {
-    getLogger().debug('docdb: CreateCluster called for: %O', node)
-
-    await telemetry.docdb_createCluster.run(async (span) => {
-        if (!node) {
-            throw new ToolkitError('No node specified for CreateCluster')
-        }
-
-        span.record({ awsRegion: node?.client.regionCode })
-        const wizard = new CreateClusterWizard(node?.client, {})
-        const result = await wizard.run()
-
-        if (!result) {
-            getLogger().debug('docdb: createCluster cancelled')
-            throw new ToolkitError('User cancelled createCluster wizard', { cancelled: true })
-        }
-
-        const clusterName = result.RegionalCluster?.DBClusterIdentifier ?? result.ElasticCluster?.clusterName
-        getLogger().info(`docdb: Creating cluster: ${clusterName}`)
-        let cluster
-
-        try {
-            if (result.ClusterType === 'elastic') {
-                cluster = await node.client.createElasticCluster(result.ElasticCluster as CreateClusterInput)
-            } else {
-                cluster = await node.client.createCluster(result.RegionalCluster)
-
-                // create instances for cluster
-                if (cluster && result.RegionalCluster.DBInstanceCount) {
-                    await createInstancesForCluster(
-                        node.client,
-                        clusterName,
-                        result.RegionalCluster.DBInstanceClass,
-                        result.RegionalCluster.DBInstanceCount
-                    )
-                }
-            }
-
-            getLogger().info('docdb: Created cluster: %O', cluster)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.createCluster.success', 'Created cluster: {0}', clusterName)
-            )
-
-            node?.refresh()
-            return cluster
-        } catch (e) {
-            getLogger().error(`docdb: Failed to create cluster ${clusterName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.createCluster.error', 'Failed to create cluster: {0}', clusterName)
-            )
-            throw ToolkitError.chain(e, `Failed to create cluster ${clusterName}`)
-        }
-    })
-}
-
-export async function createInstancesForCluster(
-    client: DocumentDBClient,
-    clusterName: string,
-    instanceClass: string = 'db.t3.medium',
-    instanceCount: number
-) {
-    const tasks = []
-
-    for (let index = 0; index < instanceCount; index++) {
-        tasks.push(
-            client.createInstance({
-                Engine: DocDBEngine,
-                DBClusterIdentifier: clusterName,
-                DBInstanceIdentifier: index === 0 ? clusterName : `${clusterName}${index + 1}`,
-                DBInstanceClass: instanceClass,
-            })
-        )
-    }
-
-    try {
-        await Promise.all(tasks)
-    } catch (e) {
-        throw ToolkitError.chain(e, `Failed to create instance for cluster ${clusterName}`, {
-            code: 'docdbCreateInstanceForCluster',
-        })
-    }
-}
diff --git a/packages/core/src/docdb/commands/createInstance.ts b/packages/core/src/docdb/commands/createInstance.ts
deleted file mode 100644
index 34e7808ce6..0000000000
--- a/packages/core/src/docdb/commands/createInstance.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-import { CreateInstanceWizard } from '../wizards/createInstanceWizard'
-import { CreateDBInstanceMessage } from '@aws-sdk/client-docdb'
-import { DocDBEngine, MaxInstanceCount } from '../../shared/clients/docdbClient'
-
-/**
- * Creates a DocumentDB instance.
- *
- * Prompts the user for the instance name and class.
- * Creates the instance.
- * Refreshes the cluster node.
- */
-export async function createInstance(node: DBClusterNode) {
-    getLogger().debug('docdb: CreateInstance called for: %O', node)
-
-    await telemetry.docdb_createInstance.run(async () => {
-        if (!node) {
-            throw new ToolkitError('No node specified for CreateInstance')
-        }
-
-        const instances = await node.client.listInstances([node.arn])
-        if (instances.length >= MaxInstanceCount) {
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.createInstance.limitReached', 'Max instances in use')
-            )
-            throw new ToolkitError('Max instances in use', { code: 'documentDBMaxInstancesInUse' })
-        }
-
-        const generateInstanceName = (clusterName: string) =>
-            instances.length === 0 ? clusterName : `${clusterName}${++instances.length}`
-
-        const options = {
-            implicitState: {
-                DBInstanceIdentifier: generateInstanceName(node.cluster.DBClusterIdentifier ?? ''),
-                DBInstanceClass: instances[0]?.DBInstanceClass,
-            },
-        }
-        const wizard = new CreateInstanceWizard(node.regionCode, node.cluster, options, node.client)
-
-        const result = await wizard.run()
-
-        if (!result) {
-            getLogger().debug('docdb: CreateInstance cancelled')
-            throw new ToolkitError('User cancelled createInstance wizard', { cancelled: true })
-        }
-
-        const instanceName = result.DBInstanceIdentifier
-        getLogger().info(`docdb: Creating instance: ${instanceName}`)
-
-        try {
-            const request: CreateDBInstanceMessage = {
-                Engine: DocDBEngine,
-                DBClusterIdentifier: node.cluster.DBClusterIdentifier,
-                DBInstanceIdentifier: result.DBInstanceIdentifier,
-                DBInstanceClass: result.DBInstanceClass !== '' ? result.DBInstanceClass : undefined,
-            }
-
-            const instance = await node.createInstance(request)
-
-            getLogger().info('docdb: Created instance: %O', instance)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.createInstance.success', 'Creating instance: {0}', instanceName)
-            )
-
-            node.refresh()
-            return instance
-        } catch (e) {
-            getLogger().error(`docdb: Failed to create instance ${instanceName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.createInstance.error', 'Failed to create instance: {0}', instanceName)
-            )
-            throw ToolkitError.chain(e, `Failed to create instance ${instanceName}`)
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/deleteCluster.ts b/packages/core/src/docdb/commands/deleteCluster.ts
deleted file mode 100644
index bb725ae2ab..0000000000
--- a/packages/core/src/docdb/commands/deleteCluster.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-import { showQuickPick } from '../../shared/ui/pickerPrompter'
-import { formatDate, formatTime } from '../../shared/date'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { DBElasticClusterNode } from '../explorer/dbElasticClusterNode'
-import { assertNodeAvailable } from '../utils'
-
-/**
- * Deletes a DocumentDB cluster.
- *
- * Prompts the user for confirmation, and whether to keep a snapshot
- * Deletes the cluster and all instances.
- * Refreshes the cluster node.
- */
-export async function deleteCluster(node: DBClusterNode | DBElasticClusterNode) {
-    getLogger().debug('docdb: DeleteCluster called for: %O', node)
-
-    await telemetry.docdb_deleteCluster.run(async (span) => {
-        assertNodeAvailable(node, 'DeleteCluster')
-        const clusterName = node.name
-        const isRegionalCluster = node instanceof DBClusterNode
-
-        if (isRegionalCluster && node.cluster.DeletionProtection) {
-            void vscode.window.showErrorMessage(
-                localize(
-                    'AWS.docdb.deleteCluster.protected',
-                    'Clusters cannot be deleted while deletion protection is enabled'
-                )
-            )
-            throw new ToolkitError('Deletion protection is active', {
-                cancelled: true,
-                code: 'docdbDeletionProtectionInUse',
-            })
-        }
-
-        const takeSnapshot = await showQuickPick(
-            [
-                { label: localize('AWS.generic.response.yes', 'Yes'), data: true },
-                { label: localize('AWS.generic.response.no', 'No'), data: false },
-            ],
-            {
-                title: localize(
-                    'AWS.docdb.deleteCluster.promptSnapshot',
-                    'Delete Cluster - Keep a snapshot of the data?'
-                ),
-            }
-        )
-
-        if (takeSnapshot === undefined) {
-            getLogger().debug('docdb: DeleteCluster cancelled')
-            throw new ToolkitError('User cancelled deleteCluster wizard', { cancelled: true })
-        }
-
-        const isConfirmed = await showConfirmationDialog()
-        if (!isConfirmed) {
-            getLogger().debug('docdb: DeleteCluster cancelled')
-            throw new ToolkitError('User cancelled deleteCluster wizard', { cancelled: true })
-        }
-
-        try {
-            getLogger().debug(`docdb: Deleting cluster: ${clusterName}`)
-
-            let finalSnapshotId: string | undefined = undefined
-            if (takeSnapshot) {
-                finalSnapshotId = `${clusterName}-${formatDate()}-${formatTime()}`
-            }
-
-            const cluster = await node.deleteCluster(finalSnapshotId)
-
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.deleteCluster.success', 'Deleting cluster: {0}', clusterName)
-            )
-
-            await node.waitUntilStatusChanged()
-            node.parent.refresh()
-            getLogger().info('docdb: Deleted cluster: %O', cluster)
-            return cluster
-        } catch (e) {
-            getLogger().error(`docdb: Failed to delete cluster ${clusterName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.deleteCluster.error', 'Failed to delete cluster: {0}', clusterName)
-            )
-            throw ToolkitError.chain(e, `Failed to delete cluster ${clusterName}`)
-        }
-    })
-}
-
-async function showConfirmationDialog(): Promise {
-    const prompt = localize('AWS.docdb.deleteCluster.prompt', "Enter 'delete entire cluster' to confirm deletion")
-    const confirmValue = localize('AWS.docdb.deleteCluster.confirmValue', 'delete entire cluster').toLowerCase()
-    const confirmationInput = await vscode.window.showInputBox({
-        prompt,
-        placeHolder: confirmValue,
-        validateInput: (input) => (input?.toLowerCase() !== confirmValue ? prompt : undefined),
-    })
-
-    return confirmationInput === confirmValue
-}
diff --git a/packages/core/src/docdb/commands/deleteInstance.ts b/packages/core/src/docdb/commands/deleteInstance.ts
deleted file mode 100644
index 6202d317a0..0000000000
--- a/packages/core/src/docdb/commands/deleteInstance.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { DBInstanceNode } from '../explorer/dbInstanceNode'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { assertNodeAvailable } from '../utils'
-
-/**
- * Deletes a DocumentDB instance.
- *
- * Prompts the user for confirmation.
- * Deletes the instance.
- * Refreshes the parent cluster node.
- */
-export async function deleteInstance(node: DBInstanceNode) {
-    getLogger().debug('docdb: DeleteInstance called for: %O', node)
-
-    await telemetry.docdb_deleteInstance.run(async () => {
-        assertNodeAvailable(node, 'DeleteInstance')
-        const parent = node.parent as DBClusterNode
-        const client = parent.client
-        const instanceName = node.instance.DBInstanceIdentifier ?? ''
-
-        if (!parent?.isAvailable) {
-            void vscode.window.showErrorMessage(
-                localize('AWS.docdb.deleteInstance.clusterStopped', 'Cluster must be started to delete instances')
-            )
-            throw new ToolkitError('Cluster not running', { cancelled: true })
-        }
-
-        const isConfirmed = await showConfirmationDialog(instanceName)
-        if (!isConfirmed) {
-            getLogger().debug('docdb: DeleteInstance cancelled')
-            throw new ToolkitError('User cancelled deleteInstance', { cancelled: true })
-        }
-
-        try {
-            getLogger().info(`docdb: Deleting instance: ${instanceName}`)
-
-            const instance = await client.deleteInstance({
-                DBInstanceIdentifier: instanceName,
-            })
-
-            getLogger().info('docdb: Deleted instance: %O', instance)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.deleteInstance.success', 'Deleting instance: {0}', instanceName)
-            )
-
-            parent.refresh()
-            return instance
-        } catch (e) {
-            getLogger().error(`docdb: Failed to delete instance ${instanceName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.deleteInstance.error', 'Failed to delete instance: {0}', instanceName)
-            )
-            throw ToolkitError.chain(e, `Failed to delete instance ${instanceName}`)
-        }
-    })
-}
-
-async function showConfirmationDialog(instanceName: string): Promise {
-    const prompt = localize('AWS.docdb.deleteInstance.prompt', 'Enter {0} to confirm deletion', instanceName)
-    const confirmationInput = await vscode.window.showInputBox({
-        prompt,
-        placeHolder: instanceName,
-        validateInput: (input) => (input !== instanceName ? prompt : undefined),
-    })
-
-    return confirmationInput === instanceName
-}
diff --git a/packages/core/src/docdb/commands/modifyInstance.ts b/packages/core/src/docdb/commands/modifyInstance.ts
deleted file mode 100644
index 3df5febaf4..0000000000
--- a/packages/core/src/docdb/commands/modifyInstance.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { DBInstanceNode } from '../explorer/dbInstanceNode'
-import { DBCluster, ModifyDBInstanceMessage } from '@aws-sdk/client-docdb'
-import { DBStorageType, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { createQuickPick, DataQuickPickItem } from '../../shared/ui/pickerPrompter'
-import { isValidResponse } from '../../shared/wizards/wizard'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { assertNodeAvailable } from '../utils'
-
-/**
- * Modifies a DocumentDB instance.
- *
- * Prompts the user for the instance class.
- * Updates the instance.
- * Refreshes the node.
- */
-export async function modifyInstance(node: DBInstanceNode) {
-    getLogger().debug('docdb: ModifyInstance called for: %O', node)
-
-    await telemetry.docdb_resizeInstance.run(async () => {
-        assertNodeAvailable(node, 'ModifyInstance')
-        const instanceName = node.instance.DBInstanceIdentifier
-        const parent = node.parent
-
-        const quickPickItems = await getInstanceClassOptions(parent.client, parent.cluster)
-        const newInstanceClass = await promptForInstanceClass(quickPickItems, node.instance.DBInstanceClass ?? '')
-
-        if (!newInstanceClass) {
-            getLogger().debug('docdb: ModifyInstance cancelled')
-            throw new ToolkitError('User cancelled modifyInstance wizard', { cancelled: true })
-        }
-
-        try {
-            const request: ModifyDBInstanceMessage = {
-                DBInstanceIdentifier: instanceName,
-                DBInstanceClass: newInstanceClass,
-                ApplyImmediately: true,
-            }
-
-            const instance = await parent.client.modifyInstance(request)
-
-            getLogger().info('docdb: Modified instance: %O', instanceName)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.modifyInstance.success', 'Modified instance: {0}', instanceName)
-            )
-
-            await node.waitUntilStatusChanged()
-            parent.refresh()
-            return instance
-        } catch (e) {
-            getLogger().error(`docdb: Failed to modify instance ${instanceName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.modifyInstance.error', 'Failed to modify instance: {0}', instanceName)
-            )
-            throw ToolkitError.chain(e, `Failed to modify instance ${instanceName}`)
-        }
-    })
-}
-
-async function getInstanceClassOptions(
-    client: DocumentDBClient,
-    cluster: DBCluster
-): Promise[]> {
-    const options = await client.listInstanceClassOptions(
-        cluster.EngineVersion,
-        cluster.StorageType ?? DBStorageType.Standard
-    )
-
-    const items: DataQuickPickItem[] = options.map((option) => {
-        return {
-            data: option.DBInstanceClass,
-            label: option.DBInstanceClass ?? '(unknown)',
-        }
-    })
-
-    return items
-}
-
-async function promptForInstanceClass(items: any[], currentValue: string) {
-    const prompter = createQuickPick(items, {
-        title: localize('AWS.docdb.createInstance.instanceClass.prompt', 'Select instance class'),
-        value: currentValue,
-    })
-
-    prompter.recentItem = items.find((item) => item.data === currentValue)
-
-    const response = await prompter.prompt()
-    return isValidResponse(response) ? response : undefined
-}
diff --git a/packages/core/src/docdb/commands/rebootInstance.ts b/packages/core/src/docdb/commands/rebootInstance.ts
deleted file mode 100644
index ab706803d8..0000000000
--- a/packages/core/src/docdb/commands/rebootInstance.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as localizedText from '../../shared/localizedText'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { CancellationError } from '../../shared/utilities/timeoutUtils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { DBInstanceNode } from '../explorer/dbInstanceNode'
-import { assertNodeAvailable } from '../utils'
-
-/**
- * Reboots a DocumentDB instance.
- * Refreshes the parent node.
- */
-export async function rebootInstance(node: DBInstanceNode) {
-    getLogger().debug('docdb: RebootInstance called for: %O', node)
-
-    await telemetry.docdb_rebootInstance.run(async () => {
-        assertNodeAvailable(node, 'RebootInstance')
-
-        const isConfirmed = await showConfirmationMessage({
-            prompt: localize(
-                'AWS.docdb.rebootInstance.prompt',
-                'Are you sure you want to reboot instance {0}?',
-                node.name
-            ),
-            confirm: localizedText.yes,
-            cancel: localizedText.cancel,
-        })
-        if (!isConfirmed) {
-            getLogger().debug('docdb: RebootInstance canceled')
-            throw new CancellationError('user')
-        }
-
-        const instanceName = node.instance.DBInstanceIdentifier
-        try {
-            const instance = await node.rebootInstance()
-
-            getLogger().info('docdb: Rebooting instance: %s', instanceName)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.rebootInstance.success', 'Rebooting instance: {0}', instanceName)
-            )
-
-            node.parent.refresh()
-            return instance
-        } catch (e) {
-            getLogger().error(`docdb: Failed to reboot instance ${instanceName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.rebootInstance.error', 'Failed to reboot instance: {0}', instanceName)
-            )
-            throw ToolkitError.chain(e, `Failed to reboot instance ${instanceName}`)
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/renameCluster.ts b/packages/core/src/docdb/commands/renameCluster.ts
deleted file mode 100644
index f64ae7b1d9..0000000000
--- a/packages/core/src/docdb/commands/renameCluster.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../../shared/errors'
-import { getLogger } from '../../shared/logger/logger'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { assertNodeAvailable, validateClusterName } from '../utils'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-import { DBGlobalClusterNode } from '../explorer/dbGlobalClusterNode'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { sleep } from '../../shared/utilities/timeoutUtils'
-
-/**
- * Renames a DocumentDB cluster.
- *
- * Prompts the user for the new cluster name
- * Updates the cluster.
- * Refreshes the node.
- */
-export async function renameCluster(node: DBClusterNode | DBGlobalClusterNode) {
-    getLogger().debug('docdb: RenameCluster called for: %O', node)
-
-    await telemetry.docdb_renameCluster.run(async () => {
-        assertNodeAvailable(node, 'RenameCluster')
-        const clusterName = node.name
-
-        const newClusterName = await vscode.window.showInputBox({
-            prompt: localize('AWS.docdb.renameCluster.prompt', 'New cluster name'),
-            value: clusterName,
-            validateInput: validateClusterName,
-        })
-
-        if (!newClusterName) {
-            getLogger().debug('docdb: RenameCluster cancelled')
-            throw new ToolkitError('User cancelled renameCluster', { cancelled: true })
-        }
-
-        try {
-            const cluster = await node.renameCluster(newClusterName)
-
-            getLogger().info('docdb: Renamed cluster: %O', cluster)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.renameCluster.success', 'Updated cluster: {0}', clusterName)
-            )
-
-            await sleep(1000) // wait for server to update status
-            node.parent.refresh()
-            return cluster
-        } catch (e) {
-            getLogger().error(`docdb: Failed to rename cluster ${clusterName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.renameCluster.error', 'Failed to rename cluster: {0}', clusterName)
-            )
-            throw ToolkitError.chain(e, `Failed to rename cluster ${clusterName}`)
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/renameInstance.ts b/packages/core/src/docdb/commands/renameInstance.ts
deleted file mode 100644
index 414f780e52..0000000000
--- a/packages/core/src/docdb/commands/renameInstance.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { ToolkitError } from '../../shared/errors'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { assertNodeAvailable, validateInstanceName } from '../utils'
-import { DBInstanceNode } from '../explorer/dbInstanceNode'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-/**
- * Renames a DocumentDB instance.
- *
- * Prompts the user for the new instance name
- * Updates the instance.
- * Refreshes the node.
- */
-export async function renameInstance(node: DBInstanceNode) {
-    getLogger().debug('docdb: RenameInstance called for: %O', node)
-
-    await telemetry.docdb_renameInstance.run(async () => {
-        assertNodeAvailable(node, 'RenameInstance')
-        const instanceName = node.instance.DBInstanceIdentifier
-
-        const newInstanceName = await vscode.window.showInputBox({
-            prompt: localize('AWS.docdb.renameInstance.prompt', 'New instance name'),
-            value: instanceName,
-            validateInput: validateInstanceName,
-        })
-
-        if (!newInstanceName) {
-            getLogger().debug('docdb: RenameInstance cancelled')
-            throw new ToolkitError('User cancelled renameInstance', { cancelled: true })
-        }
-
-        try {
-            const instance = await node.renameInstance(newInstanceName)
-
-            getLogger().info('docdb: Renamed instance: %O', instance)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.renameInstance.success', 'Updated instance: {0}', instanceName)
-            )
-
-            node.refresh()
-            return instance
-        } catch (e) {
-            getLogger().error(`docdb: Failed to rename instance ${instanceName}: %s`, e)
-            void showViewLogsMessage(
-                localize('AWS.docdb.renameInstance.error', 'Failed to rename instance: {0}', instanceName)
-            )
-            throw ToolkitError.chain(e, `Failed to rename instance ${instanceName}`)
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/startCluster.ts b/packages/core/src/docdb/commands/startCluster.ts
deleted file mode 100644
index 60dbda80e7..0000000000
--- a/packages/core/src/docdb/commands/startCluster.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-
-export function startCluster(node?: DBClusterNode): Promise {
-    return telemetry.docdb_startCluster.run(async () => {
-        if (node?.arn && node?.regionCode) {
-            await node.client.startCluster(node.arn)
-            getLogger().info('docdb: Start cluster: %O', node.name)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.startCluster.success', 'Starting cluster: {0}', node.name)
-            )
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/stopCluster.ts b/packages/core/src/docdb/commands/stopCluster.ts
deleted file mode 100644
index 46f23f3eab..0000000000
--- a/packages/core/src/docdb/commands/stopCluster.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as localizedText from '../../shared/localizedText'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { CancellationError } from '../../shared/utilities/timeoutUtils'
-import { showConfirmationMessage } from '../../shared/utilities/messages'
-import { DBClusterNode } from '../explorer/dbClusterNode'
-
-export function stopCluster(node?: DBClusterNode): Promise {
-    return telemetry.docdb_stopCluster.run(async () => {
-        if (node?.arn && node?.regionCode) {
-            const isConfirmed = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.docdb.stopCluster.prompt',
-                    'Are you sure you want to stop cluster {0}?',
-                    node.name
-                ),
-                confirm: localizedText.yes,
-                cancel: localizedText.cancel,
-            })
-            if (!isConfirmed) {
-                getLogger().debug('docdb: StopCluster cancelled')
-                throw new CancellationError('user')
-            }
-
-            await node.client.stopCluster(node.arn)
-            getLogger().info('docdb: Stop cluster: %O', node.name)
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.stopCluster.success', 'Stopping cluster: {0}', node.name)
-            )
-        }
-    })
-}
diff --git a/packages/core/src/docdb/commands/tagCommands.ts b/packages/core/src/docdb/commands/tagCommands.ts
deleted file mode 100644
index d3c46176ad..0000000000
--- a/packages/core/src/docdb/commands/tagCommands.ts
+++ /dev/null
@@ -1,112 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { DBResourceNode } from '../explorer/dbResourceNode'
-import { DataQuickPickItem, showQuickPick } from '../../shared/ui/pickerPrompter'
-import { ToolkitError } from '../../shared/errors'
-
-export async function listTags(node: DBResourceNode): Promise {
-    return telemetry.docdb_listTags.run(async () => {
-        const tagMap = await node.listTags()
-        const tags = Object.entries(tagMap ?? {}).map(([key, value]) => `• ${key} = ${value}`)
-        const detail = tags.length ? tags.join('\r\n') : '[No tags assigned]'
-
-        const addCommandText = localize('AWS.docdb.tags.add', 'Add tag...')
-        const removeCommandText = tags.length ? localize('AWS.docdb.tags.remove', 'Remove...') : ''
-        const commands = tags.length ? [addCommandText, removeCommandText] : [addCommandText]
-
-        const response = await vscode.window.showInformationMessage(
-            `Tags for ${node.name}:`,
-            { modal: true, detail },
-            ...commands
-        )
-
-        switch (response) {
-            case addCommandText:
-                await addTag(node)
-                break
-            case removeCommandText:
-                await removeTag(node)
-                break
-        }
-    })
-}
-
-export async function addTag(node: DBResourceNode): Promise {
-    return telemetry.docdb_addTag.run(async () => {
-        const key = await vscode.window.showInputBox({
-            title: 'Add Tag',
-            prompt: localize('AWS.docdb.tags.add.keyPrompt', 'Enter a key for the new tag'),
-            validateInput: (input) => validateTag(input, 1, 'key'),
-        })
-        if (key === undefined) {
-            getLogger().info('docdb: AddTag cancelled')
-            throw new ToolkitError('User cancelled', { cancelled: true })
-        }
-
-        const value = await vscode.window.showInputBox({
-            title: 'Add Tag',
-            prompt: localize('AWS.docdb.tags.add.valuePrompt', 'Enter the value for the new tag (optional)'),
-            validateInput: (input) => validateTag(input, 0, 'value'),
-        })
-        if (value === undefined) {
-            getLogger().info('docdb: AddTag cancelled')
-            throw new ToolkitError('User cancelled', { cancelled: true })
-        }
-
-        const tag = { [key.trim()]: value.trim() }
-        await node.client.addResourceTags({ resourceArn: node.arn, tags: tag })
-        getLogger().info('docdb: Added resource tag for: %O', node.name)
-        void vscode.window.showInformationMessage(localize('AWS.docdb.tags.add.success', 'Tag added'))
-    })
-}
-
-export async function removeTag(node: DBResourceNode): Promise {
-    return telemetry.docdb_removeTag.run(async () => {
-        const tagMap = await node.listTags()
-        const items = Object.entries(tagMap ?? {}).map>(([key, value]) => {
-            return {
-                data: key,
-                label: key,
-                description: value,
-            }
-        })
-        if (items.length === 0) {
-            return
-        }
-
-        const resp = await showQuickPick(items, {
-            title: localize('AWS.docdb.tags.remove.title', 'Remove a tag from {0}', node.name),
-        })
-
-        if (resp === undefined) {
-            getLogger().info('docdb: RemoveTag cancelled')
-            throw new ToolkitError('User cancelled', { cancelled: true })
-        }
-
-        await node.client.removeResourceTags({ resourceArn: node.arn, tagKeys: [resp] })
-        getLogger().info('docdb: Removed resource tag for: %O', node.name)
-        void vscode.window.showInformationMessage(localize('AWS.docdb.tags.remove.success', 'Tag removed'))
-    })
-}
-
-export function validateTag(input: string, minLength: number, name: string): string | undefined {
-    if (input.trim().length < minLength) {
-        return localize('AWS.docdb.validateTag.error.invalidLength', `Tag ${name} cannot be blank`)
-    }
-
-    if (!/^([\p{L}\p{Z}\p{N}\._:/=+\-@]*)$/u.test(input)) {
-        return localize(
-            'AWS.docdb.validateTag.error.invalidCharacters',
-            `Tag ${name} may only contain unicode letters, digits, whitespace, or one of these symbols: _ . : / = + - @`
-        )
-    }
-
-    return undefined
-}
diff --git a/packages/core/src/docdb/explorer/dbClusterNode.ts b/packages/core/src/docdb/explorer/dbClusterNode.ts
deleted file mode 100644
index 5544523d6f..0000000000
--- a/packages/core/src/docdb/explorer/dbClusterNode.ts
+++ /dev/null
@@ -1,192 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as os from 'os'
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { copyToClipboard } from '../../shared/utilities/messages'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { CreateDBInstanceMessage, DBCluster, ModifyDBClusterMessage } from '@aws-sdk/client-docdb'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { DBResourceNode } from './dbResourceNode'
-import { DBInstanceNode } from './dbInstanceNode'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { DBInstance, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { DocDBContext } from './docdbContext'
-import { toTitleCase } from '../../shared/utilities/textUtilities'
-import { getAwsConsoleUrl } from '../../shared/awsConsole'
-import { getLogger } from '../../shared/logger/logger'
-
-export type DBClusterRole = 'global' | 'regional' | 'primary' | 'secondary'
-
-/**
- * An AWS Explorer node representing DocumentDB clusters.
- *
- * Contains instances for a specific cluster as child nodes.
- */
-export class DBClusterNode extends DBResourceNode {
-    override name = this.cluster.DBClusterIdentifier!
-    override arn = this.cluster.DBClusterArn!
-    public instances: DBInstance[] = []
-    private childNodes: DBInstanceNode[] = []
-
-    constructor(
-        public readonly parent: AWSTreeNodeBase,
-        readonly cluster: DBCluster,
-        client: DocumentDBClient,
-        readonly clusterRole: DBClusterRole = 'regional'
-    ) {
-        super(client, cluster.DBClusterIdentifier ?? '[Cluster]', vscode.TreeItemCollapsibleState.Collapsed)
-        getLogger().debug(`NEW DBClusterNode: ${cluster.DBClusterArn}`)
-        this.arn = cluster.DBClusterArn ?? ''
-        this.name = cluster.DBClusterIdentifier ?? ''
-        this.contextValue = this.getContext()
-        this.iconPath = new vscode.ThemeIcon(
-            this.isAvailable ? 'layers-active' : this.isStopped ? 'layers-dot' : 'loading~spin'
-        )
-        this.description = this.getDescription()
-        this.tooltip = `${this.name}${os.EOL}Engine: ${this.cluster.EngineVersion}${os.EOL}Status: ${this.cluster.Status}`
-        if (this.isStatusRequiringPolling()) {
-            getLogger().debug(`${this.arn} requires polling.`)
-            this.trackChanges()
-        } else {
-            getLogger().debug(`${this.arn} does NOT require polling.`)
-        }
-    }
-
-    public override async getChildren(): Promise {
-        getLogger().debug(`DBClusterNode.getChildren() called`)
-        return telemetry.docdb_listInstances.run(async () => {
-            return await makeChildrenNodes({
-                getChildNodes: async () => {
-                    this.instances = (await this.client.listInstances([this.arn])).map((i) => {
-                        const member = this.cluster.DBClusterMembers?.find(
-                            (m) => m.DBInstanceIdentifier === i.DBInstanceIdentifier
-                        )
-                        return { ...i, ...member }
-                    })
-                    const nodes = this.instances.map((instance) => new DBInstanceNode(this, instance))
-                    this.childNodes = nodes
-                    return nodes
-                },
-                getNoChildrenPlaceholderNode: async () => {
-                    const title = localize('AWS.explorerNode.docdb.addInstance', 'Add instance...')
-                    const placeholder = new PlaceholderNode(this, title)
-                    placeholder.contextValue = 'awsDocDB.placeholder'
-                    placeholder.command = { title, command: 'aws.docdb.createInstance', arguments: [this] }
-                    return placeholder
-                },
-                sort: (item1, item2) => item1.name.localeCompare(item2.name),
-            })
-        })
-    }
-
-    private getContext() {
-        const context = `${DocDBContext.Cluster}-${this.clusterRole}`
-        if (this.isAvailable) {
-            return `${context}-running`
-        } else if (this.isStopped) {
-            return `${context}-stopped`
-        }
-        return context
-    }
-
-    public getDescription(): string | boolean {
-        const role = toTitleCase(this.clusterRole)
-        if (!this.isAvailable) {
-            return `${role} cluster • ${toTitleCase(this.status ?? ' ')}`
-        }
-        return `${role} cluster`
-    }
-
-    public async createInstance(request: CreateDBInstanceMessage): Promise {
-        return await this.client.createInstance(request)
-    }
-
-    public async renameCluster(clusterName: string): Promise {
-        const request: ModifyDBClusterMessage = {
-            DBClusterIdentifier: this.cluster.DBClusterIdentifier,
-            NewDBClusterIdentifier: clusterName,
-            ApplyImmediately: true,
-        }
-        const response = await this.client.modifyCluster(request)
-        this.name = response?.DBClusterIdentifier ?? this.name
-        return response
-    }
-
-    public async deleteCluster(finalSnapshotId: string | undefined): Promise {
-        const instances = await this.client.listInstances([this.arn])
-
-        const tasks = []
-        for (const instance of instances) {
-            tasks.push(
-                this.client.deleteInstance({
-                    DBInstanceIdentifier: instance.DBInstanceIdentifier,
-                })
-            )
-        }
-        await Promise.all(tasks)
-
-        return await this.client.deleteCluster({
-            DBClusterIdentifier: this.cluster.DBClusterIdentifier,
-            FinalDBSnapshotIdentifier: finalSnapshotId,
-            SkipFinalSnapshot: finalSnapshotId === undefined,
-        })
-    }
-
-    override get status() {
-        return this.cluster.Status
-    }
-
-    override async getStatus() {
-        const clusters = await this.client.listClusters(this.arn)
-        const cluster = clusters[0]
-
-        if (!cluster) {
-            getLogger().warn(`No cluster found for ARN: ${this.arn}`)
-            return undefined
-        }
-
-        getLogger().debug(`Get Status: status ${cluster.Status} for cluster ${this.arn}`)
-
-        this.cluster.Status = cluster.Status
-        return cluster.Status
-    }
-
-    override getConsoleUrl() {
-        return getAwsConsoleUrl('docdb', this.regionCode).with({
-            fragment: `cluster-details/${this.name}`,
-        })
-    }
-
-    override copyEndpoint() {
-        if (this.cluster.Endpoint) {
-            return copyToClipboard(this.cluster.Endpoint, this.name)
-        }
-        return Promise.reject()
-    }
-
-    override refreshTree(): void {
-        getLogger().debug(`(DBClusterNode) Refreshing tree for instance: ${this.arn}`)
-        this.refresh()
-        this.parent.refresh()
-    }
-
-    override clearTimer(): void {
-        this.pollingSet.delete(this.arn)
-        this.pollingSet.clearTimer()
-        for (const node of this.childNodes) {
-            getLogger().debug(`(clearTimer) Removing Polling from node: ${node.arn}`)
-            node.pollingSet.delete(node.arn)
-            node.pollingSet.clearTimer()
-        }
-    }
-
-    public override [inspect.custom](): string {
-        return 'DBClusterNode'
-    }
-}
diff --git a/packages/core/src/docdb/explorer/dbElasticClusterNode.ts b/packages/core/src/docdb/explorer/dbElasticClusterNode.ts
deleted file mode 100644
index d228c3bbef..0000000000
--- a/packages/core/src/docdb/explorer/dbElasticClusterNode.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { DBResourceNode } from './dbResourceNode'
-import { DBElasticCluster, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { DocDBContext } from './docdbContext'
-import { copyToClipboard } from '../../shared/utilities/messages'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { getAwsConsoleUrl } from '../../shared/awsConsole'
-import { getLogger } from '../../shared/logger/logger'
-
-/**
- * An AWS Explorer node representing DocumentDB elastic clusters.
- */
-export class DBElasticClusterNode extends DBResourceNode {
-    override name = this.cluster.clusterName!
-    override arn = this.cluster.clusterArn!
-
-    constructor(
-        public readonly parent: AWSTreeNodeBase,
-        public cluster: DBElasticCluster,
-        client: DocumentDBClient
-    ) {
-        super(client, cluster.clusterName ?? '[Cluster]', vscode.TreeItemCollapsibleState.None)
-        this.contextValue = this.getContext()
-        this.iconPath = new vscode.ThemeIcon(
-            this.isAvailable ? 'layers-active' : this.isStopped ? 'layers-dot' : 'loading~spin'
-        )
-        this.description = this.getDescription()
-        this.tooltip = `${this.name}\nStatus: ${this.status}`
-        if (this.isStatusRequiringPolling()) {
-            getLogger().debug(`${this.arn} requires polling.`)
-            this.trackChanges()
-        } else {
-            getLogger().debug(`${this.arn} does NOT require polling.`)
-        }
-    }
-
-    private getContext() {
-        if (this.isAvailable) {
-            return `${DocDBContext.ElasticCluster}-running`
-        } else if (this.isStopped) {
-            return `${DocDBContext.ElasticCluster}-stopped`
-        }
-        return DocDBContext.ElasticCluster
-    }
-
-    public getDescription(): string | boolean {
-        if (!this.isAvailable) {
-            return `Elastic cluster • ${this.status}`
-        }
-        return 'Elastic cluster'
-    }
-
-    public async deleteCluster(finalSnapshotId: string | undefined): Promise {
-        if (finalSnapshotId !== undefined) {
-            void vscode.window.showInformationMessage(
-                localize('AWS.docdb.deleteCluster.snapshot', 'Taking snapshot of cluster: {0}', this.name)
-            )
-
-            await this.client.createClusterSnapshot({
-                clusterArn: this.cluster.clusterArn,
-                snapshotName: finalSnapshotId,
-            })
-        }
-        return await this.client.deleteElasticCluster(this.arn)
-    }
-
-    override get status() {
-        return this.cluster.status?.toLowerCase()
-    }
-
-    override async getStatus() {
-        const cluster = await this.client.getElasticCluster(this.arn)
-        getLogger().debug(`Get Status: status ${cluster?.status} for elastic-cluster ${this.arn}`)
-        this.cluster.status = cluster?.status
-        return cluster?.status?.toLowerCase()
-    }
-
-    override get isAvailable() {
-        return this.status === 'active'
-    }
-
-    override getConsoleUrl() {
-        return getAwsConsoleUrl('docdb', this.regionCode).with({
-            fragment: `elastic-cluster-details/${this.arn}`,
-        })
-    }
-
-    override async copyEndpoint() {
-        // get the full cluster record if we don't have it already
-        if (this.cluster.clusterEndpoint === undefined) {
-            this.cluster = (await this.client.getElasticCluster(this.arn)) ?? this.cluster
-        }
-        await copyToClipboard(this.cluster.clusterEndpoint!, this.name)
-    }
-
-    override clearTimer(): void {
-        this.pollingSet.delete(this.arn)
-        this.pollingSet.clearTimer()
-    }
-
-    override refreshTree(): void {
-        getLogger().debug(`(DBElasticClusterNode) Refreshing tree for instance: ${this.arn}`)
-        this.refresh()
-        this.parent.refresh()
-    }
-
-    public override [inspect.custom](): string {
-        return 'DBElasticClusterNode'
-    }
-}
diff --git a/packages/core/src/docdb/explorer/dbGlobalClusterNode.ts b/packages/core/src/docdb/explorer/dbGlobalClusterNode.ts
deleted file mode 100644
index 7ab15e01a1..0000000000
--- a/packages/core/src/docdb/explorer/dbGlobalClusterNode.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { DBClusterNode, DBClusterRole } from './dbClusterNode'
-import { DefaultDocumentDBClient, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { DBCluster, GlobalCluster, GlobalClusterMember, ModifyGlobalClusterMessage } from '@aws-sdk/client-docdb'
-import { DBResourceNode } from './dbResourceNode'
-import { DocDBContext } from './docdbContext'
-import { copyToClipboard } from '../../shared/utilities/messages'
-import { getAwsConsoleUrl } from '../../shared/awsConsole'
-import { getLogger } from '../../shared/logger/logger'
-
-function getRegionFromArn(arn: string) {
-    const match = arn.match(/:rds:([^:]+):.*:cluster:/)
-    return match?.at(1)
-}
-
-/**
- * An AWS Explorer node representing DocumentDB global clusters.
- *
- * Contains regional clusters of a global cluster as child nodes.
- */
-export class DBGlobalClusterNode extends DBResourceNode {
-    override name = this.cluster.GlobalClusterIdentifier!
-    override arn = this.cluster.GlobalClusterArn!
-    readonly clusterRole: DBClusterRole = 'global'
-
-    constructor(
-        public readonly parent: AWSTreeNodeBase,
-        readonly cluster: GlobalCluster,
-        private readonly clusterMap: Map,
-        client: DocumentDBClient
-    ) {
-        super(client, cluster.GlobalClusterIdentifier ?? '[Cluster]', vscode.TreeItemCollapsibleState.Collapsed)
-        this.arn = cluster.GlobalClusterArn ?? ''
-        this.name = cluster.GlobalClusterIdentifier ?? ''
-        this.contextValue = this.getContext()
-        this.iconPath = new vscode.ThemeIcon('globe') // TODO: determine icon for global cluster
-        this.description = 'Global cluster'
-        this.tooltip = `${this.name}\nEngine: ${this.cluster.EngineVersion}\nStatus: ${this.cluster.Status} (read-only)`
-        if (this.isStatusRequiringPolling()) {
-            getLogger().debug(`${this.arn} requires polling.`)
-            this.trackChanges()
-        } else {
-            getLogger().debug(`${this.arn} does NOT require polling.`)
-        }
-    }
-
-    public override async getChildren(): Promise {
-        return telemetry.docdb_listInstances.run(async () => {
-            return await makeChildrenNodes({
-                getChildNodes: async () => {
-                    const members = this.cluster.GlobalClusterMembers ?? []
-                    await this.getMemberClusters(members)
-
-                    const nodes = members.map((member) => {
-                        const memberRole: DBClusterRole = member.IsWriter ? 'primary' : 'secondary'
-                        const [cluster, client] = this.clusterMap.get(member.DBClusterArn!) ?? []
-
-                        return new DBClusterNode(this, cluster!, client!, memberRole)
-                    })
-
-                    return nodes
-                },
-                getNoChildrenPlaceholderNode: async () =>
-                    new PlaceholderNode(this, localize('AWS.explorerNode.docdb.noClusters', '[No Clusters found]')),
-                sort: (item1, item2) => {
-                    if (item1.clusterRole === 'primary') {
-                        return -1
-                    }
-                    if (item2.clusterRole === 'primary') {
-                        return 1
-                    }
-                    return item1.name.localeCompare(item2.name)
-                },
-            })
-        })
-    }
-
-    private getContext() {
-        if (this.isAvailable) {
-            return `${DocDBContext.GlobalCluster}-running`
-        } else if (this.status === 'stopped') {
-            return `${DocDBContext.GlobalCluster}-stopped`
-        }
-        return DocDBContext.GlobalCluster
-    }
-
-    // retrieve member cluster details from other regions
-    private async getMemberClusters(members: GlobalClusterMember[]): Promise {
-        await Promise.all(
-            members.map(async (member) => {
-                if (!this.clusterMap.has(member.DBClusterArn!)) {
-                    const regionCode = getRegionFromArn(member.DBClusterArn!)
-                    if (regionCode) {
-                        const client = DefaultDocumentDBClient.create(regionCode)
-                        const [cluster] = await client.listClusters(member.DBClusterArn!)
-                        this.clusterMap.set(member.DBClusterArn!, [cluster, client])
-                    }
-                }
-            })
-        )
-    }
-
-    public async renameCluster(clusterName: string): Promise {
-        const request: ModifyGlobalClusterMessage = {
-            GlobalClusterIdentifier: this.cluster.GlobalClusterIdentifier,
-            NewGlobalClusterIdentifier: clusterName,
-        }
-        const response = await this.client.modifyGlobalCluster(request)
-        this.name = response?.GlobalClusterIdentifier ?? this.name
-        return response
-    }
-
-    override get status() {
-        return this.cluster.Status
-    }
-
-    override async getStatus() {
-        const client = DefaultDocumentDBClient.create(this.regionCode)
-        const clusters = await client.listClusters(this.arn)
-        const cluster = clusters[0]
-
-        if (!cluster) {
-            getLogger().warn(`No global cluster found for ARN: ${this.arn}`)
-            return undefined
-        }
-
-        getLogger().debug(`Get Status: status ${cluster.Status} for global cluster ${this.arn}`)
-        this.cluster.Status = cluster.Status
-        return cluster.Status
-    }
-
-    override copyEndpoint(): Promise {
-        return copyToClipboard(this.cluster.GlobalClusterResourceId!, this.name)
-    }
-
-    override getConsoleUrl(): vscode.Uri {
-        return getAwsConsoleUrl('docdb', this.regionCode).with({
-            fragment: `global-cluster-details/${this.name}`,
-        })
-    }
-
-    override refreshTree(): void {
-        getLogger().debug(`(DBGlobalClusterNode) Refreshing tree for instance: ${this.arn}`)
-        this.refresh()
-        this.parent.refresh()
-    }
-
-    override clearTimer(): void {
-        this.pollingSet.delete(this.arn)
-        this.pollingSet.clearTimer()
-    }
-
-    public override [inspect.custom](): string {
-        return 'DBGlobalClusterNode'
-    }
-}
diff --git a/packages/core/src/docdb/explorer/dbInstanceNode.ts b/packages/core/src/docdb/explorer/dbInstanceNode.ts
deleted file mode 100644
index 3a839971e0..0000000000
--- a/packages/core/src/docdb/explorer/dbInstanceNode.ts
+++ /dev/null
@@ -1,119 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { DBInstance } from '../../shared/clients/docdbClient'
-import { DocDBContext, DocDBNodeContext } from './docdbContext'
-import { DBResourceNode } from './dbResourceNode'
-import { DBClusterNode } from './dbClusterNode'
-import { ModifyDBInstanceMessage } from '@aws-sdk/client-docdb'
-import { copyToClipboard } from '../../shared/utilities/messages'
-import { getAwsConsoleUrl } from '../../shared/awsConsole'
-import { getLogger } from '../../shared/logger/logger'
-import { toTitleCase } from '../../shared/utilities/textUtilities'
-
-/**
- * An AWS Explorer node representing a DocumentDB instance.
- */
-export class DBInstanceNode extends DBResourceNode {
-    override name = this.instance.DBInstanceIdentifier!
-    override arn = this.instance.DBInstanceArn!
-
-    constructor(
-        public readonly parent: DBClusterNode,
-        readonly instance: DBInstance
-    ) {
-        super(parent.client, instance.DBInstanceIdentifier ?? '[Instance]', vscode.TreeItemCollapsibleState.None)
-        getLogger().debug(`NEW DBInstanceNode: ${instance.DBInstanceArn}`)
-        this.description = this.makeDescription()
-        this.contextValue = this.getContext()
-        this.iconPath = this.isAvailable || this.isStopped ? undefined : new vscode.ThemeIcon('loading~spin')
-        this.tooltip = `${this.name}\nClass: ${this.instance.DBInstanceClass}\nStatus: ${this.status}`
-        getLogger().debug(`Parent of ${instance.DBInstanceArn} is ${parent.arn}`)
-        if (this.isStatusRequiringPolling()) {
-            getLogger().debug(`${instance.DBInstanceArn} requires polling.`)
-            this.trackChanges()
-        } else {
-            getLogger().debug(`${instance.DBInstanceArn} does NOT require polling.`)
-        }
-    }
-
-    public override isStatusRequiringPolling(): boolean {
-        const instanceRequiresPolling = super.isStatusRequiringPolling()
-        const parentRequiresPolling = this.parent.isStatusRequiringPolling()
-        const requiresPolling = instanceRequiresPolling || parentRequiresPolling
-
-        getLogger().debug(
-            `isStatusRequiringPolling (DBInstanceNode): Instance ${this.arn} requires polling: ${instanceRequiresPolling}, Parent ${this.parent.arn} requires polling: ${parentRequiresPolling}, Combined result: ${requiresPolling}`
-        )
-
-        return requiresPolling
-    }
-
-    private makeDescription(): string {
-        const type = this.instance.IsClusterWriter ? 'Primary' : 'Replica'
-        if (this.getContext() !== DocDBContext.InstanceAvailable) {
-            return `${toTitleCase(this.status ?? ' ')} ${type} instance`
-        }
-        return `${type} instance • ${this.instance.DBInstanceClass}`
-    }
-
-    private getContext(): DocDBNodeContext {
-        if (this.isAvailable) {
-            return DocDBContext.InstanceAvailable
-        }
-        return DocDBContext.Instance
-    }
-
-    public async rebootInstance(): Promise {
-        const client = this.parent.client
-        return await client.rebootInstance(this.instance.DBInstanceIdentifier!)
-    }
-
-    public async renameInstance(instanceName: string): Promise {
-        const request: ModifyDBInstanceMessage = {
-            DBInstanceIdentifier: this.instance.DBInstanceIdentifier,
-            NewDBInstanceIdentifier: instanceName,
-            ApplyImmediately: true,
-        }
-        return await this.parent.client.modifyInstance(request)
-    }
-
-    override get status() {
-        return this.instance.DBInstanceStatus
-    }
-
-    override async getStatus() {
-        const instance = await this.parent.client.getInstance(this.instance.DBInstanceIdentifier!)
-        this.instance.DBInstanceStatus = instance?.DBInstanceStatus
-        return instance?.DBInstanceStatus
-    }
-
-    override getConsoleUrl() {
-        return getAwsConsoleUrl('docdb', this.regionCode).with({
-            fragment: `instance-details/${this.name}`,
-        })
-    }
-
-    override copyEndpoint() {
-        return copyToClipboard(this.instance.Endpoint?.Address ?? '', this.name)
-    }
-
-    override refreshTree(): void {
-        getLogger().debug(`(DBInstanceNode) Refreshing tree for instance: ${this.arn}`)
-        this.refresh()
-        this.parent.refreshTree()
-    }
-
-    override clearTimer(): void {
-        this.pollingSet.delete(this.arn)
-        this.pollingSet.clearTimer()
-    }
-
-    public override [inspect.custom](): string {
-        return 'DBInstanceNode'
-    }
-}
diff --git a/packages/core/src/docdb/explorer/dbResourceNode.ts b/packages/core/src/docdb/explorer/dbResourceNode.ts
deleted file mode 100644
index 259a867024..0000000000
--- a/packages/core/src/docdb/explorer/dbResourceNode.ts
+++ /dev/null
@@ -1,175 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { DocumentDBClient } from '../../shared/clients/docdbClient'
-import { waitUntil } from '../../shared/utilities/timeoutUtils'
-import { getLogger } from '../../shared/logger/logger'
-import { PollingSet } from '../../shared/utilities/pollingSet'
-
-/** An AWS Explorer node representing a DocumentDB resource. */
-export abstract class DBResourceNode extends AWSTreeNodeBase implements AWSResourceNode {
-    public override readonly regionCode: string
-    public abstract readonly arn: string
-    public abstract readonly name: string
-    public readonly pollingSet: PollingSet = new PollingSet(30000, this.updateNodeStatus.bind(this))
-    private static readonly globalPollingArns: Set = new Set()
-    public processingStatuses = new Set([
-        'creating',
-        'modifying',
-        'rebooting',
-        'starting',
-        'stopping',
-        'renaming',
-    ])
-
-    protected constructor(
-        public readonly client: DocumentDBClient,
-        label: string,
-        collapsibleState?: vscode.TreeItemCollapsibleState
-    ) {
-        super(label, collapsibleState)
-        this.regionCode = client.regionCode
-        getLogger().debug(`NEW DBResourceNode`)
-    }
-
-    public isStatusRequiringPolling(): boolean {
-        const currentStatus = this.status?.toLowerCase()
-        const isProcessingStatus = currentStatus !== undefined && this.processingStatuses.has(currentStatus)
-        getLogger().debug(
-            `isStatusRequiringPolling (DBResourceNode):: Checking if status "${currentStatus}" for ARN: ${this.arn} requires polling: ${isProcessingStatus}`
-        )
-        return isProcessingStatus
-    }
-
-    public [inspect.custom](): string {
-        return 'DBResourceNode'
-    }
-
-    public abstract get status(): string | undefined
-
-    public abstract getStatus(): Promise
-
-    public abstract refreshTree(): void
-
-    public abstract clearTimer(): void
-
-    public get isAvailable(): boolean {
-        return this.status === 'available'
-    }
-
-    public get isStopped(): boolean {
-        return this.status === 'stopped'
-    }
-
-    public get isPolling(): boolean {
-        const isPolling = DBResourceNode.globalPollingArns.has(this.arn)
-        getLogger().debug(`isPolling: ARN ${this.arn} is ${isPolling ? '' : 'not '}being polled.`)
-        return isPolling
-    }
-
-    public set isPolling(value: boolean) {
-        if (value) {
-            if (!this.isPolling) {
-                DBResourceNode.globalPollingArns.add(this.arn)
-                getLogger().info(`Polling started for ARN: ${this.arn}`)
-            } else {
-                getLogger().info(`Polling already active for ARN: ${this.arn}`)
-            }
-        } else {
-            if (this.isPolling) {
-                DBResourceNode.globalPollingArns.delete(this.arn)
-                getLogger().info(`Polling stopped for ARN: ${this.arn}`)
-            } else {
-                getLogger().info(`Polling was not active for ARN: ${this.arn}`)
-            }
-        }
-    }
-
-    public async waitUntilStatusChanged(
-        checkProcessingStatuses: boolean = false,
-        timeout: number = 1200000,
-        interval: number = 5000
-    ): Promise {
-        await waitUntil(
-            async () => {
-                const status = await this.getStatus()
-                if (checkProcessingStatuses) {
-                    const isProcessingStatus = status !== undefined && this.processingStatuses.has(status.toLowerCase())
-                    getLogger().debug('docdb: waitUntilStatusChangedToProcessingStatus: %O', isProcessingStatus)
-                    return isProcessingStatus
-                } else {
-                    const hasStatusChanged = status !== this.status
-                    getLogger().debug('docdb: waitUntilStatusChanged (status): %O', hasStatusChanged)
-                    return hasStatusChanged
-                }
-            },
-            { timeout, interval, truthy: true }
-        )
-        this.refreshTree()
-        return false
-    }
-
-    public async trackChangesWithWaitProcessingStatus() {
-        getLogger().debug(
-            `Preparing to track changes with waiting a processing status for ARN: ${this.arn}; condition: ${this.isPolling};`
-        )
-        if (!this.isPolling) {
-            this.isPolling = true
-            await this.waitUntilStatusChanged(true, 60000, 1000)
-            getLogger().debug(`Tracking changes for a processing status wait is over`)
-            this.pollingSet.add(this.arn)
-            getLogger().debug(`Tracking changes for ARN: ${this.arn}; condition: ${this.isPolling};`)
-        } else {
-            getLogger().debug(`ARN: ${this.arn} already being tracked`)
-        }
-    }
-
-    public trackChanges() {
-        getLogger().debug(`Preparing to track immediately for ARN: ${this.arn}; condition: ${this.isPolling};`)
-        if (!this.isPolling) {
-            this.isPolling = true
-            this.pollingSet.add(this.arn)
-            getLogger().debug(`Tracking changes for ARN: ${this.arn}; condition: ${this.isPolling};`)
-        } else {
-            getLogger().debug(`ARN: ${this.arn} already being tracked`)
-        }
-    }
-
-    public async listTags() {
-        return await this.client.listResourceTags(this.arn)
-    }
-
-    public abstract copyEndpoint(): Promise
-
-    public abstract getConsoleUrl(): vscode.Uri
-
-    public openInBrowser() {
-        return vscode.env.openExternal(this.getConsoleUrl())
-    }
-
-    private async updateNodeStatus() {
-        const currentStatus = this.status
-        const newStatus = await this.getStatus()
-        getLogger().debug(
-            `docdb: ${this.arn} updateNodeStatus (new status): ${newStatus} (old status): ${currentStatus}`
-        )
-        if (currentStatus !== newStatus) {
-            getLogger().info(`docdb: ${this.arn} status: ${newStatus}, refreshing UI`)
-            this.refreshTree()
-        }
-        if (!this.isStatusRequiringPolling()) {
-            getLogger().info(`docdb: ${this.arn} status: ${newStatus}, refreshing UI`)
-            getLogger().debug(`pollingSet delete ${this.arn} updateNodeStatus`)
-            this.pollingSet.delete(this.arn)
-            this.pollingSet.clearTimer()
-            this.isPolling = false
-            this.refreshTree()
-        }
-    }
-}
diff --git a/packages/core/src/docdb/explorer/docdbContext.ts b/packages/core/src/docdb/explorer/docdbContext.ts
deleted file mode 100644
index 23e2b2fbba..0000000000
--- a/packages/core/src/docdb/explorer/docdbContext.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export const DocDBContext = {
-    Cluster: 'awsDocDB-cluster',
-    ElasticCluster: 'awsDocDB-cluster-elastic',
-    GlobalCluster: 'awsDocDB-cluster-global',
-    Instance: 'awsDocDB-instance',
-    InstanceAvailable: 'awsDocDB-instance-available',
-} as const
-
-export type DocDBNodeContext = (typeof DocDBContext)[keyof typeof DocDBContext]
diff --git a/packages/core/src/docdb/explorer/docdbNode.ts b/packages/core/src/docdb/explorer/docdbNode.ts
deleted file mode 100644
index e94e7c4115..0000000000
--- a/packages/core/src/docdb/explorer/docdbNode.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { inspect } from 'util'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { DBElasticCluster, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { DBClusterNode } from './dbClusterNode'
-import { DBElasticClusterNode } from './dbElasticClusterNode'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { DBGlobalClusterNode } from './dbGlobalClusterNode'
-import { DBCluster } from '@aws-sdk/client-docdb'
-import { getLogger } from '../../shared/logger/logger'
-import { DBResourceNode } from './dbResourceNode'
-
-/**
- * An AWS Explorer node representing DocumentDB.
- *
- * Contains clusters for a specific region as child nodes.
- */
-export class DocumentDBNode extends AWSTreeNodeBase {
-    public override readonly regionCode: string
-
-    public constructor(public readonly client: DocumentDBClient) {
-        super('DocumentDB', vscode.TreeItemCollapsibleState.Collapsed)
-        this.contextValue = 'awsDocDBNode'
-        this.regionCode = client.regionCode
-    }
-
-    public override async getChildren(): Promise {
-        return telemetry.docdb_listClusters.run(async () => {
-            return await makeChildrenNodes({
-                getChildNodes: () => {
-                    return this.getClusterNodes()
-                },
-                getNoChildrenPlaceholderNode: async () =>
-                    new PlaceholderNode(this, localize('AWS.explorerNode.docdb.noClusters', '[No Clusters found]')),
-                sort: (item1, item2) => item1.name.localeCompare(item2.name),
-            })
-        })
-    }
-
-    private async getClusterNodes() {
-        const [globalClusters, clusters, elasticClusters] = await Promise.all([
-            this.client.listGlobalClusters(),
-            this.client.listClusters(),
-            this.client.listElasticClusters(),
-        ])
-
-        // contains clusters that are part of a global cluster
-        const globalClusterMap = new Map()
-
-        for (const globalCluster of globalClusters) {
-            for (const member of globalCluster.GlobalClusterMembers ?? []) {
-                const match = clusters.find((c) => c.DBClusterArn === member.DBClusterArn)
-                if (match?.DBClusterArn) {
-                    globalClusterMap.set(match.DBClusterArn, [match, this.client])
-                }
-            }
-        }
-
-        // contains clusters that are not part of a global cluster
-        const regionalClusters = clusters.filter((c) => !globalClusterMap.has(c.DBClusterArn!))
-
-        const nodes: DBResourceNode[] = []
-        nodes.push(
-            ...globalClusters.map((cluster) => new DBGlobalClusterNode(this, cluster, globalClusterMap, this.client))
-        )
-        getLogger().info(`Repopulating child regional clusters...`)
-        nodes.push(...regionalClusters.map((cluster) => new DBClusterNode(this, cluster, this.client)))
-        nodes.push(
-            ...elasticClusters.map(
-                (cluster) => new DBElasticClusterNode(this, cluster as DBElasticCluster, this.client)
-            )
-        )
-
-        return nodes
-    }
-
-    public [inspect.custom](): string {
-        return 'DocumentDBNode'
-    }
-}
diff --git a/packages/core/src/docdb/utils.ts b/packages/core/src/docdb/utils.ts
deleted file mode 100644
index 09b71d9fdf..0000000000
--- a/packages/core/src/docdb/utils.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ToolkitError } from '../shared/errors'
-import { localize } from '../shared/utilities/vsCodeUtils'
-import { DBInstanceNode } from './explorer/dbInstanceNode'
-import { DBResourceNode } from './explorer/dbResourceNode'
-
-/**
- * Validates a cluster name for the CreateCluster API.
- * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/docdb/command/CreateDBClusterCommand/
- * @returns undefined if the name passes validation. Otherwise, an error message is returned.
- */
-export function validateClusterName(name: string): string | undefined {
-    if (name.length < 1 || name.length > 63) {
-        return localize(
-            'AWS.docdb.validateClusterName.error.invalidLength',
-            'Cluster name must be between 1 and 63 characters long'
-        )
-    }
-
-    if (!/^[a-z]/.test(name)) {
-        return localize(
-            'AWS.docdb.validateClusterName.error.invalidStart',
-            'Cluster name must start with a lowercase letter'
-        )
-    }
-
-    if (/-$/.test(name) || /--/.test(name)) {
-        return localize(
-            'AWS.docdb.validateClusterName.error.invalidEnd',
-            'Cluster name cannot end with a hyphen or contain 2 consecutive hyphens'
-        )
-    }
-
-    if (!/^[a-z0-9\-]+$/.test(name)) {
-        return localize(
-            'AWS.docdb.validateClusterName.error.invalidCharacters',
-            'Cluster name must only contain lowercase letters, numbers, and hyphens'
-        )
-    }
-
-    return undefined
-}
-
-/**
- * Validates a username
- * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/docdb/command/CreateDBClusterCommand/
- * @returns undefined if the name passes validation. Otherwise, an error message is returned.
- */
-export function validateUsername(name: string): string | undefined {
-    if (name.length < 1 || name.length > 63) {
-        return localize(
-            'AWS.docdb.validateUsername.error.invalidLength',
-            'Username name must be between 1 and 63 characters long'
-        )
-    }
-
-    if (!/^[a-zA-Z]/.test(name)) {
-        return localize('AWS.docdb.validateUsername.error.invalidStart', 'Username must start with a letter')
-    }
-
-    if (!/^[a-zA-Z0-9]+$/.test(name)) {
-        return localize(
-            'AWS.docdb.validateUsername.error.invalidCharacters',
-            'Username must only contain letters and numbers'
-        )
-    }
-
-    return undefined
-}
-
-/**
- * Validates a password
- * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/docdb/command/CreateDBClusterCommand/
- * @returns undefined if validation passes. Otherwise, an error message is returned.
- */
-export function validatePassword(password: string): string | undefined {
-    if (password.length < 8 || password.length > 100) {
-        return localize(
-            'AWS.docdb.validatePassword.error.invalidLength',
-            'Password must be between 8 and 100 characters long'
-        )
-    }
-
-    if (/["\/\@]/.test(password) || !/^[ -~]*$/.test(password)) {
-        return localize(
-            'AWS.docdb.validatePassword.error.invalidCharacters',
-            'Password must only contain printable ASCII characters (except for slash, double quotes and @ symbol)'
-        )
-    }
-
-    return undefined
-}
-
-/**
- * Validates an instance name for the CreateInstance API.
- * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/docdb/command/CreateDBInstanceCommand/
- * @returns undefined if the name passes validation. Otherwise, an error message is returned.
- */
-export function validateInstanceName(name: string): string | undefined {
-    if (name.length < 1 || name.length > 63) {
-        return localize(
-            'AWS.docdb.validateInstanceName.error.invalidLength',
-            'Instance name must be between 1 and 63 characters long'
-        )
-    }
-
-    if (!/^[a-z]/.test(name)) {
-        return localize(
-            'AWS.docdb.validateInstanceName.error.invalidStart',
-            'Instance name must start with a lowercase letter'
-        )
-    }
-
-    if (/-$/.test(name) || /--/.test(name)) {
-        return localize(
-            'AWS.docdb.validateInstanceName.error.invalidEnd',
-            'Instance name cannot end with a hyphen or contain 2 consecutive hyphens'
-        )
-    }
-
-    if (!/^[a-z0-9\-]+$/.test(name)) {
-        return localize(
-            'AWS.docdb.validateInstanceName.error.invalidCharacters',
-            'Instance name must only contain lowercase letters, numbers, and hyphens'
-        )
-    }
-
-    return undefined
-}
-
-export function isSupportedGlobalInstanceClass(instanceClass: string) {
-    return /(t3|t4g|r4)/.test(instanceClass) === false
-}
-
-export function assertNodeAvailable(node: DBResourceNode | undefined, action: string) {
-    if (!node) {
-        throw new ToolkitError(`No node specified for ${action}`)
-    }
-
-    if (!node.isAvailable) {
-        if (node instanceof DBInstanceNode) {
-            void vscode.window.showErrorMessage(localize('AWS.docdb.instanceStopped', 'Instance must be running'))
-            throw new ToolkitError('Instance not running', { cancelled: true, code: 'docdbInstanceNotAvailable' })
-        }
-
-        void vscode.window.showErrorMessage(localize('AWS.docdb.clusterStopped', 'Cluster must be running'))
-        throw new ToolkitError('Cluster not running', { cancelled: true, code: 'docdbClusterStopped' })
-    }
-}
diff --git a/packages/core/src/docdb/wizards/createClusterWizard.ts b/packages/core/src/docdb/wizards/createClusterWizard.ts
deleted file mode 100644
index 5e81dd89c6..0000000000
--- a/packages/core/src/docdb/wizards/createClusterWizard.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { createExitPrompter } from '../../shared/ui/common/exitPrompter'
-import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { Wizard, WizardOptions } from '../../shared/wizards/wizard'
-import { RegionalClusterConfiguration, RegionalClusterWizard } from './regionalClusterWizard'
-import { ElasticClusterConfiguration, ElasticClusterWizard } from './elasticClusterWizard'
-import { DocumentDBClient } from '../../shared/clients/docdbClient'
-
-const DocDBClusterHelpUrl =
-    'https://docs.aws.amazon.com/documentdb/latest/developerguide/docdb-using-elastic-clusters.html'
-
-type ClusterType = 'regional' | 'elastic' | undefined
-
-export interface CreateClusterState {
-    ClusterType: ClusterType
-    readonly RegionalCluster: RegionalClusterConfiguration
-    readonly ElasticCluster: ElasticClusterConfiguration
-}
-
-/**
- * A wizard to prompt configuration of a new cluster
- */
-export class CreateClusterWizard extends Wizard {
-    title: string
-    constructor(
-        readonly client: DocumentDBClient,
-        options: WizardOptions = {}
-    ) {
-        super({
-            initState: options.initState,
-            implicitState: options.implicitState,
-            exitPrompterProvider: createExitPrompter,
-        })
-        this.title = localize('AWS.docdb.createCluster.title', 'Create DocumentDB Cluster')
-    }
-
-    public override async init(): Promise {
-        this.form.ClusterType.bindPrompter(() => createClusterTypePrompter())
-
-        const regionalClusterWizard = await new RegionalClusterWizard(this.client, this.title).init()
-        this.form.RegionalCluster.applyBoundForm(regionalClusterWizard.boundForm, {
-            showWhen: (state) => state.ClusterType === 'regional',
-        })
-
-        const elasticClusterWizard = new ElasticClusterWizard(this.client, this.title)
-        this.form.ElasticCluster.applyBoundForm(elasticClusterWizard.boundForm, {
-            showWhen: (state) => state.ClusterType === 'elastic',
-        })
-
-        return this
-    }
-}
-
-function createClusterTypePrompter() {
-    const regionalType: DataQuickPickItem = {
-        data: 'regional',
-        label: localize('AWS.docdb.createCluster.clusterType.regional.label', 'Instance Based Cluster'),
-        detail: localize(
-            'AWS.docdb.createCluster.clusterType.regional.detail',
-            'Instance based cluster can scale your database to millions of reads per second and up to 128 TiB of storage capacity. With instance based clusters you can choose your instance type based on your requirements.'
-        ),
-    }
-    const elasticType: DataQuickPickItem = {
-        data: 'elastic',
-        label: localize('AWS.docdb.createCluster.clusterType.elastic.label', 'Elastic Cluster'),
-        detail: localize(
-            'AWS.docdb.createCluster.clusterType.elastic.detail',
-            'Elastic clusters can scale your database to millions of reads and writes per second, with petabytes of storage capacity. Elastic clusters support MongoDB compatible sharding APIs. With Elastic Clusters, you do not need to choose, manage or upgrade instances.'
-        ),
-    }
-
-    return createQuickPick([regionalType, elasticType], {
-        title: localize('AWS.docdb.createCluster.clusterType.prompt', 'Cluster type'),
-        buttons: createCommonButtons(DocDBClusterHelpUrl),
-    })
-}
diff --git a/packages/core/src/docdb/wizards/createGlobalClusterWizard.ts b/packages/core/src/docdb/wizards/createGlobalClusterWizard.ts
deleted file mode 100644
index de88522dff..0000000000
--- a/packages/core/src/docdb/wizards/createGlobalClusterWizard.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import globals from '../../shared/extensionGlobals'
-import { DocDBEngine, DocumentDBClient } from '../../shared/clients/docdbClient'
-import { createExitPrompter } from '../../shared/ui/common/exitPrompter'
-import { createRegionPrompter } from '../../shared/ui/common/region'
-import { createInputBox } from '../../shared/ui/inputPrompter'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { Wizard, WizardOptions } from '../../shared/wizards/wizard'
-import { validateClusterName } from '../utils'
-import { RegionalClusterConfiguration, RegionalClusterWizard } from './regionalClusterWizard'
-
-const DocDBGlobalHelpUrl = 'https://docs.aws.amazon.com/documentdb/latest/developerguide/global-clusters.html'
-
-export interface CreateGlobalClusterState {
-    RegionCode: string
-    GlobalClusterName: string
-    readonly Cluster: RegionalClusterConfiguration
-}
-
-/**
- * A wizard to prompt configuration of a new global cluster
- */
-export class CreateGlobalClusterWizard extends Wizard {
-    constructor(
-        readonly region: string,
-        readonly engineVersion: string | undefined,
-        readonly client: DocumentDBClient,
-        options: WizardOptions = {}
-    ) {
-        super({
-            initState: options.initState,
-            implicitState: options.implicitState,
-            exitPrompterProvider: createExitPrompter,
-        })
-    }
-
-    public override async init(): Promise {
-        this.form.RegionCode.bindPrompter(async () => {
-            const regions = globals.regionProvider.getRegions().filter((r) => r.id !== this.region)
-            return createRegionPrompter(regions, {
-                serviceFilter: DocDBEngine,
-                title: localize('AWS.docdb.addRegion.region.prompt', 'Secondary region'),
-                helpUrl: DocDBGlobalHelpUrl,
-            }).transform((region) => region.id)
-        })
-
-        this.form.GlobalClusterName.bindPrompter(
-            () =>
-                createInputBox({
-                    title: localize('AWS.docdb.addRegion.name.title', 'Global Cluster Id'),
-                    prompt: localize(
-                        'AWS.docdb.addRegion.name.prompt',
-                        'Specify a unique identifier for the global cluster'
-                    ),
-                    validateInput: validateClusterName,
-                }),
-            {
-                showWhen: (state) => state.GlobalClusterName === undefined,
-            }
-        )
-
-        const title = localize('AWS.docdb.addRegion.cluster.title', 'Secondary cluster')
-        const regionalClusterWizard = await new RegionalClusterWizard(this.client, title, false, {
-            initState: { EngineVersion: this.engineVersion },
-        }).init()
-        this.form.Cluster.applyBoundForm(regionalClusterWizard.boundForm)
-
-        return this
-    }
-}
diff --git a/packages/core/src/docdb/wizards/createInstanceWizard.ts b/packages/core/src/docdb/wizards/createInstanceWizard.ts
deleted file mode 100644
index 8bf5b952e0..0000000000
--- a/packages/core/src/docdb/wizards/createInstanceWizard.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { DBCluster, OrderableDBInstanceOption } from '@aws-sdk/client-docdb'
-import { DefaultDocumentDBClient, DocumentDBClient, DBStorageType } from '../../shared/clients/docdbClient'
-import { validateInstanceName } from '../utils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { Wizard, WizardOptions } from '../../shared/wizards/wizard'
-import { createInputBox } from '../../shared/ui/inputPrompter'
-import { createExitPrompter } from '../../shared/ui/common/exitPrompter'
-import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter'
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { SkipPrompter } from '../../shared/ui/common/skipPrompter'
-
-const DocDBHelpUrl = 'https://docs.aws.amazon.com/documentdb/latest/developerguide/db-instances.html'
-
-export interface CreateInstanceState {
-    DBInstanceIdentifier: string
-    DBInstanceClass: string
-}
-
-/**
- * A wizard to prompt configuration of a new instance
- */
-export class CreateInstanceWizard extends Wizard {
-    title: string
-    cluster: DBCluster
-    constructor(
-        region: string,
-        cluster: DBCluster,
-        options: WizardOptions = {},
-        readonly client: DocumentDBClient = DefaultDocumentDBClient.create(region)
-    ) {
-        super({
-            initState: {
-                ...options.initState,
-            },
-            implicitState: options.implicitState,
-            exitPrompterProvider: createExitPrompter,
-        })
-        this.cluster = cluster
-        this.client = client
-        this.title = localize('AWS.docdb.createInstance.title', 'Add Instance')
-    }
-
-    public override async init(): Promise {
-        const form = this.form
-
-        form.DBInstanceIdentifier.bindPrompter(() =>
-            createInputBox({
-                step: 1,
-                title: this.title,
-                prompt: localize('AWS.docdb.createInstance.name.prompt', 'Instance Name'),
-                placeholder: localize('AWS.docdb.createInstance.name.placeholder', 'Specify a unique identifier'),
-                validateInput: validateInstanceName,
-            })
-        )
-
-        form.DBInstanceClass.bindPrompter(async (state) => await this.createInstanceClassPrompter(state.stepCache))
-
-        return this
-    }
-
-    private async createInstanceClassPrompter(cache: { [key: string]: any }) {
-        const cachedOptions: OrderableDBInstanceOption[] = cache[this.client.regionCode]
-        const options =
-            cachedOptions ??
-            (await this.client.listInstanceClassOptions(
-                this.cluster.EngineVersion,
-                this.cluster.StorageType ?? DBStorageType.Standard
-            ))
-        cache[this.client.regionCode] = options
-
-        const items: DataQuickPickItem[] = options.map((option) => {
-            return {
-                data: option.DBInstanceClass,
-                label: option.DBInstanceClass ?? '(unknown)',
-                description: undefined,
-                detail: undefined,
-                recentlyUsed: false,
-            }
-        })
-
-        if (items.length === 0) {
-            return new SkipPrompter()
-        }
-
-        return createQuickPick(items, {
-            step: 2,
-            title: localize('AWS.docdb.createInstance.instanceClass.prompt', 'Select instance class'),
-            buttons: createCommonButtons(DocDBHelpUrl),
-        })
-    }
-}
diff --git a/packages/core/src/docdb/wizards/elasticClusterWizard.ts b/packages/core/src/docdb/wizards/elasticClusterWizard.ts
deleted file mode 100644
index b28d2f2075..0000000000
--- a/packages/core/src/docdb/wizards/elasticClusterWizard.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { DocumentDBClient } from '../../shared/clients/docdbClient'
-import { validateClusterName, validatePassword, validateUsername } from '../utils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { Wizard } from '../../shared/wizards/wizard'
-import { createInputBox } from '../../shared/ui/inputPrompter'
-import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter'
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { Auth, CreateClusterInput } from '@aws-sdk/client-docdb-elastic'
-import { Prompter } from '../../shared/ui/prompter'
-
-const DocDBElasticHelpUrl = 'https://docs.aws.amazon.com/documentdb/latest/developerguide/elastic-how-it-works.html'
-const DefaultShardCount = 2
-const DefaultCapacity = 2
-
-export interface ElasticClusterConfiguration extends Partial {}
-
-/**
- * A wizard to prompt configuration of a new cluster
- */
-export class ElasticClusterWizard extends Wizard {
-    constructor(
-        readonly client: DocumentDBClient,
-        readonly title: string
-    ) {
-        super()
-        this.client = client
-        const form = this.form
-
-        form.clusterName.bindPrompter(() =>
-            createInputBox({
-                title: this.title,
-                prompt: localize('AWS.docdb.createCluster.name.prompt', 'Specify a unique cluster name'),
-                validateInput: validateClusterName,
-                buttons: createCommonButtons(DocDBElasticHelpUrl),
-            })
-        )
-
-        form.adminUserName.bindPrompter(() => createUsernamePrompter(this.title))
-        form.adminUserPassword.bindPrompter(() => createPasswordPrompter(this.title))
-        form.authType.setDefault(() => Auth.PLAIN_TEXT)
-        form.shardCount.bindPrompter(() => createShardCountPrompter(this.title), {
-            setDefault: () => DefaultShardCount,
-        })
-        form.shardInstanceCount.bindPrompter(
-            () =>
-                createQuickPick(instanceCountItems(2), {
-                    title: localize(
-                        'AWS.docdb.createCluster.dbInstanceCount.prompt',
-                        'The number of replica instances applying to all shards in the elastic cluster'
-                    ),
-                    buttons: createCommonButtons(DocDBElasticHelpUrl),
-                }),
-            { setDefault: () => 2 }
-        )
-        form.shardCapacity.bindPrompter(() => createShardCapacityPrompter(this.title), {
-            setDefault: () => DefaultCapacity,
-        })
-
-        return this
-    }
-}
-
-function createUsernamePrompter(title: string) {
-    return createInputBox({
-        title,
-        prompt: localize('AWS.docdb.createCluster.username.prompt', 'Specify a login username'),
-        validateInput: validateUsername,
-        buttons: createCommonButtons(DocDBElasticHelpUrl),
-    })
-}
-
-function createPasswordPrompter(title: string) {
-    return createInputBox({
-        title,
-        prompt: localize('AWS.docdb.createCluster.password.prompt', 'Specify a login password (8 characters minimum)'),
-        password: true,
-        validateInput: validatePassword,
-        buttons: createCommonButtons(DocDBElasticHelpUrl),
-    })
-}
-
-function createShardCountPrompter(title: string): Prompter {
-    const maxShardCount = 32
-    const prompter = createInputBox({
-        title,
-        prompt: localize('AWS.docdb.createCluster.shardCount.prompt', 'Number of shards the Elastic Cluster will use'),
-        validateInput: (value) => {
-            const num = parseInt(value)
-            if (num < 1 || num > maxShardCount || isNaN(num)) {
-                return localize(
-                    'AWS.docdb.createCluster.shardCount.invalidValue',
-                    `Enter a numeric value between 1 and ${maxShardCount}`
-                )
-            }
-            return undefined
-        },
-        buttons: createCommonButtons(DocDBElasticHelpUrl),
-    })
-    return prompter.transform((value) => parseInt(value))
-}
-
-function createShardCapacityPrompter(title: string): Prompter {
-    const items = [2, 4, 8, 16, 32, 64].map>((data) => ({
-        data,
-        label: data.toString(),
-    }))
-    return createQuickPick(items, {
-        title,
-        placeholder: localize('AWS.docdb.createCluster.shardCapacity.placeholder', 'vCPU capacity of shard instances'),
-        buttons: createCommonButtons(DocDBElasticHelpUrl),
-    })
-}
-
-function instanceCountItems(defaultCount: number, max: number = 16): DataQuickPickItem[] {
-    const items = []
-    for (let i = 1; i <= max; i++) {
-        const item: DataQuickPickItem = {
-            label: i.toString(),
-            data: i,
-            description: i === defaultCount ? '(recommended)' : undefined,
-        }
-
-        items.push(item)
-    }
-
-    return items
-}
diff --git a/packages/core/src/docdb/wizards/regionalClusterWizard.ts b/packages/core/src/docdb/wizards/regionalClusterWizard.ts
deleted file mode 100644
index 069c4f6d36..0000000000
--- a/packages/core/src/docdb/wizards/regionalClusterWizard.ts
+++ /dev/null
@@ -1,206 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { CreateDBClusterCommandInput } from '@aws-sdk/client-docdb'
-import { DBStorageType, DocDBEngine, DocumentDBClient, MaxInstanceCount } from '../../shared/clients/docdbClient'
-import { isSupportedGlobalInstanceClass, validateClusterName, validatePassword, validateUsername } from '../utils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { Wizard, WizardOptions } from '../../shared/wizards/wizard'
-import { createInputBox } from '../../shared/ui/inputPrompter'
-import { DataQuickPickItem, createQuickPick } from '../../shared/ui/pickerPrompter'
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { SkipPrompter } from '../../shared/ui/common/skipPrompter'
-
-const DocDBClusterHelpUrl = 'https://docs.aws.amazon.com/documentdb/latest/developerguide/db-cluster-parameters.html'
-
-export interface RegionalClusterConfiguration extends CreateDBClusterCommandInput {
-    DBClusterIdentifier: string
-    // These options cannot be changed later
-    EngineVersion: string
-    MasterUsername: string
-    MasterUserPassword: string
-    StorageEncrypted: boolean
-    KmsKeyId?: string | undefined
-    DBSubnetGroupName?: string | undefined
-    VpcSecurityGroupIds?: string[] | undefined
-    // Instance fields
-    DBInstanceCount?: number | undefined
-    DBInstanceClass?: string | undefined
-}
-
-/**
- * A wizard to prompt configuration of a new cluster
- */
-export class RegionalClusterWizard extends Wizard {
-    constructor(
-        readonly client: DocumentDBClient,
-        readonly title: string,
-        readonly isPrimaryCluster: boolean = true,
-        options: WizardOptions = {}
-    ) {
-        super(options)
-        this.client = client
-    }
-
-    public override async init(): Promise {
-        const form = this.form
-
-        form.DBClusterIdentifier.bindPrompter(
-            () =>
-                createInputBox({
-                    step: 1,
-                    title: this.title,
-                    prompt: localize('AWS.docdb.createCluster.name.prompt', 'Specify a unique cluster name'),
-                    validateInput: validateClusterName,
-                    buttons: createCommonButtons(DocDBClusterHelpUrl),
-                }),
-            { relativeOrder: 1 }
-        )
-
-        form.Engine.setDefault(() => DocDBEngine)
-        form.EngineVersion.bindPrompter(async () => await createEngineVersionPrompter(this.client), {
-            showWhen: () => this.isPrimaryCluster,
-            setDefault: () => this.options.initState?.EngineVersion,
-        })
-        form.MasterUsername.bindPrompter(() => createUsernamePrompter(this.title), {
-            showWhen: () => this.isPrimaryCluster,
-        })
-        form.MasterUserPassword.bindPrompter(() => createPasswordPrompter(this.title), {
-            showWhen: () => this.isPrimaryCluster,
-        })
-        form.StorageEncrypted.bindPrompter(() => createEncryptedStoragePrompter(), {
-            showWhen: () => this.isPrimaryCluster,
-        })
-
-        form.DBInstanceCount.bindPrompter(
-            () =>
-                createQuickPick(instanceCountItems(3), {
-                    title: localize('AWS.docdb.createCluster.dbInstanceCount.prompt', 'Number of instances'),
-                    buttons: createCommonButtons(DocDBClusterHelpUrl),
-                }),
-            {
-                setDefault: () => 3,
-            }
-        )
-
-        form.DBInstanceClass.bindPrompter(
-            async (state) =>
-                await createInstanceClassPrompter(this.client, state.EngineVersion!, this.isPrimaryCluster),
-            {
-                setDefault: () => 'db.t3.medium',
-                showWhen: (state) => state.DBInstanceCount! > 0,
-            }
-        )
-
-        return this
-    }
-}
-
-function createUsernamePrompter(title: string) {
-    return createInputBox({
-        step: 3,
-        title,
-        prompt: localize('AWS.docdb.createCluster.username.prompt', 'Specify a login username'),
-        validateInput: validateUsername,
-        buttons: createCommonButtons(DocDBClusterHelpUrl),
-    })
-}
-
-function createPasswordPrompter(title: string) {
-    return createInputBox({
-        step: 4,
-        title,
-        prompt: localize('AWS.docdb.createCluster.password.prompt', 'Specify a login password (8 characters minimum)'),
-        password: true,
-        validateInput: validatePassword,
-        buttons: createCommonButtons(DocDBClusterHelpUrl),
-    })
-}
-
-function createEncryptedStoragePrompter() {
-    return createQuickPick(
-        [
-            {
-                label: localize('AWS.docdb.createCluster.storage.encrypted', 'Encrypt'),
-                description: '(recommended)',
-                data: true,
-            },
-            {
-                label: localize('AWS.docdb.createCluster.storage.notEncrypted', "Don't encrypt"),
-                data: false,
-            },
-        ],
-        {
-            title: localize('AWS.docdb.createCluster.storageEncrypted.prompt', 'Specify storage encryption'),
-            buttons: createCommonButtons(DocDBClusterHelpUrl),
-        }
-    )
-}
-
-async function createEngineVersionPrompter(docdbClient: DocumentDBClient) {
-    const versions = await docdbClient.listEngineVersions()
-    // sort in descending order
-    versions.sort((a, b) => b.EngineVersion!.localeCompare(a.EngineVersion!))
-
-    const items: DataQuickPickItem[] = versions.map((v) => {
-        return {
-            label: v.EngineVersion ?? '',
-            data: v.EngineVersion,
-        }
-    })
-
-    if (items.length === 0) {
-        items.push({ label: '5.0.0 (default)', data: '5.0.0' })
-    }
-
-    items[0].picked = true
-
-    return createQuickPick(items, {
-        title: localize('AWS.docdb.createCluster.engineVersion.prompt', 'Select engine version'),
-        buttons: createCommonButtons(DocDBClusterHelpUrl),
-    })
-}
-
-async function createInstanceClassPrompter(
-    docdbClient: DocumentDBClient,
-    engineVersion: string,
-    isPrimaryCluster: boolean
-) {
-    const options = await docdbClient.listInstanceClassOptions(engineVersion, DBStorageType.Standard)
-
-    const items: DataQuickPickItem[] = options
-        .filter((option) => isPrimaryCluster || isSupportedGlobalInstanceClass(option.DBInstanceClass!))
-        .map((option) => ({
-            data: option.DBInstanceClass,
-            label: option.DBInstanceClass ?? '(unknown)',
-            description: undefined,
-            detail: undefined,
-        }))
-
-    if (items.length === 0) {
-        return new SkipPrompter()
-    }
-
-    return createQuickPick(items, {
-        title: localize('AWS.docdb.createInstance.instanceClass.prompt', 'Select instance class'),
-        buttons: createCommonButtons(DocDBClusterHelpUrl),
-    })
-}
-
-// TODO: Make this it's own picker class
-function instanceCountItems(defaultCount: number, max: number = MaxInstanceCount): DataQuickPickItem[] {
-    const items = []
-
-    for (let index = 1; index <= max; index++) {
-        const item: DataQuickPickItem = {
-            label: index.toString(),
-            data: index,
-            description: index === defaultCount ? '(recommended)' : undefined,
-        }
-        items.push(item)
-    }
-
-    return items
-}
diff --git a/packages/core/src/dynamicResources/activation.ts b/packages/core/src/dynamicResources/activation.ts
deleted file mode 100644
index a40002ae12..0000000000
--- a/packages/core/src/dynamicResources/activation.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { ResourceCodeLensProvider } from '../shared/codelens/resourceCodeLensProvider'
-import { configureResources } from './commands/configure'
-import { copyIdentifier } from './commands/copyIdentifier'
-import { deleteResource } from './commands/deleteResource'
-import { getDiagnostics, openResource } from './commands/openResource'
-import { saveResource } from './commands/saveResource'
-import { ResourcesNode } from './explorer/nodes/resourcesNode'
-import { ResourceNode } from './explorer/nodes/resourceNode'
-import { ResourceTypeNode } from './explorer/nodes/resourceTypeNode'
-import { resourceFileGlobPattern } from './awsResourceManager'
-import { Commands } from '../shared/vscode/commands2'
-import globals from '../shared/extensionGlobals'
-import { openUrl } from '../shared/utilities/vsCodeUtils'
-
-const localize = nls.loadMessageBundle()
-
-/**
- * Activates Resources components.
- */
-export async function activate(context: vscode.ExtensionContext): Promise {
-    const resourceDiagnostics = vscode.languages.createDiagnosticCollection(
-        localize('AWS.explorerNode.resources.label', 'Resources')
-    )
-    const resourceManager = globals.resourceManager
-
-    context.subscriptions.push(
-        vscode.workspace.registerTextDocumentContentProvider('awsResource', new VirtualDocumentProvider()),
-        Commands.register('aws.resources.openResourcePreview', async (node: ResourceNode) => {
-            await openResource({
-                source: node,
-                preview: true,
-                resourceManager,
-                diagnostics: resourceDiagnostics,
-            })
-        }),
-        Commands.register('aws.resources.copyIdentifier', async (node: ResourceNode) => {
-            await copyIdentifier(node.parent.typeName, node.identifier)
-        }),
-        Commands.register('aws.resources.configure', async (node: ResourcesNode) => {
-            if (await configureResources()) {
-                node.refresh()
-            }
-        }),
-        Commands.register('aws.resources.createResource', async (node: ResourceTypeNode) => {
-            await resourceManager.new(node)
-        }),
-        Commands.register('aws.resources.deleteResource', async (node: ResourceNode) => {
-            if (await deleteResource(node.parent.cloudControl, node.parent.typeName, node.identifier)) {
-                await resourceManager.close(resourceManager.toUri(node)!)
-                node.parent.clearChildren()
-                node.parent.refresh()
-            }
-        }),
-        Commands.register('aws.resources.updateResource', async (node: ResourceNode) => {
-            await openResource({
-                source: node,
-                preview: false,
-                resourceManager,
-                diagnostics: resourceDiagnostics,
-            })
-        }),
-        Commands.register('aws.resources.updateResourceInline', async (uri: vscode.Uri) => {
-            await openResource({
-                source: uri,
-                preview: false,
-                resourceManager,
-                diagnostics: resourceDiagnostics,
-            })
-        }),
-        vscode.workspace.onDidSaveTextDocument(async (doc: vscode.TextDocument) => {
-            return await saveResource(doc, resourceManager, resourceDiagnostics)
-        }),
-        Commands.register('aws.resources.saveResource', async (uri: vscode.Uri) => {
-            await vscode.window.showTextDocument(uri)
-            await vscode.commands.executeCommand('workbench.action.files.save')
-        }),
-        Commands.register('aws.resources.closeResource', async (uri: vscode.Uri) => {
-            if (resourceManager.fromUri(uri) instanceof ResourceNode) {
-                await openResource({
-                    source: uri,
-                    preview: true,
-                    resourceManager,
-                    diagnostics: resourceDiagnostics,
-                })
-                resourceDiagnostics.delete(uri)
-            } else {
-                await resourceManager.close(uri)
-            }
-        }),
-        Commands.register('aws.resources.viewDocs', async (node: ResourceTypeNode) => {
-            await openUrl(vscode.Uri.parse(node.metadata.documentation))
-        }),
-        vscode.workspace.onDidChangeTextDocument((textDocumentEvent) => {
-            if (resourceDiagnostics.has(textDocumentEvent.document.uri)) {
-                let diagnostics: vscode.Diagnostic[] = []
-                const resource = resourceManager.fromUri(textDocumentEvent.document.uri)
-                if (resource instanceof ResourceNode) {
-                    const schema = resourceManager.getSchema(resource.parent.typeName)
-                    if (schema) {
-                        diagnostics = getDiagnostics(schema, textDocumentEvent.document)
-                    }
-                }
-                resourceDiagnostics.set(textDocumentEvent.document.uri, diagnostics)
-            }
-        }),
-        vscode.workspace.onDidCloseTextDocument((closeDocumentEvent) => {
-            return resourceManager.close(closeDocumentEvent.uri, true)
-        }),
-        vscode.languages.registerCodeLensProvider(
-            {
-                language: 'json',
-                scheme: 'file',
-                pattern: resourceFileGlobPattern,
-            },
-            new ResourceCodeLensProvider(resourceManager)
-        )
-    )
-}
-
-export class VirtualDocumentProvider implements vscode.TextDocumentContentProvider {
-    provideTextDocumentContent(uri: vscode.Uri): string {
-        return uri.query
-    }
-}
diff --git a/packages/core/src/dynamicResources/awsResourceManager.ts b/packages/core/src/dynamicResources/awsResourceManager.ts
deleted file mode 100644
index 6dbcc64e1e..0000000000
--- a/packages/core/src/dynamicResources/awsResourceManager.ts
+++ /dev/null
@@ -1,239 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { writeFileSync } from 'fs' // eslint-disable-line no-restricted-imports
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { CloudFormationClient } from '../shared/clients/cloudFormation'
-import {
-    getNonexistentFilename,
-    makeTemporaryToolkitFolder,
-    readFileAsString,
-    tryRemoveFolder,
-} from '../shared/filesystemUtilities'
-import { getLogger } from '../shared/logger/logger'
-import { getTabSizeSetting } from '../shared/utilities/editorUtilities'
-import { ResourceNode } from './explorer/nodes/resourceNode'
-import { ResourceTypeNode } from './explorer/nodes/resourceTypeNode'
-import globals from '../shared/extensionGlobals'
-import { fs } from '../shared/fs/fs'
-
-export const resourceFileGlobPattern = '**/*.awsResource.json'
-
-export class AwsResourceManager {
-    private folder: string | undefined
-    private schemas: Map
-    private openResources: Map
-
-    public constructor(private readonly extensionContext: vscode.ExtensionContext) {
-        this.schemas = new Map()
-        this.openResources = new Map()
-    }
-
-    public async new(type: ResourceTypeNode): Promise {
-        const uri = await this.createFile(type.typeName, 'new', getNewResourceJson())
-        await this.registerSchema(type.typeName, uri, type.parent.cloudFormation)
-        this.openResources.set(uri.toString(), type)
-
-        getLogger().debug(`resourceManager: created resource for type ${type.typeName} at ${uri.fsPath}`)
-        const doc = await vscode.workspace.openTextDocument(uri)
-        const editor = await vscode.window.showTextDocument(doc, { preview: false })
-        const pos = editor.document.lineAt(1).range.end
-        editor.selection = new vscode.Selection(pos, pos)
-        return editor
-    }
-
-    public async open(resource: ResourceNode, preview?: boolean): Promise {
-        const typeName = resource.parent.typeName
-        const identifier = resource.identifier
-        const resourceDetails = await resource.parent.cloudControl.getResource({
-            TypeName: typeName,
-            Identifier: identifier,
-        })
-
-        if (resourceDetails.ResourceDescription && resourceDetails.ResourceDescription.Properties) {
-            const formattedModel = formatResourceModel(resourceDetails.ResourceDescription.Properties)
-            const normalizedTypeName = getNormalizedTypeName(typeName)
-            const existing = this.toUri(resource)
-
-            let uri: vscode.Uri
-            if (preview) {
-                uri = vscode.Uri.parse(`awsResource:${identifier}.${normalizedTypeName}.preview.json?${formattedModel}`)
-                getLogger().debug(`resourceManager: opening resource ${identifier} (${typeName}) in preview`)
-            } else {
-                uri = await this.createFile(typeName, identifier, formattedModel)
-                await this.registerSchema(typeName, uri, resource.parent.parent.cloudFormation)
-                getLogger().debug(
-                    `resourceManager: opening resource ${identifier} (${typeName}) in edit at ${uri.fsPath}`
-                )
-            }
-
-            const doc = await vscode.workspace.openTextDocument(uri)
-            if (existing) {
-                await this.close(existing)
-            }
-
-            this.openResources.set(uri.toString(), resource)
-            return await vscode.window.showTextDocument(doc, { preview })
-        } else {
-            throw new Error(
-                `failed to retrieve resource definition ${resource.identifier} (${resource.parent.typeName})`
-            )
-        }
-    }
-
-    public async close(uri: vscode.Uri, isEditorClosed?: boolean): Promise {
-        const path = uri.toString()
-        if (this.openResources.has(path)) {
-            getLogger().debug(`resourceManager: closing ${uri}`)
-
-            if (!isEditorClosed) {
-                // grab focus back to the desired document before closing the active editor
-                await vscode.window.showTextDocument(uri)
-                await vscode.commands.executeCommand('workbench.action.closeActiveEditor')
-            }
-
-            if (uri.scheme === 'file') {
-                await fs.delete(uri.fsPath)
-
-                globals.schemaService.registerMapping({
-                    uri,
-                    type: 'json',
-                    schema: undefined,
-                })
-            }
-
-            this.openResources.delete(path)
-        }
-    }
-
-    public toUri(resource: ResourceNode): vscode.Uri | undefined {
-        const existing = [...this.openResources.entries()]
-            .filter(([_, v]) => {
-                return (
-                    v instanceof ResourceNode &&
-                    v.identifier === resource.identifier &&
-                    v.parent.typeName === resource.parent.typeName &&
-                    v.parent.parent.region === resource.parent.parent.region
-                )
-            })
-            .map(([k]) => k)
-        return existing.length > 0 ? vscode.Uri.parse(existing[0]) : undefined
-    }
-
-    public fromUri(uri: vscode.Uri): ResourceNode | ResourceTypeNode | undefined {
-        return this.openResources.get(uri.toString())
-    }
-
-    public getSchema(typeName: string): TypeSchema | undefined {
-        return this.schemas.get(typeName)?.typeSchema
-    }
-
-    private async initialize(): Promise {
-        if (!this.folder) {
-            try {
-                this.folder = await makeTemporaryToolkitFolder()
-            } catch (err) {
-                getLogger().error(`resourceManager: unable to create resource folder: %O`, err as Error)
-                throw err
-            }
-        }
-    }
-
-    public async dispose(): Promise {
-        if (this.folder) {
-            try {
-                await tryRemoveFolder(this.folder)
-            } catch (err) {
-                getLogger().warn(`resourceManager: unable to remove folder on dispose: %O`, err as Error)
-            }
-        }
-    }
-
-    private async createFile(typeName: string, identifier: string, contents: string): Promise {
-        await this.initialize()
-
-        const normalizedTypeName = getNormalizedTypeName(typeName)
-        const filename = await getNonexistentFilename(
-            this.folder!,
-            encodeURIComponent(identifier),
-            `.${normalizedTypeName}.awsResource.json`
-        )
-        const fullPath = path.join(this.folder!, filename)
-        writeFileSync(fullPath, contents)
-
-        return vscode.Uri.file(fullPath)
-    }
-
-    private async registerSchema(
-        typeName: string,
-        uri: vscode.Uri,
-        cloudFormation: CloudFormationClient
-    ): Promise {
-        await this.initialize()
-        const normalizedTypeName = getNormalizedTypeName(typeName)
-
-        let location = this.schemas.get(typeName)?.location
-        if (!location) {
-            const type = await cloudFormation.describeType(typeName)
-            if (type && type.Schema) {
-                const schemaFile = path.join(
-                    this.extensionContext.globalStorageUri.fsPath,
-                    `${normalizedTypeName}.schema.json`
-                )
-                writeFileSync(schemaFile, JSON.stringify(JSON.parse(type.Schema), undefined, 2))
-                location = vscode.Uri.file(schemaFile)
-                const typeSchema = JSON.parse(await readFileAsString(schemaFile)) as TypeSchema
-                this.schemas.set(typeName, {
-                    location,
-                    typeSchema,
-                })
-            } else {
-                getLogger().warn(`unable to download schema for ${typeName}`)
-                return
-            }
-        }
-
-        globals.schemaService.registerMapping(
-            {
-                uri,
-                type: 'json',
-                schema: location,
-            },
-            // Flush immediately so the onDidOpenTextDocument handler can work.
-            true
-        )
-    }
-}
-
-export function formatResourceModel(input: string): string {
-    return JSON.stringify(JSON.parse(input), undefined, getTabSizeSetting())
-}
-
-export function getNormalizedTypeName(typeName: string): string {
-    return typeName.replace(/::/g, '')
-}
-
-function getNewResourceJson(): string {
-    const spaces = getTabSizeSetting()
-    return `{\n${' '.repeat(spaces)}\n}`
-}
-
-type Schema = {
-    location: vscode.Uri
-    typeSchema: TypeSchema
-}
-
-export interface TypeSchema {
-    typeName?: string
-    description?: string
-    properties?: any
-    primaryIdentifier?: string[]
-    definitions?: any
-    readOnlyProperties?: string[]
-    createOnlyProperties?: string[]
-    writeOnlyProperties?: string[]
-    required?: string[]
-}
diff --git a/packages/core/src/dynamicResources/commands/configure.ts b/packages/core/src/dynamicResources/commands/configure.ts
deleted file mode 100644
index e2ce2a5451..0000000000
--- a/packages/core/src/dynamicResources/commands/configure.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { memoizedGetResourceTypes } from '../model/resources'
-import { fromExtensionManifest } from '../../shared/settings'
-import { ArrayConstructor } from '../../shared/utilities/typeConstructors'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-export class ResourcesSettings extends fromExtensionManifest('aws.resources', {
-    enabledResources: ArrayConstructor(String),
-}) {}
-
-/**
- * Shows a picker menu and stores the selected Resources in user settings.
- *
- * @returns true if the user accepted the picker, false if they canceled it
- */
-export async function configureResources(settings = new ResourcesSettings()): Promise {
-    const window = vscode.window
-    const enabledResources = settings.get('enabledResources', [])
-
-    const quickPickItems: vscode.QuickPickItem[] = []
-    const resourceTypes = memoizedGetResourceTypes().keys()
-    for (const type of resourceTypes) {
-        quickPickItems.push({
-            label: type,
-            picked: enabledResources.includes(type),
-        })
-    }
-
-    const result = await window.showQuickPick(quickPickItems, {
-        placeHolder: localize('aws.resources.resourcesToInclude', 'Select resources to include'),
-        canPickMany: true,
-    })
-
-    if (result) {
-        await settings.update(
-            'enabledResources',
-            result.map((res) => res.label)
-        )
-        telemetry.dynamicresource_selectResources.emit()
-        return true
-    }
-
-    return false
-}
diff --git a/packages/core/src/dynamicResources/commands/copyIdentifier.ts b/packages/core/src/dynamicResources/commands/copyIdentifier.ts
deleted file mode 100644
index 11f1f68499..0000000000
--- a/packages/core/src/dynamicResources/commands/copyIdentifier.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { copyToClipboard } from '../../shared/utilities/messages'
-
-export async function copyIdentifier(typeName: string, identifier: string) {
-    await copyToClipboard(identifier, 'identifier')
-    telemetry.dynamicresource_copyIdentifier.emit({ resourceType: typeName })
-}
diff --git a/packages/core/src/dynamicResources/commands/deleteResource.ts b/packages/core/src/dynamicResources/commands/deleteResource.ts
deleted file mode 100644
index 8ed475aea6..0000000000
--- a/packages/core/src/dynamicResources/commands/deleteResource.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { getLogger } from '../../shared/logger/logger'
-import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages'
-import { CloudControlClient } from '../../shared/clients/cloudControl'
-import globals from '../../shared/extensionGlobals'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { millisecondsSince, Result } from '../../shared/telemetry/telemetry'
-const localize = nls.loadMessageBundle()
-
-export async function deleteResource(
-    cloudControl: CloudControlClient,
-    typeName: string,
-    identifier: string
-): Promise {
-    getLogger().info(`deleteResource called for type ${typeName} identifier ${identifier}`)
-    const ok = await showConfirmationMessage({
-        prompt: localize('aws.resources.deleteResource.prompt', 'Delete resource {0} ({1})?', identifier, typeName),
-        confirm: localize('AWS.generic.delete', 'Delete'),
-        cancel: localize('AWS.generic.cancel', 'Cancel'),
-    })
-    if (!ok) {
-        getLogger().info(`Cancelled delete resource type ${typeName} identifier ${identifier}`)
-        return false
-    }
-
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            let result: Result = 'Succeeded'
-            const startTime = new globals.clock.Date()
-
-            try {
-                progress.report({
-                    message: `Deleting resource ${identifier} (${typeName})...`,
-                })
-
-                await cloudControl.deleteResource({
-                    TypeName: typeName,
-                    Identifier: identifier,
-                })
-
-                getLogger().info(`Deleted resource type ${typeName} identifier ${identifier}`)
-
-                void vscode.window.showInformationMessage(
-                    localize('aws.resources.deleteResource.success', 'Deleted resource {0} ({1})', identifier, typeName)
-                )
-                return true
-            } catch (e) {
-                const error = e as Error
-                if (error.name === 'UnsupportedActionException') {
-                    result = 'Cancelled'
-                    getLogger().warn(
-                        `Resource type ${typeName} does not support DELETE action in ${cloudControl.regionCode}`
-                    )
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'aws.resources.deleteResource.unsupported',
-                            'Resource type {0} does not currently support delete in {1}',
-                            typeName,
-                            cloudControl.regionCode
-                        )
-                    )
-                    return false
-                }
-                result = 'Failed'
-                getLogger().error(`Failed to delete resource type ${typeName} identifier ${identifier}: %s`, e)
-                void showViewLogsMessage(
-                    localize(
-                        'aws.resources.deleteResource.failure',
-                        'Failed to delete resource {0} ({1})',
-                        identifier,
-                        typeName
-                    )
-                )
-                return false
-            } finally {
-                telemetry.dynamicresource_mutateResource.emit({
-                    dynamicResourceOperation: 'Delete',
-                    duration: millisecondsSince(startTime),
-                    resourceType: typeName,
-                    result: result,
-                })
-            }
-        }
-    )
-}
diff --git a/packages/core/src/dynamicResources/commands/openResource.ts b/packages/core/src/dynamicResources/commands/openResource.ts
deleted file mode 100644
index 8b4cb24ca7..0000000000
--- a/packages/core/src/dynamicResources/commands/openResource.ts
+++ /dev/null
@@ -1,132 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { getLogger } from '../../shared/logger/logger'
-import { Result } from '../../shared/telemetry/telemetry'
-import { ResourceNode } from '../explorer/nodes/resourceNode'
-import { AwsResourceManager, TypeSchema } from '../awsResourceManager'
-import { telemetry } from '../../shared/telemetry/telemetry'
-const localize = nls.loadMessageBundle()
-
-export async function openResource(opts: {
-    source: ResourceNode | vscode.Uri
-    preview: boolean
-    resourceManager: AwsResourceManager
-    diagnostics: vscode.DiagnosticCollection
-}): Promise {
-    const resource = opts.source instanceof vscode.Uri ? opts.resourceManager.fromUri(opts.source) : opts.source
-    if (!resource || !(resource instanceof ResourceNode)) {
-        throw new Error('could not resolve resource')
-    }
-    getLogger().info(`openResource called for type ${resource.parent.typeName} identifier ${resource.identifier}`)
-
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            let result: Result = 'Succeeded'
-
-            progress.report({
-                message: `Opening resource ${resource.identifier} (${resource.parent.typeName})...`,
-            })
-
-            try {
-                const editor = await opts.resourceManager.open(resource, opts.preview)
-                if (!opts.preview) {
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'aws.resources.editResource.notice',
-                            'You are editing an AWS resource. Any saved changes will be reflected on the remote resource.'
-                        )
-                    )
-                    const schema = opts.resourceManager.getSchema(resource.parent.typeName)
-                    if (schema) {
-                        const diag = getDiagnostics(schema, editor.document)
-                        opts.diagnostics.set(editor.document.uri, diag)
-                    }
-                }
-            } catch (err) {
-                const error = err as Error
-                const errorMessage = localize(
-                    'AWS.message.error.resources.openResource.failed',
-                    'Failed to open resource {0} ({1})',
-                    resource.identifier,
-                    resource.parent.typeName
-                )
-
-                void vscode.window.showErrorMessage(errorMessage)
-                getLogger().error('Error opening resource: %s', error)
-                result = 'Failed'
-            } finally {
-                telemetry.dynamicresource_getResource.emit({
-                    resourceType: resource.parent.typeName,
-                    result: result,
-                })
-            }
-        }
-    )
-}
-
-export function getDiagnostics(schema: TypeSchema, doc: vscode.TextDocument): vscode.Diagnostic[] {
-    const diagnostics: vscode.Diagnostic[] = []
-    const text = doc.getText()
-    if (schema.createOnlyProperties) {
-        for (const property of schema.createOnlyProperties) {
-            const propertyName = getPropertyName(property)
-            const range = getPropertyRange(propertyName, text, doc)
-            if (range) {
-                diagnostics.push(
-                    new vscode.Diagnostic(
-                        range,
-                        localize(
-                            'AWS.message.information.resources.createOnlyProperty',
-                            '"{0}" is a create-only property and cannot be modified on an existing resource',
-                            propertyName
-                        ),
-                        vscode.DiagnosticSeverity.Information
-                    )
-                )
-            }
-        }
-    }
-
-    if (schema.readOnlyProperties) {
-        for (const property of schema.readOnlyProperties) {
-            const propertyName = getPropertyName(property)
-            const range = getPropertyRange(propertyName, text, doc)
-            if (range) {
-                diagnostics.push(
-                    new vscode.Diagnostic(
-                        range,
-                        localize(
-                            'AWS.message.information.resources.readOnlyProperty',
-                            '"{0}" is a read-only property and cannot be modified',
-                            propertyName
-                        ),
-                        vscode.DiagnosticSeverity.Information
-                    )
-                )
-            }
-        }
-    }
-    return diagnostics
-}
-
-function getPropertyName(property: string) {
-    // the returned format is `/properties/`
-    return property.slice(property.lastIndexOf('/') + 1)
-}
-
-function getPropertyRange(property: string, text: string, doc: vscode.TextDocument): vscode.Range | undefined {
-    const index = text.indexOf(`"${property}":`)
-    if (index > -1) {
-        const start = doc.positionAt(index)
-        return new vscode.Range(start, doc.lineAt(start.line).range.end)
-    }
-}
diff --git a/packages/core/src/dynamicResources/commands/saveResource.ts b/packages/core/src/dynamicResources/commands/saveResource.ts
deleted file mode 100644
index be395bc7f4..0000000000
--- a/packages/core/src/dynamicResources/commands/saveResource.ts
+++ /dev/null
@@ -1,238 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { getLogger } from '../../shared/logger/logger'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { millisecondsSince, Result } from '../../shared/telemetry/telemetry'
-import { compare, Operation } from 'fast-json-patch'
-import { ResourceNode } from '../explorer/nodes/resourceNode'
-import { ResourceTypeNode } from '../explorer/nodes/resourceTypeNode'
-import { AwsResourceManager } from '../awsResourceManager'
-import { CloudControlClient } from '../../shared/clients/cloudControl'
-import { ResourceDescription } from '@aws-sdk/client-cloudcontrol'
-import globals from '../../shared/extensionGlobals'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-const localize = nls.loadMessageBundle()
-
-export async function saveResource(
-    resourceDoc: vscode.TextDocument,
-    resourceManager: AwsResourceManager,
-    diagnostics: vscode.DiagnosticCollection
-): Promise {
-    const resource = resourceManager.fromUri(resourceDoc.uri)
-    if (resource) {
-        try {
-            if (resource instanceof ResourceTypeNode) {
-                getLogger().info(`saveResource called for new resource (type ${resource.typeName})`)
-                const identifier = await createResource(resource.typeName, resourceDoc.getText(), resource.cloudControl)
-                if (identifier) {
-                    resource.clearChildren()
-                    await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', resource)
-                    const resourceNodes = (await resource.getChildren()) as ResourceNode[]
-                    const newNode = resourceNodes.find((node) => node.identifier === identifier)
-                    if (newNode) {
-                        await resourceManager.open(newNode, true)
-                    }
-                    await resourceManager.close(resourceDoc.uri)
-                    diagnostics.delete(resourceDoc.uri)
-                }
-                return true
-            } else if (resource instanceof ResourceNode) {
-                getLogger().info(
-                    `saveResource called for existing resource ${resource.identifier} (type ${resource.parent.typeName}))`
-                )
-                const parent = resource.parent
-                const updated = await updateResource(
-                    parent.typeName,
-                    resource.identifier,
-                    resourceDoc.getText(),
-                    parent.cloudControl
-                )
-                if (updated) {
-                    diagnostics.delete(resourceDoc.uri)
-                    await resourceManager.open(resource, true)
-                    return true
-                }
-            }
-        } catch (e) {
-            const error = e as Error
-            updateDiagnostics(error, resourceDoc, diagnostics)
-        }
-    }
-    return false
-}
-
-export async function createResource(
-    typeName: string,
-    definition: string,
-    cloudControl: CloudControlClient
-): Promise {
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            const startTime = new globals.clock.Date()
-            let result: Result = 'Succeeded'
-
-            try {
-                progress.report({
-                    message: `Creating resource (${typeName})...`,
-                })
-                const result = await cloudControl.createResource({
-                    TypeName: typeName,
-                    DesiredState: JSON.stringify(JSON.parse(definition)),
-                })
-                const identifier = result.ProgressEvent!.Identifier!
-                getLogger().info(`Created resource type ${typeName} identifier ${identifier}`)
-                void vscode.window.showInformationMessage(
-                    localize('aws.resources.createResource.success', 'Created resource {0} ({1})', identifier, typeName)
-                )
-                return identifier
-            } catch (e) {
-                const error = e as Error
-                if (error.name === 'UnsupportedActionException') {
-                    result = 'Cancelled'
-                    getLogger().warn(
-                        `Resource type ${typeName} does not support CREATE action in ${cloudControl.regionCode}`
-                    )
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'aws.resources.createResource.unsupported',
-                            '{0} does not currently support resource creation in {1}',
-                            typeName,
-                            cloudControl.regionCode
-                        )
-                    )
-                } else {
-                    result = 'Failed'
-                    getLogger().error(`Failed to create resource type ${typeName}: %O`, error.message)
-                    void showViewLogsMessage(
-                        localize('aws.resources.createResource.failure', 'Failed to create resource ({0})', typeName)
-                    )
-                    throw e
-                }
-            } finally {
-                telemetry.dynamicresource_mutateResource.emit({
-                    dynamicResourceOperation: 'Create',
-                    duration: millisecondsSince(startTime),
-                    resourceType: typeName,
-                    result: result,
-                })
-            }
-        }
-    )
-}
-
-export async function updateResource(
-    typeName: string,
-    identifier: string,
-    definition: string,
-    cloudControl: CloudControlClient,
-    diff?: Operation[]
-): Promise {
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            const startTime = new globals.clock.Date()
-            let result: Result = 'Succeeded'
-            try {
-                progress.report({
-                    message: `Updating resource ${identifier} (${typeName})...`,
-                })
-
-                const current = await cloudControl.getResource({
-                    TypeName: typeName,
-                    Identifier: identifier,
-                })
-                const patch = diff ?? computeDiff(current.ResourceDescription!, definition)
-
-                if (patch.length === 0) {
-                    result = 'Cancelled'
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'aws.resources.updateResource.noDiff',
-                            'Update cancelled - no diff between local and remote definitions',
-                            identifier,
-                            typeName
-                        )
-                    )
-                    return false
-                }
-
-                await cloudControl.updateResource({
-                    TypeName: typeName,
-                    Identifier: identifier,
-                    PatchDocument: JSON.stringify(patch),
-                })
-                getLogger().info(`Updated resource type ${typeName} identifier ${identifier}`)
-
-                void vscode.window.showInformationMessage(
-                    localize('aws.resources.updateResource.success', 'Updated resource {0} ({1})', identifier, typeName)
-                )
-                return true
-            } catch (e) {
-                const error = e as Error
-                if (error.name === 'UnsupportedActionException') {
-                    result = 'Cancelled'
-                    getLogger().warn(
-                        `Resource type ${typeName} does not support UPDATE action in ${cloudControl.regionCode}`
-                    )
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'aws.resources.createResource.unsupported',
-                            '{0} does not currently support resource updating in {1}',
-                            typeName,
-                            cloudControl.regionCode
-                        )
-                    )
-                    return false
-                } else {
-                    result = 'Failed'
-                    getLogger().error(
-                        `Failed to update resource type ${typeName} identifier ${identifier}: %O`,
-                        error.message
-                    )
-                    void showViewLogsMessage(
-                        localize(
-                            'aws.resources.updateResource.failure',
-                            'Failed to update resource {0} ({1})',
-                            identifier,
-                            typeName
-                        )
-                    )
-                    throw e
-                }
-            } finally {
-                telemetry.dynamicresource_mutateResource.emit({
-                    dynamicResourceOperation: 'Update',
-                    duration: millisecondsSince(startTime),
-                    resourceType: typeName,
-                    result: result,
-                })
-            }
-        }
-    )
-}
-
-function computeDiff(currentDefinition: ResourceDescription, updatedDefinition: string): Operation[] {
-    const current = JSON.parse(currentDefinition.Properties!)
-    const updated = JSON.parse(updatedDefinition)
-    return compare(current, updated)
-}
-
-export function updateDiagnostics(err: Error, doc: vscode.TextDocument, diagnostics: vscode.DiagnosticCollection) {
-    const range = new vscode.Range(doc.positionAt(0), doc.positionAt(doc.getText().length))
-    const diag = new vscode.Diagnostic(range, err.message)
-    diagnostics.set(doc.uri, [diag])
-    void vscode.commands.executeCommand('workbench.actions.view.problems')
-}
diff --git a/packages/core/src/dynamicResources/explorer/nodes/resourceNode.ts b/packages/core/src/dynamicResources/explorer/nodes/resourceNode.ts
deleted file mode 100644
index 28e655abc9..0000000000
--- a/packages/core/src/dynamicResources/explorer/nodes/resourceNode.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-
-import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
-import { ResourceTypeNode } from './resourceTypeNode'
-import { parse, validate } from '@aws-sdk/util-arn-parser'
-
-const localize = nls.loadMessageBundle()
-
-export class ResourceNode extends AWSTreeNodeBase {
-    public constructor(
-        public readonly parent: ResourceTypeNode,
-        public readonly identifier: string,
-        public override contextValue?: string
-    ) {
-        super('')
-        this.contextValue = contextValue ?? 'ResourceNode'
-        const friendlyName = this.getFriendlyName(identifier)
-        this.label = friendlyName
-        this.tooltip = identifier
-        this.command = {
-            title: localize('AWS.generic.preview', 'Preview'),
-            command: 'aws.resources.openResourcePreview',
-            arguments: [this],
-        }
-    }
-
-    private getFriendlyName(identifier: string): string {
-        return validate(identifier) ? parse(identifier).resource : identifier
-    }
-}
diff --git a/packages/core/src/dynamicResources/explorer/nodes/resourceTypeNode.ts b/packages/core/src/dynamicResources/explorer/nodes/resourceTypeNode.ts
deleted file mode 100644
index afc21ff5d1..0000000000
--- a/packages/core/src/dynamicResources/explorer/nodes/resourceTypeNode.ts
+++ /dev/null
@@ -1,146 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ChildNodeLoader, ChildNodePage } from '../../../awsexplorer/childNodeLoader'
-import { CloudControlClient } from '../../../shared/clients/cloudControl'
-import { getLogger } from '../../../shared/logger/logger'
-import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
-import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode'
-import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode'
-import { createErrorItem, makeChildrenNodes, TreeShim } from '../../../shared/treeview/utils'
-import { localize } from '../../../shared/utilities/vsCodeUtils'
-import { ResourcesNode } from './resourcesNode'
-import { ResourceNode } from './resourceNode'
-import { Result } from '../../../shared/telemetry/telemetry'
-import { ResourceDescription } from '@aws-sdk/client-cloudcontrol'
-import { ResourceTypeMetadata } from '../../model/resources'
-import { S3Client } from '../../../shared/clients/s3'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-
-export const contextValueResourceOperations: any = {
-    CREATE: 'Creatable',
-    DELETE: 'Deletable',
-    UPDATE: 'Updatable',
-}
-export const contextValueResource = 'ResourceNode'
-
-const unavailableResource = localize('AWS.explorerNode.resources.unavailable', 'Unavailable in region')
-
-export class ResourceTypeNode extends AWSTreeNodeBase implements LoadMoreNode {
-    private readonly childLoader: ChildNodeLoader = new ChildNodeLoader(this, (token) => this.loadPage(token))
-    private readonly childContextValue: string
-
-    public constructor(
-        public readonly parent: ResourcesNode,
-        public readonly typeName: string,
-        public readonly cloudControl: CloudControlClient,
-        public readonly metadata: ResourceTypeMetadata
-    ) {
-        super(
-            ResourceTypeNode.getFriendlyName(typeName),
-            metadata.available ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None
-        )
-        this.tooltip = typeName
-        const supportedOperations = metadata.operations
-            ? metadata.operations.map((op) => contextValueResourceOperations[op])
-            : Object.values(contextValueResourceOperations)
-
-        if (!metadata.available) {
-            this.contextValue = 'UnavailableResourceTypeNode'
-            this.description = !metadata.available ? unavailableResource : ''
-        } else {
-            const documentedContextValue = metadata.documentation ? 'Documented' : ''
-            const createContextValue = supportedOperations.includes('Creatable') ? 'Creatable' : ''
-            this.contextValue = `${documentedContextValue}${createContextValue}ResourceTypeNode`
-        }
-
-        this.childContextValue = supportedOperations.join('') + contextValueResource
-    }
-
-    public override async getChildren(): Promise {
-        if (!this.metadata.available) {
-            return []
-        }
-        let result: Result = 'Succeeded'
-        const children = await makeChildrenNodes({
-            getChildNodes: async () => this.childLoader.getChildren(),
-            getErrorNode: (error: Error) => {
-                if (error.name === 'UnsupportedActionException') {
-                    result = 'Cancelled'
-                    getLogger().warn(
-                        `Resource type ${this.typeName} does not support LIST action in ${this.parent.region}`
-                    )
-                    return new PlaceholderNode(this, `[${unavailableResource}]`)
-                } else {
-                    result = 'Failed'
-                    return new TreeShim(createErrorItem(error, `Resources: unexpected error: ${error.message}`))
-                }
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.resources.noResources', '[No resources found]')),
-            sort: (nodeA: AWSTreeNodeBase, nodeB: AWSTreeNodeBase) => {
-                if (nodeA instanceof ResourceNode && nodeB instanceof ResourceNode) {
-                    return nodeA.identifier.localeCompare(nodeB.identifier)
-                }
-                return 0
-            },
-        })
-        telemetry.dynamicresource_listResource.emit({
-            resourceType: this.typeName,
-            result: result,
-        })
-        return children
-    }
-
-    public async loadMoreChildren(): Promise {
-        await this.childLoader.loadMoreChildren()
-    }
-
-    public isLoadingMoreChildren(): boolean {
-        return this.childLoader.isLoadingMoreChildren()
-    }
-
-    public clearChildren(): void {
-        this.childLoader.clearChildren()
-    }
-
-    private async loadPage(continuationToken: string | undefined): Promise> {
-        let newResources: ResourceNode[]
-        let nextToken: string | undefined
-
-        // S3::Bucket's resource handler LIST is not regionalized at this time
-        if (this.typeName === 'AWS::S3::Bucket') {
-            const s3 = new S3Client(this.parent.region)
-            const buckets = await s3.listBuckets()
-            newResources = buckets.buckets.map((bucket) => new ResourceNode(this, bucket.Name, this.childContextValue))
-        } else {
-            const response = await this.cloudControl.listResources({
-                TypeName: this.typeName,
-                NextToken: continuationToken,
-            })
-
-            newResources = response.ResourceDescriptions
-                ? response.ResourceDescriptions.reduce((accumulator: ResourceNode[], current: ResourceDescription) => {
-                      if (current.Identifier) {
-                          accumulator.push(new ResourceNode(this, current.Identifier, this.childContextValue))
-                      }
-                      return accumulator
-                  }, [])
-                : []
-            nextToken = response.NextToken
-        }
-
-        getLogger().debug(`Loaded resources: %O`, newResources)
-        return {
-            newContinuationToken: nextToken,
-            newChildren: [...newResources],
-        }
-    }
-
-    private static getFriendlyName(typeName: string): string {
-        return typeName.startsWith('AWS::') ? typeName.slice(5) : typeName
-    }
-}
diff --git a/packages/core/src/dynamicResources/explorer/nodes/resourcesNode.ts b/packages/core/src/dynamicResources/explorer/nodes/resourcesNode.ts
deleted file mode 100644
index 65cc8c09d3..0000000000
--- a/packages/core/src/dynamicResources/explorer/nodes/resourcesNode.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { CloudFormationClient } from '../../../shared/clients/cloudFormation'
-import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode'
-import { makeChildrenNodes } from '../../../shared/treeview/utils'
-import { toArrayAsync, updateInPlace } from '../../../shared/utilities/collectionUtils'
-import { ResourceTypeNode } from './resourceTypeNode'
-import { CloudControlClient } from '../../../shared/clients/cloudControl'
-import { memoizedGetResourceTypes, ResourceTypeMetadata } from '../../model/resources'
-import { ResourcesSettings } from '../../commands/configure'
-import { TypeSummary } from '@aws-sdk/client-cloudformation'
-
-const localize = nls.loadMessageBundle()
-
-export class ResourcesNode extends AWSTreeNodeBase {
-    private readonly resourceTypeNodes: Map
-
-    public constructor(
-        public readonly region: string,
-        public readonly cloudFormation: CloudFormationClient = new CloudFormationClient(region),
-        private readonly cloudControl: CloudControlClient = new CloudControlClient(region),
-        private readonly settings = new ResourcesSettings()
-    ) {
-        super(localize('AWS.explorerNode.resources.label', 'Resources'), vscode.TreeItemCollapsibleState.Collapsed)
-        this.resourceTypeNodes = new Map()
-        this.contextValue = 'resourcesRootNode'
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-                return [...this.resourceTypeNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () => {
-                const placeholder = new PlaceholderNode(
-                    this,
-                    localize('AWS.explorerNode.resources.noResourceTypes', '[Enable resource types...]')
-                )
-                placeholder.command = {
-                    title: localize('AWS.command.resources.configure', 'Show Resources....'),
-                    command: 'aws.resources.configure',
-                    arguments: [this],
-                }
-                return placeholder
-            },
-            sort: (nodeA: ResourceTypeNode, nodeB: ResourceTypeNode) => nodeA.typeName.localeCompare(nodeB.typeName),
-        })
-    }
-
-    public async updateChildren(): Promise {
-        const resourceTypes = memoizedGetResourceTypes()
-        const enabledResources = this.settings.get('enabledResources', [])
-
-        // Use the most recently update type definition per-type
-        const types = await toArrayAsync(this.cloudFormation.listTypes())
-        types.sort((a, b) => (a.LastUpdated?.getTime() ?? 0) - (b.LastUpdated?.getTime() ?? 0))
-
-        const availableTypes: Map = new Map()
-        for (const type of types) {
-            if (type.TypeName) {
-                availableTypes.set(type.TypeName!, type)
-            }
-        }
-
-        updateInPlace(
-            this.resourceTypeNodes,
-            enabledResources,
-            (key) => this.resourceTypeNodes.get(key)!.clearChildren(),
-            (key) => {
-                const metadata = resourceTypes.get(key) ?? ({} as ResourceTypeMetadata)
-                metadata.available = availableTypes.has(key)
-                return new ResourceTypeNode(this, key, this.cloudControl, metadata)
-            }
-        )
-    }
-}
diff --git a/packages/core/src/dynamicResources/model/resources.ts b/packages/core/src/dynamicResources/model/resources.ts
deleted file mode 100644
index fcf92a3255..0000000000
--- a/packages/core/src/dynamicResources/model/resources.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import supportedResources = require('./supported_resources.json')
-
-export const memoizedGetResourceTypes = memoize(getResourceTypes)
-
-export function getResourceTypes(resources: any = supportedResources): Map {
-    const typesNames = Object.keys(resources)
-    const resourceTypes = new Map()
-    for (const typeName of typesNames) {
-        const metadata = resources[typeName as keyof typeof resources] as ResourceTypeMetadata
-        if (metadata.operations?.includes('LIST')) {
-            resourceTypes.set(typeName, metadata)
-        }
-    }
-    return resourceTypes
-}
-
-function memoize R, R>(fn: F): F {
-    const store: { [key: string]: R } = {}
-    return ((...args) => (store[JSON.stringify(args)] ??= fn(...args))) as F
-}
-
-export interface ResourceTypeMetadata {
-    [x: string]: any
-    operations: string[]
-    documentation: string
-    available: boolean
-}
diff --git a/packages/core/src/dynamicResources/model/supported_resources.json b/packages/core/src/dynamicResources/model/supported_resources.json
deleted file mode 100644
index 617acbc775..0000000000
--- a/packages/core/src/dynamicResources/model/supported_resources.json
+++ /dev/null
@@ -1,2744 +0,0 @@
-{
-    "AWS::ACMPCA::Certificate": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificate.html"
-    },
-    "AWS::ACMPCA::CertificateAuthority": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):acm-pca:(?[a-z0-9-]+):(?[0-9]{12}):certificate-authority/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificateauthority.html"
-    },
-    "AWS::ACMPCA::CertificateAuthorityActivation": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-certificateauthorityactivation.html"
-    },
-    "AWS::ACMPCA::Permission": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-acmpca-permission.html"
-    },
-    "AWS::APS::RuleGroupsNamespace": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-aps-rulegroupsnamespace.html"
-    },
-    "AWS::APS::Workspace": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-aps-workspace.html"
-    },
-    "AWS::AccessAnalyzer::Analyzer": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):access-analyzer:(?[a-z0-9-]+):(?[0-9]{12}):analyzer/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-accessanalyzer-analyzer.html"
-    },
-    "AWS::Amplify::App": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amplify-app.html"
-    },
-    "AWS::Amplify::Branch": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amplify-branch.html"
-    },
-    "AWS::Amplify::Domain": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amplify-domain.html"
-    },
-    "AWS::AmplifyUIBuilder::Component": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amplifyuibuilder-component.html"
-    },
-    "AWS::AmplifyUIBuilder::Theme": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-amplifyuibuilder-theme.html"
-    },
-    "AWS::ApiGateway::Account": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/account/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-account.html"
-    },
-    "AWS::ApiGateway::ApiKey": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/apikeys/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-apikey.html"
-    },
-    "AWS::ApiGateway::Authorizer": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/authorizers/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-authorizer.html"
-    },
-    "AWS::ApiGateway::BasePathMapping": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/domainnames/(?[^/:]+)/basepathmappings/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-basepathmapping.html"
-    },
-    "AWS::ApiGateway::ClientCertificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/clientcertificates/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-clientcertificate.html"
-    },
-    "AWS::ApiGateway::Deployment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/deployments/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-deployment.html"
-    },
-    "AWS::ApiGateway::DocumentationPart": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/documentation/parts/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-documentationpart.html"
-    },
-    "AWS::ApiGateway::DocumentationVersion": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/documentation/versions/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-documentationversion.html"
-    },
-    "AWS::ApiGateway::DomainName": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/domainnames/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-domainname.html"
-    },
-    "AWS::ApiGateway::Method": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/resources/(?[^/:]+)/methods/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-method.html"
-    },
-    "AWS::ApiGateway::Model": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/models/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-model.html"
-    },
-    "AWS::ApiGateway::RequestValidator": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/requestvalidators/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-requestvalidator.html"
-    },
-    "AWS::ApiGateway::Resource": {
-        "operations": ["READ", "CREATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/resources/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-resource.html"
-    },
-    "AWS::ApiGateway::Stage": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/restapis/(?[^/:]+)/stages/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html"
-    },
-    "AWS::ApiGateway::UsagePlan": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/usageplans/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-usageplan.html"
-    },
-    "AWS::ApiGateway::UsagePlanKey": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):apigateway:(?[a-z0-9-]+)::/usageplans/(?[^/:]+)/keys/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-usageplankey.html"
-    },
-    "AWS::ApiGatewayV2::VpcLink": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigatewayv2-vpclink.html"
-    },
-    "AWS::AppFlow::ConnectorProfile": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):appflow:(?[a-z0-9-]+):(?[0-9]{12}):connectorprofile/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appflow-connectorprofile.html"
-    },
-    "AWS::AppFlow::Flow": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):appflow:(?[a-z0-9-]+):(?[0-9]{12}):flow/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appflow-flow.html"
-    },
-    "AWS::AppIntegrations::DataIntegration": {
-        "operations": ["CREATE", "READ", "LIST", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):app-integrations:(?[a-z0-9-]+):(?[0-9]{12}):data-integration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appintegrations-dataintegration.html"
-    },
-    "AWS::AppIntegrations::EventIntegration": {
-        "operations": ["CREATE", "READ", "LIST", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):app-integrations:(?[a-z0-9-]+):(?[0-9]{12}):event-integration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appintegrations-eventintegration.html"
-    },
-    "AWS::AppRunner::ObservabilityConfiguration": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apprunner:(?[a-z0-9-]+):(?[0-9]{12}):observabilityconfiguration/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apprunner-observabilityconfiguration.html"
-    },
-    "AWS::AppRunner::Service": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apprunner:(?[a-z0-9-]+):(?[0-9]{12}):service/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apprunner-service.html"
-    },
-    "AWS::AppRunner::VpcConnector": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):apprunner:(?[a-z0-9-]+):(?[0-9]{12}):vpcconnector/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apprunner-vpcconnector.html"
-    },
-    "AWS::AppStream::AppBlock": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):appstream:(?[a-z0-9-]+):(?[0-9]{12}):app-block/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-appblock.html"
-    },
-    "AWS::AppStream::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):appstream:(?[a-z0-9-]+):(?[0-9]{12}):application/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-application.html"
-    },
-    "AWS::AppStream::ApplicationEntitlementAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationentitlementassociation.html"
-    },
-    "AWS::AppStream::ApplicationFleetAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-applicationfleetassociation.html"
-    },
-    "AWS::AppStream::DirectoryConfig": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-directoryconfig.html"
-    },
-    "AWS::AppStream::Entitlement": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-entitlement.html"
-    },
-    "AWS::AppStream::ImageBuilder": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):appstream:(?[a-z0-9-]+):(?[0-9]{12}):image-builder/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appstream-imagebuilder.html"
-    },
-    "AWS::AppSync::DomainName": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainname.html"
-    },
-    "AWS::AppSync::DomainNameApiAssociation": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-appsync-domainnameapiassociation.html"
-    },
-    "AWS::ApplicationInsights::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-applicationinsights-application.html"
-    },
-    "AWS::Athena::DataCatalog": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):athena:(?[a-z0-9-]+):(?[0-9]{12}):datacatalog/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-datacatalog.html"
-    },
-    "AWS::Athena::NamedQuery": {
-        "operations": ["CREATE", "READ", "LIST", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-namedquery.html"
-    },
-    "AWS::Athena::PreparedStatement": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-preparedstatement.html"
-    },
-    "AWS::Athena::WorkGroup": {
-        "operations": ["CREATE", "READ", "LIST", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):athena:(?[a-z0-9-]+):(?[0-9]{12}):workgroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-athena-workgroup.html"
-    },
-    "AWS::AuditManager::Assessment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):auditmanager:(?[a-z0-9-]+):(?[0-9]{12}):assessment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-auditmanager-assessment.html"
-    },
-    "AWS::AutoScaling::LaunchConfiguration": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):autoscaling:(?[a-z0-9-]+):(?[0-9]{12}):launchConfiguration:(?[^/:]+):launchConfigurationName/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-launchconfiguration.html"
-    },
-    "AWS::AutoScaling::LifecycleHook": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-lifecyclehook.html"
-    },
-    "AWS::AutoScaling::ScalingPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-scalingpolicy.html"
-    },
-    "AWS::AutoScaling::WarmPool": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-autoscaling-warmpool.html"
-    },
-    "AWS::Backup::BackupPlan": {
-        "operations": ["READ", "CREATE", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):backup:(?[a-z0-9-]+):(?[0-9]{12}):backup-plan:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-backupplan.html"
-    },
-    "AWS::Backup::BackupSelection": {
-        "operations": ["DELETE", "READ", "CREATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-backupselection.html"
-    },
-    "AWS::Backup::BackupVault": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):backup:(?[a-z0-9-]+):(?[0-9]{12}):backup-vault:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-backupvault.html"
-    },
-    "AWS::Backup::Framework": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):backup:(?[a-z0-9-]+):(?[0-9]{12}):framework:(?[^/:]+)-(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-framework.html"
-    },
-    "AWS::Backup::ReportPlan": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):backup:(?[a-z0-9-]+):(?[0-9]{12}):report-plan:(?[^/:]+)-(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-backup-reportplan.html"
-    },
-    "AWS::Batch::ComputeEnvironment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):batch:(?[a-z0-9-]+):(?[0-9]{12}):compute-environment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-computeenvironment.html"
-    },
-    "AWS::Batch::JobQueue": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):batch:(?[a-z0-9-]+):(?[0-9]{12}):job-queue/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-jobqueue.html"
-    },
-    "AWS::Batch::SchedulingPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):batch:(?[a-z0-9-]+):(?[0-9]{12}):scheduling-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-batch-schedulingpolicy.html"
-    },
-    "AWS::Budgets::BudgetsAction": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-budgets-budgetsaction.html"
-    },
-    "AWS::CE::CostCategory": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ce::(?[0-9]{12}):costcategory/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ce-costcategory.html"
-    },
-    "AWS::Cassandra::Keyspace": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):cassandra:(?[a-z0-9-]+):(?[0-9]{12}):/keyspace/(?[^/:]+)/",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cassandra-keyspace.html"
-    },
-    "AWS::Cassandra::Table": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):cassandra:(?[a-z0-9-]+):(?[0-9]{12}):/keyspace/(?[^/:]+)/table/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cassandra-table.html"
-    },
-    "AWS::CertificateManager::Account": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-certificatemanager-account.html"
-    },
-    "AWS::Chatbot::SlackChannelConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-chatbot-slackchannelconfiguration.html"
-    },
-    "AWS::CloudFormation::HookDefaultVersion": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-hookdefaultversion.html"
-    },
-    "AWS::CloudFormation::HookTypeConfig": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-hooktypeconfig.html"
-    },
-    "AWS::CloudFormation::HookVersion": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-hookversion.html"
-    },
-    "AWS::CloudFormation::ModuleDefaultVersion": {
-        "operations": ["CREATE", "DELETE", "READ", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-moduledefaultversion.html"
-    },
-    "AWS::CloudFormation::ModuleVersion": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-moduleversion.html"
-    },
-    "AWS::CloudFormation::PublicTypeVersion": {
-        "operations": ["CREATE", "DELETE", "READ", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-publictypeversion.html"
-    },
-    "AWS::CloudFormation::Publisher": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-publisher.html"
-    },
-    "AWS::CloudFormation::ResourceDefaultVersion": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-resourcedefaultversion.html"
-    },
-    "AWS::CloudFormation::ResourceVersion": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-resourceversion.html"
-    },
-    "AWS::CloudFormation::StackSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):cloudformation:(?[a-z0-9-]+):(?[0-9]{12}):stackset/(?[^/:]+):(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-stackset.html"
-    },
-    "AWS::CloudFormation::TypeActivation": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudformation-typeactivation.html"
-    },
-    "AWS::CloudFront::CachePolicy": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):cache-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-cachepolicy.html"
-    },
-    "AWS::CloudFront::CloudFrontOriginAccessIdentity": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-cloudfrontoriginaccessidentity.html"
-    },
-    "AWS::CloudFront::Distribution": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):distribution/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-distribution.html"
-    },
-    "AWS::CloudFront::Function": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):function/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-function.html"
-    },
-    "AWS::CloudFront::KeyGroup": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-keygroup.html"
-    },
-    "AWS::CloudFront::OriginAccessControl": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"]
-    },
-    "AWS::CloudFront::OriginRequestPolicy": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):origin-request-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-originrequestpolicy.html"
-    },
-    "AWS::CloudFront::PublicKey": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-publickey.html"
-    },
-    "AWS::CloudFront::RealtimeLogConfig": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):realtime-log-config/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-realtimelogconfig.html"
-    },
-    "AWS::CloudFront::ResponseHeadersPolicy": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):cloudfront::(?[0-9]{12}):response-headers-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudfront-responseheaderspolicy.html"
-    },
-    "AWS::CloudTrail::EventDataStore": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):cloudtrail:(?[a-z0-9-]+):(?[0-9]{12}):eventdatastore/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudtrail-eventdatastore.html"
-    },
-    "AWS::CloudTrail::Trail": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):cloudtrail:(?[a-z0-9-]+):(?[0-9]{12}):trail/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudtrail-trail.html"
-    },
-    "AWS::CloudWatch::CompositeAlarm": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-compositealarm.html"
-    },
-    "AWS::CloudWatch::MetricStream": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "LIST", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):cloudwatch:(?[a-z0-9-]+):(?[0-9]{12}):metric-stream/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-cloudwatch-metricstream.html"
-    },
-    "AWS::CodeArtifact::Domain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):codeartifact:(?[a-z0-9-]+):(?[0-9]{12}):domain/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-domain.html"
-    },
-    "AWS::CodeArtifact::Repository": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):codeartifact:(?[a-z0-9-]+):(?[0-9]{12}):repository/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeartifact-repository.html"
-    },
-    "AWS::CodeGuruProfiler::ProfilingGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):codeguru-profiler:(?[a-z0-9-]+):(?[0-9]{12}):profilingGroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codeguruprofiler-profilinggroup.html"
-    },
-    "AWS::CodeGuruReviewer::RepositoryAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):codeguru-reviewer:(?[a-z0-9-]+):(?[0-9]{12}):association:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codegurureviewer-repositoryassociation.html"
-    },
-    "AWS::CodeStarConnections::Connection": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):codestar-connections:(?[a-z0-9-]+):(?[0-9]{12}):connection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarconnections-connection.html"
-    },
-    "AWS::CodeStarNotifications::NotificationRule": {
-        "operations": ["CREATE", "LIST", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):codestar-notifications:(?[a-z0-9-]+):(?[0-9]{12}):notificationrule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codestarnotifications-notificationrule.html"
-    },
-    "AWS::Config::AggregationAuthorization": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):config:(?[a-z0-9-]+):(?[0-9]{12}):aggregation-authorization/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-aggregationauthorization.html"
-    },
-    "AWS::Config::ConfigurationAggregator": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):config:(?[a-z0-9-]+):(?[0-9]{12}):config-aggregator/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-configurationaggregator.html"
-    },
-    "AWS::Config::ConformancePack": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):config:(?[a-z0-9-]+):(?[0-9]{12}):conformance-pack/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-conformancepack.html"
-    },
-    "AWS::Config::OrganizationConformancePack": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):config:(?[a-z0-9-]+):(?[0-9]{12}):organization-conformance-pack/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-organizationconformancepack.html"
-    },
-    "AWS::Config::StoredQuery": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):config:(?[a-z0-9-]+):(?[0-9]{12}):stored-query/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-config-storedquery.html"
-    },
-    "AWS::Connect::ContactFlow": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/contact-flow/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-contactflow.html"
-    },
-    "AWS::Connect::ContactFlowModule": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/flow-module/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-contactflowmodule.html"
-    },
-    "AWS::Connect::HoursOfOperation": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/operating-hours/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-hoursofoperation.html"
-    },
-    "AWS::Connect::Instance": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-instance.html"
-    },
-    "AWS::Connect::InstanceStorageConfig": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-instancestorageconfig.html"
-    },
-    "AWS::Connect::PhoneNumber": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):phone-number/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-phonenumber.html"
-    },
-    "AWS::Connect::QuickConnect": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/transfer-destination/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-quickconnect.html"
-    },
-    "AWS::Connect::TaskTemplate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/task-template/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-tasktemplate.html"
-    },
-    "AWS::Connect::User": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect:(?[a-z0-9-]+):(?[0-9]{12}):instance/(?[^/:]+)/agent/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-user.html"
-    },
-    "AWS::Connect::UserHierarchyGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connect-userhierarchygroup.html"
-    },
-    "AWS::ConnectCampaigns::Campaign": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):connect-campaigns:(?[a-z0-9-]+):(?[0-9]{12}):campaign/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-connectcampaigns-campaign.html"
-    },
-    "AWS::ControlTower::EnabledControl": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-controltower-enabledcontrol.html"
-    },
-    "AWS::CustomerProfiles::Domain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-customerprofiles-domain.html"
-    },
-    "AWS::CustomerProfiles::Integration": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-customerprofiles-integration.html"
-    },
-    "AWS::CustomerProfiles::ObjectType": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-customerprofiles-objecttype.html"
-    },
-    "AWS::DataBrew::Dataset": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):dataset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-dataset.html"
-    },
-    "AWS::DataBrew::Job": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):job/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-job.html"
-    },
-    "AWS::DataBrew::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):project/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-project.html"
-    },
-    "AWS::DataBrew::Recipe": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):recipe/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-recipe.html"
-    },
-    "AWS::DataBrew::Ruleset": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):ruleset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-ruleset.html"
-    },
-    "AWS::DataBrew::Schedule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):databrew:(?[a-z0-9-]+):(?[0-9]{12}):schedule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-databrew-schedule.html"
-    },
-    "AWS::DataSync::Agent": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):datasync:(?[a-z0-9-]+):(?[^/:]+):agent/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-agent.html"
-    },
-    "AWS::DataSync::LocationEFS": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationefs.html"
-    },
-    "AWS::DataSync::LocationFSxLustre": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationfsxlustre.html"
-    },
-    "AWS::DataSync::LocationFSxONTAP": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationfsxontap.html"
-    },
-    "AWS::DataSync::LocationFSxOpenZFS": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationfsxopenzfs.html"
-    },
-    "AWS::DataSync::LocationFSxWindows": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationfsxwindows.html"
-    },
-    "AWS::DataSync::LocationHDFS": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationhdfs.html"
-    },
-    "AWS::DataSync::LocationNFS": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationnfs.html"
-    },
-    "AWS::DataSync::LocationObjectStorage": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationobjectstorage.html"
-    },
-    "AWS::DataSync::LocationS3": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locations3.html"
-    },
-    "AWS::DataSync::LocationSMB": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-locationsmb.html"
-    },
-    "AWS::DataSync::Task": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):datasync:(?[a-z0-9-]+):(?[^/:]+):task/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-datasync-task.html"
-    },
-    "AWS::Detective::Graph": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):detective:(?[a-z0-9-]+):(?[0-9]{12}):graph:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-detective-graph.html"
-    },
-    "AWS::Detective::MemberInvitation": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-detective-memberinvitation.html"
-    },
-    "AWS::DevOpsGuru::NotificationChannel": {
-        "operations": ["CREATE", "LIST", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devopsguru-notificationchannel.html"
-    },
-    "AWS::DevOpsGuru::ResourceCollection": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devopsguru-resourcecollection.html"
-    },
-    "AWS::DeviceFarm::DevicePool": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):devicepool:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-devicepool.html"
-    },
-    "AWS::DeviceFarm::InstanceProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):instanceprofile:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-instanceprofile.html"
-    },
-    "AWS::DeviceFarm::NetworkProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):networkprofile:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-networkprofile.html"
-    },
-    "AWS::DeviceFarm::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):project:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-project.html"
-    },
-    "AWS::DeviceFarm::TestGridProject": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):testgrid-project:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-testgridproject.html"
-    },
-    "AWS::DeviceFarm::VPCEConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):devicefarm:(?[a-z0-9-]+):(?[0-9]{12}):vpceconfiguration:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-devicefarm-vpceconfiguration.html"
-    },
-    "AWS::DynamoDB::GlobalTable": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):dynamodb::(?[0-9]{12}):global-table/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-globaltable.html"
-    },
-    "AWS::DynamoDB::Table": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):dynamodb:(?[a-z0-9-]+):(?[0-9]{12}):table/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html"
-    },
-    "AWS::EC2::CapacityReservation": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):capacity-reservation/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservation.html"
-    },
-    "AWS::EC2::CapacityReservationFleet": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):capacity-reservation-fleet/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-capacityreservationfleet.html"
-    },
-    "AWS::EC2::CarrierGateway": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):carrier-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-carriergateway.html"
-    },
-    "AWS::EC2::CustomerGateway": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):customer-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-customergateway.html"
-    },
-    "AWS::EC2::DHCPOptions": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):dhcp-options/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-dhcpoptions.html"
-    },
-    "AWS::EC2::EC2Fleet": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ec2fleet.html"
-    },
-    "AWS::EC2::EgressOnlyInternetGateway": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):egress-only-internet-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-egressonlyinternetgateway.html"
-    },
-    "AWS::EC2::EnclaveCertificateIamRoleAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-enclavecertificateiamroleassociation.html"
-    },
-    "AWS::EC2::FlowLog": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-flowlog.html"
-    },
-    "AWS::EC2::GatewayRouteTableAssociation": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-gatewayroutetableassociation.html"
-    },
-    "AWS::EC2::Host": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):dedicated-host/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-host.html"
-    },
-    "AWS::EC2::IPAM": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2::(?[0-9]{12}):ipam/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ipam.html"
-    },
-    "AWS::EC2::IPAMAllocation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ipamallocation.html"
-    },
-    "AWS::EC2::IPAMPool": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2::(?[0-9]{12}):ipam-pool/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ipampool.html"
-    },
-    "AWS::EC2::IPAMScope": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2::(?[0-9]{12}):ipam-scope/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-ipamscope.html"
-    },
-    "AWS::EC2::InternetGateway": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):internet-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html"
-    },
-    "AWS::EC2::KeyPair": {
-        "operations": ["CREATE", "READ", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):key-pair/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-keypair.html"
-    },
-    "AWS::EC2::LocalGatewayRoute": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroute.html"
-    },
-    "AWS::EC2::LocalGatewayRouteTableVPCAssociation": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):local-gateway-route-table-vpc-association/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-localgatewayroutetablevpcassociation.html"
-    },
-    "AWS::EC2::NatGateway": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):natgateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html"
-    },
-    "AWS::EC2::NetworkAcl": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-acl/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkacl.html"
-    },
-    "AWS::EC2::NetworkInsightsAccessScope": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-insights-access-scope/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinsightsaccessscope.html"
-    },
-    "AWS::EC2::NetworkInsightsAccessScopeAnalysis": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-insights-access-scope-analysis/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinsightsaccessscopeanalysis.html"
-    },
-    "AWS::EC2::NetworkInsightsAnalysis": {
-        "operations": ["CREATE", "DELETE", "READ", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-insights-analysis/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinsightsanalysis.html"
-    },
-    "AWS::EC2::NetworkInsightsPath": {
-        "operations": ["CREATE", "DELETE", "READ", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-insights-path/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinsightspath.html"
-    },
-    "AWS::EC2::NetworkInterface": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):network-interface/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html"
-    },
-    "AWS::EC2::PlacementGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):placement-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-placementgroup.html"
-    },
-    "AWS::EC2::PrefixList": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):prefix-list/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-prefixlist.html"
-    },
-    "AWS::EC2::RouteTable": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):route-table/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-routetable.html"
-    },
-    "AWS::EC2::SpotFleet": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-spotfleet.html"
-    },
-    "AWS::EC2::Subnet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):subnet/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html"
-    },
-    "AWS::EC2::SubnetNetworkAclAssociation": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-network-acl-assoc.html"
-    },
-    "AWS::EC2::SubnetRouteTableAssociation": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnetroutetableassociation.html"
-    },
-    "AWS::EC2::TransitGateway": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):transit-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgateway.html"
-    },
-    "AWS::EC2::TransitGatewayAttachment": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):transit-gateway-attachment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewayattachment.html"
-    },
-    "AWS::EC2::TransitGatewayConnect": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewayconnect.html"
-    },
-    "AWS::EC2::TransitGatewayMulticastDomain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):transit-gateway-multicast-domain/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaymulticastdomain.html"
-    },
-    "AWS::EC2::TransitGatewayMulticastDomainAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaymulticastdomainassociation.html"
-    },
-    "AWS::EC2::TransitGatewayMulticastGroupMember": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaymulticastgroupmember.html"
-    },
-    "AWS::EC2::TransitGatewayMulticastGroupSource": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaymulticastgroupsource.html"
-    },
-    "AWS::EC2::TransitGatewayPeeringAttachment": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewaypeeringattachment.html"
-    },
-    "AWS::EC2::TransitGatewayVpcAttachment": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-transitgatewayvpcattachment.html"
-    },
-    "AWS::EC2::VPC": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):vpc/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html"
-    },
-    "AWS::EC2::VPCDHCPOptionsAssociation": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "READ", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcdhcpoptionsassociation.html"
-    },
-    "AWS::EC2::VPCPeeringConnection": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):vpc-peering-connection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcpeeringconnection.html"
-    },
-    "AWS::EC2::VPNConnection": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "READ", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):vpn-connection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpn-connection.html"
-    },
-    "AWS::EC2::VPNGateway": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ec2:(?[a-z0-9-]+):(?[0-9]{12}):vpn-gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpngateway.html"
-    },
-    "AWS::ECR::PullThroughCacheRule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-pullthroughcacherule.html"
-    },
-    "AWS::ECR::RegistryPolicy": {
-        "operations": ["CREATE", "READ", "LIST", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-registrypolicy.html"
-    },
-    "AWS::ECR::ReplicationConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-replicationconfiguration.html"
-    },
-    "AWS::ECR::Repository": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ecr:(?[a-z0-9-]+):(?[0-9]{12}):repository/(?[^:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecr-repository.html"
-    },
-    "AWS::ECS::CapacityProvider": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ecs:(?[a-z0-9-]+):(?[0-9]{12}):capacity-provider/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-capacityprovider.html"
-    },
-    "AWS::ECS::Cluster": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ecs:(?[a-z0-9-]+):(?[0-9]{12}):cluster/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html"
-    },
-    "AWS::ECS::ClusterCapacityProviderAssociations": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-clustercapacityproviderassociations.html"
-    },
-    "AWS::ECS::PrimaryTaskSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-primarytaskset.html"
-    },
-    "AWS::ECS::Service": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ecs:(?[a-z0-9-]+):(?[0-9]{12}):service/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-service.html"
-    },
-    "AWS::ECS::TaskDefinition": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ecs:(?[a-z0-9-]+):(?[0-9]{12}):task-definition/(?[^/:]+):(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html"
-    },
-    "AWS::ECS::TaskSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ecs:(?[a-z0-9-]+):(?[0-9]{12}):task-set/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html"
-    },
-    "AWS::EFS::AccessPoint": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):elasticfilesystem:(?[a-z0-9-]+):(?[0-9]{12}):access-point/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-accesspoint.html"
-    },
-    "AWS::EFS::FileSystem": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticfilesystem:(?[a-z0-9-]+):(?[0-9]{12}):file-system/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-filesystem.html"
-    },
-    "AWS::EFS::MountTarget": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-efs-mounttarget.html"
-    },
-    "AWS::EKS::Addon": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):eks:(?[a-z0-9-]+):(?[0-9]{12}):addon/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-addon.html"
-    },
-    "AWS::EKS::Cluster": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):eks:(?[a-z0-9-]+):(?[0-9]{12}):cluster/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html"
-    },
-    "AWS::EKS::FargateProfile": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):eks:(?[a-z0-9-]+):(?[0-9]{12}):fargateprofile/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-fargateprofile.html"
-    },
-    "AWS::EKS::IdentityProviderConfig": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):eks:(?[a-z0-9-]+):(?[0-9]{12}):identityproviderconfig/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-identityproviderconfig.html"
-    },
-    "AWS::EKS::Nodegroup": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):eks:(?[a-z0-9-]+):(?[0-9]{12}):nodegroup/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-nodegroup.html"
-    },
-    "AWS::EMR::SecurityConfiguration": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-emr-securityconfiguration.html"
-    },
-    "AWS::EMR::Studio": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticmapreduce:(?[a-z0-9-]+):(?[0-9]{12}):studio/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-emr-studio.html"
-    },
-    "AWS::EMR::StudioSessionMapping": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-emr-studiosessionmapping.html"
-    },
-    "AWS::EMRContainers::VirtualCluster": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):emr-containers:(?[a-z0-9-]+):(?[0-9]{12}):/virtualclusters/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-emrcontainers-virtualcluster.html"
-    },
-    "AWS::EMRServerless::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):emr-serverless:(?[a-z0-9-]+):(?[0-9]{12}):/applications/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-emrserverless-application.html"
-    },
-    "AWS::ElastiCache::GlobalReplicationGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticache::(?[0-9]{12}):globalreplicationgroup:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-globalreplicationgroup.html"
-    },
-    "AWS::ElastiCache::User": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticache:(?[a-z0-9-]+):(?[0-9]{12}):user:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-user.html"
-    },
-    "AWS::ElastiCache::UserGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticache:(?[a-z0-9-]+):(?[0-9]{12}):usergroup:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticache-usergroup.html"
-    },
-    "AWS::ElasticBeanstalk::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):elasticbeanstalk:(?[a-z0-9-]+):(?[0-9]{12}):application/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticbeanstalk-application.html"
-    },
-    "AWS::ElasticLoadBalancingV2::Listener": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listener.html"
-    },
-    "AWS::ElasticLoadBalancingV2::ListenerRule": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-elasticloadbalancingv2-listenerrule.html"
-    },
-    "AWS::EventSchemas::RegistryPolicy": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eventschemas-registrypolicy.html"
-    },
-    "AWS::Events::ApiDestination": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):events:(?[a-z0-9-]+):(?[0-9]{12}):api-destination/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-apidestination.html"
-    },
-    "AWS::Events::Archive": {
-        "operations": ["CREATE", "DELETE", "LIST", "UPDATE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):events:(?[a-z0-9-]+):(?[0-9]{12}):archive/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-archive.html"
-    },
-    "AWS::Events::Connection": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):events:(?[a-z0-9-]+):(?[0-9]{12}):connection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-connection.html"
-    },
-    "AWS::Events::Endpoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):events:(?[a-z0-9-]+):(?[0-9]{12}):endpoint/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-endpoint.html"
-    },
-    "AWS::Evidently::Experiment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):evidently:(?[a-z0-9-]+):(?[^/:]+):project/(?[^/:]+)/experiment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-experiment.html"
-    },
-    "AWS::Evidently::Feature": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):evidently:(?[a-z0-9-]+):(?[^/:]+):project/(?[^/:]+)/feature/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-feature.html"
-    },
-    "AWS::Evidently::Launch": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):evidently:(?[a-z0-9-]+):(?[^/:]+):project/(?[^/:]+)/launch/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-launch.html"
-    },
-    "AWS::Evidently::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):evidently:(?[a-z0-9-]+):(?[^/:]+):project/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-project.html"
-    },
-    "AWS::Evidently::Segment": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-evidently-segment.html"
-    },
-    "AWS::FIS::ExperimentTemplate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):fis:(?[a-z0-9-]+):(?[0-9]{12}):experiment-template/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fis-experimenttemplate.html"
-    },
-    "AWS::FMS::NotificationChannel": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-notificationchannel.html"
-    },
-    "AWS::FMS::Policy": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):fms:(?[a-z0-9-]+):(?[0-9]{12}):policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-fms-policy.html"
-    },
-    "AWS::FinSpace::Environment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):finspace:(?[a-z0-9-]+):(?[0-9]{12}):environment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-finspace-environment.html"
-    },
-    "AWS::Forecast::Dataset": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):forecast:(?[a-z0-9-]+):(?[0-9]{12}):dataset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-forecast-dataset.html"
-    },
-    "AWS::Forecast::DatasetGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):forecast:(?[a-z0-9-]+):(?[0-9]{12}):dataset-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-forecast-datasetgroup.html"
-    },
-    "AWS::FraudDetector::Detector": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "READ", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):detector/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-detector.html"
-    },
-    "AWS::FraudDetector::EntityType": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):entity-type/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-entitytype.html"
-    },
-    "AWS::FraudDetector::EventType": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "READ", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):event-type/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-eventtype.html"
-    },
-    "AWS::FraudDetector::Label": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):label/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-label.html"
-    },
-    "AWS::FraudDetector::Outcome": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):outcome/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-outcome.html"
-    },
-    "AWS::FraudDetector::Variable": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):frauddetector:(?[a-z0-9-]+):(?[0-9]{12}):variable/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-frauddetector-variable.html"
-    },
-    "AWS::GameLift::Alias": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):gamelift:(?[a-z0-9-]+)::alias/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-gamelift-alias.html"
-    },
-    "AWS::GameLift::Fleet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):gamelift:(?[a-z0-9-]+):(?[0-9]{12}):fleet/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-gamelift-fleet.html"
-    },
-    "AWS::GameLift::GameServerGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):gamelift:(?[a-z0-9-]+):(?[0-9]{12}):gameservergroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-gamelift-gameservergroup.html"
-    },
-    "AWS::GlobalAccelerator::Accelerator": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):globalaccelerator::(?[0-9]{12}):accelerator/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-accelerator.html"
-    },
-    "AWS::GlobalAccelerator::EndpointGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):globalaccelerator::(?[0-9]{12}):accelerator/(?[^/:]+)/listener/(?[^/:]+)/endpoint-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-endpointgroup.html"
-    },
-    "AWS::GlobalAccelerator::Listener": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):globalaccelerator::(?[0-9]{12}):accelerator/(?[^/:]+)/listener/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-globalaccelerator-listener.html"
-    },
-    "AWS::Glue::Registry": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):glue:(?[a-z0-9-]+):(?[0-9]{12}):registry/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-registry.html"
-    },
-    "AWS::Glue::Schema": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):glue:(?[a-z0-9-]+):(?[0-9]{12}):schema/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-schema.html"
-    },
-    "AWS::Glue::SchemaVersion": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-schemaversion.html"
-    },
-    "AWS::Glue::SchemaVersionMetadata": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-glue-schemaversionmetadata.html"
-    },
-    "AWS::GreengrassV2::ComponentVersion": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):greengrass:(?[a-z0-9-]+):(?[0-9]{12}):components:(?[^/:]+):versions:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-greengrassv2-componentversion.html"
-    },
-    "AWS::GroundStation::Config": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):groundstation:(?[a-z0-9-]+):(?[0-9]{12}):config/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-groundstation-config.html"
-    },
-    "AWS::GroundStation::DataflowEndpointGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):groundstation:(?[a-z0-9-]+):(?[0-9]{12}):dataflow-endpoint-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-groundstation-dataflowendpointgroup.html"
-    },
-    "AWS::GroundStation::MissionProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):groundstation:(?[a-z0-9-]+):(?[0-9]{12}):mission-profile/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-groundstation-missionprofile.html"
-    },
-    "AWS::HealthLake::FHIRDatastore": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-healthlake-fhirdatastore.html"
-    },
-    "AWS::IAM::InstanceProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iam::(?[0-9]{12}):instance-profile/(?[^:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html"
-    },
-    "AWS::IAM::OIDCProvider": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-oidcprovider.html"
-    },
-    "AWS::IAM::Role": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iam::(?[0-9]{12}):role/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html"
-    },
-    "AWS::IAM::SAMLProvider": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iam::(?[0-9]{12}):saml-provider/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-samlprovider.html"
-    },
-    "AWS::IAM::ServerCertificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iam::(?[0-9]{12}):server-certificate/(?[^:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-servercertificate.html"
-    },
-    "AWS::IAM::VirtualMFADevice": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iam::(?[0-9]{12}):mfa/(?[^:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-virtualmfadevice.html"
-    },
-    "AWS::IVS::Channel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ivs:(?[a-z0-9-]+):(?[0-9]{12}):channel/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-channel.html"
-    },
-    "AWS::IVS::PlaybackKeyPair": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ivs:(?[a-z0-9-]+):(?[0-9]{12}):playback-key/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-playbackkeypair.html"
-    },
-    "AWS::IVS::RecordingConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ivs:(?[a-z0-9-]+):(?[0-9]{12}):recording-configuration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-recordingconfiguration.html"
-    },
-    "AWS::IVS::StreamKey": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ivs:(?[a-z0-9-]+):(?[0-9]{12}):stream-key/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ivs-streamkey.html"
-    },
-    "AWS::ImageBuilder::Component": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):component/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-component.html"
-    },
-    "AWS::ImageBuilder::ContainerRecipe": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):container-recipe/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-containerrecipe.html"
-    },
-    "AWS::ImageBuilder::DistributionConfiguration": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):distribution-configuration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-distributionconfiguration.html"
-    },
-    "AWS::ImageBuilder::Image": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):image/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-image.html"
-    },
-    "AWS::ImageBuilder::ImagePipeline": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):image-pipeline/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagepipeline.html"
-    },
-    "AWS::ImageBuilder::ImageRecipe": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):image-recipe/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-imagerecipe.html"
-    },
-    "AWS::ImageBuilder::InfrastructureConfiguration": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):imagebuilder:(?[a-z0-9-]+):(?[0-9]{12}):infrastructure-configuration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-imagebuilder-infrastructureconfiguration.html"
-    },
-    "AWS::Inspector::AssessmentTarget": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-inspector-assessmenttarget.html"
-    },
-    "AWS::Inspector::AssessmentTemplate": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):inspector:(?[a-z0-9-]+):(?[0-9]{12}):target/(?[^/:]+)/template/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-inspector-assessmenttemplate.html"
-    },
-    "AWS::Inspector::ResourceGroup": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-inspector-resourcegroup.html"
-    },
-    "AWS::InspectorV2::Filter": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-inspectorv2-filter.html"
-    },
-    "AWS::IoT::AccountAuditConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-accountauditconfiguration.html"
-    },
-    "AWS::IoT::Authorizer": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):authorizer/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-authorizer.html"
-    },
-    "AWS::IoT::CACertificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-cacertificate.html"
-    },
-    "AWS::IoT::Certificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-certificate.html"
-    },
-    "AWS::IoT::CustomMetric": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):custommetric/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-custommetric.html"
-    },
-    "AWS::IoT::Dimension": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):dimension/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-dimension.html"
-    },
-    "AWS::IoT::DomainConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):domainconfiguration/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-domainconfiguration.html"
-    },
-    "AWS::IoT::FleetMetric": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):fleetmetric/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-fleetmetric.html"
-    },
-    "AWS::IoT::JobTemplate": {
-        "operations": ["READ", "CREATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):jobtemplate/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-jobtemplate.html"
-    },
-    "AWS::IoT::Logging": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-logging.html"
-    },
-    "AWS::IoT::MitigationAction": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):mitigationaction/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-mitigationaction.html"
-    },
-    "AWS::IoT::Policy": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-policy.html"
-    },
-    "AWS::IoT::ProvisioningTemplate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):provisioningtemplate/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html"
-    },
-    "AWS::IoT::ResourceSpecificLogging": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-resourcespecificlogging.html"
-    },
-    "AWS::IoT::RoleAlias": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):rolealias/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-rolealias.html"
-    },
-    "AWS::IoT::ScheduledAudit": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):scheduledaudit/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-scheduledaudit.html"
-    },
-    "AWS::IoT::SecurityProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):securityprofile/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-securityprofile.html"
-    },
-    "AWS::IoT::TopicRule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iot:(?[a-z0-9-]+):(?[0-9]{12}):rule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-topicrule.html"
-    },
-    "AWS::IoT::TopicRuleDestination": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-topicruledestination.html"
-    },
-    "AWS::IoTAnalytics::Channel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotanalytics:(?[a-z0-9-]+):(?[0-9]{12}):channel/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-channel.html"
-    },
-    "AWS::IoTAnalytics::Dataset": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotanalytics:(?[a-z0-9-]+):(?[0-9]{12}):dataset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-dataset.html"
-    },
-    "AWS::IoTAnalytics::Datastore": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotanalytics:(?[a-z0-9-]+):(?[0-9]{12}):datastore/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-datastore.html"
-    },
-    "AWS::IoTAnalytics::Pipeline": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotanalytics:(?[a-z0-9-]+):(?[0-9]{12}):pipeline/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotanalytics-pipeline.html"
-    },
-    "AWS::IoTCoreDeviceAdvisor::SuiteDefinition": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotcoredeviceadvisor-suitedefinition.html"
-    },
-    "AWS::IoTEvents::AlarmModel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotevents:(?[a-z0-9-]+):(?[0-9]{12}):alarmModel/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotevents-alarmmodel.html"
-    },
-    "AWS::IoTEvents::DetectorModel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotevents:(?[a-z0-9-]+):(?[0-9]{12}):detectorModel/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotevents-detectormodel.html"
-    },
-    "AWS::IoTEvents::Input": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotevents:(?[a-z0-9-]+):(?[0-9]{12}):input/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotevents-input.html"
-    },
-    "AWS::IoTFleetHub::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotfleethub:(?[a-z0-9-]+):(?[0-9]{12}):application/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotfleethub-application.html"
-    },
-    "AWS::IoTSiteWise::AccessPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):access-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-accesspolicy.html"
-    },
-    "AWS::IoTSiteWise::Asset": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):asset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-asset.html"
-    },
-    "AWS::IoTSiteWise::AssetModel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):asset-model/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-assetmodel.html"
-    },
-    "AWS::IoTSiteWise::Dashboard": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):dashboard/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-dashboard.html"
-    },
-    "AWS::IoTSiteWise::Gateway": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):gateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-gateway.html"
-    },
-    "AWS::IoTSiteWise::Portal": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):portal/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-portal.html"
-    },
-    "AWS::IoTSiteWise::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iotsitewise:(?[a-z0-9-]+):(?[0-9]{12}):project/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotsitewise-project.html"
-    },
-    "AWS::IoTTwinMaker::ComponentType": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iottwinmaker:(?[a-z0-9-]+):(?[0-9]{12}):workspace/(?[^/:]+)/component-type/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-componenttype.html"
-    },
-    "AWS::IoTTwinMaker::Entity": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iottwinmaker:(?[a-z0-9-]+):(?[0-9]{12}):workspace/(?[^/:]+)/entity/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-entity.html"
-    },
-    "AWS::IoTTwinMaker::Scene": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):iottwinmaker:(?[a-z0-9-]+):(?[0-9]{12}):workspace/(?[^/:]+)/scene/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-scene.html"
-    },
-    "AWS::IoTTwinMaker::Workspace": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iottwinmaker:(?[a-z0-9-]+):(?[0-9]{12}):workspace/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iottwinmaker-workspace.html"
-    },
-    "AWS::IoTWireless::Destination": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):Destination/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-destination.html"
-    },
-    "AWS::IoTWireless::DeviceProfile": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):DeviceProfile/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-deviceprofile.html"
-    },
-    "AWS::IoTWireless::FuotaTask": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):FuotaTask/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-fuotatask.html"
-    },
-    "AWS::IoTWireless::MulticastGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):MulticastGroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-multicastgroup.html"
-    },
-    "AWS::IoTWireless::NetworkAnalyzerConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):NetworkAnalyzerConfiguration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-networkanalyzerconfiguration.html"
-    },
-    "AWS::IoTWireless::ServiceProfile": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):ServiceProfile/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-serviceprofile.html"
-    },
-    "AWS::IoTWireless::TaskDefinition": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-taskdefinition.html"
-    },
-    "AWS::IoTWireless::WirelessDevice": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):WirelessDevice/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-wirelessdevice.html"
-    },
-    "AWS::IoTWireless::WirelessGateway": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):iotwireless:(?[a-z0-9-]+):(?[0-9]{12}):WirelessGateway/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iotwireless-wirelessgateway.html"
-    },
-    "AWS::KMS::Alias": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):kms:(?[a-z0-9-]+):(?[0-9]{12}):alias/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-alias.html"
-    },
-    "AWS::KMS::Key": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):kms:(?[a-z0-9-]+):(?[0-9]{12}):key/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-key.html"
-    },
-    "AWS::KMS::ReplicaKey": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kms-replicakey.html"
-    },
-    "AWS::KafkaConnect::Connector": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):kafkaconnect:(?[a-z0-9-]+):(?[0-9]{12}):connector/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kafkaconnect-connector.html"
-    },
-    "AWS::Kendra::DataSource": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):kendra:(?[a-z0-9-]+):(?[0-9]{12}):index/(?[^/:]+)/data-source/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kendra-datasource.html"
-    },
-    "AWS::Kendra::Faq": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):kendra:(?[a-z0-9-]+):(?[0-9]{12}):index/(?[^/:]+)/faq/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kendra-faq.html"
-    },
-    "AWS::Kendra::Index": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):kendra:(?[a-z0-9-]+):(?[0-9]{12}):index/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kendra-index.html"
-    },
-    "AWS::Kinesis::Stream": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):kinesis:(?[a-z0-9-]+):(?[0-9]{12}):stream/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesis-stream.html"
-    },
-    "AWS::KinesisAnalyticsV2::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):kinesisanalytics:(?[a-z0-9-]+):(?[0-9]{12}):application/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisanalyticsv2-application.html"
-    },
-    "AWS::KinesisFirehose::DeliveryStream": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):firehose:(?[a-z0-9-]+):(?[0-9]{12}):deliverystream/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisfirehose-deliverystream.html"
-    },
-    "AWS::KinesisVideo::SignalingChannel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisvideo-signalingchannel.html"
-    },
-    "AWS::KinesisVideo::Stream": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):kinesisvideo:(?[a-z0-9-]+):(?[0-9]{12}):stream/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-kinesisvideo-stream.html"
-    },
-    "AWS::LakeFormation::DataCellsFilter": {
-        "operations": ["CREATE", "DELETE", "READ", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lakeformation-datacellsfilter.html"
-    },
-    "AWS::LakeFormation::PrincipalPermissions": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lakeformation-principalpermissions.html"
-    },
-    "AWS::LakeFormation::Tag": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lakeformation-tag.html"
-    },
-    "AWS::LakeFormation::TagAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lakeformation-tagassociation.html"
-    },
-    "AWS::Lambda::CodeSigningConfig": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-codesigningconfig.html"
-    },
-    "AWS::Lambda::EventSourceMapping": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):lambda:(?[a-z0-9-]+):(?[0-9]{12}):event-source-mapping:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-eventsourcemapping.html"
-    },
-    "AWS::Lambda::Function": {
-        "operations": ["READ", "CREATE", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):lambda:(?[a-z0-9-]+):(?[0-9]{12}):function:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html"
-    },
-    "AWS::Lambda::Url": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-url.html"
-    },
-    "AWS::Lex::Bot": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lex:(?[a-z0-9-]+):(?[0-9]{12}):bot:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-bot.html"
-    },
-    "AWS::Lex::BotAlias": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botalias.html"
-    },
-    "AWS::Lex::BotVersion": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-botversion.html"
-    },
-    "AWS::Lex::ResourcePolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lex-resourcepolicy.html"
-    },
-    "AWS::LicenseManager::Grant": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):license-manager::(?[0-9]{12}):grant:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-licensemanager-grant.html"
-    },
-    "AWS::LicenseManager::License": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):license-manager::(?[0-9]{12}):license:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-licensemanager-license.html"
-    },
-    "AWS::Lightsail::Alarm": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):Alarm/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-alarm.html"
-    },
-    "AWS::Lightsail::Bucket": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):Bucket/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-bucket.html"
-    },
-    "AWS::Lightsail::Certificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):Certificate/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-certificate.html"
-    },
-    "AWS::Lightsail::Container": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-container.html"
-    },
-    "AWS::Lightsail::Database": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-database.html"
-    },
-    "AWS::Lightsail::Disk": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):Disk/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-disk.html"
-    },
-    "AWS::Lightsail::Instance": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):Instance/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-instance.html"
-    },
-    "AWS::Lightsail::LoadBalancer": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):LoadBalancer/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-loadbalancer.html"
-    },
-    "AWS::Lightsail::LoadBalancerTlsCertificate": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):LoadBalancerTlsCertificate/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-loadbalancertlscertificate.html"
-    },
-    "AWS::Lightsail::StaticIp": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lightsail:(?[a-z0-9-]+):(?[0-9]{12}):StaticIp/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lightsail-staticip.html"
-    },
-    "AWS::Location::GeofenceCollection": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-geofencecollection.html"
-    },
-    "AWS::Location::Map": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-map.html"
-    },
-    "AWS::Location::PlaceIndex": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-placeindex.html"
-    },
-    "AWS::Location::RouteCalculator": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-routecalculator.html"
-    },
-    "AWS::Location::Tracker": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-tracker.html"
-    },
-    "AWS::Location::TrackerConsumer": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-location-trackerconsumer.html"
-    },
-    "AWS::Logs::Destination": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):logs:(?[a-z0-9-]+):(?[0-9]{12}):destination:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-destination.html"
-    },
-    "AWS::Logs::LogGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):logs:(?[a-z0-9-]+):(?[0-9]{12}):log-group:(?[^:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-loggroup.html"
-    },
-    "AWS::Logs::LogStream": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):logs:(?[a-z0-9-]+):(?[0-9]{12}):log-group:(?[^/:]+):log-stream:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-logstream.html"
-    },
-    "AWS::Logs::MetricFilter": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-metricfilter.html"
-    },
-    "AWS::Logs::QueryDefinition": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-querydefinition.html"
-    },
-    "AWS::Logs::ResourcePolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-resourcepolicy.html"
-    },
-    "AWS::Logs::SubscriptionFilter": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-logs-subscriptionfilter.html"
-    },
-    "AWS::LookoutMetrics::Alert": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lookoutmetrics:(?[a-z0-9-]+):(?[0-9]{12}):Alert:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lookoutmetrics-alert.html"
-    },
-    "AWS::LookoutMetrics::AnomalyDetector": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lookoutmetrics:(?[a-z0-9-]+):(?[0-9]{12}):AnomalyDetector:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lookoutmetrics-anomalydetector.html"
-    },
-    "AWS::LookoutVision::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):lookoutvision:(?[a-z0-9-]+):(?[0-9]{12}):project/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lookoutvision-project.html"
-    },
-    "AWS::M2::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"]
-    },
-    "AWS::M2::Environment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"]
-    },
-    "AWS::MSK::BatchScramSecret": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-batchscramsecret.html"
-    },
-    "AWS::MSK::Cluster": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "LIST", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-cluster.html"
-    },
-    "AWS::MSK::Configuration": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-configuration.html"
-    },
-    "AWS::MSK::ServerlessCluster": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-msk-serverlesscluster.html"
-    },
-    "AWS::MWAA::Environment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mwaa-environment.html"
-    },
-    "AWS::Macie::AllowList": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-allowlist.html"
-    },
-    "AWS::Macie::CustomDataIdentifier": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):macie2:(?[a-z0-9-]+):(?[0-9]{12}):custom-data-identifier/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-customdataidentifier.html"
-    },
-    "AWS::Macie::FindingsFilter": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):macie2:(?[a-z0-9-]+):(?[0-9]{12}):findings-filter/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-findingsfilter.html"
-    },
-    "AWS::Macie::Session": {
-        "operations": ["CREATE", "READ", "LIST", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-macie-session.html"
-    },
-    "AWS::MediaConnect::Flow": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):mediaconnect:(?[a-z0-9-]+):(?[0-9]{12}):flow:(?[^/:]+):(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flow.html"
-    },
-    "AWS::MediaConnect::FlowEntitlement": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowentitlement.html"
-    },
-    "AWS::MediaConnect::FlowOutput": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowoutput.html"
-    },
-    "AWS::MediaConnect::FlowSource": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowsource.html"
-    },
-    "AWS::MediaConnect::FlowVpcInterface": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediaconnect-flowvpcinterface.html"
-    },
-    "AWS::MediaPackage::Asset": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediapackage-asset.html"
-    },
-    "AWS::MediaPackage::Channel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediapackage-channel.html"
-    },
-    "AWS::MediaPackage::OriginEndpoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediapackage-originendpoint.html"
-    },
-    "AWS::MediaPackage::PackagingConfiguration": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediapackage-packagingconfiguration.html"
-    },
-    "AWS::MediaPackage::PackagingGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "LIST", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediapackage-packaginggroup.html"
-    },
-    "AWS::MediaTailor::PlaybackConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):mediatailor:(?[a-z0-9-]+):(?[0-9]{12}):playbackConfiguration/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-mediatailor-playbackconfiguration.html"
-    },
-    "AWS::MemoryDB::ACL": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):memorydb:(?[a-z0-9-]+):(?[0-9]{12}):acl/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-acl.html"
-    },
-    "AWS::MemoryDB::Cluster": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):memorydb:(?[a-z0-9-]+):(?[0-9]{12}):cluster/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-cluster.html"
-    },
-    "AWS::MemoryDB::ParameterGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):memorydb:(?[a-z0-9-]+):(?[0-9]{12}):parametergroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-parametergroup.html"
-    },
-    "AWS::MemoryDB::SubnetGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):memorydb:(?[a-z0-9-]+):(?[0-9]{12}):subnetgroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-subnetgroup.html"
-    },
-    "AWS::MemoryDB::User": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):memorydb:(?[a-z0-9-]+):(?[0-9]{12}):user/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-memorydb-user.html"
-    },
-    "AWS::NetworkFirewall::Firewall": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):network-firewall:(?[a-z0-9-]+):(?[0-9]{12}):firewall/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-firewall.html"
-    },
-    "AWS::NetworkFirewall::FirewallPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):network-firewall:(?[a-z0-9-]+):(?[0-9]{12}):firewall-policy/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-firewallpolicy.html"
-    },
-    "AWS::NetworkFirewall::LoggingConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-loggingconfiguration.html"
-    },
-    "AWS::NetworkFirewall::RuleGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkfirewall-rulegroup.html"
-    },
-    "AWS::NetworkManager::ConnectAttachment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-connectattachment.html"
-    },
-    "AWS::NetworkManager::ConnectPeer": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):connect-peer/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-connectpeer.html"
-    },
-    "AWS::NetworkManager::CoreNetwork": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):core-network/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-corenetwork.html"
-    },
-    "AWS::NetworkManager::CustomerGatewayAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-customergatewayassociation.html"
-    },
-    "AWS::NetworkManager::Device": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):device/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-device.html"
-    },
-    "AWS::NetworkManager::GlobalNetwork": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):global-network/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-globalnetwork.html"
-    },
-    "AWS::NetworkManager::Link": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):link/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-link.html"
-    },
-    "AWS::NetworkManager::LinkAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-linkassociation.html"
-    },
-    "AWS::NetworkManager::Site": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):networkmanager::(?[0-9]{12}):site/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-site.html"
-    },
-    "AWS::NetworkManager::SiteToSiteVpnAttachment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-sitetositevpnattachment.html"
-    },
-    "AWS::NetworkManager::TransitGatewayRegistration": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-transitgatewayregistration.html"
-    },
-    "AWS::NetworkManager::VpcAttachment": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-networkmanager-vpcattachment.html"
-    },
-    "AWS::NimbleStudio::LaunchProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-nimblestudio-launchprofile.html"
-    },
-    "AWS::NimbleStudio::StreamingImage": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-nimblestudio-streamingimage.html"
-    },
-    "AWS::NimbleStudio::Studio": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-nimblestudio-studio.html"
-    },
-    "AWS::NimbleStudio::StudioComponent": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-nimblestudio-studiocomponent.html"
-    },
-    "AWS::OpenSearchService::Domain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opensearchservice-domain.html"
-    },
-    "AWS::OpsWorksCM::Server": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "LIST", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):opsworks-cm::(?[0-9]{12}):server/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-opsworkscm-server.html"
-    },
-    "AWS::Panorama::ApplicationInstance": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):panorama:(?[a-z0-9-]+):(?[0-9]{12}):applicationInstance/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-applicationinstance.html"
-    },
-    "AWS::Panorama::Package": {
-        "operations": ["CREATE", "READ", "UPDATE", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):panorama:(?[a-z0-9-]+):(?[0-9]{12}):package/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-package.html"
-    },
-    "AWS::Panorama::PackageVersion": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-panorama-packageversion.html"
-    },
-    "AWS::Personalize::Dataset": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):personalize:(?[a-z0-9-]+):(?[0-9]{12}):dataset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-personalize-dataset.html"
-    },
-    "AWS::Personalize::DatasetGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):personalize:(?[a-z0-9-]+):(?[0-9]{12}):dataset-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-personalize-datasetgroup.html"
-    },
-    "AWS::Personalize::Schema": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):personalize:(?[a-z0-9-]+):(?[0-9]{12}):schema/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-personalize-schema.html"
-    },
-    "AWS::Personalize::Solution": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):personalize:(?[a-z0-9-]+):(?[0-9]{12}):solution/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-personalize-solution.html"
-    },
-    "AWS::Pinpoint::InAppTemplate": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-pinpoint-inapptemplate.html"
-    },
-    "AWS::QLDB::Stream": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):qldb:(?[a-z0-9-]+):(?[0-9]{12}):stream/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-qldb-stream.html"
-    },
-    "AWS::QuickSight::Analysis": {
-        "operations": ["READ", "CREATE", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):analysis/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-analysis.html"
-    },
-    "AWS::QuickSight::Dashboard": {
-        "operations": ["READ", "CREATE", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):dashboard/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-dashboard.html"
-    },
-    "AWS::QuickSight::DataSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):dataset/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-dataset.html"
-    },
-    "AWS::QuickSight::DataSource": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):datasource/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-datasource.html"
-    },
-    "AWS::QuickSight::Template": {
-        "operations": ["READ", "CREATE", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):template/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-template.html"
-    },
-    "AWS::QuickSight::Theme": {
-        "operations": ["READ", "CREATE", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):quicksight:(?[a-z0-9-]+):(?[0-9]{12}):theme/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-quicksight-theme.html"
-    },
-    "AWS::RDS::DBCluster": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):cluster:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html"
-    },
-    "AWS::RDS::DBClusterParameterGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):cluster-pg:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbclusterparametergroup.html"
-    },
-    "AWS::RDS::DBInstance": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):db:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbinstance.html"
-    },
-    "AWS::RDS::DBParameterGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):pg:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbparametergroup.html"
-    },
-    "AWS::RDS::DBProxy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxy.html"
-    },
-    "AWS::RDS::DBProxyEndpoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxyendpoint.html"
-    },
-    "AWS::RDS::DBProxyTargetGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbproxytargetgroup.html"
-    },
-    "AWS::RDS::DBSubnetGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):subgrp:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbsubnetgroup.html"
-    },
-    "AWS::RDS::EventSubscription": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):es:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-eventsubscription.html"
-    },
-    "AWS::RDS::GlobalCluster": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds::(?[0-9]{12}):global-cluster:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-globalcluster.html"
-    },
-    "AWS::RDS::OptionGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rds:(?[a-z0-9-]+):(?[0-9]{12}):og:(?[^/]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-optiongroup.html"
-    },
-    "AWS::RUM::AppMonitor": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rum-appmonitor.html"
-    },
-    "AWS::Redshift::Cluster": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift:(?[a-z0-9-]+):(?[0-9]{12}):cluster:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-cluster.html"
-    },
-    "AWS::Redshift::ClusterParameterGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift:(?[a-z0-9-]+):(?[0-9]{12}):parametergroup:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clusterparametergroup.html"
-    },
-    "AWS::Redshift::ClusterSubnetGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift:(?[a-z0-9-]+):(?[0-9]{12}):subnetgroup:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-clustersubnetgroup.html"
-    },
-    "AWS::Redshift::EndpointAccess": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointaccess.html"
-    },
-    "AWS::Redshift::EndpointAuthorization": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-endpointauthorization.html"
-    },
-    "AWS::Redshift::EventSubscription": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift:(?[a-z0-9-]+):(?[0-9]{12}):eventsubscription:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-eventsubscription.html"
-    },
-    "AWS::Redshift::ScheduledAction": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshift-scheduledaction.html"
-    },
-    "AWS::RedshiftServerless::Namespace": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift-serverless:(?[a-z0-9-]+):(?[0-9]{12}):namespace/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshiftserverless-namespace.html"
-    },
-    "AWS::RedshiftServerless::Workgroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):redshift-serverless:(?[a-z0-9-]+):(?[0-9]{12}):workgroup/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-redshiftserverless-workgroup.html"
-    },
-    "AWS::RefactorSpaces::Application": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):refactor-spaces:(?[a-z0-9-]+):(?[0-9]{12}):environment/(?[^/:]+)/application/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-refactorspaces-application.html"
-    },
-    "AWS::RefactorSpaces::Environment": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):refactor-spaces:(?[a-z0-9-]+):(?[0-9]{12}):environment/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-refactorspaces-environment.html"
-    },
-    "AWS::RefactorSpaces::Route": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):refactor-spaces:(?[a-z0-9-]+):(?[0-9]{12}):environment/(?[^/:]+)/application/(?[^/:]+)/route/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-refactorspaces-route.html"
-    },
-    "AWS::RefactorSpaces::Service": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):refactor-spaces:(?[a-z0-9-]+):(?[0-9]{12}):environment/(?[^/:]+)/application/(?[^/:]+)/service/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-refactorspaces-service.html"
-    },
-    "AWS::Rekognition::Collection": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rekognition:(?[a-z0-9-]+):(?[0-9]{12}):collection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rekognition-collection.html"
-    },
-    "AWS::Rekognition::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rekognition:(?[a-z0-9-]+):(?[0-9]{12}):project/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rekognition-project.html"
-    },
-    "AWS::Rekognition::StreamProcessor": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):rekognition:(?[a-z0-9-]+):(?[0-9]{12}):streamprocessor/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rekognition-streamprocessor.html"
-    },
-    "AWS::ResilienceHub::App": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-resiliencehub-app.html"
-    },
-    "AWS::ResilienceHub::ResiliencyPolicy": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-resiliencehub-resiliencypolicy.html"
-    },
-    "AWS::ResourceGroups::Group": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):resource-groups(?:-(?test|beta|gamma))?:(?[a-z0-9-]+):(?[0-9]{12}):group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-resourcegroups-group.html"
-    },
-    "AWS::RoboMaker::Fleet": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):robomaker:(?[a-z0-9-]+):(?[0-9]{12}):deployment-fleet/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-fleet.html"
-    },
-    "AWS::RoboMaker::Robot": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):robomaker:(?[a-z0-9-]+):(?[0-9]{12}):robot/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-robot.html"
-    },
-    "AWS::RoboMaker::RobotApplication": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):robomaker:(?[a-z0-9-]+):(?[0-9]{12}):robot-application/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-robotapplication.html"
-    },
-    "AWS::RoboMaker::RobotApplicationVersion": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-robotapplicationversion.html"
-    },
-    "AWS::RoboMaker::SimulationApplication": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):robomaker:(?[a-z0-9-]+):(?[0-9]{12}):simulation-application/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-simulationapplication.html"
-    },
-    "AWS::RoboMaker::SimulationApplicationVersion": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-robomaker-simulationapplicationversion.html"
-    },
-    "AWS::RolesAnywhere::CRL": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rolesanywhere-crl.html"
-    },
-    "AWS::RolesAnywhere::Profile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rolesanywhere-profile.html"
-    },
-    "AWS::RolesAnywhere::TrustAnchor": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rolesanywhere-trustanchor.html"
-    },
-    "AWS::Route53::CidrCollection": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53:::cidrcollection/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-cidrcollection.html"
-    },
-    "AWS::Route53::DNSSEC": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-dnssec.html"
-    },
-    "AWS::Route53::HealthCheck": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53:::healthcheck/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-healthcheck.html"
-    },
-    "AWS::Route53::HostedZone": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53:::hostedzone/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-hostedzone.html"
-    },
-    "AWS::Route53::KeySigningKey": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53-keysigningkey.html"
-    },
-    "AWS::Route53RecoveryControl::Cluster": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-cluster.html"
-    },
-    "AWS::Route53RecoveryControl::ControlPanel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-controlpanel.html"
-    },
-    "AWS::Route53RecoveryControl::RoutingControl": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-routingcontrol.html"
-    },
-    "AWS::Route53RecoveryControl::SafetyRule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoverycontrol-safetyrule.html"
-    },
-    "AWS::Route53RecoveryReadiness::Cell": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53-recovery-readiness::(?[0-9]{12}):cell/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoveryreadiness-cell.html"
-    },
-    "AWS::Route53RecoveryReadiness::ReadinessCheck": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53-recovery-readiness::(?[0-9]{12}):readiness-check/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoveryreadiness-readinesscheck.html"
-    },
-    "AWS::Route53RecoveryReadiness::RecoveryGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53-recovery-readiness::(?[0-9]{12}):recovery-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoveryreadiness-recoverygroup.html"
-    },
-    "AWS::Route53RecoveryReadiness::ResourceSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53-recovery-readiness::(?[0-9]{12}):resource-set/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53recoveryreadiness-resourceset.html"
-    },
-    "AWS::Route53Resolver::FirewallDomainList": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):firewall-domain-list/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-firewalldomainlist.html"
-    },
-    "AWS::Route53Resolver::FirewallRuleGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):firewall-rule-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-firewallrulegroup.html"
-    },
-    "AWS::Route53Resolver::FirewallRuleGroupAssociation": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):firewall-rule-group-association/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-firewallrulegroupassociation.html"
-    },
-    "AWS::Route53Resolver::ResolverConfig": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):resolver-config/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverconfig.html"
-    },
-    "AWS::Route53Resolver::ResolverDNSSECConfig": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):resolver-dnssec-config/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverdnssecconfig.html"
-    },
-    "AWS::Route53Resolver::ResolverQueryLoggingConfig": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverqueryloggingconfig.html"
-    },
-    "AWS::Route53Resolver::ResolverQueryLoggingConfigAssociation": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverqueryloggingconfigassociation.html"
-    },
-    "AWS::Route53Resolver::ResolverRule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):route53resolver:(?[a-z0-9-]+):(?[0-9]{12}):resolver-rule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverrule.html"
-    },
-    "AWS::Route53Resolver::ResolverRuleAssociation": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-route53resolver-resolverruleassociation.html"
-    },
-    "AWS::S3::AccessPoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):s3:(?[a-z0-9-]+):(?[0-9]{12}):accesspoint/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-accesspoint.html"
-    },
-    "AWS::S3::Bucket": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):s3:::(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html"
-    },
-    "AWS::S3::MultiRegionAccessPoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):s3::(?[0-9]{12}):accesspoint/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-multiregionaccesspoint.html"
-    },
-    "AWS::S3::MultiRegionAccessPointPolicy": {
-        "operations": ["UPDATE", "READ", "DELETE", "CREATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-multiregionaccesspointpolicy.html"
-    },
-    "AWS::S3::StorageLens": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3-storagelens.html"
-    },
-    "AWS::S3ObjectLambda::AccessPoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3objectlambda-accesspoint.html"
-    },
-    "AWS::S3ObjectLambda::AccessPointPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3objectlambda-accesspointpolicy.html"
-    },
-    "AWS::S3Outposts::AccessPoint": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):s3-outposts:(?[a-z0-9-]+):(?[0-9]{12}):outpost/(?[^/:]+)/accesspoint/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-accesspoint.html"
-    },
-    "AWS::S3Outposts::Bucket": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):s3-outposts:(?[a-z0-9-]+):(?[0-9]{12}):outpost/(?[^/:]+)/bucket/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucket.html"
-    },
-    "AWS::S3Outposts::BucketPolicy": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-bucketpolicy.html"
-    },
-    "AWS::S3Outposts::Endpoint": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):s3-outposts:(?[a-z0-9-]+):(?[0-9]{12}):outpost/(?[^/:]+)/endpoint/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-s3outposts-endpoint.html"
-    },
-    "AWS::SES::ConfigurationSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ses:(?[a-z0-9-]+):(?[0-9]{12}):configuration-set/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-configurationset.html"
-    },
-    "AWS::SES::ConfigurationSetEventDestination": {
-        "operations": ["CREATE", "UPDATE", "DELETE", "READ"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-configurationseteventdestination.html"
-    },
-    "AWS::SES::ContactList": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ses:(?[a-z0-9-]+):(?[0-9]{12}):contact-list/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-contactlist.html"
-    },
-    "AWS::SES::DedicatedIpPool": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ses:(?[a-z0-9-]+):(?[0-9]{12}):dedicated-ip-pool/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-dedicatedippool.html"
-    },
-    "AWS::SES::EmailIdentity": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-emailidentity.html"
-    },
-    "AWS::SES::Template": {
-        "operations": ["CREATE", "READ", "DELETE", "UPDATE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ses:(?[a-z0-9-]+):(?[0-9]{12}):template/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ses-template.html"
-    },
-    "AWS::SNS::Topic": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sns:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-topic.html"
-    },
-    "AWS::SQS::Queue": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sqs:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sqs-queue.html"
-    },
-    "AWS::SSM::Association": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):ssm:(?[a-z0-9-]+):(?[0-9]{12}):association/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-association.html"
-    },
-    "AWS::SSM::Document": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ssm:(?[a-z0-9-]+):(?[0-9]{12})?:document/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-document.html"
-    },
-    "AWS::SSM::ResourceDataSync": {
-        "operations": ["CREATE", "DELETE", "UPDATE", "LIST", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):ssm:(?[a-z0-9-]+):(?[0-9]{12}):resource-data-sync/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssm-resourcedatasync.html"
-    },
-    "AWS::SSMContacts::Contact": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ssm-contacts:(?[a-z0-9-]+):(?[0-9]{12}):contact/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssmcontacts-contact.html"
-    },
-    "AWS::SSMContacts::ContactChannel": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):ssm-contacts:(?[a-z0-9-]+):(?[0-9]{12}):contactchannel/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssmcontacts-contactchannel.html"
-    },
-    "AWS::SSMIncidents::ReplicationSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ssm-incidents::(?[0-9]{12}):replication-set/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssmincidents-replicationset.html"
-    },
-    "AWS::SSMIncidents::ResponsePlan": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):ssm-incidents::(?[0-9]{12}):response-plan/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ssmincidents-responseplan.html"
-    },
-    "AWS::SSO::Assignment": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-assignment.html"
-    },
-    "AWS::SSO::InstanceAccessControlAttributeConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-instanceaccesscontrolattributeconfiguration.html"
-    },
-    "AWS::SSO::PermissionSet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):sso:::permissionSet/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sso-permissionset.html"
-    },
-    "AWS::SageMaker::App": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):app/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-app.html"
-    },
-    "AWS::SageMaker::AppImageConfig": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):app-image-config/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-appimageconfig.html"
-    },
-    "AWS::SageMaker::DataQualityJobDefinition": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):data-quality-job-definition/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-dataqualityjobdefinition.html"
-    },
-    "AWS::SageMaker::Device": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):device-fleet/(?[^/:]+)/device/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-device.html"
-    },
-    "AWS::SageMaker::DeviceFleet": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):device-fleet/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-devicefleet.html"
-    },
-    "AWS::SageMaker::Domain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):domain/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-domain.html"
-    },
-    "AWS::SageMaker::FeatureGroup": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):feature-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-featuregroup.html"
-    },
-    "AWS::SageMaker::Image": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):image/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-image.html"
-    },
-    "AWS::SageMaker::ImageVersion": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):image-version/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-imageversion.html"
-    },
-    "AWS::SageMaker::ModelBiasJobDefinition": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):model-bias-job-definition/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-modelbiasjobdefinition.html"
-    },
-    "AWS::SageMaker::ModelExplainabilityJobDefinition": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):model-explainability-job-definition/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-modelexplainabilityjobdefinition.html"
-    },
-    "AWS::SageMaker::ModelPackage": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):model-package/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-modelpackage.html"
-    },
-    "AWS::SageMaker::ModelPackageGroup": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):model-package-group/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-modelpackagegroup.html"
-    },
-    "AWS::SageMaker::ModelQualityJobDefinition": {
-        "operations": ["CREATE", "DELETE", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):model-quality-job-definition/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-modelqualityjobdefinition.html"
-    },
-    "AWS::SageMaker::MonitoringSchedule": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):monitoring-schedule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-monitoringschedule.html"
-    },
-    "AWS::SageMaker::Pipeline": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):pipeline/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-pipeline.html"
-    },
-    "AWS::SageMaker::Project": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):project/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-project.html"
-    },
-    "AWS::SageMaker::UserProfile": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):sagemaker:(?[a-z0-9-]+):(?[0-9]{12}):user-profile/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-userprofile.html"
-    },
-    "AWS::ServiceCatalog::CloudFormationProvisionedProduct": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-cloudformationprovisionedproduct.html"
-    },
-    "AWS::ServiceCatalog::ServiceAction": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-serviceaction.html"
-    },
-    "AWS::ServiceCatalog::ServiceActionAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalog-serviceactionassociation.html"
-    },
-    "AWS::ServiceCatalogAppRegistry::Application": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalogappregistry-application.html"
-    },
-    "AWS::ServiceCatalogAppRegistry::AttributeGroup": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalogappregistry-attributegroup.html"
-    },
-    "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalogappregistry-attributegroupassociation.html"
-    },
-    "AWS::ServiceCatalogAppRegistry::ResourceAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-servicecatalogappregistry-resourceassociation.html"
-    },
-    "AWS::Signer::ProfilePermission": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-signer-profilepermission.html"
-    },
-    "AWS::Signer::SigningProfile": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):signer:(?[a-z0-9-]+):(?[0-9]{12}):/signing-profiles/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-signer-signingprofile.html"
-    },
-    "AWS::StepFunctions::Activity": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):states:(?[a-z0-9-]+):(?[0-9]{12}):activity:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-activity.html"
-    },
-    "AWS::StepFunctions::StateMachine": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):states:(?[a-z0-9-]+):(?[0-9]{12}):stateMachine:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-stepfunctions-statemachine.html"
-    },
-    "AWS::SupportApp::AccountAlias": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-supportapp-accountalias.html"
-    },
-    "AWS::SupportApp::SlackChannelConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-supportapp-slackchannelconfiguration.html"
-    },
-    "AWS::Synthetics::Canary": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):synthetics:(?[a-z0-9-]+):(?[0-9]{12}):canary:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html"
-    },
-    "AWS::Synthetics::Group": {
-        "operations": ["CREATE", "UPDATE", "READ", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):synthetics:(?[a-z0-9-]+):(?[0-9]{12}):group:(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-group.html"
-    },
-    "AWS::Timestream::Database": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):timestream:(?[a-z0-9-]+):(?[0-9]{12}):database/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-database.html"
-    },
-    "AWS::Timestream::ScheduledQuery": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):timestream:(?[a-z0-9-]+):(?[0-9]{12}):scheduled-query/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-scheduledquery.html"
-    },
-    "AWS::Timestream::Table": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):timestream:(?[a-z0-9-]+):(?[0-9]{12}):database/(?[^/:]+)/table/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-timestream-table.html"
-    },
-    "AWS::Transfer::Workflow": {
-        "operations": ["CREATE", "READ", "DELETE", "LIST", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):transfer:(?[a-z0-9-]+):(?[0-9]{12}):workflow/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-transfer-workflow.html"
-    },
-    "AWS::VoiceID::Domain": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "arnRegex": "arn:(?[a-z-]+):voiceid:(?[a-z0-9-]+):(?[0-9]{12}):domain/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-voiceid-domain.html"
-    },
-    "AWS::WAFv2::IPSet": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):wafv2:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)/ipset/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-ipset.html"
-    },
-    "AWS::WAFv2::LoggingConfiguration": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE", "LIST"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-loggingconfiguration.html"
-    },
-    "AWS::WAFv2::RegexPatternSet": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):wafv2:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)/regexpatternset/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-regexpatternset.html"
-    },
-    "AWS::WAFv2::RuleGroup": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):wafv2:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)/rulegroup/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-rulegroup.html"
-    },
-    "AWS::WAFv2::WebACL": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "arnRegex": "arn:(?[a-z-]+):wafv2:(?[a-z0-9-]+):(?[0-9]{12}):(?[^/:]+)/webacl/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webacl.html"
-    },
-    "AWS::WAFv2::WebACLAssociation": {
-        "operations": ["CREATE", "DELETE", "READ", "UPDATE"],
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wafv2-webaclassociation.html"
-    },
-    "AWS::Wisdom::Assistant": {
-        "operations": ["CREATE", "READ", "LIST", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):wisdom:(?[a-z0-9-]+):(?[0-9]{12}):assistant/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistant.html"
-    },
-    "AWS::Wisdom::AssistantAssociation": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):wisdom:(?[a-z0-9-]+):(?[0-9]{12}):association/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-assistantassociation.html"
-    },
-    "AWS::Wisdom::KnowledgeBase": {
-        "operations": ["CREATE", "DELETE", "LIST", "READ"],
-        "arnRegex": "arn:(?[a-z-]+):wisdom:(?[a-z0-9-]+):(?[0-9]{12}):knowledge-base/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-wisdom-knowledgebase.html"
-    },
-    "AWS::WorkSpaces::ConnectionAlias": {
-        "operations": ["CREATE", "READ", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):workspaces:(?[a-z0-9-]+):(?[0-9]{12}):connectionalias/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-workspaces-connectionalias.html"
-    },
-    "AWS::XRay::Group": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):xray:(?[a-z0-9-]+):(?[0-9]{12}):group/(?[^/:]+)/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-xray-group.html"
-    },
-    "AWS::XRay::SamplingRule": {
-        "operations": ["CREATE", "READ", "UPDATE", "DELETE"],
-        "arnRegex": "arn:(?[a-z-]+):xray:(?[a-z0-9-]+):(?[0-9]{12}):sampling-rule/(?[^/:]+)",
-        "documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-xray-samplingrule.html"
-    }
-}
diff --git a/packages/core/src/eventSchemas/activation.ts b/packages/core/src/eventSchemas/activation.ts
deleted file mode 100644
index 5e68b7df06..0000000000
--- a/packages/core/src/eventSchemas/activation.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { downloadSchemaItemCode } from '../eventSchemas/commands/downloadSchemaItemCode'
-import { createSearchSchemasWebView } from './vue/searchSchemas'
-import { viewSchemaItem } from '../eventSchemas/commands/viewSchemaItem'
-import { RegistryItemNode } from '../eventSchemas/explorer/registryItemNode'
-import { SchemaItemNode } from '../eventSchemas/explorer/schemaItemNode'
-import { SchemasNode } from '../eventSchemas/explorer/schemasNode'
-import { ExtContext } from '../shared/extensions'
-import { Commands } from '../shared/vscode/commands2'
-
-/**
- * Activate Schemas functionality for the extension.
- */
-export async function activate(context: ExtContext): Promise {
-    await registerSchemasCommands(context)
-}
-
-async function registerSchemasCommands(context: ExtContext): Promise {
-    context.extensionContext.subscriptions.push(
-        Commands.register('aws.viewSchemaItem', async (node: SchemaItemNode) => await viewSchemaItem(node)),
-        Commands.register(
-            'aws.downloadSchemaItemCode',
-            async (node: SchemaItemNode) => await downloadSchemaItemCode(node, context.outputChannel)
-        ),
-        Commands.register(
-            'aws.searchSchema',
-            async (node: SchemasNode) => await createSearchSchemasWebView(context, node)
-        ),
-        Commands.register(
-            'aws.searchSchemaPerRegistry',
-            async (node: RegistryItemNode) => await createSearchSchemasWebView(context, node)
-        )
-    )
-}
diff --git a/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts b/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts
deleted file mode 100644
index 0abc1e340e..0000000000
--- a/packages/core/src/eventSchemas/commands/downloadSchemaItemCode.ts
+++ /dev/null
@@ -1,405 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-import { PutCodeBindingResponse } from '@aws-sdk/client-schemas'
-import fs = require('fs')
-import path = require('path')
-import * as vscode from 'vscode'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
-import * as localizedText from '../../shared/localizedText'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { Result } from '../../shared/telemetry/telemetry'
-import { SchemaItemNode } from '../explorer/schemaItemNode'
-import { getLanguageDetails } from '../models/schemaCodeLangs'
-
-import {
-    DefaultSchemaCodeDownloadWizardContext,
-    SchemaCodeDownloadWizard,
-    SchemaCodeDownloadWizardResponse,
-} from '../wizards/schemaCodeDownloadWizard'
-
-import admZip from 'adm-zip'
-import globals from '../../shared/extensionGlobals'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-enum CodeGenerationStatus {
-    CREATE_COMPLETE = 'CREATE_COMPLETE',
-    CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS',
-}
-
-const retryintervalms = 2000
-const _maxRetries = 150 // p100 of Java code generation is 250 seconds. So retry for an even 5 minutes.
-
-export async function downloadSchemaItemCode(node: SchemaItemNode, outputChannel: vscode.OutputChannel) {
-    const logger: Logger = getLogger()
-    let downloadResult: Result = 'Succeeded'
-
-    try {
-        const wizardContext = new DefaultSchemaCodeDownloadWizardContext(node)
-        const wizardResponse: SchemaCodeDownloadWizardResponse | undefined = await new SchemaCodeDownloadWizard(
-            wizardContext
-        ).run()
-        if (!wizardResponse) {
-            return
-        }
-
-        void vscode.window.showInformationMessage(
-            localize(
-                'AWS.message.info.schemas.downloadCodeBindings.start',
-                'Downloading code for schema {0}...',
-                node.schemaName
-            )
-        )
-
-        const coreFileName = getCoreFileName(node.schemaName, getLanguageDetails(wizardResponse.language).extension)
-        const request: SchemaCodeDownloadRequestDetails = {
-            registryName: node.registryName,
-            schemaName: node.schemaName,
-            language: getLanguageDetails(wizardResponse.language).apiValue,
-            schemaVersion: wizardResponse.schemaVersion,
-            destinationDirectory: wizardResponse.location,
-            schemaCoreCodeFileName: coreFileName,
-        }
-        const schemaCodeDownloader = createSchemaCodeDownloaderObject(node.client, outputChannel)
-        const coreCodeFilePath = await schemaCodeDownloader.downloadCode(request)
-        void vscode.window.showInformationMessage(
-            localize(
-                'AWS.message.info.schemas.downloadCodeBindings.finished',
-                'Downloaded code for schema {0}!',
-                request.schemaName
-            )
-        )
-
-        if (coreCodeFilePath) {
-            await vscode.window.showTextDocument(vscode.Uri.file(coreCodeFilePath))
-        }
-    } catch (err) {
-        downloadResult = 'Failed'
-        const error = err as Error
-        let errorMessage = localize(
-            'AWS.message.error.schemas.downloadCodeBindings.failed_to_download',
-            'Unable to download schema code'
-        )
-
-        if (error instanceof UserNotifiedError && error.message) {
-            errorMessage = error.message
-        }
-        void vscode.window.showErrorMessage(errorMessage)
-        logger.error('Error downloading schema: %s', error)
-    } finally {
-        telemetry.schemas_download.emit({ result: downloadResult })
-    }
-}
-
-function getCoreFileName(schemaName: string, fileExtension: string) {
-    const parsedName = schemaName.split('@')
-
-    return parsedName[parsedName.length - 1].concat(fileExtension)
-}
-
-export function createSchemaCodeDownloaderObject(
-    client: SchemaClient,
-    outputChannel: vscode.OutputChannel
-): SchemaCodeDownloader {
-    const downloader = new CodeDownloader(client)
-    const generator = new CodeGenerator(client)
-    const poller = new CodeGenerationStatusPoller(client)
-    const extractor = new CodeExtractor(outputChannel)
-
-    return new SchemaCodeDownloader(downloader, generator, poller, extractor)
-}
-
-export interface SchemaCodeDownloadRequestDetails {
-    registryName: string
-    schemaName: string
-    language: string
-    schemaVersion: string
-    destinationDirectory: vscode.Uri
-    schemaCoreCodeFileName?: string
-}
-
-export class SchemaCodeDownloader {
-    public constructor(
-        private readonly downloader: CodeDownloader,
-        private readonly generator: CodeGenerator,
-        private readonly poller: CodeGenerationStatusPoller,
-        private readonly extractor: CodeExtractor
-    ) {}
-
-    public async downloadCode(request: SchemaCodeDownloadRequestDetails): Promise {
-        let zipContents: ArrayBuffer
-        try {
-            // If the code bindings for a given schema previously generated, this would succeed
-            zipContents = await this.downloader.download(request)
-        } catch (err) {
-            const error = err as Error
-            if (error.name === 'ResourceNotFound') {
-                // If the code generation wasn't previously kicked off, do so
-                void vscode.window.showInformationMessage(
-                    localize(
-                        'AWS.message.info.schemas.downloadCodeBindings.generate',
-                        '{0}: Generating code (this may take a few seconds the first time)...',
-                        request.schemaName
-                    )
-                )
-                await this.generator.generate(request)
-
-                // Then, poll for completion
-                await this.poller.pollForCompletion(request)
-
-                // Download generated code bindings
-                void vscode.window.showInformationMessage(
-                    localize(
-                        'AWS.message.info.schemas.downloadCodeBindings.downloading',
-                        '{0}: Downloading code...',
-                        request.schemaName
-                    )
-                )
-                zipContents = await this.downloader.download(request)
-            } else {
-                throw err // Unexpected exception, throw
-            }
-        }
-        void vscode.window.showInformationMessage(
-            localize(
-                'AWS.message.info.schemas.downloadCodeBindings.extracting',
-                '{0}: Extracting/copying code...',
-                request.schemaName
-            )
-        )
-
-        return await this.extractor.extractAndPlace(zipContents, request)
-    }
-}
-
-export class CodeGenerator {
-    public constructor(public client: SchemaClient) {}
-
-    public async generate(codeDownloadRequest: SchemaCodeDownloadRequestDetails): Promise {
-        let response: PutCodeBindingResponse
-        try {
-            response = await this.client.putCodeBinding(
-                codeDownloadRequest.language,
-                codeDownloadRequest.registryName,
-                codeDownloadRequest.schemaName,
-                codeDownloadRequest.schemaVersion
-            )
-        } catch (err) {
-            const error = err as Error
-            if (error.stack && error.stack!.includes('ConflictException')) {
-                response = {
-                    Status: CodeGenerationStatus.CREATE_IN_PROGRESS,
-                }
-            } else {
-                getLogger().error(error)
-                throw new UserNotifiedError(
-                    localize(
-                        'AWS.message.error.schemas.downloadCodeBindings.failed_to_generate',
-                        'Unable to generate schema code'
-                    )
-                )
-            }
-        }
-
-        return response
-    }
-}
-
-export class CodeGenerationStatusPoller {
-    public constructor(public client: SchemaClient) {}
-
-    public async pollForCompletion(
-        codeDownloadRequest: SchemaCodeDownloadRequestDetails,
-        retryInterval: number = retryintervalms,
-        maxRetries: number = _maxRetries
-    ): Promise {
-        for (let i = 0; i < maxRetries; i++) {
-            const codeGenerationStatus = await this.getCurrentStatus(codeDownloadRequest)
-
-            if (codeGenerationStatus === CodeGenerationStatus.CREATE_COMPLETE) {
-                return codeGenerationStatus
-            }
-            if (codeGenerationStatus !== CodeGenerationStatus.CREATE_IN_PROGRESS) {
-                throw new UserNotifiedError(
-                    localize(
-                        'AWS.message.error.schemas.downloadCodeBindings.invalid_code_generation_status',
-                        'Invalid Code generation status {0}',
-                        codeGenerationStatus
-                    )
-                )
-            }
-
-            await new Promise((resolve) => globals.clock.setTimeout(resolve, retryInterval))
-        }
-        throw new UserNotifiedError(
-            localize(
-                'AWS.message.error.schemas.downloadCodeBindings.timeout',
-                'Failed to download code for schema {0} before timeout. Please try again later',
-                codeDownloadRequest.schemaName
-            )
-        )
-    }
-
-    public async getCurrentStatus(codeDownloadRequest: SchemaCodeDownloadRequestDetails): Promise {
-        const response = await this.client.describeCodeBinding(
-            codeDownloadRequest.language,
-            codeDownloadRequest.registryName,
-            codeDownloadRequest.schemaName,
-            codeDownloadRequest.schemaVersion
-        )
-
-        return response.Status
-    }
-}
-export class CodeDownloader {
-    public constructor(public client: SchemaClient) {}
-
-    public async download(codeDownloadRequest: SchemaCodeDownloadRequestDetails): Promise {
-        const response = await this.client.getCodeBindingSource(
-            codeDownloadRequest.language,
-            codeDownloadRequest.registryName,
-            codeDownloadRequest.schemaName,
-            codeDownloadRequest.schemaVersion
-        )
-
-        if (Buffer.isBuffer(response.Body)) {
-            const zipContents = response.Body!.buffer
-
-            return zipContents
-        } else {
-            throw new TypeError('Response body should be Buffer type')
-        }
-    }
-}
-
-export class CodeExtractor {
-    public constructor(private readonly outputChannel: vscode.OutputChannel) {}
-    public async extractAndPlace(
-        zipContents: ArrayBuffer,
-        request: SchemaCodeDownloadRequestDetails
-    ): Promise {
-        let codeZipDir: string | undefined
-        try {
-            const fileName = `${request.schemaName}.${request.schemaVersion}.${request.language}.zip`
-
-            codeZipDir = await makeTemporaryToolkitFolder()
-
-            const codeZipFile = path.join(codeZipDir, fileName)
-            const destinationDirectory = request.destinationDirectory.fsPath
-
-            // write binary data into a temp zip file in a temp directory
-            const zipContentsBinary = new Uint8Array(zipContents)
-            const fd = fs.openSync(codeZipFile, 'w')
-            fs.writeSync(fd, zipContentsBinary, 0, zipContentsBinary.byteLength, 0)
-            fs.closeSync(fd)
-
-            let overwriteFiles: boolean = false
-            const collisionExist = this.checkFileCollisions(codeZipFile, destinationDirectory)
-
-            if (collisionExist) {
-                overwriteFiles = await this.confirmOverwriteCollisions()
-            }
-
-            const zip = new admZip(codeZipFile)
-            zip.extractAllTo(destinationDirectory, overwriteFiles)
-
-            const coreCodeFilePath = this.getCoreCodeFilePath(codeZipFile, request.schemaCoreCodeFileName)
-
-            if (coreCodeFilePath) {
-                return path.join(destinationDirectory, coreCodeFilePath)
-            }
-
-            return undefined
-        } finally {
-            await tryRemoveFolder(codeZipDir)
-        }
-    }
-
-    // Check if downloaded code hierarchy has collisions with the destination directory and display them in output channel
-    public checkFileCollisions(codeZipFile: string, destinationDirectory: string): boolean {
-        const zip = new admZip(codeZipFile)
-        const zipEntries = zip.getEntries()
-        const detectedCollisions: string[] = []
-
-        for (const zipEntry of zipEntries) {
-            if (zipEntry.isDirectory) {
-                // Ignore directories because those can/will merged
-            } else {
-                const intendedDestinationPath = path.join(destinationDirectory, '/', zipEntry.entryName)
-                if (fs.existsSync(intendedDestinationPath)) {
-                    detectedCollisions.push(intendedDestinationPath)
-                }
-            }
-        }
-
-        if (detectedCollisions.length > 0) {
-            this.writeToOutputChannel(detectedCollisions)
-        }
-
-        return detectedCollisions.length > 0
-    }
-
-    public writeToOutputChannel(detectedCollisions: string[]) {
-        this.outputChannel.show(true)
-        this.outputChannel.appendLine(
-            localize(
-                'AWS.message.info.schemas.downloadCodeBindings.colliding_files',
-                'Following files already exist in the folder hierarchy :'
-            )
-        )
-
-        for (const filePath of detectedCollisions) {
-            this.outputChannel.appendLine(filePath)
-        }
-    }
-
-    public async confirmOverwriteCollisions(): Promise {
-        const userResponse = await vscode.window.showInformationMessage(
-            localize(
-                'AWS.message.info.schemas.downloadCodeBindings.colliding_override',
-                'Downloaded code hierarchy has collisions in the destination directory. Overwrite it?'
-            ),
-            { modal: true },
-            localizedText.yes,
-            localizedText.no
-        )
-
-        if (!userResponse) {
-            throw new UserNotifiedError(
-                localize('AWS.message.error.schemas.downloadCodeBindings.cancelled', 'Download code bindings cancelled')
-            )
-        }
-
-        return userResponse === localizedText.yes
-    }
-
-    public getCoreCodeFilePath(codeZipFile: string, coreFileName: string | undefined): string | undefined {
-        if (coreFileName) {
-            const zip = new admZip(codeZipFile)
-            const zipEntries = zip.getEntries()
-
-            for (const zipEntry of zipEntries) {
-                if (zipEntry.isDirectory) {
-                    // Ignore directories
-                } else {
-                    if (zipEntry.name === coreFileName) {
-                        return zipEntry.entryName
-                    }
-                }
-            }
-        }
-
-        return undefined
-    }
-}
-
-class UserNotifiedError extends Error {
-    public constructor(message?: string | undefined) {
-        super(message)
-    }
-}
diff --git a/packages/core/src/eventSchemas/commands/viewSchemaItem.ts b/packages/core/src/eventSchemas/commands/viewSchemaItem.ts
deleted file mode 100644
index cc40911ea9..0000000000
--- a/packages/core/src/eventSchemas/commands/viewSchemaItem.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as vscode from 'vscode'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { Result } from '../../shared/telemetry/telemetry'
-import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
-import { SchemaItemNode } from '../explorer/schemaItemNode'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-export async function viewSchemaItem(node: SchemaItemNode) {
-    const logger: Logger = getLogger()
-
-    let viewResult: Result = 'Succeeded'
-    try {
-        const rawSchemaContent = await node.getSchemaContent()
-        await showSchemaContent(rawSchemaContent)
-    } catch (err) {
-        viewResult = 'Failed'
-        const error = err as Error
-        void vscode.window.showErrorMessage(
-            localize(
-                'AWS.message.error.schemas.viewSchema.could_not_open',
-                'Could not fetch and display schema {0} contents',
-                node.schemaName
-            )
-        )
-        logger.error('Error on schema preview: %s', error)
-    } finally {
-        telemetry.schemas_view.emit({ result: viewResult })
-    }
-}
-
-export function schemaFormatter(rawSchemaContent: string, tabSize: number = getTabSizeSetting()): string {
-    const prettySchemaContent = JSON.stringify(JSON.parse(rawSchemaContent), undefined, tabSize)
-
-    return prettySchemaContent
-}
-
-export async function showSchemaContent(
-    rawSchemaContent: string,
-    tabSize: number = getTabSizeSetting()
-): Promise {
-    const prettySchemaContent = schemaFormatter(rawSchemaContent, tabSize)
-    const newDoc = await vscode.workspace.openTextDocument({
-        language: 'json',
-    })
-    const editor = await vscode.window.showTextDocument(newDoc, vscode.ViewColumn.One, false)
-    await editor.edit((edit) => edit.insert(new vscode.Position(/* line*/ 0, /* character*/ 0), prettySchemaContent))
-}
diff --git a/packages/core/src/eventSchemas/explorer/registryItemNode.ts b/packages/core/src/eventSchemas/explorer/registryItemNode.ts
deleted file mode 100644
index 8141259b95..0000000000
--- a/packages/core/src/eventSchemas/explorer/registryItemNode.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import { RegistrySummary } from '@aws-sdk/client-schemas'
-import * as os from 'os'
-import * as vscode from 'vscode'
-
-import { listSchemaItems } from '../utils'
-
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { toMapAsync, updateInPlace } from '../../shared/utilities/collectionUtils'
-import { SchemaItemNode } from './schemaItemNode'
-import { getIcon } from '../../shared/icons'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-
-export class RegistryItemNode extends AWSTreeNodeBase {
-    private readonly schemaNodes: Map
-    public override readonly regionCode: string = this.client.regionCode
-
-    public constructor(
-        private registryItemOutput: RegistrySummary,
-        private readonly client: SchemaClient
-    ) {
-        super('', vscode.TreeItemCollapsibleState.Collapsed)
-
-        this.update(registryItemOutput)
-        this.contextValue = 'awsRegistryItemNode'
-        this.schemaNodes = new Map()
-        this.iconPath = getIcon('aws-schemas-registry')
-    }
-
-    public get registryName(): string {
-        return (
-            this.registryItemOutput.RegistryName ||
-            localize('AWS.explorerNode.registry.registryName.Not.Found', 'Registry name not found')
-        )
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-
-                return [...this.schemaNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.registry.noSchemas', '[No Registry Schemas]')),
-            sort: (nodeA, nodeB) => nodeA.schemaName.localeCompare(nodeB.schemaName),
-        })
-    }
-
-    public update(registryItemOutput: RegistrySummary): void {
-        this.registryItemOutput = registryItemOutput
-        this.label = `${this.registryName}`
-        let registryArn = ''
-        if (this.registryItemOutput.RegistryArn) {
-            registryArn = `${os.EOL}${this.registryItemOutput.RegistryArn}`
-        }
-        this.tooltip = `${this.registryName}${registryArn}`
-    }
-
-    public async updateChildren(): Promise {
-        const schemas = await toMapAsync(listSchemaItems(this.client, this.registryName), (schema) => schema.SchemaName)
-
-        updateInPlace(
-            this.schemaNodes,
-            schemas.keys(),
-            (key) => this.schemaNodes.get(key)!.update(schemas.get(key)!),
-            (key) => new SchemaItemNode(schemas.get(key)!, this.client, this.registryName)
-        )
-    }
-}
diff --git a/packages/core/src/eventSchemas/explorer/schemaItemNode.ts b/packages/core/src/eventSchemas/explorer/schemaItemNode.ts
deleted file mode 100644
index 790834e165..0000000000
--- a/packages/core/src/eventSchemas/explorer/schemaItemNode.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { SchemaSummary, SchemaVersionSummary } from '@aws-sdk/client-schemas'
-import * as os from 'os'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { toArrayAsync } from '../../shared/utilities/collectionUtils'
-import { getIcon } from '../../shared/icons'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-
-export class SchemaItemNode extends AWSTreeNodeBase {
-    public constructor(
-        private schemaItem: SchemaSummary,
-        public readonly client: SchemaClient,
-        public readonly registryName: string
-    ) {
-        super('')
-        this.update(schemaItem)
-        this.contextValue = 'awsSchemaItemNode'
-        this.iconPath = getIcon('aws-schemas-schema')
-        this.command = {
-            command: 'aws.viewSchemaItem',
-            title: localize('AWS.command.viewSchemaItem', 'Open Schema'),
-            arguments: [this],
-        }
-    }
-
-    public update(schemaItem: SchemaSummary): void {
-        this.schemaItem = schemaItem
-        this.label = this.schemaItem.SchemaName || ''
-        let schemaArn = ''
-        if (this.schemaItem.SchemaArn) {
-            schemaArn = `${os.EOL}${this.schemaItem.SchemaArn}`
-        }
-        this.tooltip = `${this.schemaItem.SchemaName}${schemaArn}`
-    }
-
-    public get schemaName(): string {
-        return this.schemaItem.SchemaName || ''
-    }
-
-    public async getSchemaContent(): Promise {
-        const response = await this.client.describeSchema(this.registryName, this.schemaName)
-
-        return response.Content!
-    }
-
-    public async listSchemaVersions(): Promise {
-        const versions = await toArrayAsync(this.client.listSchemaVersions(this.registryName, this.schemaName))
-
-        return versions
-    }
-}
diff --git a/packages/core/src/eventSchemas/explorer/schemasNode.ts b/packages/core/src/eventSchemas/explorer/schemasNode.ts
deleted file mode 100644
index 4f9b584b82..0000000000
--- a/packages/core/src/eventSchemas/explorer/schemasNode.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as vscode from 'vscode'
-
-import { listRegistryItems } from '../../eventSchemas/utils'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { toMapAsync, updateInPlace } from '../../shared/utilities/collectionUtils'
-import { RegistryItemNode } from './registryItemNode'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-
-export class SchemasNode extends AWSTreeNodeBase {
-    private readonly registryNodes: Map
-    public override readonly regionCode = this.client.regionCode
-
-    public constructor(private readonly client: SchemaClient) {
-        super('Schemas', vscode.TreeItemCollapsibleState.Collapsed)
-        this.registryNodes = new Map()
-        this.contextValue = 'awsSchemasNode'
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-
-                return [...this.registryNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.schemas.noRegistry', '[No Schema Registries]')),
-            sort: (nodeA, nodeB) => nodeA.registryName.localeCompare(nodeB.registryName),
-        })
-    }
-
-    public async updateChildren(): Promise {
-        const registries = await toMapAsync(listRegistryItems(this.client), (registry) => registry.RegistryName)
-
-        updateInPlace(
-            this.registryNodes,
-            registries.keys(),
-            (key) => this.registryNodes.get(key)!.update(registries.get(key)!),
-            (key) => new RegistryItemNode(registries.get(key)!, this.client)
-        )
-    }
-}
diff --git a/packages/core/src/eventSchemas/models/schemaCodeGenUtils.ts b/packages/core/src/eventSchemas/models/schemaCodeGenUtils.ts
deleted file mode 100644
index 155b600b82..0000000000
--- a/packages/core/src/eventSchemas/models/schemaCodeGenUtils.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// TODO: This is fragile. Very fragile. But it is necessary to get Schemas service launched, and we've evaluated all other tradeoffs
-// This will be done on the server-side as soon as we can, but for now the client needs to do this
-export class SchemaCodeGenUtils {
-    private readonly schemaPackagePrefix = 'schema'
-    private readonly AWS = 'aws'
-    private readonly PARTNER = 'partner'
-    private readonly awsPartnerPrefix = `${this.AWS}.${this.PARTNER}-` // dash suffix because of 3p partner registry name format
-    private readonly awsEventsPrefix = `${this.AWS}.` // . suffix because of 1p event registry schema format
-
-    public buildSchemaPackageName(schemaName: string): string {
-        const builder = new CodeGenPackageBuilder()
-        builder.append(this.schemaPackagePrefix)
-        this.buildPackageName(builder, schemaName)
-
-        return builder.build()
-    }
-
-    private buildPackageName(builder: CodeGenPackageBuilder, schemaName: string): void {
-        // do not modify the order of conditional checks
-        if (this.isAwsPartnerEvent(schemaName)) {
-            this.buildPartnerEventPackageName(builder, schemaName)
-        } else if (this.isAwsEvent(schemaName)) {
-            this.buildAwsEventPackageName(builder, schemaName)
-        } else {
-            this.buildCustomPackageName(builder, schemaName)
-        }
-    }
-
-    private isAwsPartnerEvent(schemaName: string): boolean {
-        return schemaName.startsWith(this.awsPartnerPrefix)
-    }
-
-    private buildPartnerEventPackageName(builder: CodeGenPackageBuilder, schemaName: string): void {
-        const partnerSchemaString = schemaName.substring(this.awsPartnerPrefix.length)
-
-        builder.append(this.AWS).append(this.PARTNER).append(partnerSchemaString)
-    }
-
-    private isAwsEvent(name: string): boolean {
-        return name.startsWith(this.awsEventsPrefix)
-    }
-
-    private buildAwsEventPackageName(builder: CodeGenPackageBuilder, schemaName: string): void {
-        const awsEventSchemaParts = schemaName.split('.')
-        for (const part of awsEventSchemaParts) {
-            builder.append(part)
-        }
-    }
-
-    private buildCustomPackageName(builder: CodeGenPackageBuilder, schemaName: string): void {
-        builder.append(schemaName)
-    }
-}
-
-class CodeGenPackageBuilder {
-    private builder = ''
-    public build(): string {
-        return this.builder
-    }
-
-    public append(segment: string): CodeGenPackageBuilder {
-        if (this.builder.length > 0) {
-            this.builder = this.builder.concat(packageSeparator)
-        }
-        this.builder = this.builder.concat(toValidIdentifier(segment.toLowerCase()))
-
-        return this
-    }
-}
-
-export const packageSeparator = '.'
-const potentialPackageSeparator = '@'
-const notValidIdentifierRegex = new RegExp(`[^a-zA-Z0-9_${potentialPackageSeparator}]`, 'g')
-const potentialPackageSeparatorRegex = new RegExp(potentialPackageSeparator, 'g')
-const underscore = '_'
-
-export function toValidIdentifier(name: string): string {
-    return name.replace(notValidIdentifierRegex, underscore).replace(potentialPackageSeparatorRegex, packageSeparator)
-}
diff --git a/packages/core/src/eventSchemas/models/schemaCodeLangs.ts b/packages/core/src/eventSchemas/models/schemaCodeLangs.ts
deleted file mode 100644
index 81814a34cc..0000000000
--- a/packages/core/src/eventSchemas/models/schemaCodeLangs.ts
+++ /dev/null
@@ -1,77 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { Runtime } from '@aws-sdk/client-lambda'
-import { Set as ImmutableSet } from 'immutable'
-import { goRuntimes } from '../../lambda/models/samLambdaRuntime'
-
-export const JAVA = 'Java 8+' // eslint-disable-line @typescript-eslint/naming-convention
-export const PYTHON = 'Python 3.6+' // eslint-disable-line @typescript-eslint/naming-convention
-export const TYPESCRIPT = 'Typescript 3+' // eslint-disable-line @typescript-eslint/naming-convention
-export const GO = 'Go 1+' // eslint-disable-line @typescript-eslint/naming-convention
-export type SchemaCodeLangs = 'Java 8+' | 'Python 3.6+' | 'Typescript 3+' | 'Go 1+'
-
-export const schemaCodeLangs: ImmutableSet = ImmutableSet([JAVA, PYTHON, TYPESCRIPT, GO])
-
-const javaDetail = {
-    apiValue: 'Java8',
-    extension: '.java',
-}
-
-const pythonDetail = {
-    apiValue: 'Python36',
-    extension: '.py',
-}
-
-const typescriptDetail = {
-    apiValue: 'TypeScript3',
-    extension: '.ts',
-}
-
-const goDetail = {
-    apiValue: 'Go1',
-    extension: '.go',
-}
-
-export function getLanguageDetails(language: SchemaCodeLangs): {
-    apiValue: string
-    extension: string
-} {
-    switch (language) {
-        case JAVA:
-            return javaDetail
-        case PYTHON:
-            return pythonDetail
-        case TYPESCRIPT:
-            return typescriptDetail
-        case GO:
-            return goDetail
-        default:
-            throw new Error(`Language ${language} is not supported as Schema Code Language`)
-    }
-}
-
-export function supportsEventBridgeTemplates(runtime: Runtime): boolean {
-    return [
-        'python3.7',
-        'python3.8',
-        'python3.9',
-        'python3.10',
-        'python3.11',
-        'python3.12',
-        'python3.13',
-        'python3.14',
-        'go1.x',
-    ].includes(runtime)
-}
-
-export function getApiValueForSchemasDownload(runtime: Runtime): string {
-    if (supportsEventBridgeTemplates(runtime)) {
-        // Python36 really means python3.x for schema downloading
-        return goRuntimes.has(runtime) ? 'Go1' : 'Python36'
-    }
-
-    throw new Error(`Runtime ${runtime} is not supported by eventBridge application`)
-}
diff --git a/packages/core/src/eventSchemas/providers/schemasDataProvider.ts b/packages/core/src/eventSchemas/providers/schemasDataProvider.ts
deleted file mode 100644
index e0717b6b6d..0000000000
--- a/packages/core/src/eventSchemas/providers/schemasDataProvider.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as AWS from '@aws-sdk/types'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { toArrayAsync } from '../../shared/utilities/collectionUtils'
-import { SchemaSummary } from '@aws-sdk/client-schemas'
-
-export class Cache {
-    public constructor(public readonly credentialsRegionDataList: credentialsRegionDataListMap[]) {}
-}
-
-export interface credentialsRegionDataListMap {
-    credentials: AWS.Credentials
-    regionDataList: regionRegistryMap[]
-}
-
-export interface regionRegistryMap {
-    region: string
-    registryNames: string[]
-    registrySchemasMapList: registrySchemasMap[]
-}
-
-export interface registrySchemasMap {
-    registryName: string
-    schemaList: SchemaSummary[]
-}
-
-/**
- * Responsible for retaining registry && schema list per region for Create-New-SAM-Application wizard
- */
-export class SchemasDataProvider {
-    private static INSTANCE: SchemasDataProvider | undefined
-    private readonly logger: Logger = getLogger()
-
-    public constructor(private readonly cache: Cache) {}
-
-    public async getRegistries(region: string, client: SchemaClient, credentials: AWS.Credentials) {
-        const cachedRegion = this.cache.credentialsRegionDataList
-            .filter((x) => x.credentials === credentials)
-            .shift()
-            ?.regionDataList.filter((x) => x.region === region)
-            .shift()
-
-        try {
-            // if region is not cached, make api query and retain results
-            if (!cachedRegion || cachedRegion.registryNames.length === 0) {
-                const registrySummary = await toArrayAsync(client.listRegistries())
-                const registryNames = registrySummary.map((x) => x.RegistryName!)
-                this.pushRegionDataIntoCache(region, registryNames, [], credentials)
-
-                return registryNames
-            }
-        } catch (err) {
-            const error = err as Error
-            this.logger.error('Failed to get registries: %s', error)
-
-            return undefined
-        }
-
-        return cachedRegion!.registryNames
-    }
-
-    public async getSchemas(region: string, registryName: string, client: SchemaClient, credentials: AWS.Credentials) {
-        const registrySchemasMapList = this.cache.credentialsRegionDataList
-            .filter((x) => x.credentials === credentials)
-            .shift()
-            ?.regionDataList.filter((x) => x.region === region)
-            .shift()?.registrySchemasMapList
-        let schemas = registrySchemasMapList?.filter((x) => x.registryName === registryName).shift()?.schemaList
-        try {
-            // if no schemas found, make api query and retain results given that registryName && region already cached
-            if (!schemas || schemas.length === 0) {
-                schemas = await toArrayAsync(client.listSchemas(registryName))
-                const singleItem: registrySchemasMap = { registryName: registryName, schemaList: schemas }
-                // wizard setup always calls getRegistries method prior to getSchemas, so this shouldn't be undefined
-                if (!registrySchemasMapList) {
-                    this.pushRegionDataIntoCache(region, [], [singleItem], credentials)
-                }
-
-                if (registrySchemasMapList) {
-                    registrySchemasMapList.push(singleItem)
-                }
-            }
-        } catch (err) {
-            const error = err as Error
-            this.logger.error('Failed to get schemas: %s', error)
-
-            return undefined
-        }
-
-        return schemas
-    }
-
-    private pushRegionDataIntoCache(
-        region: string,
-        registryNames: string[],
-        registrySchemasMapList: registrySchemasMap[],
-        credentials?: AWS.Credentials
-    ): void {
-        const regionData: regionRegistryMap = {
-            region: region,
-            registryNames: registryNames,
-            registrySchemasMapList: registrySchemasMapList,
-        }
-
-        const cachedCredential = this.cache.credentialsRegionDataList
-            .filter((x) => x.credentials === credentials)
-            .shift()
-        cachedCredential?.regionDataList.push(regionData)
-
-        if (!cachedCredential) {
-            const regionDataWithCredentials: credentialsRegionDataListMap = {
-                credentials: credentials!,
-                regionDataList: [regionData],
-            }
-            this.cache.credentialsRegionDataList.push(regionDataWithCredentials)
-        }
-    }
-
-    public static getInstance(): SchemasDataProvider {
-        if (!SchemasDataProvider.INSTANCE) {
-            SchemasDataProvider.INSTANCE = new SchemasDataProvider(new Cache([]))
-        }
-
-        return SchemasDataProvider.INSTANCE
-    }
-}
diff --git a/packages/core/src/eventSchemas/templates/schemasAppTemplateUtils.ts b/packages/core/src/eventSchemas/templates/schemasAppTemplateUtils.ts
deleted file mode 100644
index 51bbaca269..0000000000
--- a/packages/core/src/eventSchemas/templates/schemasAppTemplateUtils.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import _ = require('lodash')
-import { SchemaCodeGenUtils, toValidIdentifier } from '../../eventSchemas/models/schemaCodeGenUtils'
-import { SchemaClient } from '../../shared/clients/schemaClient'
-
-const xAmazonEventSource = 'x-amazon-events-source'
-const xAmazonEventDetailType = 'x-amazon-events-detail-type'
-
-const components = 'components'
-const schemas = 'schemas'
-const componentsSchemasPath = '#/components/schemas/'
-const awsEvent = 'AWSEvent'
-const properties = 'properties'
-const detail = 'detail'
-const ref = '$ref'
-
-const defaultEventSource = 'INSERT-YOUR-EVENT-SOURCE'
-const defaultEventDetailType = 'INSERT-YOUR-DETAIL-TYPE'
-
-export interface SchemaTemplateParameters {
-    SchemaVersion: string
-    templateExtraContent: SchemaTemplateExtraContext
-}
-
-// This matches the extra_content parameters to Schema-based templates in both key and value names in cookiecutter.json in the templates used by SamEventBridgeSchemaAppPython
-export interface SchemaTemplateExtraContext {
-    AWS_Schema_registry: string // eslint-disable-line @typescript-eslint/naming-convention
-    AWS_Schema_name: string // eslint-disable-line @typescript-eslint/naming-convention
-    AWS_Schema_root: string // eslint-disable-line @typescript-eslint/naming-convention
-    AWS_Schema_source: string // eslint-disable-line @typescript-eslint/naming-convention
-    AWS_Schema_detail_type: string // eslint-disable-line @typescript-eslint/naming-convention
-    user_agent: string // eslint-disable-line @typescript-eslint/naming-convention
-}
-
-export async function buildSchemaTemplateParameters(schemaName: string, registryName: string, client: SchemaClient) {
-    const response = await client.describeSchema(registryName, schemaName)
-    const schemaNode = JSON.parse(response.Content!)
-    const latestSchemaVersion = response.SchemaVersion
-    // Standard OpenAPI specification for AwsEventNode
-    const awsEventNode = _.get(schemaNode, components.concat('.', schemas, '.', awsEvent))
-
-    // Derive source from custom OpenAPI metadata provided by Schemas service
-    let source = _.get(awsEventNode, xAmazonEventSource)
-    if (!_.isString(source)) {
-        source = defaultEventSource
-    }
-
-    // Derive detail type from custom OpenAPI metadata provided by Schemas service
-    let detailType = _.get(awsEventNode, xAmazonEventDetailType)
-    if (!_.isString(detailType)) {
-        detailType = defaultEventDetailType
-    }
-
-    // Generate schema root/package from the scheme name
-    // In the near future, this will be returned as part of a Schemas Service API call
-    const schemaPackageHierarchy = buildSchemaPackageHierarchy(schemaName)
-
-    // Derive root schema event name from OpenAPI metadata, or if ambiguous, use the last post-character section of a schema name
-    const rootSchemaEventName = buildRootSchemaEventName(schemaNode, awsEventNode) || getCoreFileName(schemaName)
-
-    const templateExtraContent: SchemaTemplateExtraContext = {
-        AWS_Schema_registry: registryName, // eslint-disable-line @typescript-eslint/naming-convention
-        AWS_Schema_name: rootSchemaEventName!, // eslint-disable-line @typescript-eslint/naming-convention
-        AWS_Schema_root: schemaPackageHierarchy, // eslint-disable-line @typescript-eslint/naming-convention
-        AWS_Schema_source: source, // eslint-disable-line @typescript-eslint/naming-convention
-        AWS_Schema_detail_type: detailType, // eslint-disable-line @typescript-eslint/naming-convention
-        // Need to provide user agent to SAM CLI so that it will enable appTemplate-based
-        user_agent: 'AWSToolkit',
-    }
-
-    const schemaTemplateParameters: SchemaTemplateParameters = {
-        SchemaVersion: latestSchemaVersion!,
-        templateExtraContent: templateExtraContent,
-    }
-
-    return schemaTemplateParameters
-}
-
-function buildSchemaPackageHierarchy(schemaName: string) {
-    const codeGenUtilsObject = new SchemaCodeGenUtils()
-
-    return codeGenUtilsObject.buildSchemaPackageName(schemaName)
-}
-
-function buildRootSchemaEventName(schemaNode: any, awsEventNode: any) {
-    const refValue = _.get(awsEventNode, properties.concat('.', detail, '.', ref))
-
-    if (_.isString(refValue) && refValue.includes(componentsSchemasPath)) {
-        const awsEventDetailRef = refValue.split(componentsSchemasPath).pop()
-        if (!_.isEmpty(awsEventDetailRef)) {
-            return toValidIdentifier(awsEventDetailRef!)
-        }
-    }
-
-    const schemaRoots = _.keysIn(_.get(schemaNode, components.concat('.', schemas)))
-    if (!_.isEmpty(schemaRoots)) {
-        return toValidIdentifier(schemaRoots[0])
-    }
-
-    return undefined
-}
-
-function getCoreFileName(schemaName: string) {
-    const parsedName = schemaName.split('@')
-
-    return parsedName[parsedName.length - 1]
-}
diff --git a/packages/core/src/eventSchemas/utils.ts b/packages/core/src/eventSchemas/utils.ts
deleted file mode 100644
index a42d4a2682..0000000000
--- a/packages/core/src/eventSchemas/utils.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import { RegistrySummary, SchemaSummary, SearchSchemaSummary } from '@aws-sdk/client-schemas'
-import * as vscode from 'vscode'
-import { SchemaClient } from '../shared/clients/schemaClient'
-
-export async function* listRegistryItems(client: SchemaClient): AsyncIterableIterator {
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.loading.registries', 'Loading Registry Items...')
-    )
-
-    try {
-        yield* client.listRegistries()
-    } finally {
-        status.dispose()
-    }
-}
-
-export async function* listSchemaItems(
-    client: SchemaClient,
-    registryName: string
-): AsyncIterableIterator {
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.loading.schemaItems', 'Loading Schema Items...')
-    )
-
-    try {
-        yield* client.listSchemas(registryName)
-    } finally {
-        status.dispose()
-    }
-}
-
-export async function* searchSchemas(
-    client: SchemaClient,
-    keyword: string,
-    registryName: string
-): AsyncIterableIterator {
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.searching.schemas', 'Searching Schemas...')
-    )
-
-    try {
-        yield* client.searchSchemas(keyword, registryName)
-    } finally {
-        status.dispose()
-    }
-}
diff --git a/packages/core/src/eventSchemas/vue/index.ts b/packages/core/src/eventSchemas/vue/index.ts
deleted file mode 100644
index eac937636d..0000000000
--- a/packages/core/src/eventSchemas/vue/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { createApp } from 'vue'
-import component from './searchSchemas.vue'
-
-const create = () => createApp(component)
-const app = create()
-app.mount('#vue-app')
-
-window.addEventListener('remount', () => {
-    app.unmount()
-    create().mount('#vue-app')
-})
diff --git a/packages/core/src/eventSchemas/vue/searchSchemas.ts b/packages/core/src/eventSchemas/vue/searchSchemas.ts
deleted file mode 100644
index 7d5c71fa3b..0000000000
--- a/packages/core/src/eventSchemas/vue/searchSchemas.ts
+++ /dev/null
@@ -1,253 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-import { SchemaSummary, SearchSchemaSummary } from '@aws-sdk/client-schemas'
-import * as vscode from 'vscode'
-import { downloadSchemaItemCode } from '../commands/downloadSchemaItemCode'
-import { RegistryItemNode } from '../explorer/registryItemNode'
-import { SchemaItemNode } from '../explorer/schemaItemNode'
-import { SchemasNode } from '../explorer/schemasNode'
-import { listRegistryItems, searchSchemas } from '../utils'
-import { DefaultSchemaClient, SchemaClient } from '../../shared/clients/schemaClient'
-
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { Result } from '../../shared/telemetry/telemetry'
-import { toArrayAsync } from '../../shared/utilities/collectionUtils'
-import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
-import { ExtContext } from '../../shared/extensions'
-import { VueWebview } from '../../webviews/main'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-interface InitialData {
-    Header: string
-    SearchInputPlaceholder: string
-    VersionPrefix: string
-    RegistryNames: string[]
-    Region: string
-    LocalizedMessages: {
-        noSchemasFound: string
-        searching: string
-        loading: string
-        select: string
-    }
-}
-
-export class SearchSchemasWebview extends VueWebview {
-    public static readonly sourcePath: string = 'src/eventSchemas/vue/index.js'
-    public readonly id = 'remoteInvoke'
-
-    public constructor(
-        private readonly channel: vscode.OutputChannel,
-        private readonly client: SchemaClient,
-        private readonly data: InitialData
-    ) {
-        super(SearchSchemasWebview.sourcePath)
-    }
-
-    public init() {
-        telemetry.schemas_view.emit({ result: 'Succeeded' })
-
-        return this.data
-    }
-
-    public async fetchSchemaContent(summary: SchemaVersionedSummary, version?: string) {
-        let selectedVersion = version
-        let versionList: string[] | undefined
-
-        if (!selectedVersion) {
-            versionList = summary.VersionList
-            selectedVersion = versionList[0]
-        }
-
-        const response = await this.client.describeSchema(
-            summary.RegistryName,
-            getSchemaNameFromTitle(summary.Title),
-            selectedVersion
-        )
-        const prettySchema = JSON.stringify(JSON.parse(response.Content!), undefined, getTabSizeSetting())
-
-        return {
-            results: prettySchema,
-            version: selectedVersion,
-            versionList,
-        }
-    }
-
-    public async searchSchemas(keyword: string) {
-        try {
-            const results = await getSearchResults(this.client, this.data.RegistryNames, keyword)
-            telemetry.schemas_search.emit({ result: 'Succeeded' })
-
-            return {
-                results: results,
-                resultsNotFound: results.length === 0,
-            }
-        } catch (error) {
-            telemetry.schemas_search.emit({ result: 'Failed' })
-            throw error
-        }
-    }
-
-    public async downloadCodeBindings(summary: SchemaVersionedSummary) {
-        const schemaItem: SchemaSummary = {
-            SchemaName: getSchemaNameFromTitle(summary.Title),
-        }
-        const schemaItemNode = new SchemaItemNode(schemaItem, this.client, summary.RegistryName)
-        await downloadSchemaItemCode(schemaItemNode, this.channel)
-    }
-}
-
-const Panel = VueWebview.compilePanel(SearchSchemasWebview)
-
-export async function createSearchSchemasWebView(context: ExtContext, node: RegistryItemNode | SchemasNode) {
-    const logger: Logger = getLogger()
-
-    // note: this isn't tied to actually running a search (it's tied to opening the webview successfully), but this preserves existing metric behavior
-    let webviewResult: Result = 'Succeeded'
-
-    try {
-        const client = new DefaultSchemaClient(node.regionCode)
-        const registryNames = await getRegistryNames(node, client)
-        if (registryNames.length === 0) {
-            void vscode.window.showInformationMessage(
-                localize('AWS.schemas.search.no_registries', 'No Schema Registries')
-            )
-
-            return
-        }
-        const wv = new Panel(context.extensionContext, context.outputChannel, client, {
-            RegistryNames: registryNames,
-            Header: getPageHeader(registryNames),
-            SearchInputPlaceholder: localize('AWS.schemas.search.input.placeholder', 'Search for schema keyword...'),
-            VersionPrefix: localize('AWS.schemas.search.version.prefix', 'Search matched version:'),
-            Region: node.regionCode,
-            LocalizedMessages: {
-                noSchemasFound: localize('AWS.schemas.search.no_results', 'No schemas found'),
-                searching: localize('AWS.schemas.search.searching', 'Searching for schemas...'),
-                loading: localize('AWS.schemas.search.loading', 'Loading...'),
-                select: localize('AWS.schemas.search.select', 'Select a schema'),
-            },
-        })
-        await wv.show({
-            title: localize('AWS.executeStateMachine.title', 'Start Execution'),
-            cssFiles: ['searchSchemas.css'],
-        })
-    } catch (err) {
-        webviewResult = 'Failed'
-        const error = err as Error
-        logger.error('Error searching schemas: %s', error)
-    } finally {
-        telemetry.schemas_search.emit({ result: webviewResult })
-    }
-}
-
-export async function getRegistryNames(node: RegistryItemNode | SchemasNode, client: SchemaClient): Promise {
-    const registryNames: string[] = []
-
-    if (node instanceof SchemasNode) {
-        try {
-            const registries = await toArrayAsync(listRegistryItems(client))
-            for (const element of registries) {
-                registryNames.push(element.RegistryName!)
-            }
-        } catch (err) {
-            const error = err as Error
-            getLogger().error(error)
-            void vscode.window.showErrorMessage(
-                localize('AWS.message.error.schemas.search.failed_to_load_resources', 'Error loading Schemas resources')
-            )
-        }
-    }
-
-    if (node instanceof RegistryItemNode) {
-        registryNames.push(node.registryName)
-    }
-
-    return registryNames
-}
-
-export function getPageHeader(registryNames: string[]): string {
-    if (registryNames.length === 1) {
-        return localize('AWS.schemas.search.header.text.singleRegistry', 'Search "{0}" registry', registryNames[0])
-    }
-
-    return localize('AWS.schemas.search.header.text.allRegistries', 'Search across all registries')
-}
-export interface SchemaVersionedSummary {
-    RegistryName: string
-    Title: string
-    VersionList: string[]
-}
-
-export async function getSearchListForSingleRegistry(
-    schemaClient: SchemaClient,
-    registryName: string,
-    keyword: string,
-    prefix: string = ''
-): Promise {
-    let results: SchemaVersionedSummary[] = []
-    try {
-        const schemas = await toArrayAsync(searchSchemas(schemaClient, keyword, registryName))
-        results = getSchemaVersionedSummary(schemas, prefix)
-    } catch (error) {
-        const err = error as Error
-        getLogger().error(err)
-
-        void vscode.window.showErrorMessage(
-            localize(
-                'AWS.message.error.schemas.search.failed_to_search_registry',
-                'Unable to search registry {0}',
-                registryName
-            )
-        )
-    }
-
-    return results
-}
-
-export async function getSearchResults(
-    schemaClient: SchemaClient,
-    registries: string[],
-    keyword: string
-): Promise {
-    let results: SchemaVersionedSummary[] = []
-
-    await Promise.all(
-        registries.map(async (registryName) => {
-            let prefix = ''
-            if (registries.length !== 1) {
-                prefix = registryName.concat('/')
-            }
-            const perRegistryResults = await getSearchListForSingleRegistry(schemaClient, registryName, keyword, prefix)
-            results = results.concat(perRegistryResults)
-        })
-    )
-
-    return results
-}
-
-export function getSchemaVersionedSummary(searchSummary: SearchSchemaSummary[], prefix: string) {
-    const results = searchSummary.map((searchSchemaSummary) => ({
-        RegistryName: searchSchemaSummary.RegistryName!,
-        Title: prefix.concat(searchSchemaSummary.SchemaName!),
-        VersionList: searchSchemaSummary
-            .SchemaVersions!.map((summary) => summary.SchemaVersion!)
-            .sort(sortNumericStringsInDescendingOrder),
-    }))
-
-    return results
-}
-
-export function getSchemaNameFromTitle(title: string) {
-    const name = title.split('/')
-
-    return name[name.length - 1]
-}
-
-function sortNumericStringsInDescendingOrder(a: string, b: string) {
-    return b.localeCompare(a, undefined, { numeric: true })
-}
diff --git a/packages/core/src/eventSchemas/vue/searchSchemas.vue b/packages/core/src/eventSchemas/vue/searchSchemas.vue
deleted file mode 100644
index 65f72209d7..0000000000
--- a/packages/core/src/eventSchemas/vue/searchSchemas.vue
+++ /dev/null
@@ -1,128 +0,0 @@
-
-
-
diff --git a/packages/core/src/eventSchemas/wizards/schemaCodeDownloadWizard.ts b/packages/core/src/eventSchemas/wizards/schemaCodeDownloadWizard.ts
deleted file mode 100644
index 4db9d29ba2..0000000000
--- a/packages/core/src/eventSchemas/wizards/schemaCodeDownloadWizard.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import { Set as ImmutableSet } from 'immutable'
-import * as vscode from 'vscode'
-
-import { eventBridgeSchemasDocUrl } from '../../shared/constants'
-import { createHelpButton } from '../../shared/ui/buttons'
-import * as picker from '../../shared/ui/picker'
-import {
-    MultiStepWizard,
-    promptUserForLocation,
-    WIZARD_GOBACK,
-    WIZARD_TERMINATE,
-    WizardContext,
-    wizardContinue,
-    WizardStep,
-} from '../../shared/wizards/multiStepWizard'
-
-import * as codeLang from '../models/schemaCodeLangs'
-
-import { SchemaItemNode } from '../explorer/schemaItemNode'
-import { recentlyUsed } from '../../shared/localizedText'
-import { openUrl } from '../../shared/utilities/vsCodeUtils'
-
-export interface SchemaCodeDownloadWizardContext {
-    readonly schemaLangs: ImmutableSet
-    readonly workspaceFolders: readonly vscode.WorkspaceFolder[] | undefined
-
-    promptUserForVersion(currSchemaVersion?: string): Promise
-
-    promptUserForLanguage(currLang?: codeLang.SchemaCodeLangs): Promise
-
-    promptUserForLocation(): Promise
-
-    showOpenDialog(options: vscode.OpenDialogOptions): Thenable
-}
-
-export class DefaultSchemaCodeDownloadWizardContext extends WizardContext implements SchemaCodeDownloadWizardContext {
-    public readonly schemaLangs = codeLang.schemaCodeLangs
-    private readonly helpButton = createHelpButton()
-    private readonly totalSteps = 3
-    public constructor(private readonly node: SchemaItemNode) {
-        super()
-        this.node = node
-    }
-
-    public async promptUserForLanguage(
-        currLanguage?: codeLang.SchemaCodeLangs
-    ): Promise {
-        const quickPick = picker.createQuickPick({
-            options: {
-                ignoreFocusOut: true,
-                title: localize(
-                    'AWS.schemas.downloadCodeBindings.initWizard.language.prompt',
-                    'Select a code binding language'
-                ),
-                value: currLanguage ? currLanguage : '',
-                step: 2,
-                totalSteps: this.totalSteps,
-            },
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-            items: this.schemaLangs.toArray().map((language) => ({
-                label: language,
-                alwaysShow: language === currLanguage,
-                description: language === currLanguage ? recentlyUsed : '',
-            })),
-        })
-
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(vscode.Uri.parse(eventBridgeSchemasDocUrl))
-                }
-            },
-        })
-        const val = picker.verifySinglePickerOutput(choices)
-
-        return val ? (val.label as codeLang.SchemaCodeLangs) : undefined
-    }
-
-    public async promptUserForVersion(currSchemaVersion?: string): Promise {
-        const versions = await this.node.listSchemaVersions()
-
-        const quickPick = picker.createQuickPick({
-            options: {
-                ignoreFocusOut: true,
-                title: localize(
-                    'AWS.schemas.downloadCodeBindings.initWizard.version.prompt',
-                    'Select a version for schema {0} :',
-                    this.node.schemaName
-                ),
-                value: currSchemaVersion ? currSchemaVersion : '',
-                step: 1,
-                totalSteps: this.totalSteps,
-            },
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-            items: versions!.map((schemaVersion) => ({
-                label: schemaVersion.SchemaVersion!,
-                alwaysShow: schemaVersion.SchemaVersion === currSchemaVersion,
-                description: schemaVersion === currSchemaVersion ? recentlyUsed : '',
-            })),
-        })
-
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(vscode.Uri.parse(eventBridgeSchemasDocUrl))
-                }
-            },
-        })
-        const val = picker.verifySinglePickerOutput(choices)
-
-        return val ? (val.label as codeLang.SchemaCodeLangs) : undefined
-    }
-
-    public async promptUserForLocation(): Promise {
-        return promptUserForLocation(this, {
-            helpButton: { button: this.helpButton, url: eventBridgeSchemasDocUrl },
-            overrideText: {
-                detail: localize(
-                    'AWS.schemas.downloadCodeBindings.initWizard.location.select.folder.detail',
-                    'Code bindings will be downloaded to selected folder.'
-                ),
-                title: localize(
-                    'AWS.schemas.downloadCodeBindings.initWizard.location.prompt',
-                    'Select a workspace folder to download code bindings'
-                ),
-            },
-            step: 3,
-            totalSteps: this.totalSteps,
-        })
-    }
-}
-
-export interface SchemaCodeDownloadWizardResponse {
-    language: codeLang.SchemaCodeLangs
-    location: vscode.Uri
-    schemaVersion: string
-}
-
-export class SchemaCodeDownloadWizard extends MultiStepWizard {
-    private schemaVersion?: string
-    private language?: codeLang.SchemaCodeLangs
-    private location?: vscode.Uri
-
-    public constructor(private readonly context: SchemaCodeDownloadWizardContext) {
-        super()
-    }
-
-    protected get startStep() {
-        return this.SCHEMA_VERSION
-    }
-
-    protected getResult(): SchemaCodeDownloadWizardResponse | undefined {
-        if (!this.language || !this.location || !this.schemaVersion) {
-            return undefined
-        }
-
-        return {
-            schemaVersion: this.schemaVersion,
-            language: this.language,
-            location: this.location,
-        }
-    }
-
-    private readonly SCHEMA_VERSION: WizardStep = async () => {
-        this.schemaVersion = await this.context.promptUserForVersion(this.schemaVersion)
-
-        return this.schemaVersion ? wizardContinue(this.LANGUAGE) : WIZARD_TERMINATE
-    }
-
-    private readonly LANGUAGE: WizardStep = async () => {
-        this.language = await this.context.promptUserForLanguage(this.language)
-
-        return this.language ? wizardContinue(this.LOCATION) : WIZARD_GOBACK
-    }
-
-    private readonly LOCATION: WizardStep = async () => {
-        this.location = await this.context.promptUserForLocation()
-
-        return this.location ? WIZARD_TERMINATE : WIZARD_GOBACK
-    }
-}
diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts
index e400c3e0dd..645dbcb677 100644
--- a/packages/core/src/extension.ts
+++ b/packages/core/src/extension.ts
@@ -21,7 +21,6 @@ import { getIdeProperties, aboutExtension, getDocUrl, isSageMaker } from './shar
 import { logAndShowError, logAndShowWebviewError } from './shared/utilities/logAndShowUtils'
 import { telemetry } from './shared/telemetry/telemetry'
 import { openUrl } from './shared/utilities/vsCodeUtils'
-import { activateViewsShared } from './awsexplorer/activationShared'
 import fs from './shared/fs/fs'
 import * as errors from './shared/errors'
 import { activate as activateLogger } from './shared/logger/activation'
@@ -39,7 +38,6 @@ import { getMachineId, isAutomation } from './shared/vscode/env'
 import { registerCommandErrorHandler } from './shared/vscode/commands2'
 import { registerWebviewErrorHandler } from './webviews/server'
 import { ExtContext, VSCODE_EXTENSION_ID } from './shared/extensions'
-import { getSamCliContext } from './shared/sam/cli/samCliContext'
 import { UriHandler } from './shared/vscode/uriHandler'
 import { disableAwsSdkWarning } from './shared/awsClientBuilder'
 import { FileResourceFetcher } from './shared/resourcefetcher/fileResourceFetcher'
@@ -155,7 +153,6 @@ export async function activateCommon(
     const extContext: ExtContext = {
         extensionContext: context,
         awsContext: globals.awsContext,
-        samCliContext: getSamCliContext,
         regionProvider: globals.regionProvider,
         outputChannel: globals.outputChannel,
         telemetryService: globals.telemetry,
@@ -163,8 +160,6 @@ export async function activateCommon(
         credentialsStore: globals.loginManager.store,
     }
 
-    await activateViewsShared(extContext.extensionContext)
-
     return extContext
 }
 
diff --git a/packages/core/src/extensionNode.ts b/packages/core/src/extensionNode.ts
index 17cef511ea..73341bfffd 100644
--- a/packages/core/src/extensionNode.ts
+++ b/packages/core/src/extensionNode.ts
@@ -4,380 +4,18 @@
  */
 
 import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-
-import * as codecatalyst from './codecatalyst/activation'
-import { activate as activateAppBuilder } from './awsService/appBuilder/activation'
-import { activate as activateCloudFormation } from './awsService/cloudformation/extension'
-import { activate as activateAwsExplorer } from './awsexplorer/activation'
-import { activate as activateCloudWatchLogs } from './awsService/cloudWatchLogs/activation'
-import { activate as activateSchemas } from './eventSchemas/activation'
-import { activate as activateLambda } from './lambda/activation'
-import { activate as activateCloudFormationTemplateRegistry } from './shared/cloudformation/activation'
-
-import { AwsContextCommands } from './shared/awsContextCommands'
-import {
-    getIdeProperties,
-    getExtEnvironmentDetails,
-    isSageMaker,
-    showWelcomeMessage,
-} from './shared/extensionUtilities'
-import { getLogger, Logger } from './shared/logger/logger'
-import { activate as activateEcr } from './awsService/ecr/activation'
-import { activate as activateEc2, deactivate as deactivateEc2 } from './awsService/ec2/activation'
-import { activate as activateSam } from './shared/sam/activation'
-import { activate as activateS3 } from './awsService/s3/activation'
-import * as filetypes from './shared/filetypes'
-import { activate as activateApiGateway } from './awsService/apigateway/activation'
-import { activate as activateStepFunctions } from './stepFunctions/activation'
-import { activate as activateStepFunctionsWorkflowStudio } from './stepFunctions/workflowStudio/activation'
-import { activate as activateSsmDocument } from './ssmDocument/activation'
-import { activate as activateDynamicResources } from './dynamicResources/activation'
-import { activate as activateEcs } from './awsService/ecs/activation'
-import { activate as activateAppRunner } from './awsService/apprunner/activation'
-import { activate as activateIot } from './awsService/iot/activation'
-import { activate as activateDev } from './dev/activation'
-import * as beta from './dev/beta'
-import { activate as activateApplicationComposer } from './applicationcomposer/activation'
-import { activate as activateRedshift } from './awsService/redshift/activation'
-import { activate as activateDocumentDb } from './docdb/activation'
-import { activate as activateIamPolicyChecks } from './awsService/accessanalyzer/activation'
-import { activate as activateNotifications } from './notifications/activation'
-import { activate as activateSagemaker } from './awsService/sagemaker/activation'
-import { activate as activateSageMakerUnifiedStudio } from './sagemakerunifiedstudio/activation'
-import { SchemaService } from './shared/schemas'
-import { AwsResourceManager } from './dynamicResources/awsResourceManager'
-import globals from './shared/extensionGlobals'
-import { Experiments, Settings, showSettingsFailedMsg } from './shared/settings'
-import { isReleaseVersion } from './shared/vscode/env'
-import { AuthStatus, AuthUserState, telemetry } from './shared/telemetry/telemetry'
-import { ExtStartUpSources } from './shared/telemetry/util'
-import { Auth } from './auth/auth'
-import { getTelemetryMetadataForConn } from './auth/connection'
-import { registerSubmitFeedback } from './feedback/vue/submitFeedback'
-import { activateCommon, deactivateCommon } from './extension'
-import { learnMoreAmazonQCommand, qExtensionPageCommand, dismissQTree } from './amazonq/explorer/amazonQChildrenNodes'
-import { codeWhispererCoreScopes } from './codewhisperer/util/authUtil'
-import { installAmazonQExtension } from './codewhisperer/commands/basicCommands'
-import { VSCODE_EXTENSION_ID } from './shared/extensions'
-import { isExtensionInstalled } from './shared/utilities/vsCodeUtils'
-import { ExtensionUse, getAuthFormIdsFromConnection, initializeCredentialsProviderManager } from './auth/utils'
-import { activate as activateThreatComposerEditor } from './threatComposer/activation'
-import { isSsoConnection, hasScopes } from './auth/connection'
-import { CrashMonitoring } from './shared/crashMonitoring'
-import { setContext } from './shared/vscode/setContext'
-import { AuthFormId } from './login/webview/vue/types'
-
-let localize: nls.LocalizeFunc
 
 /**
- * The entrypoint for the nodejs version of the toolkit
+ * The entrypoint for the nodejs version of the toolkit.
  *
- * **CONTRIBUTORS** If you are adding code to this function prioritize adding it to
- * {@link activateCommon} if appropriate
+ * Note: This is a legacy entry point from the AWS Toolkit extension.
+ * The Amazon Q extension uses packages/amazonq/src/extensionNode.ts instead.
  */
-export async function activate(context: vscode.ExtensionContext) {
-    const activationStartedOn = Date.now()
-    localize = nls.loadMessageBundle()
-    const contextPrefix = 'toolkit'
-
-    try {
-        // IMPORTANT: If you are doing setup that should also work in web mode (browser), it should be done in the function below
-        const extContext = await activateCommon(context, contextPrefix, false)
-
-        // Intentionally do not await since this can be slow and non-critical
-        void (await CrashMonitoring.instance())?.start()
-
-        initializeCredentialsProviderManager()
-
-        const toolkitEnvDetails = getExtEnvironmentDetails()
-        // Splits environment details by new line, filter removes the empty string
-        for (const line of toolkitEnvDetails.split(/\r?\n/).filter(Boolean)) {
-            getLogger().info(line)
-        }
-
-        globals.awsContextCommands = new AwsContextCommands(globals.regionProvider, Auth.instance)
-        globals.schemaService = new SchemaService()
-        globals.resourceManager = new AwsResourceManager(context)
-
-        const settings = Settings.instance
-        const experiments = Experiments.instance
-
-        experiments.onDidChange(({ key }) => {
-            telemetry.aws_experimentActivation.run((span) => {
-                // Record the key prior to reading the setting as `get` may throw
-                span.record({ experimentId: key })
-                span.record({ experimentState: experiments.get(key) ? 'activated' : 'deactivated' })
-            })
-        })
-
-        await globals.schemaService.start()
-        filetypes.activate()
-
-        try {
-            await activateDev(context)
-            await beta.activate(context)
-        } catch (error) {
-            getLogger().debug(`Developer Tools (internal): failed to activate: ${(error as Error).message}`)
-        }
-
-        context.subscriptions.push(registerSubmitFeedback(context, 'AWS Toolkit', contextPrefix))
-
-        // do not enable codecatalyst for sagemaker
-        // TODO: remove setContext if SageMaker adds the context to their IDE
-        if (!isSageMaker()) {
-            await setContext('aws.isSageMaker', false)
-            await codecatalyst.activate(extContext)
-        } else {
-            await setContext('aws.isSageMaker', true)
-        }
-
-        // wrap auth related setup in a context for telemetry
-        await telemetry.function_call.run(
-            async () => {
-                // Clean up remaining logins after codecatalyst activated and ran its cleanup.
-                // Because we are splitting auth sessions by extension, we can't use Amazon Q
-                // connections anymore.
-                // TODO: Remove after some time?
-                for (const conn of await Auth.instance.listConnections()) {
-                    if (isSsoConnection(conn) && hasScopes(conn, codeWhispererCoreScopes)) {
-                        getLogger().debug(
-                            `forgetting connection: ${conn.id} with starturl/scopes: ${conn.startUrl} / %O`,
-                            conn.scopes
-                        )
-                        await Auth.instance.forgetConnection(conn)
-                    }
-                }
-            },
-            { emit: false, functionId: { name: 'activate', class: 'ExtensionNodeCore' } }
-        )
-
-        await activateCloudFormationTemplateRegistry(context)
-
-        // Start CloudFormation activation in background to avoid blocking other services
-        activateCloudFormation(context).catch((error) => {
-            getLogger().error(`CloudFormation activation failed: ${error}`)
-        })
-
-        await activateAwsExplorer({
-            context: extContext,
-            regionProvider: globals.regionProvider,
-            toolkitOutputChannel: globals.outputChannel,
-        })
-
-        await activateAppRunner(extContext)
-
-        await activateApiGateway({
-            extContext: extContext,
-            outputChannel: globals.outputChannel,
-        })
-
-        await activateLambda(extContext)
-
-        await activateSsmDocument(context, globals.awsContext, globals.regionProvider, globals.outputChannel)
-
-        await activateSam(extContext)
-
-        await activateS3(extContext)
-
-        await activateEc2(extContext)
-
-        await activateEcr(context)
-
-        await activateCloudWatchLogs(context, settings)
-
-        await activateDynamicResources(context)
-
-        await activateIot(extContext)
-
-        await activateEcs(extContext)
-
-        await activateSchemas(extContext)
-
-        await activateSagemaker(extContext)
-
-        if (!isSageMaker()) {
-            // Amazon Q Tree setup.
-            learnMoreAmazonQCommand.register()
-            qExtensionPageCommand.register()
-            dismissQTree.register()
-            installAmazonQExtension.register()
-
-            await handleAmazonQInstall()
-        }
-
-        await activateSageMakerUnifiedStudio(extContext)
-
-        await activateApplicationComposer(context)
-        await activateThreatComposerEditor(context)
-
-        await activateStepFunctions(context, globals.awsContext, globals.outputChannel)
-
-        await activateStepFunctionsWorkflowStudio()
-
-        await activateRedshift(extContext)
-
-        await activateAppBuilder(extContext)
-
-        await activateDocumentDb(extContext)
-
-        await activateIamPolicyChecks(extContext)
-
-        context.subscriptions.push(
-            vscode.window.registerUriHandler({
-                handleUri: (uri) =>
-                    telemetry.runRoot(() => {
-                        telemetry.record({ source: 'UriHandler' })
-
-                        return globals.uriHandler.handleUri(uri)
-                    }),
-            })
-        )
-
-        showWelcomeMessage(context)
-
-        const settingsValid = await settings.isReadable()
-        if (!settingsValid) {
-            void showSettingsFailedMsg('read')
-        }
-        recordToolkitInitialization(activationStartedOn, settingsValid, getLogger())
-
-        if (!isReleaseVersion()) {
-            globals.telemetry.assertPassiveTelemetry(globals.didReload)
-        }
-
-        // TODO: Should probably emit for web as well.
-        // Will the web metric look the same?
-        telemetry.auth_userState.emit({
-            passive: true,
-            result: 'Succeeded',
-            source: ExtensionUse.instance.sourceForTelemetry(),
-            ...(await getAuthState()),
-        })
-
-        void activateNotifications(context, getAuthState)
-    } catch (error) {
-        const stacktrace = (error as Error).stack?.split('\n')
-        // truncate if the stacktrace is unusually long
-        if (stacktrace !== undefined && stacktrace.length > 40) {
-            stacktrace.length = 40
-        }
-        getLogger().error(
-            localize(
-                'AWS.channel.aws.toolkit.activation.error',
-                'Error Activating {0} Toolkit: {1} \n{2}',
-                getIdeProperties().company,
-                (error as Error).message,
-                stacktrace?.join('\n')
-            )
-        )
-        throw error
-    }
+export async function activate(_context: vscode.ExtensionContext) {
+    // No-op: AWS Toolkit services have been removed from this repo.
+    // The Amazon Q extension has its own activation path.
 }
 
 export async function deactivate() {
-    // Run concurrently to speed up execution. stop() does not throw so it is safe
-    await Promise.all([await (await CrashMonitoring.instance())?.shutdown(), deactivateCommon(), deactivateEc2()])
-    globals.sdkClientBuilderV3.clearServiceCache()
-    await globals.resourceManager.dispose()
-}
-
-async function handleAmazonQInstall() {
-    const dismissedInstall = globals.globalState.get('aws.toolkit.amazonqInstall.dismissed')
-    if (dismissedInstall) {
-        return
-    }
-
-    if (isExtensionInstalled(VSCODE_EXTENSION_ID.amazonq)) {
-        await globals.globalState.update('aws.toolkit.amazonqInstall.dismissed', true)
-        return
-    }
-
-    await telemetry.toolkit_showNotification.run(async () => {
-        telemetry.record({ id: 'amazonQStandaloneChange' })
-        void vscode.window
-            .showInformationMessage(
-                'Try Amazon Q, a generative AI assistant, with chat and code suggestions.',
-                'Install',
-                'Learn More'
-            )
-            .then(async (resp) => {
-                await telemetry.toolkit_invokeAction.run(async () => {
-                    telemetry.record({
-                        source: ExtensionUse.instance.isFirstUse()
-                            ? ExtStartUpSources.firstStartUp
-                            : ExtStartUpSources.none,
-                    })
-
-                    if (resp === 'Learn More') {
-                        // Clicking learn more will open the q extension page
-                        telemetry.record({ action: 'learnMore' })
-                        await qExtensionPageCommand.execute()
-                        return
-                    }
-
-                    if (resp === 'Install') {
-                        telemetry.record({ action: 'installAmazonQ' })
-                        await installAmazonQExtension.execute()
-                    } else {
-                        telemetry.record({ action: 'dismissQNotification' })
-                    }
-                    await globals.globalState.update('aws.toolkit.amazonqInstall.dismissed', true)
-                })
-            })
-    })
-}
-
-function recordToolkitInitialization(activationStartedOn: number, settingsValid: boolean, logger?: Logger) {
-    try {
-        const activationFinishedOn = Date.now()
-        const duration = activationFinishedOn - activationStartedOn
-
-        if (settingsValid) {
-            telemetry.toolkit_init.emit({ duration, result: 'Succeeded' })
-        } else {
-            telemetry.toolkit_init.emit({ duration, result: 'Failed', reason: 'UserSettingsRead' })
-        }
-    } catch (err) {
-        logger?.error(err as Error)
-    }
-}
-
-async function getAuthState(): Promise> {
-    let authStatus: AuthStatus = 'notConnected'
-    const enabledConnections: Set = new Set()
-    const enabledScopes: Set = new Set()
-    if (Auth.instance.hasConnections) {
-        authStatus = 'expired'
-        for (const conn of await Auth.instance.listConnections()) {
-            const state = Auth.instance.getConnectionState(conn)
-            if (state === 'valid') {
-                authStatus = 'connected'
-            }
-
-            for (const id of getAuthFormIdsFromConnection(conn)) {
-                enabledConnections.add(id)
-            }
-            if (isSsoConnection(conn)) {
-                if (conn.scopes) {
-                    for (const s of conn.scopes) {
-                        enabledScopes.add(s)
-                    }
-                }
-            }
-        }
-    }
-
-    // There may be other SSO connections in toolkit, but there is no use case for
-    // displaying registration info for non-active connections at this time.
-    const activeConn = Auth.instance.activeConnection
-    if (activeConn?.type === 'sso') {
-        telemetry.record(await getTelemetryMetadataForConn(activeConn))
-    }
-
-    return {
-        authStatus,
-        authEnabledConnections: [...enabledConnections].sort().join(','),
-        authScopes: [...enabledScopes].sort().join(','),
-    }
+    // No-op
 }
diff --git a/packages/core/src/lambda/activation.ts b/packages/core/src/lambda/activation.ts
deleted file mode 100644
index e0c03986ab..0000000000
--- a/packages/core/src/lambda/activation.ts
+++ /dev/null
@@ -1,278 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import { deleteLambda } from './commands/deleteLambda'
-import { uploadLambdaCommand } from './commands/uploadLambda'
-import { LambdaFunctionNode } from './explorer/lambdaFunctionNode'
-import { downloadLambdaCommand, openLambdaFile } from './commands/downloadLambda'
-import { tryRemoveFolder } from '../shared/filesystemUtilities'
-import { invokeRemoteLambda } from './vue/remoteInvoke/invokeLambda'
-import { registerSamDebugInvokeVueCommand, registerSamInvokeVueCommand } from './vue/configEditor/samInvokeBackend'
-import { Commands } from '../shared/vscode/commands2'
-import { DefaultLambdaClient } from '../shared/clients/lambdaClient'
-import { copyLambdaUrl } from './commands/copyLambdaUrl'
-import { generateLambdaNodeFromResource, ResourceNode } from '../awsService/appBuilder/explorer/nodes/resourceNode'
-import { isTreeNode, TreeNode } from '../shared/treeview/resourceTreeDataProvider'
-import { getSourceNode } from '../shared/utilities/treeNodeUtils'
-import { tailLogGroup } from '../awsService/cloudWatchLogs/commands/tailLogGroup'
-import { liveTailRegistry, liveTailCodeLensProvider } from '../awsService/cloudWatchLogs/activation'
-import { getFunctionLogGroupName } from '../awsService/cloudWatchLogs/activation'
-import { ToolkitError, isError } from '../shared/errors'
-import { LogStreamFilterResponse } from '../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu'
-import { tempDirPath } from '../shared/filesystemUtilities'
-import fs from '../shared/fs/fs'
-import {
-    confirmOutdatedChanges,
-    deleteFilesInFolder,
-    deployFromTemp,
-    getReadme,
-    openLambdaFolderForEdit,
-    watchForUpdates,
-} from './commands/editLambda'
-import { compareCodeSha, getFunctionInfo, getTempLocation, setFunctionInfo } from './utils'
-import { registerLambdaUriHandler } from './uriHandlers'
-import globals from '../shared/extensionGlobals'
-
-const localize = nls.loadMessageBundle()
-import { activateRemoteDebugging } from './remoteDebugging/ldkController'
-import { ExtContext } from '../shared/extensions'
-
-async function openReadme() {
-    const readmeUri = vscode.Uri.file(await getReadme())
-    // We only want to do it if there's not a readme already
-    const isPreviewOpen = vscode.window.tabGroups.all.some((group) =>
-        group.tabs.some((tab) => tab.label.includes('README'))
-    )
-    if (!isPreviewOpen) {
-        await vscode.commands.executeCommand('markdown.showPreviewToSide', readmeUri)
-    }
-}
-
-async function quickEditActivation() {
-    if (vscode.workspace.workspaceFolders) {
-        for (const workspaceFolder of vscode.workspace.workspaceFolders) {
-            // Making the comparison case insensitive because Windows can have `C\` or `c\`
-            const workspacePath = workspaceFolder.uri.fsPath.toLowerCase()
-            const tempPath = path.join(tempDirPath, 'lambda').toLowerCase()
-            if (workspacePath.includes(tempPath)) {
-                const name = path.basename(workspaceFolder.uri.fsPath)
-                const region = path.basename(path.dirname(workspaceFolder.uri.fsPath))
-
-                const lambda = { name, region, configuration: undefined }
-
-                watchForUpdates(lambda, vscode.Uri.file(workspacePath))
-
-                await openReadme()
-
-                // Open handler function
-                try {
-                    const handler = await getFunctionInfo(lambda, 'handlerFile')
-                    const lambdaLocation = path.join(workspacePath, handler)
-                    await openLambdaFile(lambdaLocation, vscode.ViewColumn.One)
-                } catch (e) {
-                    void vscode.window.showWarningMessage(
-                        localize('AWS.lambda.openFile.failure', `Failed to determine handler location: ${e}`)
-                    )
-                }
-
-                // Check if there are changes that need overwritten
-                try {
-                    // Checking if there are changes that need to be overwritten
-                    const prompt = localize(
-                        'AWS.lambda.download.confirmOutdatedSync',
-                        'There are changes to your function in the cloud since you last edited locally, do you want to overwrite your local changes?'
-                    )
-
-                    // Adding delay to give the authentication time to catch up
-                    await new Promise((resolve) => globals.clock.setTimeout(resolve, 1000))
-
-                    const overwriteChanges = !(await compareCodeSha(lambda))
-                        ? await confirmOutdatedChanges(prompt)
-                        : false
-                    if (overwriteChanges) {
-                        // Close all open tabs from this workspace
-                        const workspaceUri = vscode.Uri.file(workspacePath)
-                        for (const tabGroup of vscode.window.tabGroups.all) {
-                            const tabsToClose = tabGroup.tabs.filter(
-                                (tab) =>
-                                    tab.input instanceof vscode.TabInputText &&
-                                    tab.input.uri.fsPath.startsWith(workspaceUri.fsPath)
-                            )
-                            if (tabsToClose.length > 0) {
-                                await vscode.window.tabGroups.close(tabsToClose)
-                            }
-                        }
-
-                        // Delete all files in the directory
-                        await deleteFilesInFolder(workspacePath)
-
-                        // Show message to user about next steps
-                        void vscode.window.showInformationMessage(
-                            localize(
-                                'AWS.lambda.refresh.complete',
-                                'Local workspace cleared. Navigate to the Toolkit explorer to get fresh code from the cloud.'
-                            )
-                        )
-
-                        await setFunctionInfo(lambda, { undeployed: false })
-
-                        // Remove workspace folder
-                        const workspaceIndex = vscode.workspace.workspaceFolders?.findIndex(
-                            (folder) => folder.uri.fsPath.toLowerCase() === workspacePath
-                        )
-                        if (workspaceIndex !== undefined && workspaceIndex >= 0) {
-                            vscode.workspace.updateWorkspaceFolders(workspaceIndex, 1)
-                        }
-                    }
-                } catch (e) {
-                    void vscode.window.showWarningMessage(
-                        localize(
-                            'AWS.lambda.pull.failure',
-                            `Failed to pull latest changes from the cloud, you can still edit locally: ${e}`
-                        )
-                    )
-                }
-            }
-        }
-    }
-}
-
-/**
- * Activates Lambda components.
- */
-export async function activate(context: ExtContext): Promise {
-    void quickEditActivation()
-
-    context.extensionContext.subscriptions.push(
-        Commands.register('aws.deleteLambda', async (node: LambdaFunctionNode | TreeNode) => {
-            const sourceNode = getSourceNode(node)
-            await deleteLambda(sourceNode.configuration, new DefaultLambdaClient(sourceNode.regionCode))
-            await vscode.commands.executeCommand('aws.refreshAwsExplorerNode', sourceNode.parent)
-        }),
-        Commands.register('aws.invokeLambda', async (node: LambdaFunctionNode | TreeNode) => {
-            let source: string = 'AwsExplorerRemoteInvoke'
-            if (isTreeNode(node)) {
-                // if appbuilder, create lambda node on the fly
-                let tmpNode: LambdaFunctionNode | undefined = getSourceNode(node)
-                if (!tmpNode) {
-                    // failed to extract, meaning this is appbuilder function node
-                    tmpNode = await generateLambdaNodeFromResource(node.resource as any)
-                }
-                node = tmpNode
-                source = 'AppBuilderRemoteInvoke'
-            }
-            await invokeRemoteLambda(context, {
-                outputChannel: context.outputChannel,
-                functionNode: node,
-                source: source,
-            })
-        }),
-
-        // Capture debug finished events, and delete the temporary directory if it exists
-        vscode.debug.onDidTerminateDebugSession(async (session) => {
-            if (
-                session.configuration?.sam?.buildDir === undefined &&
-                session.configuration?.baseBuildDir !== undefined
-            ) {
-                await tryRemoveFolder(session.configuration.baseBuildDir)
-            }
-        }),
-
-        Commands.register('aws.downloadLambda', async (node: LambdaFunctionNode | TreeNode) => {
-            const sourceNode = getSourceNode(node)
-            await downloadLambdaCommand(sourceNode)
-        }),
-
-        Commands.register({ id: 'aws.uploadLambda', autoconnect: true }, async (arg?: unknown) => {
-            if (arg instanceof LambdaFunctionNode) {
-                await uploadLambdaCommand({
-                    name: arg.functionName,
-                    region: arg.regionCode,
-                    configuration: arg.configuration,
-                })
-            } else if (arg instanceof vscode.Uri) {
-                await uploadLambdaCommand(undefined, arg)
-            } else {
-                await uploadLambdaCommand()
-            }
-        }),
-
-        Commands.register({ id: 'aws.quickDeployLambda' }, async (node: LambdaFunctionNode) => {
-            const functionName = node.configuration.FunctionName!
-            const region = node.regionCode
-            const lambda = { name: functionName, region, configuration: node.configuration }
-            const tempLocation = getTempLocation(functionName, region)
-
-            if (await fs.existsDir(tempLocation)) {
-                await deployFromTemp(lambda, vscode.Uri.file(tempLocation))
-            }
-        }),
-
-        Commands.register('aws.openLambdaFile', async (path: string) => {
-            await openLambdaFile(path)
-        }),
-
-        Commands.register('aws.lambda.openWorkspace', async (node: LambdaFunctionNode) => {
-            await openLambdaFolderForEdit(node.functionName, node.regionCode)
-        }),
-
-        Commands.register('aws.copyLambdaUrl', async (node: LambdaFunctionNode | TreeNode) => {
-            const sourceNode = getSourceNode(node)
-            await copyLambdaUrl(sourceNode, new DefaultLambdaClient(sourceNode.regionCode))
-        }),
-
-        registerSamInvokeVueCommand(context.extensionContext),
-
-        Commands.register('aws.launchDebugConfigForm', async (node: ResourceNode) =>
-            registerSamDebugInvokeVueCommand(context.extensionContext, { resource: node })
-        ),
-        Commands.register('aws.appBuilder.tailLogs', async (node: LambdaFunctionNode | TreeNode) => {
-            let functionConfiguration: FunctionConfiguration
-            try {
-                let tmpNode: LambdaFunctionNode | undefined = getSourceNode(node)
-                if (!tmpNode && isTreeNode(node)) {
-                    // failed to extract, meaning this is appbuilder function node
-                    tmpNode = await generateLambdaNodeFromResource(node.resource as any)
-                }
-                functionConfiguration = tmpNode.configuration
-                const logGroupInfo = {
-                    regionName: tmpNode.regionCode,
-                    groupName: getFunctionLogGroupName(functionConfiguration),
-                }
-
-                const source = isTreeNode(node) ? 'AppBuilder' : 'AwsExplorerLambdaNode'
-                // Show all log streams without having to choose
-                const logStreamFilterData: LogStreamFilterResponse = { type: 'all' }
-                await tailLogGroup(
-                    liveTailRegistry,
-                    source,
-                    liveTailCodeLensProvider,
-                    logGroupInfo,
-                    logStreamFilterData
-                )
-            } catch (err) {
-                if (isError(err as Error, 'ResourceNotFoundException', "LogGroup doesn't exist.")) {
-                    // If we caught this error, then we know `functionConfiguration` actually has a value
-                    throw ToolkitError.chain(
-                        err,
-                        `Unable to fetch logs. Log group for function '${functionConfiguration!.FunctionName}' does not exist. ` +
-                            'Invoking your function at least once will create the log group.'
-                    )
-                } else {
-                    throw err
-                }
-            }
-        }),
-
-        registerLambdaUriHandler()
-    )
-
-    void activateRemoteDebugging()
-}
diff --git a/packages/core/src/lambda/commands/copyLambdaUrl.ts b/packages/core/src/lambda/commands/copyLambdaUrl.ts
deleted file mode 100644
index 835525d061..0000000000
--- a/packages/core/src/lambda/commands/copyLambdaUrl.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { DefaultLambdaClient, LambdaClient } from '../../shared/clients/lambdaClient'
-import { LambdaFunctionNode } from '../explorer/lambdaFunctionNode'
-import { copyToClipboard } from '../../shared/utilities/messages'
-import { addCodiconToString } from '../../shared/utilities/textUtilities'
-import { createQuickPick, QuickPickPrompter } from '../../shared/ui/pickerPrompter'
-import { isValidResponse } from '../../shared/wizards/wizard'
-import { FunctionUrlConfig } from '@aws-sdk/client-lambda'
-import { CancellationError } from '../../shared/utilities/timeoutUtils'
-import { lambdaFunctionUrlConfigUrl } from '../../shared/constants'
-
-export const noLambdaFuncMessage = `No URL for Lambda function. [How to create URL.](${lambdaFunctionUrlConfigUrl})`
-
-export async function copyLambdaUrl(
-    node: Pick,
-    client: LambdaClient = new DefaultLambdaClient(node.regionCode),
-    quickPickUrl = _quickPickUrl
-): Promise {
-    const configs = await client.getFunctionUrlConfigs(node.name)
-
-    if (configs.length === 0) {
-        void vscode.window.showWarningMessage(noLambdaFuncMessage)
-        vscode.window.setStatusBarMessage(addCodiconToString('circle-slash', 'No URL for Lambda function.'), 5000)
-    } else {
-        let url: string | undefined = undefined
-        if (configs.length > 1) {
-            url = await quickPickUrl(configs)
-        } else {
-            url = configs[0].FunctionUrl
-        }
-
-        if (url) {
-            await copyToClipboard(url, 'URL')
-        }
-    }
-}
-
-async function _quickPickUrl(configList: FunctionUrlConfig[]): Promise {
-    const res = await createLambdaFuncUrlPrompter(configList).prompt()
-    if (!isValidResponse(res)) {
-        throw new CancellationError('user')
-    }
-    return res
-}
-
-export function createLambdaFuncUrlPrompter(configList: FunctionUrlConfig[]): QuickPickPrompter {
-    const items = configList
-        .filter((c) => c.FunctionArn && c.FunctionUrl)
-        .map((c) => ({
-            label: c.FunctionArn!,
-            data: c.FunctionUrl!,
-        }))
-    return createQuickPick(items, { title: 'Select function to copy url from.' })
-}
diff --git a/packages/core/src/lambda/commands/createNewSamApp.ts b/packages/core/src/lambda/commands/createNewSamApp.ts
deleted file mode 100644
index 7801976c43..0000000000
--- a/packages/core/src/lambda/commands/createNewSamApp.ts
+++ /dev/null
@@ -1,484 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { DefaultSchemaClient, SchemaClient } from '../../../src/shared/clients/schemaClient'
-import { createSchemaCodeDownloaderObject } from '../..//eventSchemas/commands/downloadSchemaItemCode'
-import {
-    SchemaCodeDownloader,
-    SchemaCodeDownloadRequestDetails,
-} from '../../eventSchemas/commands/downloadSchemaItemCode'
-import { getApiValueForSchemasDownload } from '../../eventSchemas/models/schemaCodeLangs'
-import {
-    buildSchemaTemplateParameters,
-    SchemaTemplateParameters,
-} from '../../eventSchemas/templates/schemasAppTemplateUtils'
-import { ActivationReloadState, SamInitState } from '../../shared/activationReloadState'
-import { AwsContext } from '../../shared/awsContext'
-
-import { fileExists, isInDirectory, readFileAsString } from '../../shared/filesystemUtilities'
-import { getLogger } from '../../shared/logger/logger'
-import { RegionProvider } from '../../shared/regions/regionProvider'
-import { getSamCliVersion, getSamCliContext, SamCliContext } from '../../shared/sam/cli/samCliContext'
-import { runSamCliInit, SamCliInitArgs } from '../../shared/sam/cli/samCliInit'
-import { throwAndNotifyIfInvalid } from '../../shared/sam/cli/samCliValidationUtils'
-import { SamCliValidator } from '../../shared/sam/cli/samCliValidator'
-import { addFolderToWorkspace, tryGetAbsolutePath } from '../../shared/utilities/workspaceUtils'
-import { goRuntimes } from '../models/samLambdaRuntime'
-import { eventBridgeStarterAppTemplate } from '../models/samTemplates'
-import { CreateNewSamAppWizard, CreateNewSamAppWizardForm } from '../wizards/samInitWizard'
-import { LaunchConfiguration } from '../../shared/debug/launchConfiguration'
-import { SamDebugConfigProvider } from '../../shared/sam/debugger/awsSamDebugger'
-import { ExtContext } from '../../shared/extensions'
-import { isTemplateTargetProperties } from '../../shared/sam/debugger/awsSamDebugConfiguration'
-import { TemplateTargetProperties } from '../../shared/sam/debugger/awsSamDebugConfiguration'
-import { openLaunchJsonFile } from '../../shared/sam/debugger/commands/addSamDebugConfiguration'
-import { waitUntil } from '../../shared/utilities/timeoutUtils'
-import { getIdeProperties, getDebugNewSamAppDocUrl, getLaunchConfigDocUrl } from '../../shared/extensionUtilities'
-import { checklogs } from '../../shared/localizedText'
-import globals from '../../shared/extensionGlobals'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { LambdaArchitecture, Result, Runtime as TelemetryRuntime } from '../../shared/telemetry/telemetry'
-import { Runtime } from '@aws-sdk/client-lambda'
-import { getTelemetryReason, getTelemetryResult } from '../../shared/errors'
-import { openUrl, replaceVscodeVars } from '../../shared/utilities/vsCodeUtils'
-import { fs } from '../../shared/fs/fs'
-import { ChildProcess } from '../../shared/utilities/processUtils'
-
-export const samInitTemplateFiles: string[] = ['template.yaml', 'template.yml']
-export const samInitReadmeFile: string = 'README.TOOLKIT.md'
-export const samInitReadmeSource: string = 'resources/markdown/samReadme.md'
-
-export async function resumeCreateNewSamApp(
-    extContext: ExtContext,
-    activationReloadState: ActivationReloadState = new ActivationReloadState()
-) {
-    let createResult: Result = 'Succeeded'
-    let reason: string | undefined
-    let samVersion: string | undefined
-    const samInitState: SamInitState | undefined = activationReloadState.getSamInitState()
-    try {
-        getLogger().debug('SAM: resumeCreateNewSamApp')
-        const templateUri = vscode.Uri.file(samInitState!.template!)
-        const readmeUri = vscode.Uri.file(samInitState!.readme!)
-        const folder = vscode.workspace.getWorkspaceFolder(templateUri)
-        if (!folder) {
-            createResult = 'Failed'
-            reason = 'error'
-            // Should never happen, because `samInitState.path` is only set if
-            // `uri` is in the newly-added workspace folder.
-            void vscode.window.showErrorMessage(
-                localize(
-                    'AWS.samcli.initWizard.source.error.notInWorkspace',
-                    "Could not open file '{0}'. If this file exists on disk, try adding it to your workspace.",
-                    templateUri.fsPath
-                )
-            )
-
-            return
-        }
-
-        samVersion = await getSamCliVersion(getSamCliContext())
-
-        const configs = await addInitialLaunchConfiguration(
-            extContext,
-            folder,
-            templateUri,
-            samInitState?.isImage ? samInitState?.runtime : undefined
-        )
-        const tryOpenReadme = await writeToolkitReadme(readmeUri.fsPath, configs)
-        if (tryOpenReadme) {
-            await vscode.commands.executeCommand('markdown.showPreviewToSide', readmeUri)
-        }
-    } catch (err) {
-        createResult = 'Failed'
-        reason = 'error'
-
-        globals.outputChannel.show(true)
-        getLogger().error(
-            localize('AWS.samcli.initWizard.resume.error', 'Error resuming SAM Application creation. {0}', checklogs())
-        )
-
-        getLogger().error('Error resuming new SAM Application: %O', err as Error)
-    } finally {
-        activationReloadState.clearSamInitState()
-        const arch = samInitState?.architecture as LambdaArchitecture
-        telemetry.sam_init.emit({
-            lambdaPackageType: samInitState?.isImage ? 'Image' : 'Zip',
-            lambdaArchitecture: arch,
-            result: createResult,
-            reason: reason,
-            runtime: samInitState?.runtime as TelemetryRuntime,
-            version: samVersion,
-        })
-    }
-}
-
-export interface CreateNewSamApplicationResults {
-    runtime: string
-    result: Result
-}
-
-/**
- * Runs `sam init` in the given context and returns useful metadata about its invocation
- */
-export async function createNewSamApplication(
-    extContext: ExtContext,
-    samCliContext: SamCliContext = getSamCliContext(),
-    activationReloadState: ActivationReloadState = new ActivationReloadState()
-): Promise {
-    const awsContext: AwsContext = extContext.awsContext
-    const regionProvider: RegionProvider = extContext.regionProvider
-    let createResult: Result = 'Succeeded'
-    let reason: string | undefined
-    let lambdaPackageType: 'Zip' | 'Image' | undefined
-    let createRuntime: Runtime | undefined
-    let samVersion: string | undefined
-
-    let initArguments: SamCliInitArgs | undefined
-
-    try {
-        await validateSamCli(samCliContext.validator)
-
-        const credentials = await awsContext.getCredentials()
-        samVersion = await getSamCliVersion(samCliContext)
-        const schemaRegions = regionProvider
-            .getRegions()
-            .filter((r) => regionProvider.isServiceInRegion('schemas', r.id))
-        const defaultRegion = awsContext.getCredentialDefaultRegion()
-
-        const config = await new CreateNewSamAppWizard({
-            credentials,
-            schemaRegions,
-            defaultRegion,
-            samCliVersion: samVersion,
-        }).run()
-
-        if (!config) {
-            createResult = 'Cancelled'
-            reason = 'userCancelled'
-
-            return
-        }
-
-        createRuntime = config.runtimeAndPackage.runtime as Runtime
-
-        initArguments = {
-            name: config.name,
-            location: config.location.fsPath,
-            dependencyManager: config.dependencyManager,
-            architecture: config.architecture,
-        }
-
-        let request: SchemaCodeDownloadRequestDetails
-        let schemaCodeDownloader: SchemaCodeDownloader
-        let schemaTemplateParameters: SchemaTemplateParameters
-        let client: SchemaClient
-        if (config.template === eventBridgeStarterAppTemplate) {
-            client = new DefaultSchemaClient(config.region!)
-            schemaTemplateParameters = await buildSchemaTemplateParameters(
-                config.schemaName!,
-                config.registryName!,
-                client
-            )
-
-            initArguments.extraContent = schemaTemplateParameters.templateExtraContent
-        }
-
-        if (config.runtimeAndPackage.packageType === 'Image') {
-            lambdaPackageType = 'Image'
-            initArguments.baseImage = `amazon/${createRuntime}-base`
-        } else {
-            lambdaPackageType = 'Zip'
-            initArguments.runtime! = createRuntime
-            // in theory, templates could be provided with image-based lambdas, but that is currently not supported by SAM
-            initArguments.template = config.template
-        }
-
-        await runSamCliInit(initArguments, samCliContext)
-
-        const templateUri = await getProjectUri(config, samInitTemplateFiles)
-        if (!templateUri) {
-            reason = 'fileNotFound'
-
-            return
-        }
-
-        const readmeUri = vscode.Uri.file(path.join(path.dirname(templateUri.fsPath), samInitReadmeFile))
-
-        // Needs to be done or else gopls won't start
-        if (goRuntimes.includes(createRuntime)) {
-            try {
-                await ChildProcess.run('go', ['mod', 'tidy'], {
-                    spawnOptions: { cwd: path.join(path.dirname(templateUri.fsPath), 'hello-world') },
-                })
-            } catch (err) {
-                getLogger().warn(
-                    localize(
-                        'AWS.message.warning.gotidyfailed',
-                        'Failed to initialize package directory with "go mod tidy". Launch config will not be automatically created.'
-                    )
-                )
-            }
-        }
-
-        if (config.template === eventBridgeStarterAppTemplate) {
-            const destinationDirectory = path.join(config.location.fsPath, config.name, 'hello_world_function')
-            request = {
-                registryName: config.registryName!,
-                schemaName: config.schemaName!,
-                language: getApiValueForSchemasDownload(createRuntime),
-                schemaVersion: schemaTemplateParameters!.SchemaVersion,
-                destinationDirectory: vscode.Uri.file(destinationDirectory),
-            }
-            schemaCodeDownloader = createSchemaCodeDownloaderObject(client!, globals.outputChannel)
-            getLogger().info(
-                localize(
-                    'AWS.message.info.schemas.downloadCodeBindings.start',
-                    'Downloading code for schema {0}...',
-                    config.schemaName!
-                )
-            )
-
-            await schemaCodeDownloader!.downloadCode(request!)
-
-            void vscode.window.showInformationMessage(
-                localize(
-                    'AWS.message.info.schemas.downloadCodeBindings.finished',
-                    'Downloaded code for schema {0}!',
-                    request!.schemaName
-                )
-            )
-        }
-
-        // In case adding the workspace folder triggers a VS Code restart, persist relevant state to be used after reload
-        activationReloadState.setSamInitState({
-            template: templateUri.fsPath,
-            readme: readmeUri.fsPath,
-            runtime: createRuntime,
-            isImage: config.runtimeAndPackage.packageType === 'Image',
-            architecture: initArguments?.architecture,
-        })
-
-        await addFolderToWorkspace(
-            {
-                uri: config.location,
-                name: path.basename(config.location.fsPath),
-            },
-            true
-        )
-
-        // Race condition where SAM app is created but template doesn't register in time.
-        // Poll for 5 seconds, otherwise direct user to codelens.
-        const isTemplateRegistered = await waitUntil(
-            async () => (await globals.templateRegistry).getItem(templateUri),
-            {
-                timeout: 5000,
-                interval: 500,
-                truthy: false,
-            }
-        )
-
-        let tryOpenReadme: boolean = false
-
-        if (isTemplateRegistered) {
-            const newLaunchConfigs = await addInitialLaunchConfiguration(
-                extContext,
-                vscode.workspace.getWorkspaceFolder(templateUri)!,
-                templateUri,
-                createRuntime
-            )
-            tryOpenReadme = await writeToolkitReadme(readmeUri.fsPath, newLaunchConfigs)
-            if (newLaunchConfigs && newLaunchConfigs.length > 0) {
-                void showCompletionNotification(
-                    config.name,
-                    `"${newLaunchConfigs.map((config) => config.name).join('", "')}"`
-                )
-            }
-            reason = 'complete'
-        } else {
-            createResult = 'Failed'
-            reason = 'fileNotFound'
-
-            const helpText = localize('AWS.generic.message.getHelp', 'Get Help...')
-            void vscode.window
-                .showWarningMessage(
-                    localize(
-                        'AWS.samcli.initWizard.launchConfigFail',
-                        'Created SAM application "{0}" but failed to generate launch configurations. You can generate these via {1} in the template or handler file.',
-                        config.name,
-                        getIdeProperties().codelens
-                    ),
-                    helpText
-                )
-                .then(async (buttonText) => {
-                    if (buttonText === helpText) {
-                        void openUrl(getLaunchConfigDocUrl())
-                    }
-                })
-        }
-
-        activationReloadState.clearSamInitState()
-
-        if (tryOpenReadme) {
-            await vscode.commands.executeCommand('markdown.showPreviewToSide', readmeUri)
-        } else {
-            await vscode.workspace.openTextDocument(templateUri)
-        }
-    } catch (err) {
-        createResult = getTelemetryResult(err)
-        reason = getTelemetryReason(err)
-
-        globals.outputChannel.show(true)
-        getLogger().error(
-            localize('AWS.samcli.initWizard.general.error', 'Error creating new SAM Application. {0}', checklogs())
-        )
-
-        getLogger().error('Error creating new SAM Application: %O', err as Error)
-
-        // An error occured, so do not try to continue during the next extension activation
-        activationReloadState.clearSamInitState()
-    } finally {
-        telemetry.sam_init.emit({
-            lambdaPackageType: lambdaPackageType,
-            lambdaArchitecture: initArguments?.architecture,
-            result: createResult,
-            reason: reason,
-            runtime: createRuntime as TelemetryRuntime,
-            version: samVersion,
-        })
-    }
-}
-
-async function validateSamCli(samCliValidator: SamCliValidator): Promise {
-    const validationResult = await samCliValidator.detectValidSamCli()
-    throwAndNotifyIfInvalid(validationResult)
-}
-
-export async function getProjectUri(
-    config: Pick,
-    files: string[]
-): Promise {
-    if (files.length === 0) {
-        throw Error('expected "files" parameter to have at least one item')
-    }
-    let file: string
-    let cfnTemplatePath: string
-    for (const f of files) {
-        file = f
-        cfnTemplatePath = path.resolve(config.location.fsPath, config.name, file)
-        if (await fileExists(cfnTemplatePath)) {
-            return vscode.Uri.file(cfnTemplatePath)
-        }
-    }
-    void vscode.window.showWarningMessage(
-        localize(
-            'AWS.samcli.initWizard.source.error.notFound',
-            'Project created successfully, but {0} file not found: {1}',
-            file!,
-            cfnTemplatePath!
-        )
-    )
-}
-
-/**
- * Adds intial launch configurations when a new SAM app is created.
- * The template file must be within the same root directory as the target file.
- */
-export async function addInitialLaunchConfiguration(
-    extContext: ExtContext,
-    folder: vscode.WorkspaceFolder,
-    targetUri: vscode.Uri,
-    runtime?: Runtime,
-    launchConfiguration: LaunchConfiguration = new LaunchConfiguration(folder.uri)
-): Promise {
-    const configurations = await new SamDebugConfigProvider(extContext).provideDebugConfigurations(folder)
-    if (configurations) {
-        // add configurations that target the new template file
-        const targetDir: string = path.dirname(targetUri.fsPath)
-        const filtered = configurations.filter((config) => {
-            let templatePath: string = (config.invokeTarget as TemplateTargetProperties).templatePath
-            templatePath = replaceVscodeVars(templatePath, folder.uri.fsPath)
-
-            return (
-                isTemplateTargetProperties(config.invokeTarget) &&
-                isInDirectory(targetDir, tryGetAbsolutePath(folder, templatePath))
-            )
-        })
-
-        // optional for ZIP-lambdas but required for Image-lambdas
-        if (runtime !== undefined) {
-            for (const configuration of filtered) {
-                if (!configuration.lambda) {
-                    configuration.lambda = {}
-                }
-                configuration.lambda.runtime = runtime
-            }
-        }
-
-        await launchConfiguration.addDebugConfigurations(filtered)
-        return filtered
-    }
-}
-
-async function showCompletionNotification(appName: string, configs: string): Promise {
-    const openJson = localize('AWS.generic.open', 'Open {0}', 'launch.json')
-    const learnMore = localize('AWS.generic.message.learnMore', 'Learn More')
-    const action = await vscode.window.showInformationMessage(
-        localize(
-            'AWS.samcli.initWizard.completionMessage',
-            'Created SAM application "{0}" and added launch configurations to launch.json: {1}',
-            appName,
-            configs
-        ),
-        openJson,
-        learnMore
-    )
-
-    if (action === openJson) {
-        await openLaunchJsonFile()
-    } else if (action === learnMore) {
-        void openUrl(getDebugNewSamAppDocUrl())
-    }
-}
-
-/**
- * Creates a new SAM readme tailored to the IDE and created launch configs
- * @param readmeLocation Location of new readme file
- * @param configurations Debug configs to list in readme
- * @returns True on success, false otherwise
- */
-export async function writeToolkitReadme(
-    readmeLocation: string,
-    configurations: vscode.DebugConfiguration[] = [],
-    getText: (path: string) => Promise = readFileAsString
-): Promise {
-    try {
-        const configString: string = configurations.reduce((acc, cur) => `${acc}\n* ${cur.name}`, '')
-        const readme = (await getText(globals.context.asAbsolutePath(samInitReadmeSource)))
-            .replace(/\$\{PRODUCTNAME\}/g, `${getIdeProperties().company} Toolkit For ${getIdeProperties().longName}`)
-            .replace(/\$\{IDE\}/g, getIdeProperties().shortName)
-            .replace(/\$\{CODELENS\}/g, getIdeProperties().codelens)
-            .replace(/\$\{COMPANYNAME\}/g, getIdeProperties().company)
-            .replace(/\$\{COMMANDPALETTE\}/g, getIdeProperties().commandPalette)
-            .replace(/\$\{LISTOFCONFIGURATIONS\}/g, configString)
-            .replace(
-                /\$\{DOCURL\}/g,
-                'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/serverless-apps.html'
-            )
-
-        await fs.writeFile(readmeLocation, readme)
-        getLogger().debug(`writeToolkitReadme: wrote file: %O`, readmeLocation)
-
-        return true
-    } catch (e) {
-        getLogger().error(`writeToolkitReadme failed, skip adding toolkit readme: ${(e as Error).message}`)
-
-        return false
-    }
-}
diff --git a/packages/core/src/lambda/commands/deleteCloudFormation.ts b/packages/core/src/lambda/commands/deleteCloudFormation.ts
deleted file mode 100644
index 035e7110c8..0000000000
--- a/packages/core/src/lambda/commands/deleteCloudFormation.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as vscode from 'vscode'
-import { CloudFormationClient } from '../../shared/clients/cloudFormation'
-
-import * as localizedText from '../../shared/localizedText'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { Result } from '../../shared/telemetry/telemetry'
-import { CloudFormationStackNode } from '../explorer/cloudFormationNodes'
-import { showConfirmationMessage } from '../../shared/utilities/messages'
-import { getIdeProperties } from '../../shared/extensionUtilities'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-export async function deleteCloudFormation(refresh: () => void, node?: CloudFormationStackNode) {
-    const logger: Logger = getLogger()
-    let deleteResult: Result = 'Succeeded'
-    const stackName = node?.stackName ?? ''
-    try {
-        if (!node) {
-            deleteResult = 'Failed'
-            void vscode.window.showErrorMessage(
-                localize(
-                    'AWS.message.error.cloudFormation.unsupported',
-                    'Unable to delete a CloudFormation Stack. No stack provided.'
-                )
-            )
-
-            return
-        }
-
-        const userResponse = await showConfirmationMessage({
-            prompt: localize(
-                'AWS.message.prompt.deleteCloudFormation',
-                'Are you sure you want to delete {0}?',
-                stackName
-            ),
-            confirm: localizedText.localizedDelete,
-            cancel: localizedText.cancel,
-        })
-
-        if (userResponse) {
-            const client = new CloudFormationClient(node.regionCode)
-
-            await client.deleteStack(stackName)
-
-            void vscode.window.showInformationMessage(
-                localize('AWS.message.info.cloudFormation.delete', 'Deleted CloudFormation Stack {0}', stackName)
-            )
-
-            refresh()
-        }
-    } catch (err) {
-        deleteResult = 'Failed'
-        logger.error(err as Error)
-
-        void vscode.window.showInformationMessage(
-            localize(
-                'AWS.message.error.cloudFormation.delete',
-                'An error occurred while deleting {0}. Please check the stack events on the {1} Console',
-                stackName,
-                getIdeProperties().company
-            )
-        )
-    } finally {
-        telemetry.cloudformation_delete.emit({ result: deleteResult })
-    }
-}
diff --git a/packages/core/src/lambda/commands/deleteLambda.ts b/packages/core/src/lambda/commands/deleteLambda.ts
deleted file mode 100644
index c6290eab3c..0000000000
--- a/packages/core/src/lambda/commands/deleteLambda.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as localizedText from '../../shared/localizedText'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-import { Result } from '../../shared/telemetry/telemetry'
-import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages'
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import { getLogger } from '../../shared/logger/logger'
-import { telemetry } from '../../shared/telemetry/telemetry'
-
-async function confirmDeletion(functionName: string): Promise {
-    return showConfirmationMessage({
-        prompt: localize(
-            'AWS.command.deleteLambda.confirm',
-            "Are you sure you want to delete lambda function '{0}'?",
-            functionName
-        ),
-        confirm: localizedText.localizedDelete,
-        cancel: localizedText.cancel,
-    })
-}
-
-export async function deleteLambda(
-    lambda: Pick,
-    client: Pick
-): Promise {
-    if (!lambda.FunctionName) {
-        telemetry.lambda_delete.emit({ duration: 0, result: 'Failed' })
-
-        throw new TypeError('Lambda does not have a function name')
-    }
-
-    let deleteResult: Result = 'Succeeded'
-
-    try {
-        if (await confirmDeletion(lambda.FunctionName)) {
-            await client.deleteFunction(lambda.FunctionName)
-        } else {
-            deleteResult = 'Cancelled'
-        }
-    } catch (err) {
-        deleteResult = 'Failed'
-        getLogger().error(`Failed to delete lambda function "${lambda.FunctionName}": %s`, err)
-        const message = localize(
-            'AWS.command.deleteLambda.error',
-            "There was an error deleting lambda function '{0}'",
-            lambda.FunctionName
-        )
-
-        void showViewLogsMessage(message)
-    } finally {
-        telemetry.lambda_delete.emit({
-            result: deleteResult,
-        })
-    }
-}
diff --git a/packages/core/src/lambda/commands/downloadLambda.ts b/packages/core/src/lambda/commands/downloadLambda.ts
deleted file mode 100644
index baf82b5b30..0000000000
--- a/packages/core/src/lambda/commands/downloadLambda.ts
+++ /dev/null
@@ -1,300 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import AdmZip from 'adm-zip'
-import * as _ from 'lodash'
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { LambdaFunctionNode } from '../explorer/lambdaFunctionNode'
-import { showConfirmationMessage } from '../../shared/utilities/messages'
-import { LaunchConfiguration, getReferencedHandlerPaths } from '../../shared/debug/launchConfiguration'
-
-import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
-import * as localizedText from '../../shared/localizedText'
-import { getLogger } from '../../shared/logger/logger'
-import { HttpResourceFetcher } from '../../shared/resourcefetcher/node/httpResourceFetcher'
-import { createCodeAwsSamDebugConfig } from '../../shared/sam/debugger/awsSamDebugConfiguration'
-import * as pathutils from '../../shared/utilities/pathUtils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { addFolderToWorkspace } from '../../shared/utilities/workspaceUtils'
-import { promptUserForLocation, WizardContext } from '../../shared/wizards/multiStepWizard'
-import { getLambdaDetails } from '../utils'
-import { Progress } from 'got/dist/source'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { Result, Runtime } from '../../shared/telemetry/telemetry'
-import { fs } from '../../shared/fs/fs'
-import { LambdaFunction } from './uploadLambda'
-import globals from '../../shared/extensionGlobals'
-
-// Workspace state key for Lambda function ARN to local path cache
-const LAMBDA_ARN_CACHE_KEY = 'aws.lambda.functionArnToLocalPathCache' // eslint-disable-line @typescript-eslint/naming-convention
-
-async function setLambdaArnCache(functionArn: string, localPath: string): Promise {
-    try {
-        const cache: Record = globals.context.workspaceState.get(LAMBDA_ARN_CACHE_KEY, {})
-        cache[functionArn] = localPath
-        await globals.context.workspaceState.update(LAMBDA_ARN_CACHE_KEY, cache)
-        getLogger().debug(`lambda: cached local path for function ARN: ${functionArn} -> ${localPath}`)
-    } catch (error) {
-        getLogger().error(`lambda: failed to cache local path for function ARN: ${functionArn}`, error)
-    }
-}
-
-export function getCachedLocalPath(functionArn: string): string | undefined {
-    const cache: Record = globals.context.workspaceState.get(LAMBDA_ARN_CACHE_KEY, {})
-    return cache[functionArn]
-}
-
-export async function downloadLambdaCommand(functionNode: LambdaFunctionNode) {
-    const result = await runDownloadLambda(functionNode)
-    // check if result is Result
-    if (result instanceof vscode.Uri) {
-        return
-    }
-
-    telemetry.lambda_import.emit({
-        result,
-        runtime: functionNode.configuration.Runtime as Runtime | undefined,
-    })
-}
-
-export async function runDownloadLambda(
-    functionNode: LambdaFunctionNode,
-    returnDir: boolean = false
-): Promise {
-    const workspaceFolders = vscode.workspace.workspaceFolders || []
-    const functionName = functionNode.configuration.FunctionName!
-
-    if (workspaceFolders.length === 0) {
-        void vscode.window.showErrorMessage(
-            localize(
-                'AWS.lambda.download.noWorkspaceFolders',
-                'Open a workspace and add a folder to it before downloading a Lambda function.'
-            )
-        )
-        return 'Cancelled'
-    }
-    const selectedUri = await promptUserForLocation(new WizardContext(), { step: 1, totalSteps: 1 })
-    if (!selectedUri) {
-        return 'Cancelled'
-    }
-
-    const downloadLocation = path.join(selectedUri.fsPath, functionName, path.sep)
-    const downloadLocationName = vscode.workspace.asRelativePath(downloadLocation, true)
-
-    if (await fs.exists(downloadLocation)) {
-        const isConfirmed = await showConfirmationMessage({
-            prompt: localize(
-                'AWS.lambda.download.prompt',
-                'Downloading {0} into: {1}\nExisting directory will be overwritten: {0}\nProceed with download?',
-                functionName,
-                downloadLocationName
-            ),
-            confirm: localize('AWS.lambda.download.download', 'Download'),
-            cancel: localizedText.cancel,
-        })
-
-        if (!isConfirmed) {
-            getLogger().info('DownloadLambda cancelled')
-            return 'Cancelled'
-        }
-
-        // customer accepted, we should make sure the target dir is clean
-        await fs.delete(downloadLocation, { recursive: true })
-    }
-
-    return await downloadLambdaInLocation(
-        { name: functionName, region: functionNode.regionCode, configuration: functionNode.configuration },
-        downloadLocationName,
-        downloadLocation,
-        workspaceFolders,
-        selectedUri,
-        returnDir
-    )
-}
-
-export async function downloadLambdaInLocation(
-    lambda: LambdaFunction,
-    downloadLocationName: string,
-    downloadLocation: string,
-    workspaceFolders?: readonly vscode.WorkspaceFolder[],
-    selectedUri?: vscode.Uri,
-    returnDir: boolean = false
-): Promise {
-    const result = await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-            title: localize(
-                'AWS.lambda.download.status',
-                'Downloading Lambda function {0} into {1}...',
-                lambda.name,
-                downloadLocationName
-            ),
-        },
-        async (progress) => {
-            let lambdaLocation: string
-
-            try {
-                await downloadAndUnzipLambda(progress, lambda, downloadLocation)
-                // Cache the mapping of function ARN to downloaded location
-                if (lambda.configuration?.FunctionArn) {
-                    await setLambdaArnCache(lambda.configuration.FunctionArn, downloadLocation)
-                }
-                lambdaLocation = path.join(downloadLocation, getLambdaDetails(lambda.configuration!).fileName)
-                if (!(await fs.exists(lambdaLocation))) {
-                    // if file ext is mjs, change to js or vice versa
-                    const currentExt = path.extname(lambdaLocation)
-                    const alternativeExt = currentExt === '.mjs' ? '.js' : '.mjs'
-                    const alternativePath = lambdaLocation.replace(currentExt, alternativeExt)
-
-                    if (await fs.exists(alternativePath)) {
-                        lambdaLocation = alternativePath
-                    }
-                }
-            } catch (e) {
-                // initial download failed or runtime is unsupported.
-                // show error and return a failure
-                const err = e as Error
-                getLogger().error(err)
-                void vscode.window.showErrorMessage(
-                    localize(
-                        'AWS.lambda.download.downloadError',
-                        'Error downloading Lambda function {0}: {1}',
-                        lambda.configuration!.FunctionArn!,
-                        err.message
-                    )
-                )
-
-                return 'Failed'
-            }
-
-            try {
-                await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
-                await openLambdaFile(lambdaLocation)
-                if (workspaceFolders) {
-                    if (
-                        workspaceFolders.filter((val) => {
-                            return selectedUri === val.uri
-                        }).length === 0
-                    ) {
-                        await addFolderToWorkspace({ uri: selectedUri! }, true)
-                    }
-                    const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(downloadLocation))!
-
-                    await addLaunchConfigEntry(lambdaLocation, lambda, workspaceFolder)
-                }
-                return 'Succeeded'
-            } catch (e) {
-                // failed to open handler file or add launch config.
-                // not a failure since the function is downloaded to a workspace directory.
-                return 'Succeeded'
-            }
-        }
-    )
-
-    if (returnDir) {
-        return vscode.Uri.file(downloadLocation)
-    } else {
-        return result
-    }
-}
-
-async function downloadAndUnzipLambda(
-    progress: vscode.Progress<{
-        message?: string | undefined
-        increment?: number | undefined
-    }>,
-    lambda: LambdaFunction,
-    extractLocation: string,
-    lambdaClient = new DefaultLambdaClient(lambda.region)
-): Promise {
-    const functionArn = lambda.configuration!.FunctionArn!
-    let tempDir: string | undefined
-    try {
-        tempDir = await makeTemporaryToolkitFolder()
-        const downloadLocation = path.join(tempDir, 'function.zip')
-
-        const response = await lambdaClient.getFunction(functionArn)
-        const codeLocation = response.Code!.Location!
-
-        // arbitrary increments since there's no "busy" state for progress bars
-        progress.report({ message: 'Starting download' })
-
-        const fetcher = new HttpResourceFetcher(codeLocation, {
-            showUrl: false,
-            friendlyName: 'Lambda Function .zip file',
-        })
-        const streams = fetcher.get(downloadLocation)
-
-        let last: number = 0
-        streams.requestStream.on('downloadProgress', (p: Progress) => {
-            // I think these are bytes...
-            const message = p.total ? `Downloading ${p.transferred}/${p.total} bytes` : 'Downloading...'
-            const increment = p.total ? ((p.transferred - last) / p.total) * 100 : 0
-            last = p.transferred
-            progress.report({ message, increment })
-        })
-
-        await streams
-        progress.report({ message: 'Extracting...' })
-        new AdmZip(downloadLocation).extractAllTo(extractLocation, true)
-    } finally {
-        await tryRemoveFolder(tempDir)
-    }
-}
-
-export async function openLambdaFile(lambdaLocation: string, viewColumn?: vscode.ViewColumn): Promise {
-    if (!(await fs.exists(lambdaLocation))) {
-        const warning = localize(
-            'AWS.lambda.download.fileNotFound',
-            'Handler file {0} not found in downloaded function.',
-            lambdaLocation
-        )
-        getLogger().warn(warning)
-        void vscode.window.showWarningMessage(warning)
-        throw new Error()
-    }
-    await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
-    const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(lambdaLocation))
-    await vscode.window.showTextDocument(doc, viewColumn)
-}
-
-async function addLaunchConfigEntry(
-    lambdaLocation: string,
-    lambda: LambdaFunction,
-    workspaceFolder: vscode.WorkspaceFolder
-): Promise {
-    const handler = lambda.configuration!.Handler!
-
-    const samDebugConfig = createCodeAwsSamDebugConfig(
-        workspaceFolder,
-        handler,
-        computeLambdaRoot(lambdaLocation, lambda),
-        lambda.configuration!.Runtime!
-    )
-
-    const launchConfig = new LaunchConfiguration(vscode.Uri.file(lambdaLocation))
-    const refPaths = await getReferencedHandlerPaths(launchConfig)
-    if (!refPaths.has(pathutils.normalize(path.join(path.dirname(lambdaLocation), handler)))) {
-        await launchConfig.addDebugConfiguration(samDebugConfig)
-    }
-}
-
-/**
- * Computes the Lambda root.
- * We cannot assume that the Lambda root is the dirname since CodeUri is not a required field (can be merged with handler)
- * @param lambdaLocation Lambda handler file location
- * @param functionNode Function node
- */
-function computeLambdaRoot(lambdaLocation: string, lambda: LambdaFunction): string {
-    const lambdaDetails = getLambdaDetails(lambda.configuration!)
-    const normalizedLocation = pathutils.normalize(lambdaLocation)
-
-    const lambdaIndex = normalizedLocation.indexOf(`/${lambdaDetails.fileName}`)
-
-    return lambdaIndex > -1 ? normalizedLocation.slice(0, lambdaIndex) : path.dirname(normalizedLocation)
-}
diff --git a/packages/core/src/lambda/commands/editLambda.ts b/packages/core/src/lambda/commands/editLambda.ts
deleted file mode 100644
index 5ea8169d28..0000000000
--- a/packages/core/src/lambda/commands/editLambda.ts
+++ /dev/null
@@ -1,285 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { LambdaFunctionNode } from '../explorer/lambdaFunctionNode'
-import { downloadLambdaInLocation, openLambdaFile } from './downloadLambda'
-import { LambdaFunction, runUploadDirectory } from './uploadLambda'
-import {
-    compareCodeSha,
-    getFunctionInfo,
-    getLambdaDetails,
-    getTempLocation,
-    lambdaTempPath,
-    setFunctionInfo,
-} from '../utils'
-import { showConfirmationMessage } from '../../shared/utilities/messages'
-import fs from '../../shared/fs/fs'
-import globals from '../../shared/extensionGlobals'
-import { LambdaFunctionNodeDecorationProvider } from '../explorer/lambdaFunctionNodeDecorationProvider'
-import path from 'path'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { ToolkitError } from '../../shared/errors'
-import { getFunctionWithCredentials } from '../../shared/clients/lambdaClient'
-import { getLogger } from '../../shared/logger/logger'
-
-const localize = nls.loadMessageBundle()
-
-let lastPromptTime = Date.now() - 5000
-
-export function watchForUpdates(lambda: LambdaFunction, projectUri: vscode.Uri): void {
-    const watcher = vscode.workspace.createFileSystemWatcher(new vscode.RelativePattern(projectUri, '*'))
-    const startTime = globals.clock.Date.now()
-
-    watcher.onDidChange(async (fileUri) => {
-        await promptForSync(lambda, projectUri, fileUri)
-    })
-
-    watcher.onDidCreate(async (fileUri) => {
-        // When the code is downloaded and the watcher is set, this will immediately trigger the onDidCreate
-        // To avoid this, we must check that the file was actually created AFTER the watcher was created
-        if ((await fs.stat(fileUri.fsPath)).ctime < startTime) {
-            return
-        }
-        await promptForSync(lambda, projectUri, fileUri)
-    })
-
-    watcher.onDidDelete(async (fileUri) => {
-        // We don't want to sync if the whole directory has been deleted or emptied
-        if (fileUri.fsPath !== projectUri.fsPath) {
-            // Check if directory is empty before prompting for sync
-            try {
-                const entries = await fs.readdir(projectUri.fsPath)
-                if (entries.length > 0) {
-                    await promptForSync(lambda, projectUri, fileUri)
-                }
-            } catch (err) {
-                getLogger().debug(`Failed to check Lambda directory contents: ${err}`)
-            }
-        }
-    })
-}
-
-// Creating this function for testing, can't mock the vscode.window in the tests
-export async function promptDeploy() {
-    const confirmItem = localize('AWS.lambda.upload.sync', 'Deploy')
-    const cancelItem = localize('AWS.lambda.upload.noSync', 'No, thanks')
-    const response = await vscode.window.showInformationMessage(
-        localize('AWS.lambda.upload.confirmSync', 'Would you like to deploy these changes to the cloud?'),
-        confirmItem,
-        cancelItem
-    )
-    return response === confirmItem
-}
-
-export async function promptForSync(lambda: LambdaFunction, projectUri: vscode.Uri, fileUri: vscode.Uri) {
-    if (!(await fs.existsDir(projectUri.fsPath)) || globals.clock.Date.now() - lastPromptTime < 5000) {
-        return
-    }
-
-    await setFunctionInfo(lambda, {
-        undeployed: true,
-    })
-
-    await LambdaFunctionNodeDecorationProvider.getInstance().addBadge(
-        fileUri,
-        vscode.Uri.from({ scheme: 'lambda', path: `${lambda.region}/${lambda.name}` })
-    )
-
-    lastPromptTime = globals.clock.Date.now()
-    if (await promptDeploy()) {
-        await deployFromTemp(lambda, projectUri)
-    }
-}
-
-export async function confirmOutdatedChanges(prompt: string): Promise {
-    return await showConfirmationMessage({
-        prompt,
-        confirm: localize('AWS.lambda.upload.overwrite', 'Overwrite'),
-        cancel: localize('AWS.lambda.upload.noOverwrite', 'Cancel'),
-    })
-}
-
-export async function deployFromTemp(lambda: LambdaFunction, projectUri: vscode.Uri) {
-    return telemetry.lambda_quickDeploy.run(async () => {
-        const prompt = localize(
-            'AWS.lambda.upload.confirmOutdatedSync',
-            'There are changes to your Function in the cloud after you created this local copy, overwrite anyway?'
-        )
-
-        const isShaDifferent = !(await compareCodeSha(lambda))
-        const overwriteChanges = isShaDifferent ? await confirmOutdatedChanges(prompt) : true
-
-        if (overwriteChanges) {
-            // Reset the lastPrompt time because we don't want to retrigger the watcher flow
-            lastPromptTime = globals.clock.Date.now()
-            await vscode.workspace.saveAll()
-            try {
-                await runUploadDirectory(lambda, 'zip', projectUri)
-            } catch {
-                throw new ToolkitError('Failed to deploy Lambda function', { code: 'deployFailure' })
-            }
-            await setFunctionInfo(lambda, {
-                lastDeployed: globals.clock.Date.now(),
-                undeployed: false,
-            })
-            await LambdaFunctionNodeDecorationProvider.getInstance().removeBadge(
-                projectUri,
-                vscode.Uri.from({ scheme: 'lambda', path: `${lambda.region}/${lambda.name}` })
-            )
-            if (isShaDifferent) {
-                telemetry.record({ action: 'overwriteChanges' })
-            }
-        } else {
-            telemetry.record({ action: 'cancelOverwrite' })
-        }
-    })
-}
-
-export async function deleteFilesInFolder(location: string) {
-    const entries = await fs.readdir(location)
-    await Promise.all(
-        entries.map((entry) => fs.delete(path.join(location, entry[0]), { recursive: true, force: true }))
-    )
-}
-
-export async function editLambdaCommand(functionNode: LambdaFunctionNode) {
-    const region = functionNode.regionCode
-    const functionName = functionNode.configuration.FunctionName!
-    return await editLambda({ name: functionName, region, configuration: functionNode.configuration }, 'explorer')
-}
-
-export async function overwriteChangesForEdit(lambda: LambdaFunction, downloadLocation: string) {
-    try {
-        // Clear directory contents instead of deleting to avoid Windows EBUSY errors
-        if (await fs.existsDir(downloadLocation)) {
-            await deleteFilesInFolder(downloadLocation)
-        } else {
-            await fs.mkdir(downloadLocation)
-        }
-
-        await downloadLambdaInLocation(lambda, 'local', downloadLocation)
-
-        // Watching for updates, then setting info, then removing the badges must be done in this order
-        // This is because the files creating can throw the watcher, which sometimes leads to changes being marked as undeployed
-        watchForUpdates(lambda, vscode.Uri.file(downloadLocation))
-
-        await setFunctionInfo(lambda, {
-            lastDeployed: globals.clock.Date.now(),
-            undeployed: false,
-            sha: lambda.configuration!.CodeSha256,
-            handlerFile: getLambdaDetails(lambda.configuration!).fileName,
-        })
-        await LambdaFunctionNodeDecorationProvider.getInstance().removeBadge(
-            vscode.Uri.file(downloadLocation),
-            vscode.Uri.from({ scheme: 'lambda', path: `${lambda.region}/${lambda.name}` })
-        )
-    } catch {
-        throw new ToolkitError('Failed to download Lambda function', { code: 'failedDownload' })
-    }
-}
-
-export async function editLambda(lambda: LambdaFunction, source?: 'workspace' | 'explorer') {
-    return await telemetry.lambda_quickEditFunction.run(async () => {
-        telemetry.record({ source })
-        const downloadLocation = getTempLocation(lambda.name, lambda.region)
-
-        // We don't want to do anything if the folder already exists as a workspace folder, it means it's already being edited
-        if (vscode.workspace.workspaceFolders?.some((folder) => folder.uri.fsPath === downloadLocation)) {
-            return downloadLocation
-        }
-
-        const prompt = localize(
-            'AWS.lambda.download.confirmOutdatedSync',
-            'There are changes to your function in the cloud since you last edited locally, do you want to overwrite your local changes?'
-        )
-
-        // We want to overwrite changes in the following cases:
-        // 1. There is no code sha locally (getCodeShaLocal returns falsy)
-        // 2. There is a code sha locally, it does not match the one remotely, and the user confirms they want to overwrite it
-        const localExists = !!(await getFunctionInfo(lambda, 'sha'))
-        // This record tells us if they're attempting to edit a function they've edited before
-        telemetry.record({ action: localExists ? 'existingEdit' : 'newEdit' })
-
-        const isDirectoryEmpty = (await fs.existsDir(downloadLocation))
-            ? (await fs.readdir(downloadLocation)).length === 0
-            : true
-
-        const overwriteChanges =
-            !localExists ||
-            isDirectoryEmpty ||
-            (!(await compareCodeSha(lambda)) ? await confirmOutdatedChanges(prompt) : false)
-
-        if (overwriteChanges) {
-            await overwriteChangesForEdit(lambda, downloadLocation)
-        } else if (source === 'explorer') {
-            // If the source is the explorer, we want to open, otherwise we just wait to open in the workspace
-            const lambdaLocation = path.join(downloadLocation, getLambdaDetails(lambda.configuration!).fileName)
-            await openLambdaFile(lambdaLocation)
-            watchForUpdates(lambda, vscode.Uri.file(downloadLocation))
-        }
-
-        return downloadLocation
-    })
-}
-
-export async function openLambdaFolderForEdit(name: string, region: string) {
-    const downloadLocation = getTempLocation(name, region)
-
-    // Do all authentication work before opening workspace to avoid race condition
-    const getFunctionOutput = await getFunctionWithCredentials(region, name)
-    const configuration = getFunctionOutput.Configuration
-
-    // Download and set up Lambda code before opening workspace
-    await editLambda(
-        {
-            name,
-            region,
-            configuration: configuration as any,
-        },
-        'workspace'
-    )
-
-    try {
-        await vscode.commands.executeCommand('vscode.openFolder', vscode.Uri.file(downloadLocation), {
-            newWindow: true,
-            noRecentEntry: true,
-        })
-    } catch (e) {
-        throw new ToolkitError(`Failed to open your function as a workspace: ${e}`, { code: 'folderOpenFailure' })
-    }
-}
-
-export async function getReadme(): Promise {
-    const readmeSource = path.join('resources', 'markdown', 'lambdaEdit.md')
-    const readmeDestination = path.join(lambdaTempPath, 'README.md')
-    try {
-        const readmeContent = await fs.readFileText(globals.context.asAbsolutePath(readmeSource))
-        await fs.writeFile(readmeDestination, readmeContent)
-    } catch (e) {
-        getLogger().info(`Failed to copy content for Lambda README: ${e}`)
-    }
-
-    try {
-        const createStackIconSource = path.join('resources', 'icons', 'aws', 'lambda', 'create-stack-light.svg')
-        const createStackIconDestination = path.join(lambdaTempPath, 'create-stack.svg')
-        await fs.copy(globals.context.asAbsolutePath(createStackIconSource), createStackIconDestination)
-
-        // Copy VS Code built-in icons
-        const vscodeIconPath = path.join('resources', 'icons', 'vscode', 'light')
-
-        const invokeIconSource = path.join(vscodeIconPath, 'run.svg')
-        const invokeIconDestination = path.join(lambdaTempPath, 'invoke.svg')
-        await fs.copy(globals.context.asAbsolutePath(invokeIconSource), invokeIconDestination)
-
-        const deployIconSource = path.join(vscodeIconPath, 'cloud-upload.svg')
-        const deployIconDestination = path.join(lambdaTempPath, 'deploy.svg')
-        await fs.copy(globals.context.asAbsolutePath(deployIconSource), deployIconDestination)
-    } catch (e) {
-        getLogger().info(`Failed to copy content for Lambda README: ${e}`)
-    }
-
-    return readmeDestination
-}
diff --git a/packages/core/src/lambda/commands/listSamResources.ts b/packages/core/src/lambda/commands/listSamResources.ts
deleted file mode 100644
index 80d13a1c71..0000000000
--- a/packages/core/src/lambda/commands/listSamResources.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { runSamCliListResource } from '../../shared/sam/cli/samCliListResources'
-
-export interface StackResource {
-    LogicalResourceId: string
-    PhysicalResourceId: string
-}
-
-/*
-This function return exclusively the deployed resources
-Newly added but yet-to-be deployed resources are not included in this result
-*/
-export async function getDeployedResources(params: any) {
-    try {
-        const samCliListResourceOutput = await runSamCliListResource(params.listResourcesParams, params.invoker).then(
-            (output) => parseSamListResourceOutput(output)
-        )
-        // Filter out resources that are not deployed
-        return samCliListResourceOutput.filter((resource) => resource.PhysicalResourceId !== '-')
-    } catch (err) {
-        const error = err as Error
-        getLogger().error(error)
-    }
-}
-
-function parseSamListResourceOutput(output: any): StackResource[] {
-    try {
-        if ((Array.isArray(output) && output.length === 0) || '[]' === output) {
-            // Handle if the output is instance or stringify version of an empty array to avoid parsing error
-            return []
-        }
-        return JSON.parse(output) as StackResource[]
-    } catch (error: any) {
-        void vscode.window.showErrorMessage(`Failed to parse SAM CLI output: ${error.message}`)
-        return []
-    }
-}
diff --git a/packages/core/src/lambda/commands/uploadLambda.ts b/packages/core/src/lambda/commands/uploadLambda.ts
deleted file mode 100644
index 9814967458..0000000000
--- a/packages/core/src/lambda/commands/uploadLambda.ts
+++ /dev/null
@@ -1,603 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-
-const localize = nls.loadMessageBundle()
-
-import AdmZip from 'adm-zip'
-import * as path from 'path'
-import { fs } from '../../shared/fs/fs'
-import { showConfirmationMessage, showViewLogsMessage } from '../../shared/utilities/messages'
-import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../shared/filesystemUtilities'
-import * as localizedText from '../../shared/localizedText'
-import { getLogger } from '../../shared/logger/logger'
-import { SamCliBuildInvocation } from '../../shared/sam/cli/samCliBuild'
-import { getSamCliContext } from '../../shared/sam/cli/samCliContext'
-import { SamTemplateGenerator } from '../../shared/templates/sam/samTemplateGenerator'
-import { addCodiconToString } from '../../shared/utilities/textUtilities'
-import { getLambdaDetails, listLambdaFunctions } from '../utils'
-import { getIdeProperties } from '../../shared/extensionUtilities'
-import { createQuickPick, DataQuickPickItem } from '../../shared/ui/pickerPrompter'
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { StepEstimator, Wizard, WIZARD_BACK } from '../../shared/wizards/wizard'
-import { createSingleFileDialog } from '../../shared/ui/common/openDialog'
-import { Prompter, PromptResult } from '../../shared/ui/prompter'
-import { ToolkitError } from '../../shared/errors'
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import globals from '../../shared/extensionGlobals'
-import { toArrayAsync } from '../../shared/utilities/collectionUtils'
-import { fromExtensionManifest } from '../../shared/settings'
-import { createRegionPrompter } from '../../shared/ui/common/region'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { Result, Runtime } from '../../shared/telemetry/telemetry'
-
-interface SavedLambdas {
-    [profile: string]: { [region: string]: string }
-}
-
-class LambdaSettings extends fromExtensionManifest('aws.lambda', { recentlyUploaded: Object }) {
-    static #instance: LambdaSettings
-
-    public getRecentLambdas(): SavedLambdas | undefined {
-        try {
-            return this.get('recentlyUploaded')
-        } catch (error) {
-            this.delete('recentlyUploaded').catch((e) => {
-                getLogger().error('TypedSettings.delete() failed: %s', (e as Error).message)
-            })
-        }
-    }
-
-    /**
-     * Adds a new "recently used Lambda" to user settings for the given profile
-     * and region (limit of one item per profile+region).
-     */
-    public setRecentLambda(profile: string, region: string, lambdaName: string): Promise {
-        const oldLambdas = this.getRecentLambdas()
-        return this.update('recentlyUploaded', {
-            ...oldLambdas,
-            [profile]: {
-                ...(oldLambdas?.[profile] ?? {}),
-                [region]: lambdaName,
-            },
-        })
-    }
-
-    public static get instance() {
-        return (this.#instance ??= new this())
-    }
-}
-
-export interface LambdaFunction {
-    readonly name: string
-    readonly region: string
-    readonly configuration?: FunctionConfiguration
-}
-
-/**
- * Executes the "Upload Lambda..." command.
- * Allows for uploads of zip files, and both built and unbuilt directories.
- * Does not discriminate on runtime.
- * @param lambdaArg LambdaFunction
- * @param path Uri to the template.yaml file or the directory the command was invoked from
- */
-export async function uploadLambdaCommand(lambdaArg?: LambdaFunction, path?: vscode.Uri) {
-    let result: Result = 'Cancelled'
-    let lambda: LambdaFunction | undefined
-
-    try {
-        const response = await new UploadLambdaWizard(lambdaArg, path).run()
-        if (!response) {
-            getLogger().debug('lambda: UploadLambdaWizard returned undefined. User cancelled.')
-            return
-        }
-        lambda = response.lambda
-        if (response.uploadType === 'zip') {
-            await runUploadLambdaZipFile(lambda, response.targetUri)
-            result = 'Succeeded'
-        } else if (response.uploadType === 'directory' && response.directoryBuildType) {
-            result = (await runUploadDirectory(lambda, response.directoryBuildType, response.targetUri)) ?? result
-            result = 'Succeeded'
-        }
-        // TODO(sijaden): potentially allow the wizard to easily support tagged-union states
-    } catch (err) {
-        result = 'Failed'
-        if (err instanceof ToolkitError) {
-            void showViewLogsMessage(`Could not upload lambda: ${err.message}`)
-            getLogger().error(`Lambda upload failed: %O`, err.cause ?? err)
-        } else {
-            void showViewLogsMessage(`Could not upload lambda (unexpected exception)`)
-            getLogger().error(`Lambda upload failed: %s`, err)
-        }
-    } finally {
-        telemetry.lambda_updateFunctionCode.emit({
-            result,
-            runtime: lambda?.configuration?.Runtime as Runtime | undefined,
-        })
-        if (result === 'Succeeded') {
-            const profile = globals.awsContext.getCredentialProfileName()
-            if (profile && lambda) {
-                await LambdaSettings.instance.setRecentLambda(profile, lambda.region, lambda.name)
-            }
-        }
-    }
-}
-
-/**
- * Selects the type of file to upload (zip/dir) and proceeds with the rest of the workflow.
- */
-function createUploadTypePrompter() {
-    const items: DataQuickPickItem<'zip' | 'directory'>[] = [
-        {
-            label: addCodiconToString('file-zip', localize('AWS.generic.filetype.zipfile', 'ZIP Archive')),
-            data: 'zip',
-        },
-        {
-            label: addCodiconToString('folder', localize('AWS.generic.filetype.directory', 'Directory')),
-            data: 'directory',
-        },
-    ]
-
-    return createQuickPick(items, {
-        title: localize('AWS.lambda.upload.title', 'Select Upload Type'),
-        buttons: createCommonButtons(),
-    })
-}
-
-function createBuildPrompter() {
-    const items: DataQuickPickItem<'zip' | 'sam'>[] = [
-        {
-            label: addCodiconToString('exclude', localizedText.no),
-            detail: localize(
-                'AWS.lambda.upload.prebuiltDir.detail',
-                '{0} Toolkit will upload a ZIP of the selected directory.',
-                getIdeProperties().company
-            ),
-            data: 'zip',
-        },
-        {
-            label: addCodiconToString('gear', localizedText.yes),
-            detail: localize(
-                'AWS.lambda.upload.unbuiltDir.detail',
-                '{0} Toolkit will attempt to build the selected directory using the sam build command.',
-                getIdeProperties().company
-            ),
-            data: 'sam',
-        },
-    ]
-
-    return createQuickPick(items, {
-        title: localize('AWS.lambda.upload.buildDirectory.title', 'Build directory?'),
-        buttons: createCommonButtons(),
-    })
-}
-
-function createConfirmDeploymentPrompter(lambda: LambdaFunction) {
-    // TODO(sijaden): make this a quick pick? Tried to keep as close to possible as the original impl.
-    return new (class extends Prompter {
-        protected promptUser(): Promise> {
-            return confirmLambdaDeployment(lambda).then((res) => res || WIZARD_BACK)
-        }
-
-        // Stubs. Need to thin-out the `Prompter` interface to avoid this.
-        public setStepEstimator(estimator: StepEstimator): void {}
-        public setSteps(current: number, total: number): void {}
-
-        public set recentItem(response: any) {}
-        public get recentItem(): any {
-            return undefined
-        }
-    })()
-}
-
-export interface UploadLambdaWizardState {
-    readonly uploadType: 'zip' | 'directory'
-    readonly targetUri: vscode.Uri
-    readonly directoryBuildType: 'zip' | 'sam'
-    readonly confirmedDeploy: boolean
-    readonly lambda: LambdaFunction
-}
-
-export class UploadLambdaWizard extends Wizard {
-    public override async init(): Promise {
-        this.form.lambda.region.bindPrompter(() => createRegionPrompter().transform((region) => region.id))
-
-        if (this.invokePath) {
-            this.form.uploadType.setDefault('directory')
-            if (await fs.existsFile(this.invokePath.fsPath)) {
-                this.form.targetUri.setDefault(vscode.Uri.file(path.dirname(this.invokePath.fsPath)))
-            } else {
-                this.form.targetUri.setDefault(this.invokePath)
-            }
-        } else {
-            this.form.uploadType.bindPrompter(() => createUploadTypePrompter())
-            this.form.targetUri.bindPrompter(({ uploadType }) => {
-                if (uploadType === 'directory') {
-                    return createSingleFileDialog({
-                        canSelectFolders: true,
-                        canSelectFiles: false,
-                    })
-                } else {
-                    return createSingleFileDialog({
-                        canSelectFolders: false,
-                        canSelectFiles: true,
-                        filters: {
-                            'ZIP archive': ['zip'],
-                        },
-                    })
-                }
-            })
-        }
-
-        this.form.lambda.name.bindPrompter((state) => {
-            // invoking from the command palette passes no arguments
-            if (!this.invokePath) {
-                if (state.uploadType === 'directory') {
-                    return createFunctionNamePrompter(state.lambda!.region!, state.targetUri)
-                } else {
-                    // if a zip file is chosen, pass the parent directory
-                    return createFunctionNamePrompter(
-                        state.lambda!.region!,
-                        vscode.Uri.file(path.dirname(state.targetUri!.fsPath))
-                    )
-                }
-            } else {
-                return createFunctionNamePrompter(state.lambda!.region!, this.invokePath)
-            }
-        })
-        if (this.lambda) {
-            this.form.directoryBuildType.bindPrompter(() => createBuildPrompter(), {
-                showWhen: ({ uploadType }) => uploadType === 'directory',
-            })
-        } else {
-            this.form.directoryBuildType.setDefault('zip')
-        }
-
-        this.form.confirmedDeploy.bindPrompter((state) => createConfirmDeploymentPrompter(state.lambda!))
-
-        return this
-    }
-
-    constructor(
-        readonly lambda?: LambdaFunction,
-        readonly invokePath?: vscode.Uri
-    ) {
-        super({ initState: { lambda } })
-    }
-}
-
-/**
- * Allows the user to decide whether or not they want to build the directory in question and proceeds with the rest of the deployment workflow.
- * @param lambda LambdaFunction from either a node or input manually
- * @param type Whether to zip or sam build the directory
- * @param window Wrapper around vscode.window functionality for testing
- */
-export async function runUploadDirectory(lambda: LambdaFunction, type: 'zip' | 'sam', parentDir: vscode.Uri) {
-    if (type === 'sam' && lambda.configuration) {
-        return await runUploadLambdaWithSamBuild({ ...lambda, configuration: lambda.configuration }, parentDir)
-    } else {
-        return await vscode.window.withProgress(
-            {
-                location: vscode.ProgressLocation.Notification,
-                cancellable: false,
-            },
-            async (progress) => {
-                return await zipAndUploadDirectory(lambda, parentDir.fsPath, progress)
-            }
-        )
-    }
-}
-
-/**
- * Attempts to build a project using `sam build`.
- * * Checks supported interpreted languages to see if the handler file exists (based on upstream handler name)
- * * Creates a temporary template based on the parent dir and the upstream handler name
- * * Executes `sam build` on the temporary template
- * * Sends directory to be archived and uploaded
- * @param lambda LambdaFunction from either a node or input manually
- * @param parentDir Parent dir to build
- * @param window Wrapper around vscode.window functionality for testing
- */
-async function runUploadLambdaWithSamBuild(lambda: Required, parentDir: vscode.Uri) {
-    // Detect if handler is present and provide strong guidance against proceeding if not.
-    try {
-        const handlerFile = path.join(parentDir.fsPath, getLambdaDetails(lambda.configuration).fileName)
-        if (!(await fs.exists(handlerFile))) {
-            const isConfirmed = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.lambda.upload.handlerNotFound',
-                    "{0} Toolkit can't find a file corresponding to handler: {1} at filepath {2}.\n\nThis directory likely will not work with this function.\n\nProceed with upload anyway?",
-                    getIdeProperties().company,
-                    lambda.configuration.Handler,
-                    handlerFile
-                ),
-                confirm: localizedText.yes,
-                cancel: localizedText.no,
-            })
-
-            if (!isConfirmed) {
-                getLogger().info('Handler file not found. Aborting runUploadLambdaWithSamBuild')
-                return 'Cancelled'
-            }
-        }
-    } catch (e) {
-        // TODO: Give provisional support for Ruby?
-        getLogger().info(
-            'Attempting to build a runtime that AWS Toolkit does not have handler validation for. Ignoring handler check.'
-        )
-    }
-
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            let tempDir: string | undefined
-            try {
-                const invoker = getSamCliContext().invoker
-
-                tempDir = await makeTemporaryToolkitFolder()
-                const templatePath = path.join(tempDir, 'template.yaml')
-                const resourceName = 'tempResource'
-
-                // TODO: Use an existing template file if it's present?
-                progress.report({
-                    message: localize(
-                        'AWS.lambda.upload.progress.generatingTemplate',
-                        'Setting up temporary build files...'
-                    ),
-                })
-                await new SamTemplateGenerator()
-                    .withFunctionHandler(lambda.configuration.Handler!)
-                    .withResourceName(resourceName)
-                    .withRuntime(lambda.configuration.Runtime!)
-                    .withCodeUri(parentDir.fsPath)
-                    .generate(templatePath)
-
-                progress.report({
-                    message: localize(
-                        'AWS.lambda.upload.progress.samBuilding',
-                        'Building project via sam build command...'
-                    ),
-                })
-                const buildDir = path.join(tempDir, 'output')
-                // Note: `sam build` will fail if the selected directory does not have a valid manifest file:
-                // https://github.com/awslabs/aws-sam-cli/blob/4f12dc74ca8ff6fddd661711db7c3048812b4119/designs/sam_build_cmd.md#built-in-build-actions
-                await new SamCliBuildInvocation({
-                    buildDir,
-                    templatePath,
-                    invoker,
-                    skipPullImage: true,
-                    useContainer: false,
-                    baseDir: parentDir.fsPath,
-                }).execute()
-
-                // App builds into a folder named after the resource name. Zip the contents of that, not the whole output dir.
-                return await zipAndUploadDirectory(lambda, path.join(buildDir, resourceName), progress)
-            } catch (err) {
-                throw new ToolkitError('Failed to build directory', { cause: err as Error })
-            } finally {
-                await tryRemoveFolder(tempDir)
-            }
-        }
-    )
-}
-
-/**
- * Confirms whether or not the user wants to deploy the Lambda as it is a destructive action.
- * @param lambda LambdaFunction
- * @param window Wrapper around vscode.window functionality for testing
- */
-async function confirmLambdaDeployment(lambda: LambdaFunction): Promise {
-    const isConfirmed = await showConfirmationMessage({
-        prompt: localize(
-            'AWS.lambda.upload.confirm',
-            'This will immediately publish the selected code as the $LATEST version of Lambda: {0}.\n\nContinue?',
-            lambda.name
-        ),
-        confirm: localizedText.yes,
-        cancel: localizedText.no,
-    })
-
-    if (!isConfirmed) {
-        getLogger().info('UploadLambda confirmation cancelled.')
-    }
-
-    return isConfirmed
-}
-
-async function runUploadLambdaZipFile(lambda: LambdaFunction, zipFileUri: vscode.Uri) {
-    return await vscode.window.withProgress(
-        {
-            location: vscode.ProgressLocation.Notification,
-            cancellable: false,
-        },
-        async (progress) => {
-            const zipFile = await fs.readFileBytes(zipFileUri.fsPath).catch((err) => {
-                throw new ToolkitError('Failed to read zip', { cause: err })
-            })
-            return await uploadZipBuffer(lambda, zipFile, progress)
-        }
-    )
-}
-
-/**
- * Zips a selected directory in memory and attempts to upload archive to Lambda
- * @param lambda LambdaFunction
- * @param path Directory path to zip
- * @param progress Progress notification for displaying a status message
- */
-async function zipAndUploadDirectory(
-    lambda: LambdaFunction,
-    path: string,
-    progress: vscode.Progress<{
-        message?: string | undefined
-        increment?: number | undefined
-    }>
-) {
-    progress.report({ message: localize('AWS.lambda.upload.progress.archivingDir', 'Archiving files...') })
-    const zipBuffer = await new Promise((resolve, reject) => {
-        const zip = new AdmZip()
-        zip.addLocalFolder(path)
-        zip.toBuffer(resolve, reject)
-    }).catch((err) => {
-        throw new ToolkitError('Failed to archive directory', { cause: err })
-    })
-
-    return await uploadZipBuffer(lambda, zipBuffer, progress)
-}
-
-/**
- * Attempts to upload Buffer representation of a `.zip` file to an existing Lambda function
- * @param lambda LambdaFunction
- * @param zip Buffer to upload to Lambda
- * @param progress Progress notification for displaying a status message
- * @param lambdaClient Overwriteable Lambda client for testing purposes
- */
-async function uploadZipBuffer(
-    lambda: LambdaFunction,
-    zip: Uint8Array,
-    progress: vscode.Progress<{
-        message?: string | undefined
-        increment?: number | undefined
-    }>,
-    lambdaClient = new DefaultLambdaClient(lambda.region)
-) {
-    progress.report({
-        message: localize('AWS.lambda.upload.progress.uploadingArchive', 'Uploading archive to Lambda...'),
-    })
-    await lambdaClient.updateFunctionCode(lambda.name, zip).catch((err) => {
-        throw new ToolkitError('Failed to upload zip archive', { cause: err })
-    })
-
-    void vscode.window.showInformationMessage(
-        localize('AWS.lambda.upload.done', 'Uploaded Lambda function: {0}', lambda.name)
-    )
-}
-
-export async function findApplicationJsonFile(startPath: vscode.Uri): Promise {
-    if (!(await fs.exists(startPath.fsPath))) {
-        getLogger().error(
-            'findApplicationJsonFile() invalid path (not accessible or does not exist): "%s"',
-            startPath.fsPath
-        )
-        return undefined
-    }
-    const isdir = await fs.existsDir(startPath.fsPath)
-    const parentDir = isdir ? startPath.fsPath : path.dirname(startPath.fsPath)
-    const found = await vscode.workspace.findFiles(
-        new vscode.RelativePattern(parentDir, '**/.application.json'),
-        // exclude:
-        // - null      = NO excludes apply
-        // - undefined = default excludes apply (e.g. the `files.exclude` setting but not `search.exclude`).
-        // eslint-disable-next-line unicorn/no-null
-        null,
-        1
-    )
-    if (!found || found.length === 0) {
-        getLogger().debug('uploadLambda: .application.json not found in: "%s"', parentDir)
-    }
-    return found[0]
-}
-
-export async function getFunctionNames(file: vscode.Uri, region: string): Promise {
-    try {
-        const names: string[] = []
-        const appData = JSON.parse(await fs.readFileText(file.fsPath))
-        if (appData['Functions']) {
-            const functions = Object.keys(appData['Functions'])
-            if (functions) {
-                for (const func of functions) {
-                    if (appData['Functions'][func]['PhysicalId'] && appData['Functions'][func]['PhysicalId'][region]) {
-                        names.push(appData['Functions'][func]['PhysicalId'][region])
-                    }
-                }
-            }
-        } else {
-            getLogger().info('lambda: Incorrect JSON structure for .application.json file. Missing: "Functions"')
-        }
-        return names.length > 0 ? names : undefined
-    } catch (error) {
-        getLogger().error('lambda: failed to parse .application.json: %s', (error as Error).message)
-    }
-}
-
-async function listAllLambdaNames(region: string, path?: vscode.Uri) {
-    const lambdaFunctionNames: DataQuickPickItem[] = []
-
-    // Get Lambda functions from .application.json #2588
-    if (path) {
-        const appFile = await findApplicationJsonFile(path)
-        const namesFromAppFile = appFile ? await getFunctionNames(appFile, region) : undefined
-        if (!appFile) {
-            getLogger().debug('lambda: .application.json not found')
-        } else if (!namesFromAppFile) {
-            getLogger().debug('lambda: no functions in .application.json for region: %s', region)
-        } else {
-            lambdaFunctionNames.push(
-                ...namesFromAppFile.map((n) => {
-                    return {
-                        label: n,
-                        description: localize('AWS.lambda.upload.fromAppJson', 'from .application.json'),
-                        data: n,
-                    }
-                })
-            )
-        }
-    }
-
-    // Get Lambda functions from user AWS account.
-    const lambdaClient = new DefaultLambdaClient(region)
-    try {
-        const foundLambdas = await toArrayAsync(listLambdaFunctions(lambdaClient))
-        for (const l of foundLambdas) {
-            lambdaFunctionNames.push({ label: l.FunctionName!, data: l.FunctionName })
-        }
-    } catch (error) {
-        getLogger().error('lambda: failed to list Lambda functions: %s', (error as Error).message)
-    }
-
-    // Get "recently used" Lambda functions.
-    const recent = LambdaSettings.instance.getRecentLambdas()
-    const profile = globals.awsContext.getCredentialProfileName()
-    if (profile && recent?.[profile]?.[region]) {
-        let isInList = false
-        for (const l of lambdaFunctionNames) {
-            if (l.label === recent[profile][region]) {
-                l.recentlyUsed = true
-                isInList = true
-            }
-        }
-        if (!isInList) {
-            lambdaFunctionNames.splice(0, 0, {
-                label: recent[profile][region],
-                recentlyUsed: true,
-                data: recent[profile][region],
-            })
-        }
-    }
-
-    return lambdaFunctionNames
-}
-
-function createFunctionNamePrompter(region: string, path?: vscode.Uri) {
-    const items = listAllLambdaNames(region, path)
-
-    const prompter = createQuickPick(items, {
-        title: localize('AWS.lambda.upload.selectFunctionName', 'Select a Function'),
-        buttons: createCommonButtons(),
-        placeholder: localize(
-            'aws.lambda.upload.manualEntry.placeholder',
-            'Filter or enter existing function name or ARN'
-        ),
-        filterBoxInputSettings: { label: 'Existing lambda function: ', transform: (input) => input },
-    })
-
-    return prompter
-}
diff --git a/packages/core/src/lambda/config/configureParameterOverrides.ts b/packages/core/src/lambda/config/configureParameterOverrides.ts
deleted file mode 100644
index 70e9d1936d..0000000000
--- a/packages/core/src/lambda/config/configureParameterOverrides.ts
+++ /dev/null
@@ -1,149 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { getNormalizedRelativePath } from '../../shared/utilities/pathUtils'
-import { getChildrenRange } from '../../shared/utilities/symbolUtilities'
-import { getTabSize } from '../../shared/utilities/textDocumentUtilities'
-import {
-    ensureTemplatesConfigFileExists,
-    getTemplatesConfigPath,
-    showTemplatesConfigurationError,
-    TemplatesConfigFieldTypeError,
-    TemplatesConfigPopulator,
-} from './templates'
-
-export interface ConfigureParameterOverridesContext {
-    getWorkspaceFolder: typeof vscode.workspace.getWorkspaceFolder
-    showErrorMessage: typeof vscode.window.showErrorMessage
-    showTextDocument: typeof vscode.window.showTextDocument
-    executeCommand: typeof vscode.commands.executeCommand
-}
-
-class DefaultConfigureParamOverridesContext implements ConfigureParameterOverridesContext {
-    public readonly getWorkspaceFolder = vscode.workspace.getWorkspaceFolder
-
-    public readonly showErrorMessage = vscode.window.showErrorMessage
-
-    public readonly showTextDocument = vscode.window.showTextDocument
-
-    public readonly executeCommand = vscode.commands.executeCommand
-}
-
-export async function configureParameterOverrides(
-    {
-        templateUri,
-        requiredParameterNames = [],
-    }: {
-        templateUri: vscode.Uri
-        requiredParameterNames?: Iterable
-    },
-    context: ConfigureParameterOverridesContext = new DefaultConfigureParamOverridesContext()
-): Promise {
-    const workspaceFolder = context.getWorkspaceFolder(templateUri)
-    if (!workspaceFolder) {
-        throw new Error(`Template ${templateUri.fsPath} is not in the workspace`)
-    }
-
-    const configPath = getTemplatesConfigPath(workspaceFolder.uri.fsPath)
-    await ensureTemplatesConfigFileExists(configPath)
-    const editor: vscode.TextEditor = await context.showTextDocument(vscode.Uri.file(configPath))
-
-    const relativeTemplatePath = getNormalizedRelativePath(workspaceFolder.uri.fsPath, templateUri.fsPath)
-
-    try {
-        let populator = new TemplatesConfigPopulator(editor.document.getText(), {
-            formattingOptions: {
-                insertSpaces: true,
-                tabSize: getTabSize(editor),
-                eol: editor.document.eol === vscode.EndOfLine.CRLF ? '\r\n' : '\n',
-            },
-        })
-
-        for (const parameterName of requiredParameterNames) {
-            populator = populator.ensureTemplateParameterOverrideExists(relativeTemplatePath, parameterName)
-        }
-
-        const { json, isDirty } = populator.getResults()
-
-        if (isDirty) {
-            await editor.edit((eb) => {
-                eb.replace(
-                    new vscode.Range(
-                        editor.document.positionAt(0),
-                        editor.document.positionAt(editor.document.getText().length)
-                    ),
-                    json
-                )
-            })
-
-            // We don't save the doc. The user has the option to revert changes, or make further edits.
-        }
-
-        await context.showTextDocument(editor.document, {
-            selection: await getParameterOverridesRange(
-                {
-                    editor,
-                    relativeTemplatePath,
-                },
-                context
-            ),
-        })
-    } catch (err) {
-        if (err instanceof TemplatesConfigFieldTypeError) {
-            showTemplatesConfigurationError(err, context.showErrorMessage)
-        } else {
-            throw err
-        }
-    }
-}
-
-async function getParameterOverridesRange(
-    {
-        editor,
-        relativeTemplatePath,
-    }: {
-        editor: vscode.TextEditor
-        relativeTemplatePath: string
-    },
-    context: ConfigureParameterOverridesContext
-) {
-    const logger = getLogger()
-
-    const symbols: vscode.DocumentSymbol[] | undefined = await context.executeCommand(
-        'vscode.executeDocumentSymbolProvider',
-        editor.document.uri
-    )
-
-    const defaultRange = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0))
-
-    if (!symbols || symbols.length < 1) {
-        return defaultRange
-    }
-
-    const templatesSymbol = symbols.find((c) => c.name === 'templates')
-    if (!templatesSymbol) {
-        logger.warn(`Invalid format for document ${editor.document.uri}`)
-
-        return defaultRange
-    }
-
-    const templateSymbol = templatesSymbol.children.find((c) => c.name === relativeTemplatePath)
-    if (!templateSymbol) {
-        logger.warn(`Cannot find template section '${relativeTemplatePath}' in: ${editor.document.uri}`)
-
-        return defaultRange
-    }
-
-    const parameterOverridesSymbol = templateSymbol.children.find((c) => c.name === 'parameterOverrides')
-    if (!parameterOverridesSymbol) {
-        logger.warn(`Cannot find parameterOverrides section for '${relativeTemplatePath}' in: ${editor.document.uri}`)
-
-        return defaultRange
-    }
-
-    return getChildrenRange(parameterOverridesSymbol)
-}
diff --git a/packages/core/src/lambda/config/parameterUtils.ts b/packages/core/src/lambda/config/parameterUtils.ts
deleted file mode 100644
index 3849e466b0..0000000000
--- a/packages/core/src/lambda/config/parameterUtils.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-//
-// TODO: DEPRECATED. Remove this after removing support for the legacy
-// "templates.json" support.
-//
-import * as vscode from 'vscode'
-import * as CloudFormation from '../../shared/cloudformation/cloudformation'
-import { getNormalizedRelativePath } from '../../shared/utilities/pathUtils'
-import { load as loadTemplatesConfig } from '../config/templates'
-
-export interface GetParametersContext {
-    loadTemplate: typeof CloudFormation.load
-}
-
-export async function getParameters(
-    templateUri: vscode.Uri,
-    context: GetParametersContext = { loadTemplate: CloudFormation.load }
-): Promise> {
-    const template = await context.loadTemplate(templateUri.fsPath)
-    if (!template.Parameters) {
-        return new Map()
-    }
-
-    const result = new Map()
-
-    for (const name of Object.getOwnPropertyNames(template.Parameters)) {
-        const parameter = template.Parameters[name]!
-
-        result.set(name, {
-            // Explicitly compare with undefined, as a valid default value may be falsy.
-            required: parameter.Default === undefined,
-        })
-    }
-
-    return result
-}
-
-export async function getParameterNames(
-    templateUri: vscode.Uri,
-    context: GetParametersContext = { loadTemplate: CloudFormation.load }
-): Promise {
-    return [...(await getParameters(templateUri, context)).keys()]
-}
-
-export interface GetOverriddenParametersContext {
-    readonly loadTemplatesConfig: typeof loadTemplatesConfig
-    getWorkspaceFolder(uri: vscode.Uri): Pick | undefined
-}
-
-export class DefaultGetOverriddenParametersContext implements GetOverriddenParametersContext {
-    public readonly getWorkspaceFolder = vscode.workspace.getWorkspaceFolder
-    public readonly loadTemplatesConfig = loadTemplatesConfig
-}
-
-export async function getOverriddenParameters(
-    templateUri: vscode.Uri,
-    context: GetOverriddenParametersContext = new DefaultGetOverriddenParametersContext()
-): Promise | undefined> {
-    const workspaceFolder = context.getWorkspaceFolder(templateUri)
-    if (!workspaceFolder) {
-        // This should never happen.
-        throw new Error(`The template ${templateUri.fsPath} is not in the workspace`)
-    }
-
-    const relativeTemplatePath = getNormalizedRelativePath(workspaceFolder.uri.fsPath, templateUri.fsPath)
-    const templatesConfig = await context.loadTemplatesConfig(workspaceFolder.uri.fsPath)
-    const templateConfig = templatesConfig.templates[relativeTemplatePath]
-    if (!templateConfig || !templateConfig.parameterOverrides) {
-        return undefined
-    }
-
-    const result = new Map()
-
-    for (const name of Object.getOwnPropertyNames(templateConfig.parameterOverrides)) {
-        result.set(name, templateConfig.parameterOverrides[name]!)
-    }
-
-    return result
-}
diff --git a/packages/core/src/lambda/config/samParameterCompletionItemProvider.ts b/packages/core/src/lambda/config/samParameterCompletionItemProvider.ts
deleted file mode 100644
index f408733f88..0000000000
--- a/packages/core/src/lambda/config/samParameterCompletionItemProvider.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as CloudFormation from '../../shared/cloudformation/cloudformation'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { getChildrenRange, loadSymbols, LoadSymbolsContext } from '../../shared/utilities/symbolUtilities'
-import { getParameterNames } from '../config/parameterUtils'
-
-export interface SamParameterCompletionItemProviderContext extends LoadSymbolsContext {
-    logger: Pick
-    getWorkspaceFolder: typeof vscode.workspace.getWorkspaceFolder
-    loadTemplate: typeof CloudFormation.load
-}
-
-/**
- * Provides completion items (i.e. intellisense) for parameter names when editting `.aws/templates.json`,
- * by reading the list of parameters from the associated SAM template.
- *
- * This class may be modified in the future to also provide suggestions for parameter values, etc.
- */
-export class SamParameterCompletionItemProvider implements vscode.CompletionItemProvider {
-    public constructor(
-        private readonly context: SamParameterCompletionItemProviderContext = {
-            executeCommand: vscode.commands.executeCommand,
-            logger: getLogger(),
-            getWorkspaceFolder: vscode.workspace.getWorkspaceFolder,
-            loadTemplate: CloudFormation.load,
-        }
-    ) {}
-
-    public async provideCompletionItems(
-        document: vscode.TextDocument,
-        position: vscode.Position,
-        token: vscode.CancellationToken,
-        completionContext: vscode.CompletionContext
-    ): Promise {
-        const workspaceFolder = this.context.getWorkspaceFolder(document.uri)
-        if (!workspaceFolder) {
-            // This should never happen.
-            this.context.logger.warn(
-                `Cannot provide completion items for '${document.uri.fsPath}' beacuse it is not in the workspace.`
-            )
-
-            return []
-        }
-
-        const symbols: vscode.DocumentSymbol[] | undefined = await loadSymbols({
-            uri: document.uri,
-            context: this.context,
-            maxRetries: 0,
-        })
-        if (!symbols) {
-            return []
-        }
-
-        const templateUri = await getTemplateUri({
-            workspaceUri: workspaceFolder.uri,
-            symbols,
-            position,
-        })
-        if (!templateUri) {
-            return []
-        }
-
-        const prefix = this.getWordAt(document, position)
-        const templateParameterNames = await getParameterNames(templateUri, this.context)
-
-        return templateParameterNames
-            .filter((name) => !prefix || name.startsWith(prefix))
-            .map((name) => {
-                const completionItem: vscode.CompletionItem = {
-                    kind: vscode.CompletionItemKind.Reference,
-                    label: name,
-                    insertText: name,
-                    range: new vscode.Range(position, position),
-                }
-
-                return completionItem
-            })
-    }
-
-    private getWordAt(document: vscode.TextDocument, position: vscode.Position): string | undefined {
-        const wordRange = document.getWordRangeAtPosition(position)
-        if (!wordRange) {
-            return undefined
-        }
-
-        // The JSON spec requires the use of double-quotes rather than single-quotes.
-        return document
-            .getText(wordRange)
-            .replace(/^\"/, '') // strip leading quote character
-            .replace(/\"$/, '') // strip trailing quote character
-    }
-}
-
-async function getTemplateUri({
-    workspaceUri,
-    symbols,
-    position,
-}: {
-    workspaceUri: vscode.Uri
-    symbols: vscode.DocumentSymbol[]
-    position: vscode.Position
-}): Promise {
-    const templates = symbols.find((symbol) => symbol.name === 'templates')
-    if (!templates) {
-        return undefined
-    }
-
-    const template = templates.children.find((child) => child.range.contains(position))
-    if (!template) {
-        return undefined
-    }
-
-    // Only offer suggestions inside the 'parameterOverrides' property.
-    const parameterOverrides = template.children.find((child) => child.name === 'parameterOverrides')
-    if (!parameterOverrides) {
-        return undefined
-    }
-
-    const childrenRange = await getChildrenRange(parameterOverrides)
-    if (!childrenRange.contains(position)) {
-        return undefined
-    }
-
-    // Ensure that position is at a parameter name, not a value.
-    if (parameterOverrides.children) {
-        const override = parameterOverrides.children.find((child) => child.range.contains(position))
-        if (override) {
-            if (!override.selectionRange.contains(position)) {
-                return undefined
-            }
-        }
-    }
-
-    return vscode.Uri.file(path.join(workspaceUri.fsPath, template.name))
-}
diff --git a/packages/core/src/lambda/config/templates.ts b/packages/core/src/lambda/config/templates.ts
deleted file mode 100644
index 52beae0e95..0000000000
--- a/packages/core/src/lambda/config/templates.ts
+++ /dev/null
@@ -1,442 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Use jsonc-parser.parse instead of JSON.parse, as JSONC can handle comments. VS Code uses jsonc-parser
-// under the hood to provide symbols for JSON documents, so this will keep us consistent with VS code.
-import * as jsonParser from 'jsonc-parser'
-import * as os from 'os'
-import * as _path from 'path'
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import * as fsUtils from '../../shared/filesystemUtilities'
-import { getLogger, Logger } from '../../shared/logger/logger'
-import { ReadonlyJsonObject } from '../../shared/sam/debugger/awsSamDebugConfiguration'
-import { getTabSizeSetting } from '../../shared/utilities/editorUtilities'
-import { getNormalizedRelativePath } from '../../shared/utilities/pathUtils'
-import { saveDocumentIfDirty } from '../../shared/utilities/textDocumentUtilities'
-import { access } from 'fs/promises'
-import { fs } from '../../shared/fs/fs'
-
-const localize = nls.loadMessageBundle()
-
-export interface TemplatesConfig {
-    templates: {
-        [relativePath: string]: TemplateConfig | undefined
-    }
-}
-
-export interface TemplateConfig {
-    parameterOverrides?: {
-        [key: string]: string | undefined
-    }
-    handlers?: {
-        [handler: string]: HandlerConfig | undefined
-    }
-}
-
-export interface HandlerConfig {
-    event: Record
-    environmentVariables: {
-        [name: string]: string
-    }
-    dockerNetwork?: string
-    useContainer?: boolean
-}
-
-export function generateDefaultHandlerConfig(): HandlerConfig {
-    return {
-        event: {},
-        environmentVariables: {},
-        dockerNetwork: undefined,
-        useContainer: undefined,
-    }
-}
-
-/**
- * TODO: remove? still needed?
- */
-export async function getHandlerConfig(params: {
-    handlerName: string
-    documentUri: vscode.Uri
-    samTemplate: vscode.Uri
-}): Promise {
-    const workspaceFolder = vscode.workspace.getWorkspaceFolder(params.documentUri)
-    if (!workspaceFolder) {
-        return generateDefaultHandlerConfig()
-    }
-
-    const config: HandlerConfig = await getLocalLambdaConfiguration(
-        workspaceFolder,
-        params.handlerName,
-        params.samTemplate
-    )
-
-    return config
-}
-
-async function getLocalLambdaConfiguration(
-    workspaceFolder: vscode.WorkspaceFolder,
-    handler: string,
-    samTemplate: vscode.Uri
-): Promise {
-    try {
-        const configPath: string = getTemplatesConfigPath(workspaceFolder.uri.fsPath)
-        const templateRelativePath = getNormalizedRelativePath(workspaceFolder.uri.fsPath, samTemplate.fsPath)
-
-        await saveDocumentIfDirty(configPath)
-
-        let rawConfig: string = '{}'
-        if (await fsUtils.fileExists(configPath)) {
-            rawConfig = await fsUtils.readFileAsString(configPath)
-        }
-
-        const configPopulationResult = new TemplatesConfigPopulator(rawConfig)
-            .ensureTemplateHandlerSectionExists(templateRelativePath, handler)
-            .getResults()
-
-        const config: TemplatesConfig = loadTemplatesConfigFromJson(configPopulationResult.json)
-
-        return config.templates[templateRelativePath]!.handlers![handler]!
-    } catch (e) {
-        if (e instanceof TemplatesConfigFieldTypeError) {
-            showTemplatesConfigurationError(e)
-        }
-
-        throw e
-    }
-}
-
-export interface LoadTemplatesConfigContext {
-    fileExists(path: string): Thenable
-    readFile(path: string): Thenable
-    saveDocumentIfDirty(editorPath: string): Thenable
-}
-
-export class DefaultLoadTemplatesConfigContext implements LoadTemplatesConfigContext {
-    public readonly fileExists = fsUtils.fileExists
-    public readonly readFile = fsUtils.readFileAsString
-
-    public readonly saveDocumentIfDirty = saveDocumentIfDirty
-}
-
-export function getTemplatesConfigPath(workspaceFolderPath: string): string {
-    return _path.join(workspaceFolderPath, '.aws', 'templates.json')
-}
-
-export async function load(
-    workspaceFolderPath: string,
-    context: LoadTemplatesConfigContext = new DefaultLoadTemplatesConfigContext()
-): Promise {
-    const templatesConfigPath = getTemplatesConfigPath(workspaceFolderPath)
-
-    return await loadTemplatesConfig(templatesConfigPath, context)
-}
-
-async function loadTemplatesConfig(
-    path: string,
-    context: LoadTemplatesConfigContext = new DefaultLoadTemplatesConfigContext()
-): Promise {
-    try {
-        await context.saveDocumentIfDirty(path)
-
-        if (!(await context.fileExists(path))) {
-            return {
-                templates: {},
-            }
-        }
-
-        const raw = await context.readFile(path)
-
-        return loadTemplatesConfigFromJson(raw)
-    } catch (err) {
-        if (Array.isArray(err) && (err as any[]).length === 1) {
-            throw new Error(`Could not load .aws/templates.json: ${(err as any[])[0]}`)
-        }
-
-        throw new Error(`Could not load .aws/templates.json: ${err}`)
-    }
-}
-
-export function loadTemplatesConfigFromJson(json: string): TemplatesConfig {
-    const errors: jsonParser.ParseError[] = []
-    const config = jsonParser.parse(json, errors) as TemplatesConfig
-    if (errors.length > 0) {
-        const message =
-            errors.length === 1
-                ? ` ${formatParseError(errors[0])}`
-                : `${os.EOL}${errors.map(formatParseError).join(os.EOL)}`
-
-        throw new Error(`Could not parse .aws/templates.json:${message}`)
-    }
-
-    return config
-}
-
-export function showTemplatesConfigurationError(
-    error: TemplatesConfigFieldTypeError,
-    showErrorMessage: typeof vscode.window.showErrorMessage = vscode.window.showErrorMessage
-): void {
-    const logger: Logger = getLogger()
-
-    void showErrorMessage(
-        localize(
-            'AWS.lambda.configure.error.fieldtype',
-            'Your templates.json file has an issue. {0} was detected as {1} instead of one of the following: [{2}]. Please change or remove this field, and try again.',
-            error.jsonPath.join('.'),
-            error.actualType,
-            error.expectedTypes.join(', ')
-        )
-    )
-
-    logger.error(
-        `Error detected in templates.json: ${error.message}. Field: ${error.jsonPath.join(
-            '.'
-        )}, expected one of: [${error.expectedTypes.join(', ')}], was: ${error.actualType}`
-    )
-}
-
-export async function ensureTemplatesConfigFileExists(path: string): Promise {
-    await fs.mkdir(_path.dirname(path))
-    try {
-        await access(path)
-    } catch {
-        await fs.writeFile(path, '{}')
-    }
-}
-
-function formatParseError(error: jsonParser.ParseError) {
-    return `${getParseErrorDescription(error.error)} at offset ${error.offset}, length ${error.length}`
-}
-
-// Reverse enum mappings are only generated for non-const numerical enums,
-// but ParseErrorCode is a const enum. So we have to reverse-map manually.
-function getParseErrorDescription(code: jsonParser.ParseErrorCode): string {
-    switch (code) {
-        case jsonParser.ParseErrorCode.CloseBraceExpected:
-            return 'close brace expected'
-        case jsonParser.ParseErrorCode.CloseBracketExpected:
-            return 'close bracket expected'
-        case jsonParser.ParseErrorCode.ColonExpected:
-            return 'colon expected'
-        case jsonParser.ParseErrorCode.CommaExpected:
-            return 'command expected'
-        case jsonParser.ParseErrorCode.EndOfFileExpected:
-            return 'end of file expected'
-        case jsonParser.ParseErrorCode.InvalidCharacter:
-            return 'invalid character'
-        case jsonParser.ParseErrorCode.InvalidCommentToken:
-            return 'invalid comment token'
-        case jsonParser.ParseErrorCode.InvalidEscapeCharacter:
-            return 'invalid escape character'
-        case jsonParser.ParseErrorCode.InvalidNumberFormat:
-            return 'invalid number format'
-        case jsonParser.ParseErrorCode.InvalidSymbol:
-            return 'invalid symbol'
-        case jsonParser.ParseErrorCode.InvalidUnicode:
-            return 'invalid unicode'
-        case jsonParser.ParseErrorCode.PropertyNameExpected:
-            return 'property name expected'
-        case jsonParser.ParseErrorCode.UnexpectedEndOfComment:
-            return 'unexpected end of comment'
-        case jsonParser.ParseErrorCode.UnexpectedEndOfNumber:
-            return 'unexpected end of number'
-        case jsonParser.ParseErrorCode.UnexpectedEndOfString:
-            return 'unexpected end of string'
-        case jsonParser.ParseErrorCode.ValueExpected:
-            return 'value expected'
-        // By omitting the default case, we force the compiler to yell at us
-        // if any enum members are added/removed/changed.
-    }
-}
-
-export class TemplatesConfigFieldTypeError extends Error {
-    public readonly jsonPath: jsonParser.JSONPath
-    public readonly expectedTypes: jsonParser.NodeType[]
-    public readonly actualType: jsonParser.NodeType
-
-    public constructor(params: {
-        message?: string
-        jsonPath: jsonParser.JSONPath
-        expectedTypes: jsonParser.NodeType[]
-        actualType: jsonParser.NodeType
-    }) {
-        super(params.message)
-
-        this.jsonPath = params.jsonPath
-        this.expectedTypes = params.expectedTypes
-        this.actualType = params.actualType
-    }
-}
-
-export class TemplatesConfigPopulator {
-    private isDirty: boolean = false
-
-    public constructor(
-        private json: string,
-        private readonly modificationOptions: jsonParser.ModificationOptions = {
-            formattingOptions: {
-                insertSpaces: true,
-                tabSize: getTabSizeSetting(),
-            },
-        }
-    ) {}
-
-    public ensureTemplateSectionExists(templateRelativePath: string): TemplatesConfigPopulator {
-        this.ensureTemplatesSectionExists()
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath], {})
-
-        return this
-    }
-
-    public ensureTemplateHandlerSectionExists(templateRelativePath: string, handler: string): TemplatesConfigPopulator {
-        this.ensureTemplateHandlersSectionExists(templateRelativePath)
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath, 'handlers', handler], {
-            event: {},
-            environmentVariables: {},
-        })
-
-        return this
-    }
-
-    public ensureTemplateHandlerPropertiesExist(
-        templateRelativePath: string,
-        handler: string
-    ): TemplatesConfigPopulator {
-        this.ensureTemplateHandlerSectionExists(templateRelativePath, handler)
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath, 'handlers', handler, 'event'], {})
-
-        this.ensureJsonPropertyExists(
-            ['templates', templateRelativePath, 'handlers', handler, 'environmentVariables'],
-            {}
-        )
-
-        return this
-    }
-
-    public ensureTemplateParameterOverrideExists(
-        templateRelativePath: string,
-        parameterName: string
-    ): TemplatesConfigPopulator {
-        this.ensureTemplateParamOverrideSectionExists(templateRelativePath)
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath, 'parameterOverrides', parameterName], '', [
-            'string',
-            'null',
-        ])
-
-        return this
-    }
-
-    public getResults(): {
-        isDirty: boolean
-        json: string
-    } {
-        return {
-            isDirty: this.isDirty,
-            json: this.json,
-        }
-    }
-
-    private ensureJsonPropertyExists(
-        jsonPath: jsonParser.JSONPath,
-        value: any,
-        allowedTypes: jsonParser.NodeType[] = ['object', 'null']
-    ) {
-        const root = jsonParser.parseTree(this.json)
-        const node = jsonParser.findNodeAtLocation(root!, jsonPath)
-        const allowedTypesSet = new Set(allowedTypes)
-
-        if (node && !allowedTypesSet.has(node.type)) {
-            throw new TemplatesConfigFieldTypeError({
-                message: 'Invalid configuration',
-                jsonPath: jsonPath,
-                actualType: node.type,
-                expectedTypes: allowedTypes,
-            })
-        }
-
-        if (!node || node.type === 'null') {
-            const edits = jsonParser.modify(this.json, jsonPath, value, this.modificationOptions)
-
-            if (edits.length > 0) {
-                this.json = jsonParser.applyEdits(this.json, edits)
-                this.isDirty = true
-            }
-        }
-    }
-
-    private ensureTemplatesSectionExists(): TemplatesConfigPopulator {
-        this.ensureJsonPropertyExists(['templates'], {})
-
-        return this
-    }
-
-    private ensureTemplateHandlersSectionExists(templateRelativePath: string): TemplatesConfigPopulator {
-        this.ensureTemplateSectionExists(templateRelativePath)
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath, 'handlers'], {})
-
-        return this
-    }
-
-    private ensureTemplateParamOverrideSectionExists(templateRelativePath: string): TemplatesConfigPopulator {
-        this.ensureTemplateSectionExists(templateRelativePath)
-
-        this.ensureJsonPropertyExists(['templates', templateRelativePath, 'parameterOverrides'], {})
-
-        return this
-    }
-}
-
-/**
- * Returns a previously-configured event payload if the following are true (`undefined` if false):
- * * old `.aws/templates.json` file exists and is valid JSON
- * * handler exists in `.aws/templates.json`
- * @param workspaceFolder Current workspace folder
- * @param handler Function's handler
- * @param samTemplate Originating SAM Template
- */
-export async function getExistingConfiguration(
-    workspaceFolder: vscode.WorkspaceFolder,
-    handler: string,
-    samTemplate: vscode.Uri
-): Promise<
-    | {
-          eventJson?: ReadonlyJsonObject
-          environmentVariables?: { [key: string]: string }
-          dockerNetwork?: string
-          useContainer?: boolean
-      }
-    | undefined
-> {
-    const configPath: string = getTemplatesConfigPath(workspaceFolder.uri.fsPath)
-
-    if (await fsUtils.fileExists(configPath)) {
-        const templateRelativePath = getNormalizedRelativePath(workspaceFolder.uri.fsPath, samTemplate.fsPath)
-        try {
-            const json = JSON.parse(await fsUtils.readFileAsString(configPath)) as TemplatesConfig
-            const handlerData = json.templates[templateRelativePath]?.handlers?.[handler]
-            if (handlerData) {
-                return {
-                    eventJson: handlerData.event as any,
-                    environmentVariables: handlerData.environmentVariables,
-                    dockerNetwork: handlerData.dockerNetwork,
-                    useContainer: handlerData.useContainer,
-                }
-            }
-        } catch (e) {
-            // json was not parseable
-            const err = e as Error
-            getLogger().info(`Error parsing the legacy configuration file: ${err.message}`)
-
-            return undefined
-        }
-    }
-
-    return undefined
-}
diff --git a/packages/core/src/lambda/constants.ts b/packages/core/src/lambda/constants.ts
deleted file mode 100644
index 95cca715d2..0000000000
--- a/packages/core/src/lambda/constants.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import { hostedFilesBaseUrl } from '../shared/constants'
-
-export const sampleRequestBase: string = 'LambdaSampleFunctions/SampleRequests'
-export const sampleRequestPath: string = `${hostedFilesBaseUrl}${sampleRequestBase}/`
-export const sampleRequestManifestPath: string = `${hostedFilesBaseUrl}${sampleRequestBase}/manifest.xml`
diff --git a/packages/core/src/lambda/explorer/cloudFormationNodes.ts b/packages/core/src/lambda/explorer/cloudFormationNodes.ts
deleted file mode 100644
index daf49a06a4..0000000000
--- a/packages/core/src/lambda/explorer/cloudFormationNodes.ts
+++ /dev/null
@@ -1,187 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import * as os from 'os'
-import * as vscode from 'vscode'
-import { CloudFormationClient, StackSummary } from '../../shared/clients/cloudFormation'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { AWSCommandTreeNode } from '../../shared/treeview/nodes/awsCommandTreeNode'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { intersection, toArrayAsync, toMap, toMapAsync, updateInPlace } from '../../shared/utilities/collectionUtils'
-import { listCloudFormationStacks, listLambdaFunctions } from '../utils'
-import { LambdaFunctionNode } from './lambdaFunctionNode'
-import { getIcon } from '../../shared/icons'
-
-export const contextValueCloudformationLambdaFunction = 'awsCloudFormationFunctionNode'
-
-export class CloudFormationNode extends AWSTreeNodeBase {
-    private readonly stackNodes: Map
-
-    public constructor(
-        public override readonly regionCode: string,
-        private readonly client = new CloudFormationClient(regionCode)
-    ) {
-        super('CloudFormation', vscode.TreeItemCollapsibleState.Collapsed)
-        this.stackNodes = new Map()
-        this.contextValue = 'awsCloudFormationRootNode'
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-
-                const panelNode = new AWSCommandTreeNode(
-                    this,
-                    '✨ Try the new CloudFormation panel',
-                    'aws.cloudformation.focus',
-                    undefined,
-                    'Open the enhanced CloudFormation panel with improved features'
-                )
-                panelNode.iconPath = getIcon('vscode-star-full')
-
-                return [panelNode, ...this.stackNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () => {
-                const panelNode = new AWSCommandTreeNode(
-                    this,
-                    '✨ Try the new CloudFormation panel',
-                    'aws.cloudformation.focus',
-                    undefined,
-                    'Open the enhanced CloudFormation panel with improved features'
-                )
-                panelNode.iconPath = getIcon('vscode-star-full')
-                return panelNode
-            },
-            sort: (nodeA, nodeB) => {
-                // Keep the panel node at the top
-                if (nodeA instanceof AWSCommandTreeNode) {
-                    return -1
-                }
-                if (nodeB instanceof AWSCommandTreeNode) {
-                    return 1
-                }
-                return nodeA.stackName.localeCompare(nodeB.stackName)
-            },
-        })
-    }
-
-    public async updateChildren(): Promise {
-        const stacks = await toMapAsync(listCloudFormationStacks(this.client), (stack) => stack.StackId)
-
-        updateInPlace(
-            this.stackNodes,
-            stacks.keys(),
-            (key) => this.stackNodes.get(key)!.update(stacks.get(key)!),
-            (key) => new CloudFormationStackNode(this, this.regionCode, stacks.get(key)!)
-        )
-    }
-}
-
-export class CloudFormationStackNode extends AWSTreeNodeBase implements AWSResourceNode {
-    private readonly functionNodes: Map
-
-    public constructor(
-        public readonly parent: AWSTreeNodeBase,
-        public override readonly regionCode: string,
-        private stackSummary: StackSummary,
-        private readonly lambdaClient = new DefaultLambdaClient(regionCode),
-        private readonly cloudformationClient = new CloudFormationClient(regionCode)
-    ) {
-        super('', vscode.TreeItemCollapsibleState.Collapsed)
-
-        this.update(stackSummary)
-        this.contextValue = 'awsCloudFormationNode'
-        this.functionNodes = new Map()
-        this.iconPath = getIcon('aws-cloudformation-stack')
-    }
-
-    public get stackId(): string | undefined {
-        return this.stackSummary.StackId
-    }
-
-    public get arn(): string {
-        if (this.stackId === undefined) {
-            throw new Error('StackId expected but not found')
-        }
-
-        return this.stackId
-    }
-
-    public get name(): string {
-        return this.stackName
-    }
-
-    public get stackName(): string {
-        return this.stackSummary.StackName
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-
-                return [...this.functionNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(
-                    this,
-                    localize('AWS.explorerNode.cloudFormation.noFunctions', '[Stack has no Lambda Functions]')
-                ),
-            sort: (nodeA, nodeB) => nodeA.functionName.localeCompare(nodeB.functionName),
-        })
-    }
-
-    public update(stackSummary: StackSummary): void {
-        this.stackSummary = stackSummary
-        this.label = `${this.stackName} [${stackSummary.StackStatus}]`
-        this.tooltip = `${this.stackName}${os.EOL}${this.stackId}`
-    }
-
-    private async updateChildren(): Promise {
-        const resources: string[] = await this.resolveLambdaResources()
-        const functions: Map = toMap(
-            await toArrayAsync(listLambdaFunctions(this.lambdaClient)),
-            (functionInfo) => functionInfo.FunctionName
-        )
-
-        updateInPlace(
-            this.functionNodes,
-            intersection(resources, functions.keys()),
-            (key) => this.functionNodes.get(key)!.update(functions.get(key)!),
-            (key) => makeCloudFormationLambdaFunctionNode(this, this.regionCode, functions.get(key)!)
-        )
-    }
-
-    private async resolveLambdaResources(): Promise {
-        const response = await this.cloudformationClient.describeStackResources(this.stackSummary.StackName)
-
-        if (response.StackResources) {
-            return response.StackResources.filter((it) => it.ResourceType.includes('Lambda::Function')).map(
-                (it) => it.PhysicalResourceId || 'none'
-            )
-        }
-
-        return []
-    }
-}
-
-function makeCloudFormationLambdaFunctionNode(
-    parent: AWSTreeNodeBase,
-    regionCode: string,
-    configuration: FunctionConfiguration
-): LambdaFunctionNode {
-    const node = new LambdaFunctionNode(parent, regionCode, configuration, contextValueCloudformationLambdaFunction)
-
-    return node
-}
diff --git a/packages/core/src/lambda/explorer/lambdaCapacityProviderNode.ts b/packages/core/src/lambda/explorer/lambdaCapacityProviderNode.ts
deleted file mode 100644
index 17a5562eb1..0000000000
--- a/packages/core/src/lambda/explorer/lambdaCapacityProviderNode.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getIcon } from '../../shared/icons'
-
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import globals from '../../shared/extensionGlobals'
-import { ToolkitError } from '../../shared/errors'
-
-export const contextValueLambdaCapacityProvider = 'awsCapacityProviderNode'
-
-export class LambdaCapacityProviderNode extends AWSTreeNodeBase implements AWSResourceNode {
-    public constructor(
-        public override readonly regionCode: string,
-        public readonly deployedResource: any,
-        public override readonly contextValue?: string
-    ) {
-        super(
-            deployedResource.LogicalResourceId,
-            contextValue === contextValueLambdaCapacityProvider
-                ? vscode.TreeItemCollapsibleState.Collapsed
-                : vscode.TreeItemCollapsibleState.None
-        )
-        this.iconPath = getIcon('vscode-gear')
-        this.contextValue = contextValueLambdaCapacityProvider
-    }
-
-    public get name() {
-        return this.deployedResource.LogicalResourceId
-    }
-    private get accountId(): string {
-        const accountId = globals.awsContext.getCredentialAccountId()
-        if (!accountId) {
-            throw new ToolkitError('Aws account ID not found')
-        }
-        return accountId
-    }
-
-    public get arn() {
-        return `arn:aws:lambda:${this.regionCode}:${this.accountId}:capacity-provider:${this.deployedResource.PhysicalResourceId}`
-    }
-}
diff --git a/packages/core/src/lambda/explorer/lambdaFunctionFileNode.ts b/packages/core/src/lambda/explorer/lambdaFunctionFileNode.ts
deleted file mode 100644
index 422de99b31..0000000000
--- a/packages/core/src/lambda/explorer/lambdaFunctionFileNode.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { LambdaFunctionNode } from './lambdaFunctionNode'
-import { getIcon } from '../../shared/icons'
-import { isCloud9 } from '../../shared/extensionUtilities'
-import { LambdaFunctionFolderNode } from './lambdaFunctionFolderNode'
-
-export class LambdaFunctionFileNode extends AWSTreeNodeBase implements AWSResourceNode {
-    public constructor(
-        public readonly parent: LambdaFunctionNode | LambdaFunctionFolderNode,
-        public readonly filename: string,
-        public readonly path: string
-    ) {
-        super(filename)
-        this.iconPath = getIcon('vscode-file')
-        this.contextValue = 'lambdaFunctionFileNode'
-        this.command = !isCloud9()
-            ? {
-                  command: 'aws.openLambdaFile',
-                  title: 'Open file',
-                  arguments: [path],
-              }
-            : undefined
-    }
-
-    public get arn(): string {
-        return ''
-    }
-
-    public get name(): string {
-        return ''
-    }
-}
diff --git a/packages/core/src/lambda/explorer/lambdaFunctionFolderNode.ts b/packages/core/src/lambda/explorer/lambdaFunctionFolderNode.ts
deleted file mode 100644
index 7c67d48266..0000000000
--- a/packages/core/src/lambda/explorer/lambdaFunctionFolderNode.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { LambdaFunctionNode } from './lambdaFunctionNode'
-import { fs } from '../../shared/fs/fs'
-import { getIcon } from '../../shared/icons'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { localize } from 'vscode-nls'
-import path from 'path'
-import { LambdaFunctionFileNode } from './lambdaFunctionFileNode'
-
-export class LambdaFunctionFolderNode extends AWSTreeNodeBase implements AWSResourceNode {
-    public constructor(
-        public readonly parent: LambdaFunctionNode | LambdaFunctionFolderNode,
-        public readonly filename: string,
-        public readonly path: string
-    ) {
-        super(filename, vscode.TreeItemCollapsibleState.Collapsed)
-        this.iconPath = getIcon('vscode-folder')
-        this.contextValue = 'lambdaFunctionFolderNode'
-    }
-
-    public get arn(): string {
-        return ''
-    }
-
-    public get name(): string {
-        return ''
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => this.loadFunctionFiles(),
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.s3.noObjects', '[No Objects found]')),
-        })
-    }
-
-    public async loadFunctionFiles(): Promise {
-        const nodes: AWSTreeNodeBase[] = []
-        const files = await fs.readdir(this.path)
-        for (const file of files) {
-            const [fileName, type] = file
-            const filePath = path.join(this.path, fileName)
-            if (type === vscode.FileType.Directory) {
-                nodes.push(new LambdaFunctionFolderNode(this, fileName, filePath))
-            } else {
-                nodes.push(new LambdaFunctionFileNode(this, fileName, filePath))
-            }
-        }
-
-        return nodes
-    }
-}
diff --git a/packages/core/src/lambda/explorer/lambdaFunctionNode.ts b/packages/core/src/lambda/explorer/lambdaFunctionNode.ts
deleted file mode 100644
index 3d2f05a99f..0000000000
--- a/packages/core/src/lambda/explorer/lambdaFunctionNode.ts
+++ /dev/null
@@ -1,114 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import * as os from 'os'
-import * as vscode from 'vscode'
-import { getIcon } from '../../shared/icons'
-
-import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode'
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { editLambdaCommand } from '../commands/editLambda'
-import { fs } from '../../shared/fs/fs'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import path from 'path'
-import { localize } from 'vscode-nls'
-import { LambdaFunctionFolderNode } from './lambdaFunctionFolderNode'
-import { LambdaFunctionFileNode } from './lambdaFunctionFileNode'
-
-export const contextValueLambdaFunction = 'awsRegionFunctionNode'
-export const contextValueLambdaFunctionImportable = 'awsRegionFunctionNodeDownloadable'
-// Without "Convert to SAM application"
-export const contextValueLambdaFunctionDownloadOnly = 'awsRegionFunctionNodeDownloadableOnly'
-
-function isLambdaFunctionDownloadable(contextValue?: string): boolean {
-    return (
-        contextValue === contextValueLambdaFunctionImportable || contextValue === contextValueLambdaFunctionDownloadOnly
-    )
-}
-
-export class LambdaFunctionNode extends AWSTreeNodeBase implements AWSResourceNode {
-    public constructor(
-        public readonly parent: AWSTreeNodeBase,
-        public override readonly regionCode: string,
-        public configuration: FunctionConfiguration,
-        public override readonly contextValue?: string,
-        public localDir?: string,
-        public projectRoot?: vscode.Uri,
-        public logicalId?: string
-    ) {
-        super(
-            `${configuration.FunctionArn}`,
-            isLambdaFunctionDownloadable(contextValue)
-                ? vscode.TreeItemCollapsibleState.Collapsed
-                : vscode.TreeItemCollapsibleState.None
-        )
-        this.update(configuration)
-        this.resourceUri = vscode.Uri.from({ scheme: 'lambda', path: `${regionCode}/${configuration.FunctionName}` })
-        this.iconPath = getIcon('aws-lambda-function')
-        this.contextValue = contextValue
-    }
-
-    public update(configuration: FunctionConfiguration): void {
-        this.configuration = configuration
-        this.label = this.configuration.FunctionName || ''
-        this.tooltip = `${this.configuration.FunctionName}${os.EOL}${this.configuration.FunctionArn}`
-        if (this.contextValue === contextValueLambdaFunction) {
-            this.tooltip += `${os.EOL} This function is not downloadable`
-        }
-    }
-
-    public get functionName(): string {
-        return this.configuration.FunctionName || ''
-    }
-
-    public get arn(): string {
-        if (this.configuration.FunctionArn === undefined) {
-            throw new Error('FunctionArn expected but not found')
-        }
-
-        return this.configuration.FunctionArn
-    }
-
-    public get name(): string {
-        if (this.configuration.FunctionName === undefined) {
-            throw new Error('FunctionName expected but not found')
-        }
-
-        return this.configuration.FunctionName
-    }
-
-    public override async getChildren(): Promise {
-        if (!isLambdaFunctionDownloadable(this.contextValue)) {
-            return []
-        }
-
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                const path = await editLambdaCommand(this)
-                return path ? this.loadFunctionFiles(path) : []
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.lambda.noFiles', '[No files found]')),
-        })
-    }
-
-    public async loadFunctionFiles(tmpPath: string): Promise {
-        const nodes: AWSTreeNodeBase[] = []
-        const files = await fs.readdir(tmpPath)
-        for (const file of files) {
-            const [fileName, type] = file
-            const filePath = path.join(tmpPath, fileName)
-            if (type === vscode.FileType.Directory) {
-                nodes.push(new LambdaFunctionFolderNode(this, fileName, filePath))
-            } else {
-                nodes.push(new LambdaFunctionFileNode(this, fileName, filePath))
-            }
-        }
-
-        return nodes
-    }
-}
diff --git a/packages/core/src/lambda/explorer/lambdaFunctionNodeDecorationProvider.ts b/packages/core/src/lambda/explorer/lambdaFunctionNodeDecorationProvider.ts
deleted file mode 100644
index b31de94099..0000000000
--- a/packages/core/src/lambda/explorer/lambdaFunctionNodeDecorationProvider.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as vscode from 'vscode'
-import { fs } from '../../shared/fs/fs'
-import path from 'path'
-import { getFunctionInfo } from '../utils'
-import { LambdaFunction } from '../commands/uploadLambda'
-
-export class LambdaFunctionNodeDecorationProvider implements vscode.FileDecorationProvider {
-    // Make it a singleton so that it's easier to access
-    private static instance: LambdaFunctionNodeDecorationProvider
-    private readonly _onDidChangeFileDecorations = new vscode.EventEmitter()
-    readonly onDidChangeFileDecorations = this._onDidChangeFileDecorations.event
-
-    private constructor() {}
-
-    public static getInstance(): LambdaFunctionNodeDecorationProvider {
-        if (!LambdaFunctionNodeDecorationProvider.instance) {
-            LambdaFunctionNodeDecorationProvider.instance = new LambdaFunctionNodeDecorationProvider()
-        }
-        return LambdaFunctionNodeDecorationProvider.instance
-    }
-
-    async provideFileDecoration(uri: vscode.Uri): Promise {
-        const badge = {
-            badge: 'M',
-            color: new vscode.ThemeColor('gitDecoration.modifiedResourceForeground'),
-            tooltip: 'This function has undeployed changes',
-            propagate: true,
-        }
-
-        if (uri.scheme === 'lambda') {
-            const [region, name] = uri.path.split('/')
-            const lambda: LambdaFunction = { region, name }
-            if (await getFunctionInfo(lambda, 'undeployed')) {
-                badge.propagate = false
-                return badge
-            }
-        } else {
-            try {
-                const lambda = this.getLambdaFromPath(uri)
-                if (lambda && (await this.isFileModifiedAfterDeployment(uri.fsPath, lambda))) {
-                    return badge
-                }
-            } catch {
-                return undefined
-            }
-        }
-    }
-
-    public async addBadge(fileUri: vscode.Uri, functionUri: vscode.Uri) {
-        this._onDidChangeFileDecorations.fire(vscode.Uri.file(fileUri.fsPath))
-        this._onDidChangeFileDecorations.fire(functionUri)
-    }
-
-    public async removeBadge(fileUri: vscode.Uri, functionUri: vscode.Uri) {
-        // We need to propagate the badge removal down to all files in the dir
-        for (const path of await this.getFilePaths(fileUri.fsPath)) {
-            const subUri = vscode.Uri.file(path)
-            this._onDidChangeFileDecorations.fire(subUri)
-        }
-        this._onDidChangeFileDecorations.fire(functionUri)
-    }
-
-    private async getFilePaths(basePath: string) {
-        const files = await fs.readdir(basePath)
-        const subFiles: string[] = [basePath]
-        for (const file of files) {
-            const [fileName, type] = file
-            const filePath = path.join(basePath, fileName)
-            if (type === vscode.FileType.Directory) {
-                subFiles.push(...(await this.getFilePaths(filePath)))
-            } else {
-                subFiles.push(filePath)
-            }
-        }
-
-        return subFiles
-    }
-
-    private getLambdaFromPath(uri: vscode.Uri): LambdaFunction {
-        const pathParts = uri.fsPath.split(path.sep)
-        const lambdaIndex = pathParts.indexOf('lambda')
-        if (lambdaIndex === -1 || lambdaIndex + 2 >= pathParts.length) {
-            throw new Error('Invalid path')
-        }
-        const region = pathParts[lambdaIndex + 1]
-        const name = pathParts[lambdaIndex + 2]
-        return { region, name }
-    }
-
-    private async isFileModifiedAfterDeployment(filePath: string, lambda: LambdaFunction): Promise {
-        try {
-            const { lastDeployed, undeployed } = await getFunctionInfo(lambda)
-            if (!lastDeployed || !undeployed) {
-                return false
-            }
-
-            const fileStat = await fs.stat(filePath)
-            return fileStat.mtime > lastDeployed
-        } catch {
-            return false
-        }
-    }
-}
diff --git a/packages/core/src/lambda/explorer/lambdaNodes.ts b/packages/core/src/lambda/explorer/lambdaNodes.ts
deleted file mode 100644
index dd753ce559..0000000000
--- a/packages/core/src/lambda/explorer/lambdaNodes.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import * as vscode from 'vscode'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-
-import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase'
-import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode'
-import { makeChildrenNodes } from '../../shared/treeview/utils'
-import { toArrayAsync, toMap, updateInPlace } from '../../shared/utilities/collectionUtils'
-import { listLambdaFunctions, isHotReloadingFunction } from '../utils'
-import {
-    contextValueLambdaFunction,
-    contextValueLambdaFunctionImportable,
-    contextValueLambdaFunctionDownloadOnly,
-    LambdaFunctionNode,
-} from './lambdaFunctionNode'
-import { samLambdaImportableRuntimes } from '../models/samLambdaRuntime'
-import { isLocalStackConnection } from '../../auth/utils'
-
-/**
- * An AWS Explorer node representing the Lambda Service.
- * Contains Lambda Functions for a specific region as child nodes.
- */
-export class LambdaNode extends AWSTreeNodeBase {
-    private readonly functionNodes: Map
-
-    public constructor(
-        public override readonly regionCode: string,
-        private readonly client = new DefaultLambdaClient(regionCode)
-    ) {
-        super('Lambda', vscode.TreeItemCollapsibleState.Collapsed)
-        this.functionNodes = new Map()
-        this.contextValue = 'awsLambdaNode'
-    }
-
-    public override async getChildren(): Promise {
-        return await makeChildrenNodes({
-            getChildNodes: async () => {
-                await this.updateChildren()
-
-                return [...this.functionNodes.values()]
-            },
-            getNoChildrenPlaceholderNode: async () =>
-                new PlaceholderNode(this, localize('AWS.explorerNode.lambda.noFunctions', '[No Functions found]')),
-            sort: (nodeA, nodeB) => nodeA.functionName.localeCompare(nodeB.functionName),
-        })
-    }
-
-    public async updateChildren(): Promise {
-        const functions: Map = toMap(
-            await toArrayAsync(listLambdaFunctions(this.client)),
-            (configuration) => configuration.FunctionName
-        )
-
-        updateInPlace(
-            this.functionNodes,
-            functions.keys(),
-            (key) => this.functionNodes.get(key)!.update(functions.get(key)!),
-            (key) => makeLambdaFunctionNode(this, this.regionCode, functions.get(key)!)
-        )
-    }
-}
-
-function makeLambdaFunctionNode(
-    parent: AWSTreeNodeBase,
-    regionCode: string,
-    configuration: FunctionConfiguration
-): LambdaFunctionNode {
-    let contextValue = contextValueLambdaFunction
-    const isImportableRuntime = configuration.Runtime && samLambdaImportableRuntimes.contains(configuration.Runtime)
-    if (isLocalStackConnection()) {
-        if (isImportableRuntime && !isHotReloadingFunction(configuration?.CodeSha256)) {
-            contextValue = contextValueLambdaFunctionDownloadOnly
-        }
-    } else if (isImportableRuntime) {
-        contextValue = contextValueLambdaFunctionImportable
-    }
-    const node = new LambdaFunctionNode(parent, regionCode, configuration, contextValue)
-
-    return node
-}
diff --git a/packages/core/src/lambda/local/debugConfiguration.ts b/packages/core/src/lambda/local/debugConfiguration.ts
deleted file mode 100644
index bfccf03b07..0000000000
--- a/packages/core/src/lambda/local/debugConfiguration.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as CloudFormation from '../../shared/cloudformation/cloudformation'
-import {
-    AwsSamDebuggerConfiguration,
-    awsSamDebugTargetTypes,
-    CodeTargetProperties,
-    TemplateTargetProperties,
-} from '../../shared/sam/debugger/awsSamDebugConfiguration'
-import * as pathutil from '../../shared/utilities/pathUtils'
-import { localize } from '../../shared/utilities/vsCodeUtils'
-import { tryGetAbsolutePath } from '../../shared/utilities/workspaceUtils'
-import { Architecture, RuntimeFamily } from '../models/samLambdaRuntime'
-import { SamLaunchRequestArgs } from '../../shared/sam/debugger/awsSamDebugger'
-
-import { getLogger } from '../../shared/logger/logger'
-import globals from '../../shared/extensionGlobals'
-
-/**
- * Magic path on the Docker image.
- * https://github.com/aws/aws-sam-cli/blob/2201b17bff0a438b934abbb53f6c76eff9ccfa6d/samcli/local/docker/lambda_container.py#L25
- */
-export const dotnetDebuggerPath = '/tmp/lambci_debug_files/vsdbg'
-export const goDebuggerPath = '/tmp/lambci_debug_files'
-
-export interface NodejsDebugConfiguration extends SamLaunchRequestArgs {
-    readonly runtimeFamily: RuntimeFamily.NodeJS
-    readonly preLaunchTask?: string
-    readonly address: 'localhost'
-    readonly localRoot: string
-    readonly remoteRoot: string
-    readonly skipFiles?: string[]
-    readonly port: number
-}
-
-export interface PythonPathMapping {
-    localRoot: string
-    remoteRoot: string
-}
-
-export interface PythonDebugConfiguration extends SamLaunchRequestArgs {
-    readonly runtimeFamily: RuntimeFamily.Python
-    /** Passed to "sam build --manifest …" */
-    readonly manifestPath: string | undefined
-
-    // Fields expected by the VSCode debugpy adapter.
-    readonly host: string
-    readonly port: number
-    readonly pathMappings: PythonPathMapping[]
-}
-
-export interface DotNetDebugConfiguration extends SamLaunchRequestArgs {
-    readonly runtimeFamily: RuntimeFamily.DotNet
-    processName: string
-    pipeTransport: PipeTransport
-    windows: {
-        pipeTransport: PipeTransport
-    }
-    sourceFileMap?: {
-        [key: string]: string
-    }
-}
-
-export interface GoDebugConfiguration extends SamLaunchRequestArgs {
-    readonly runtimeFamily: RuntimeFamily.Go
-    readonly preLaunchTask?: string
-    readonly host: 'localhost'
-    readonly port: number
-}
-
-export interface PipeTransport {
-    pipeProgram: 'sh' | 'powershell'
-    pipeArgs: string[]
-    debuggerPath: typeof dotnetDebuggerPath
-    pipeCwd: string
-}
-
-/**
- * Gets the "code root" as an absolute path.
- *
- * - For "code" configs this is the `projectRoot` field.
- * - For "template" configs this is the `CodeUri` field in the template.
- */
-export async function getCodeRoot(
-    folder: vscode.WorkspaceFolder | undefined,
-    config: AwsSamDebuggerConfiguration
-): Promise {
-    switch (config.invokeTarget.target) {
-        case 'code': {
-            const codeInvoke = config.invokeTarget as CodeTargetProperties
-            return pathutil.normalize(tryGetAbsolutePath(folder, codeInvoke.projectRoot))
-        }
-        case 'api':
-        case 'template': {
-            const templateInvoke = config.invokeTarget as TemplateTargetProperties
-            const template = await getTemplate(folder, config)
-            if (!template) {
-                return undefined
-            }
-            const templateResource = await getTemplateResource(folder, config)
-            if (!templateResource?.Properties) {
-                return undefined
-            }
-            const fullPath = tryGetAbsolutePath(folder, templateInvoke.templatePath)
-            const templateDir = path.dirname(fullPath)
-            // Image lambda or ZIP lambda?
-            const isImageLambda = CloudFormation.isImageLambdaResource(templateResource.Properties)
-            const uri = isImageLambda
-                ? CloudFormation.getStringForProperty(templateResource?.Metadata, 'DockerContext', template)
-                : CloudFormation.getStringForProperty(
-                      templateResource.Properties as CloudFormation.ZipResourceProperties,
-                      'CodeUri',
-                      template
-                  )
-            return uri !== undefined ? pathutil.normalize(path.resolve(templateDir ?? '', uri)) : undefined
-        }
-        default: {
-            throw Error('invalid invokeTarget') // Must not happen.
-        }
-    }
-}
-
-/**
- * Gets the lambda handler name specified in the given config.
- */
-export async function getHandlerName(
-    folder: vscode.WorkspaceFolder | undefined,
-    config: AwsSamDebuggerConfiguration
-): Promise {
-    switch (config.invokeTarget.target) {
-        case 'code': {
-            const codeInvoke = config.invokeTarget as CodeTargetProperties
-            return codeInvoke.lambdaHandler
-        }
-        case 'api':
-        case 'template': {
-            const template = await getTemplate(folder, config)
-            if (!template) {
-                return ''
-            }
-            const templateResource = await getTemplateResource(folder, config)
-            if (CloudFormation.isImageLambdaResource(templateResource?.Properties)) {
-                return config.invokeTarget.logicalId
-            }
-
-            const propertyValue = CloudFormation.resolvePropertyWithOverrides(
-                templateResource?.Properties?.Handler,
-                template,
-                config.sam?.template?.parameters
-            )
-            return propertyValue ? propertyValue.toString() : ''
-        }
-        default: {
-            // Should never happen.
-            void vscode.window.showErrorMessage(
-                localize(
-                    'AWS.sam.debugger.invalidTarget',
-                    'Debug Configuration has an unsupported target type. Supported types: {0}',
-                    awsSamDebugTargetTypes.join(', ')
-                )
-            )
-            return ''
-        }
-    }
-}
-
-/** Gets a template object from the given config. */
-export async function getTemplate(
-    folder: vscode.WorkspaceFolder | undefined,
-    config: AwsSamDebuggerConfiguration
-): Promise {
-    if (!['api', 'template'].includes(config.invokeTarget.target)) {
-        return undefined
-    }
-    const templateInvoke = config.invokeTarget as TemplateTargetProperties
-    const fullPath = tryGetAbsolutePath(folder, templateInvoke.templatePath)
-    const cfnTemplate = (await globals.templateRegistry).getItem(fullPath)?.item
-    return cfnTemplate
-}
-
-/**
- * Gets the template resources object specified by the `logicalId`
- * field (if the config has `target=template` or `target=api`).
- */
-export async function getTemplateResource(
-    folder: vscode.WorkspaceFolder | undefined,
-    config: AwsSamDebuggerConfiguration
-): Promise {
-    if (!['api', 'template'].includes(config.invokeTarget.target)) {
-        return undefined
-    }
-    const templateInvoke = config.invokeTarget as TemplateTargetProperties
-    const cfnTemplate = await getTemplate(folder, config)
-    if (!cfnTemplate) {
-        throw Error(`template not found (not registered?): ${templateInvoke.templatePath}`)
-    }
-    if (!cfnTemplate?.Resources) {
-        throw Error(`no Resources in template: ${templateInvoke.templatePath}`)
-    }
-    const templateResource: CloudFormation.Resource | undefined = cfnTemplate?.Resources![templateInvoke.logicalId!]
-    if (!templateResource) {
-        throw Error(
-            `template Resources object does not contain key '${templateInvoke.logicalId}':` +
-                ` ${JSON.stringify(cfnTemplate?.Resources)}`
-        )
-    }
-    return templateResource
-}
-
-/**
- * Checks if the current configuration is based on an Image-based template.
- *
- * Intended for use only by the language-specific `makeConfig` functions.
- */
-export async function isImageLambdaConfig(config: SamLaunchRequestArgs): Promise {
-    const templateResource = await getTemplateResource(config.workspaceFolder, config)
-
-    return CloudFormation.isImageLambdaResource(templateResource?.Properties)
-}
-
-export function getArchitecture(
-    template: CloudFormation.Template | undefined,
-    resource: CloudFormation.Resource | undefined,
-    invokeTarget: AwsSamDebuggerConfiguration['invokeTarget']
-): Architecture | undefined {
-    let arch: string | undefined
-    let architectureLocation: string
-    if (template) {
-        arch = (CloudFormation.getArrayForProperty(resource?.Properties, 'Architectures', template) ?? [])[0]
-        architectureLocation = localize('AWS.generic.template', 'template file')
-    } else {
-        arch = (invokeTarget as CodeTargetProperties)?.architecture
-        architectureLocation = localize('AWS.generic.launchConfig', 'launch configuration')
-    }
-
-    if (arch) {
-        const isArch = isArchitecture(arch)
-
-        if (!isArch) {
-            getLogger().warn('SAM Invoke: Invalid architecture. Defaulting to x86_64.')
-            void vscode.window.showWarningMessage(
-                localize(
-                    'AWS.output.sam.invalidArchitecture',
-                    'Invalid architecture specified in {0}. Defaulting to x86_64 architecture for invocation.',
-                    architectureLocation
-                )
-            )
-            arch = 'x86_64'
-        }
-
-        return arch as Architecture
-    }
-}
-
-function isArchitecture(a: any): a is Architecture {
-    return ['x86_64', 'arm64'].includes(a)
-}
diff --git a/packages/core/src/lambda/models/samLambdaRuntime.ts b/packages/core/src/lambda/models/samLambdaRuntime.ts
deleted file mode 100644
index 4c7ea9be59..0000000000
--- a/packages/core/src/lambda/models/samLambdaRuntime.ts
+++ /dev/null
@@ -1,327 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as vscode from 'vscode'
-import { Runtime } from '@aws-sdk/client-lambda'
-import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable'
-import { isCloud9 } from '../../shared/extensionUtilities'
-import { PrompterButtons } from '../../shared/ui/buttons'
-import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../shared/ui/pickerPrompter'
-import { supportedLambdaRuntimesUrl } from '../../shared/constants'
-import { openUrl } from '../../shared/utilities/vsCodeUtils'
-
-export enum RuntimeFamily {
-    Unknown,
-    Python,
-    NodeJS,
-    DotNet,
-    Go,
-    Java,
-    Ruby,
-}
-
-export type RuntimePackageType = 'Image' | 'Zip'
-
-// TODO: Consolidate all of the runtime constructs into a single > map
-//       We should be able to eliminate a fair amount of redundancy with that.
-export const nodeJsRuntimes: ImmutableSet = ImmutableSet([
-    'nodejs24.x' as Runtime,
-    'nodejs22.x' as Runtime,
-    'nodejs20.x',
-    'nodejs18.x',
-    'nodejs16.x',
-    'nodejs14.x',
-])
-export function getNodeMajorVersion(version?: string): number | undefined {
-    if (!version) {
-        return undefined
-    }
-
-    const match = version.match(/^nodejs(\d+)\./)
-
-    if (match) {
-        return Number(match[1])
-    } else {
-        return undefined
-    }
-}
-
-export const pythonRuntimes: ImmutableSet = ImmutableSet([
-    'python3.14' as Runtime,
-    'python3.13' as Runtime,
-    'python3.12',
-    'python3.11',
-    'python3.10',
-    'python3.9',
-    'python3.8',
-    'python3.7',
-])
-export const goRuntimes: ImmutableSet = ImmutableSet(['go1.x'])
-export const javaRuntimes: ImmutableSet = ImmutableSet([
-    'java17',
-    'java11',
-    'java8',
-    'java8.al2',
-    'java21',
-    'java25' as Runtime,
-])
-export const dotNetRuntimes: ImmutableSet = ImmutableSet(['dotnet6', 'dotnet8'])
-export const rubyRuntimes: ImmutableSet = ImmutableSet(['ruby3.2', 'ruby3.3', 'ruby3.4' as Runtime])
-
-// Image runtimes are not a direct subset of valid ZIP lambda types
-const dotnet50 = 'dotnet5.0' as Runtime
-
-/**
- * Deprecated runtimes can be found at https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html
- * (or whatever shared/constants.supportedLambdaRuntimesUrl is pointing to)
- * Add runtimes as they enter Phase 2 deprecation (updating existing functions blocked)
- * Don't add unsupported languages for now (e.g. ruby25): no point in telling a user they're deprecated and then telling them we have no support after they update.
- */
-export const deprecatedRuntimes: ImmutableSet = ImmutableSet([
-    'dotnetcore1.0',
-    'dotnetcore2.0',
-    'python2.7',
-    'nodejs',
-    'nodejs4.3',
-    'nodejs4.3-edge',
-    'nodejs6.10',
-    'nodejs8.10',
-    'nodejs10.x',
-    'nodejs12.x',
-    'ruby2.5',
-    'ruby2.7',
-])
-const defaultRuntimes = ImmutableMap([
-    [RuntimeFamily.NodeJS, 'nodejs24.x' as Runtime],
-    [RuntimeFamily.Python, 'python3.14' as Runtime],
-    [RuntimeFamily.DotNet, 'dotnet8'],
-    [RuntimeFamily.Go, 'go1.x'],
-    [RuntimeFamily.Java, 'java25' as Runtime],
-    [RuntimeFamily.Ruby, 'ruby3.4' as Runtime],
-])
-
-export const mapFamilyToDebugType = ImmutableMap([
-    [RuntimeFamily.NodeJS, 'node'],
-    [RuntimeFamily.Python, 'python'],
-    [RuntimeFamily.DotNet, 'csharp'],
-    [RuntimeFamily.Go, 'go'],
-    [RuntimeFamily.Java, 'java'],
-    [RuntimeFamily.Ruby, 'ruby'],
-    [RuntimeFamily.Unknown, 'unknown'],
-])
-
-export const samZipLambdaRuntimes: ImmutableSet = ImmutableSet.union([
-    nodeJsRuntimes,
-    pythonRuntimes,
-    dotNetRuntimes,
-    goRuntimes,
-    javaRuntimes,
-])
-
-export const samArmLambdaRuntimes: ImmutableSet = ImmutableSet([
-    'python3.9',
-    'python3.8',
-    'nodejs22.x' as Runtime,
-    'nodejs20.x',
-    'nodejs18.x',
-    'nodejs16.x',
-    'nodejs14.x',
-    'java17',
-    'java11',
-    'java8.al2',
-])
-
-// Cloud9 supports a subset of runtimes for debugging.
-// * .NET is not supported
-const cloud9SupportedRuntimes: ImmutableSet = ImmutableSet.union([nodeJsRuntimes, pythonRuntimes])
-
-// only interpreted languages are importable as compiled languages won't provide a useful artifact for editing.
-export const samLambdaImportableRuntimes: ImmutableSet = ImmutableSet.union([
-    nodeJsRuntimes,
-    pythonRuntimes,
-    rubyRuntimes,
-])
-
-export function samLambdaCreatableRuntimes(cloud9: boolean = isCloud9()): ImmutableSet {
-    return cloud9 ? cloud9SupportedRuntimes : samZipLambdaRuntimes
-}
-
-export function samImageLambdaRuntimes(cloud9: boolean = isCloud9()): ImmutableSet {
-    // Note: SAM also supports ruby, but Toolkit does not.
-    return ImmutableSet([...samLambdaCreatableRuntimes(cloud9), ...(cloud9 ? [] : [dotnet50])])
-}
-
-export type DependencyManager = 'cli-package' | 'mod' | 'gradle' | 'pip' | 'npm' | 'maven' | 'bundler'
-export type Architecture = 'x86_64' | 'arm64'
-
-export function getDependencyManager(runtime: Runtime): DependencyManager[] {
-    if (deprecatedRuntimes.has(runtime)) {
-        handleDeprecatedRuntime(runtime)
-    } else if (nodeJsRuntimes.has(runtime)) {
-        return ['npm']
-    } else if (pythonRuntimes.has(runtime)) {
-        return ['pip']
-    } else if (dotNetRuntimes.has(runtime) || runtime === dotnet50) {
-        return ['cli-package']
-    } else if (goRuntimes.has(runtime)) {
-        return ['mod']
-    } else if (javaRuntimes.has(runtime)) {
-        return ['gradle', 'maven']
-    }
-    throw new Error(`Runtime ${runtime} does not have an associated DependencyManager`)
-}
-
-export function getFamily(runtime: Runtime): RuntimeFamily {
-    if (deprecatedRuntimes.has(runtime)) {
-        handleDeprecatedRuntime(runtime)
-    } else if (nodeJsRuntimes.has(runtime)) {
-        return RuntimeFamily.NodeJS
-    } else if (pythonRuntimes.has(runtime)) {
-        return RuntimeFamily.Python
-    } else if (dotNetRuntimes.has(runtime) || runtime === dotnet50) {
-        return RuntimeFamily.DotNet
-    } else if (goRuntimes.has(runtime)) {
-        return RuntimeFamily.Go
-    } else if (javaRuntimes.has(runtime)) {
-        return RuntimeFamily.Java
-    } else if (rubyRuntimes.has(runtime)) {
-        return RuntimeFamily.Ruby
-    }
-    return RuntimeFamily.Unknown
-}
-
-function handleDeprecatedRuntime(runtime: Runtime) {
-    const moreInfo = localize('AWS.generic.message.learnMore', 'Learn More')
-    void vscode.window
-        .showErrorMessage(
-            localize(
-                'AWS.samcli.deprecatedRuntime',
-                'Runtime {0} has been deprecated. Update to a currently-supported runtime.',
-                runtime
-            ),
-            moreInfo
-        )
-        .then((button) => {
-            if (button === moreInfo) {
-                void openUrl(vscode.Uri.parse(supportedLambdaRuntimesUrl))
-            }
-        })
-    throw new Error(`Runtime ${runtime} is deprecated, see: ${supportedLambdaRuntimesUrl}`)
-}
-
-/**
- * Sorts runtimes from lowest value to greatest value, helpful for outputting alphabetized lists of runtimes
- * Differs from normal sorting as it numbers into account: e.g. nodeJs8.10 < nodeJs10.x
- */
-export function compareSamLambdaRuntime(a: string, b: string): number {
-    return a.localeCompare(b, 'en', { numeric: true, ignorePunctuation: true })
-}
-
-function extractAndCompareRuntime(a: vscode.QuickPickItem, b: vscode.QuickPickItem): number {
-    return compareSamLambdaRuntime(a.label, b.label)
-}
-
-/**
- * Maps vscode document languageId to `RuntimeFamily`.
- */
-export function getRuntimeFamily(langId: string): RuntimeFamily {
-    switch (langId) {
-        case 'typescript':
-        case 'javascript':
-            return RuntimeFamily.NodeJS
-        case 'csharp':
-            return RuntimeFamily.DotNet
-        case 'python':
-            return RuntimeFamily.Python
-        case 'go':
-            return RuntimeFamily.Go
-        case 'java':
-            return RuntimeFamily.Java
-        case 'ruby':
-            return RuntimeFamily.Ruby
-        default:
-            return RuntimeFamily.Unknown
-    }
-}
-
-/**
- * Provides the default runtime for a given `RuntimeFamily` or undefined if the runtime is invalid.
- */
-export function getDefaultRuntime(runtime: RuntimeFamily): Runtime | undefined {
-    return defaultRuntimes.get(runtime)
-}
-
-/**
- * Returns a set of runtimes for a specified runtime family or undefined if not found.
- * @param family Runtime family to get runtimes for
- */
-function getRuntimesForFamily(family: RuntimeFamily): ImmutableSet | undefined {
-    switch (family) {
-        case RuntimeFamily.NodeJS:
-            return nodeJsRuntimes
-        case RuntimeFamily.Python:
-            return pythonRuntimes
-        case RuntimeFamily.DotNet:
-            return dotNetRuntimes
-        case RuntimeFamily.Go:
-            return goRuntimes
-        case RuntimeFamily.Java:
-            return javaRuntimes
-        default:
-            return undefined
-    }
-}
-
-export interface RuntimeAndPackage {
-    packageType: RuntimePackageType
-    runtime: Runtime
-}
-
-/**
- * Creates a quick pick for a Runtime with the following parameters (all optional)
- * @param {Object} params Optional parameters for creating a QuickPick for runtimes:
- * @param {vscode.QuickInputButton[]} params.buttons Array of buttons to add to the quick pick;
- * @param {RuntimeFamily} params.runtimeFamily RuntimeFamily that will define the list of runtimes to show (default: samLambdaCreatableRuntimes)
- */
-export function createRuntimeQuickPick(params: {
-    showImageRuntimes: boolean
-    buttons?: PrompterButtons
-    runtimeFamily?: RuntimeFamily
-    step?: number
-    totalSteps?: number
-}): QuickPickPrompter {
-    const zipRuntimes = params.runtimeFamily
-        ? (getRuntimesForFamily(params.runtimeFamily) ?? samLambdaCreatableRuntimes())
-        : samLambdaCreatableRuntimes()
-
-    const zipRuntimeItems = zipRuntimes
-        // remove uncreatable runtimes
-        .filter((value) => samLambdaCreatableRuntimes().has(value))
-        .toArray()
-        .map((runtime) => ({
-            data: { runtime, packageType: 'Zip' } as RuntimeAndPackage,
-            label: runtime,
-        }))
-
-    // internally, after init there is essentially no difference between a ZIP and Image runtime;
-    // behavior is keyed off of what is specified in the cloudformation template
-    let imageRuntimeItems: DataQuickPickItem[] = []
-    if (params.showImageRuntimes) {
-        imageRuntimeItems = samImageLambdaRuntimes()
-            .map((runtime) => ({
-                data: { runtime, packageType: 'Image' } as RuntimeAndPackage,
-                label: `${runtime} (Image)`,
-            }))
-            .toArray()
-    }
-
-    return createQuickPick([...zipRuntimeItems, ...imageRuntimeItems].sort(extractAndCompareRuntime), {
-        title: localize('AWS.samcli.initWizard.runtime.prompt', 'Select a SAM Application Runtime'),
-        buttons: params.buttons ?? [],
-    })
-}
diff --git a/packages/core/src/lambda/models/samTemplates.ts b/packages/core/src/lambda/models/samTemplates.ts
deleted file mode 100644
index ace9c93faf..0000000000
--- a/packages/core/src/lambda/models/samTemplates.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import * as semver from 'semver'
-import { Runtime } from '@aws-sdk/client-lambda'
-import { Set as ImmutableSet } from 'immutable'
-import { supportsEventBridgeTemplates } from '../../../src/eventSchemas/models/schemaCodeLangs'
-import { RuntimePackageType } from './samLambdaRuntime'
-import { getIdeProperties } from '../../shared/extensionUtilities'
-
-export const helloWorldTemplate = localize(
-    'AWS.samcli.initWizard.template.helloWorld.name',
-    '{0} SAM Hello World',
-    getIdeProperties().company
-)
-export const eventBridgeHelloWorldTemplate = localize(
-    'AWS.samcli.initWizard.template.helloWorld.name',
-    '{0} SAM EventBridge Hello World',
-    getIdeProperties().company
-)
-export const eventBridgeStarterAppTemplate = localize(
-    'AWS.samcli.initWizard.template.helloWorld.name',
-    '{0} SAM EventBridge App from Scratch',
-    getIdeProperties().company
-)
-export const stepFunctionsSampleApp = localize(
-    'AWS.samcli.initWizard.template.helloWorld.name',
-    '{0} Step Functions Sample App',
-    getIdeProperties().company
-)
-export const typeScriptBackendTemplate = 'App Backend using TypeScript'
-export const repromptUserForTemplate = 'REQUIRES_AWS_CREDENTIALS_REPROMPT_USER_FOR_TEMPLATE'
-
-export const cliVersionStepFunctionsTemplate = '0.52.0'
-
-export type SamTemplate = string
-
-export function getSamTemplateWizardOption(
-    runtime: Runtime,
-    packageType: RuntimePackageType,
-    samCliVersion: string
-): ImmutableSet {
-    const templateOptions = Array(helloWorldTemplate)
-
-    if (packageType === 'Image') {
-        // only supports hello world for now
-        return ImmutableSet(templateOptions)
-    }
-
-    if (supportsEventBridgeTemplates(runtime)) {
-        templateOptions.push(eventBridgeHelloWorldTemplate, eventBridgeStarterAppTemplate)
-    }
-
-    if (supportsStepFuntionsTemplate(samCliVersion)) {
-        templateOptions.push(stepFunctionsSampleApp)
-    }
-
-    if (supportsTypeScriptBackendTemplate(runtime)) {
-        templateOptions.push(typeScriptBackendTemplate)
-    }
-
-    return ImmutableSet(templateOptions)
-}
-
-export function getSamCliTemplateParameter(templateSelected: SamTemplate): string {
-    switch (templateSelected) {
-        case helloWorldTemplate:
-            return 'hello-world'
-        case eventBridgeHelloWorldTemplate:
-            return 'eventBridge-hello-world'
-        case eventBridgeStarterAppTemplate:
-            return 'eventBridge-schema-app'
-        case stepFunctionsSampleApp:
-            return 'step-functions-sample-app'
-        case typeScriptBackendTemplate:
-            return 'quick-start-typescript-app'
-        default:
-            throw new Error(`${templateSelected} is not valid sam template`)
-    }
-}
-
-export function getTemplateDescription(template: SamTemplate): string {
-    switch (template) {
-        case helloWorldTemplate:
-            return localize('AWS.samcli.initWizard.template.helloWorld.description', 'A basic SAM app')
-        case eventBridgeHelloWorldTemplate:
-            return localize(
-                'AWS.samcli.initWizard.template.eventBridge_helloWorld.description',
-                'Invokes a Lambda for every EC2 instance state change in your account'
-            )
-        case eventBridgeStarterAppTemplate:
-            return localize(
-                'AWS.samcli.initWizard.template.eventBridge_starterApp.description',
-                'Invokes a Lambda based on a dynamic event trigger for an EventBridge Schema of your choice'
-            )
-        case stepFunctionsSampleApp:
-            return localize(
-                'AWS.samcli.initWizard.template.stepFunctionsSampleApp.description',
-                'Orchestrates multiple Lambdas to execute a stock trading workflow on an hourly schedule'
-            )
-        case typeScriptBackendTemplate:
-            return localize(
-                'AWS.samcli.initWizard.template.typeScriptBackendTemplate.description',
-                'A sample TypeScript backend app with Lambda and DynamoDB'
-            )
-        default:
-            throw new Error(`No description found for template ${template}`)
-    }
-}
-
-export function supportsStepFuntionsTemplate(samCliVersion: string): boolean {
-    if (!samCliVersion) {
-        return false
-    }
-    return semver.gte(samCliVersion, cliVersionStepFunctionsTemplate)
-}
-
-export function supportsTypeScriptBackendTemplate(runtime: Runtime): boolean {
-    return runtime === 'nodejs16.x'
-}
diff --git a/packages/core/src/lambda/models/sampleRequest.ts b/packages/core/src/lambda/models/sampleRequest.ts
deleted file mode 100644
index 7e90cf631e..0000000000
--- a/packages/core/src/lambda/models/sampleRequest.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export class SampleRequest {
-    public name: string | undefined
-    public filename: string | undefined
-}
diff --git a/packages/core/src/lambda/remoteDebugging/lambdaDebugger.ts b/packages/core/src/lambda/remoteDebugging/lambdaDebugger.ts
deleted file mode 100644
index 152d591373..0000000000
--- a/packages/core/src/lambda/remoteDebugging/lambdaDebugger.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import globals from '../../shared/extensionGlobals'
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import { getLogger } from '../../shared/logger/logger'
-
-const logger = getLogger()
-
-export const remoteDebugSnapshotString = 'aws.lambda.remoteDebugSnapshot'
-
-export interface DebugConfig {
-    functionArn: string
-    functionName: string
-    port: number | undefined
-    localRoot: string
-    remoteRoot: string
-    skipFiles: string[]
-    shouldPublishVersion: boolean
-    lambdaRuntime?: string // Lambda runtime (e.g., nodejs18.x)
-    debuggerRuntime?: string // VS Code debugger runtime (e.g., node)
-    outFiles?: string[]
-    sourceMap?: boolean
-    justMyCode?: boolean
-    projectName?: string
-    otherDebugParams?: string
-    lambdaTimeout?: number
-    layerArn?: string
-    handlerFile?: string
-    samFunctionLogicalId?: string // SAM function logical ID for auto-detecting outFiles
-    samProjectRoot?: vscode.Uri // SAM project root for auto-detecting outFiles
-    isLambdaRemote: boolean // false if LocalStack connection
-}
-
-/**
- * Interface for debugging AWS Lambda functions remotely.
- *
- * This interface defines the contract for implementing remote debugging
- * for Lambda functions.
- *
- * Implementations of this interface handle the lifecycle of remote debugging sessions,
- * including checking health, set up, necessary deployment, and later clean up
- */
-export interface LambdaDebugger {
-    checkHealth(): Promise
-    setup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise
-    waitForSetup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise
-    waitForFunctionUpdates(progress: vscode.Progress<{ message?: string; increment?: number }>): Promise
-    cleanup(functionConfig: FunctionConfiguration): Promise
-}
-
-// this should be called when the debug session is started
-export async function persistLambdaSnapshot(config: FunctionConfiguration | undefined): Promise {
-    try {
-        await globals.globalState.update(remoteDebugSnapshotString, config)
-    } catch (error) {
-        // TODO raise toolkit error
-        logger.error(`Error persisting debug sessions: ${error}`)
-    }
-}
-
-export function getLambdaSnapshot(): FunctionConfiguration | undefined {
-    return globals.globalState.get(remoteDebugSnapshotString)
-}
diff --git a/packages/core/src/lambda/remoteDebugging/ldkClient.ts b/packages/core/src/lambda/remoteDebugging/ldkClient.ts
deleted file mode 100644
index 43360506ed..0000000000
--- a/packages/core/src/lambda/remoteDebugging/ldkClient.ts
+++ /dev/null
@@ -1,481 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import {
-    CloseTunnelCommand,
-    IoTSecureTunnelingClient,
-    ListTunnelsCommand,
-    OpenTunnelCommand,
-    RotateTunnelAccessTokenCommand,
-} from '@aws-sdk/client-iotsecuretunneling'
-import { getClientId } from '../../shared/telemetry/util'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-import { LocalProxy } from './localProxy'
-import globals from '../../shared/extensionGlobals'
-import { getLogger } from '../../shared/logger/logger'
-import { getIoTSTClientWithAgent, getLambdaClientWithAgent, getLambdaDebugUserAgentPairs } from './utils'
-import { ToolkitError } from '../../shared/errors'
-import * as nls from 'vscode-nls'
-
-const localize = nls.loadMessageBundle()
-
-export function isTunnelInfo(data: TunnelInfo): data is TunnelInfo {
-    return (
-        typeof data === 'object' &&
-        data !== null &&
-        typeof data.tunnelID === 'string' &&
-        typeof data.sourceToken === 'string' &&
-        typeof data.destinationToken === 'string'
-    )
-}
-
-export interface TunnelInfo {
-    tunnelID: string
-    sourceToken: string
-    destinationToken: string
-}
-
-async function callUpdateFunctionConfiguration(
-    lambda: DefaultLambdaClient,
-    config: FunctionConfiguration,
-    waitForUpdate: boolean
-): Promise {
-    // Update function configuration back to original values
-    return await lambda.updateFunctionConfiguration(
-        {
-            FunctionName: config.FunctionName!,
-            Timeout: config.Timeout,
-            Layers: config.Layers?.map((layer) => layer.Arn!).filter(Boolean) || [],
-            Environment: {
-                Variables: config.Environment?.Variables ?? {},
-            },
-        },
-        {
-            maxRetries: 5,
-            initialDelayMs: 2000,
-            backoffMultiplier: 2,
-            waitForUpdate: waitForUpdate,
-        }
-    )
-}
-
-export class LdkClient {
-    static #instance: LdkClient
-    private localProxy: LocalProxy | undefined
-    private static instanceCreating = false
-    private lambdaClientCache: Map = new Map()
-    private iotSTClientCache: Map = new Map()
-
-    constructor() {}
-
-    public static get instance() {
-        if (this.#instance !== undefined) {
-            return this.#instance
-        }
-        if (this.instanceCreating) {
-            getLogger().warn(
-                localize(
-                    'AWS.lambda.ldkClient.multipleInstancesError',
-                    'Attempt to create multiple LdkClient instances simultaneously'
-                )
-            )
-        }
-        // Set flag to prevent recursive instance creation
-        this.instanceCreating = true
-        try {
-            const self = (this.#instance = new this())
-            return self
-        } finally {
-            this.instanceCreating = false
-        }
-    }
-
-    /**
-     * Get or create a cached Lambda client for the specified region
-     */
-    private getLambdaClient(region: string): DefaultLambdaClient {
-        if (!this.lambdaClientCache.has(region)) {
-            this.lambdaClientCache.set(region, getLambdaClientWithAgent(region, getLambdaDebugUserAgentPairs()))
-        }
-        return this.lambdaClientCache.get(region)!
-    }
-
-    private getIoTSTClient(region: string): IoTSecureTunnelingClient {
-        if (!this.iotSTClientCache.has(region)) {
-            this.iotSTClientCache.set(region, getIoTSTClientWithAgent(region))
-        }
-        return this.iotSTClientCache.get(region)!
-    }
-    /**
-     * Clean up all resources held by this client
-     * Should be called when the extension is deactivated
-     */
-    public dispose(): void {
-        if (this.localProxy) {
-            this.localProxy.stop()
-            this.localProxy = undefined
-        }
-        // Clear the Lambda client cache
-        this.iotSTClientCache.clear()
-        this.lambdaClientCache.clear()
-    }
-
-    // Create or reuse tunnel
-    async createOrReuseTunnel(region: string): Promise {
-        try {
-            // Get VSCode UUID using getClientId from telemetry.utils.ts
-            const vscodeUuid = getClientId(globals.globalState)
-
-            // Create IoTSecureTunneling client
-            const iotSecureTunneling = this.getIoTSTClient(region)
-
-            // Define tunnel identifier
-            const tunnelIdentifier = `RemoteDebugging+${vscodeUuid}`
-            const timeoutInMinutes = 720
-            // List existing tunnels
-            const listTunnelsResponse = await iotSecureTunneling.send(new ListTunnelsCommand({}))
-
-            // Find tunnel with our identifier
-            const existingTunnel = listTunnelsResponse.tunnelSummaries?.find(
-                (tunnel) => tunnel.description === tunnelIdentifier && tunnel.status?.toLowerCase() === 'open'
-            )
-
-            if (existingTunnel && existingTunnel.tunnelId) {
-                const timeCreated = existingTunnel?.createdAt ? new Date(existingTunnel.createdAt) : new Date()
-                const expiryTime = new Date(timeCreated.getTime() + timeoutInMinutes * 60 * 1000)
-                const currentTime = new Date()
-                const minutesRemaining = (expiryTime.getTime() - currentTime.getTime()) / (60 * 1000)
-
-                if (minutesRemaining >= 15) {
-                    // Rotate access tokens for the existing tunnel
-                    const rotateResponse = await this.refreshTunnelTokens(existingTunnel.tunnelId, region)
-
-                    return rotateResponse
-                } else {
-                    // Close tunnel if less than 15 minutes remaining
-                    await iotSecureTunneling.send(
-                        new CloseTunnelCommand({
-                            tunnelId: existingTunnel.tunnelId,
-                            delete: false,
-                        })
-                    )
-
-                    getLogger().info(`Closed tunnel ${existingTunnel.tunnelId} with less than 15 minutes remaining`)
-                }
-            }
-
-            // Create new tunnel
-            const openTunnelResponse = await iotSecureTunneling.send(
-                new OpenTunnelCommand({
-                    description: tunnelIdentifier,
-                    timeoutConfig: {
-                        maxLifetimeTimeoutMinutes: timeoutInMinutes, // 12 hours
-                    },
-                    destinationConfig: {
-                        services: ['WSS'],
-                    },
-                })
-            )
-
-            getLogger().info(`Created new tunnel with ID: ${openTunnelResponse.tunnelId}`)
-
-            return {
-                tunnelID: openTunnelResponse.tunnelId || '',
-                sourceToken: openTunnelResponse.sourceAccessToken || '',
-                destinationToken: openTunnelResponse.destinationAccessToken || '',
-            }
-        } catch (error) {
-            throw ToolkitError.chain(error, 'Error creating/reusing tunnel')
-        }
-    }
-
-    // Refresh tunnel tokens
-    async refreshTunnelTokens(tunnelId: string, region: string): Promise {
-        try {
-            const iotSecureTunneling = this.getIoTSTClient(region)
-            const rotateResponse = await iotSecureTunneling.send(
-                new RotateTunnelAccessTokenCommand({
-                    tunnelId: tunnelId,
-                    clientMode: 'ALL',
-                })
-            )
-
-            return {
-                tunnelID: tunnelId,
-                sourceToken: rotateResponse.sourceAccessToken || '',
-                destinationToken: rotateResponse.destinationAccessToken || '',
-            }
-        } catch (error) {
-            throw ToolkitError.chain(error, 'Error refreshing tunnel tokens')
-        }
-    }
-
-    async getFunctionDetail(functionArn: string): Promise {
-        try {
-            const region = getRegionFromArn(functionArn)
-            if (!region) {
-                getLogger().error(
-                    localize(
-                        'AWS.lambda.ldkClient.couldNotDetermineRegion',
-                        'Could not determine region from Lambda ARN'
-                    )
-                )
-                return undefined
-            }
-            const client = this.getLambdaClient(region)
-            const configuration = (await client.getFunction(functionArn)).Configuration as FunctionConfiguration
-            // get function detail
-            // return function detail
-            return configuration
-        } catch (error) {
-            getLogger().warn(`Error getting function detail:${error}`)
-            return undefined
-        }
-    }
-
-    // Create debug deployment to given lambda function
-    // save a snapshot of the current config to global : aws.lambda.remoteDebugContext
-    // we are 1: changing function timeout to 15 minute
-    // 2: adding the ldk layer LDK_LAYER_ARN_X86_64 or LDK_LAYER_ARN_ARM64 (ignore if already added, fail if 5 layer already there)
-    // 3: adding two param to lambda environment variable
-    // {AWS_LAMBDA_EXEC_WRAPPER:/opt/bin/ldk_wrapper, AWS_LDK_DESTINATION_TOKEN: destinationToken }
-    async createDebugDeployment(
-        config: FunctionConfiguration,
-        destinationToken: string,
-        lambdaTimeout: number,
-        shouldPublishVersion: boolean,
-        ldkLayerArn: string,
-        progress: vscode.Progress<{ message?: string | undefined; increment?: number | undefined }>
-    ): Promise {
-        try {
-            if (!config.FunctionArn || !config.FunctionName) {
-                throw new Error(localize('AWS.lambda.ldkClient.functionArnMissing', 'Function ARN is missing'))
-            }
-            const region = getRegionFromArn(config.FunctionArn ?? '')
-            if (!region) {
-                throw new Error(
-                    localize(
-                        'AWS.lambda.ldkClient.couldNotDetermineRegion',
-                        'Could not determine region from Lambda ARN'
-                    )
-                )
-            }
-
-            // fix out of bound timeout
-            if (lambdaTimeout && (lambdaTimeout > 900 || lambdaTimeout <= 0)) {
-                lambdaTimeout = 900
-            }
-
-            // Inform user about the changes that will be made
-
-            progress.report({ message: localize('AWS.lambda.ldkClient.applyingChanges', 'Applying changes...') })
-
-            // Determine architecture and select appropriate layer
-
-            const layers = config.Layers || []
-
-            // Check if LDK layer is already added
-            const ldkLayerExists = layers.some(
-                (layer) => layer.Arn?.includes('LDKLayerX86') || layer.Arn?.includes('LDKLayerArm64')
-            )
-
-            // Check if we have room to add a layer (max 5)
-            if (!ldkLayerExists && layers.length >= 5) {
-                throw new Error(
-                    localize(
-                        'AWS.lambda.ldkClient.cannotAddLdkLayer',
-                        'Cannot add LDK layer: Lambda function already has 5 layers'
-                    )
-                )
-            }
-            // Create updated layers list
-            const updatedLayers = ldkLayerExists
-                ? layers.map((layer) => layer.Arn!).filter(Boolean)
-                : [...layers.map((layer) => layer.Arn!).filter(Boolean), ldkLayerArn]
-
-            // Create updated environment variables
-            const currentEnv = config.Environment?.Variables || {}
-            const updatedEnv: { [key: string]: string } = {
-                ...currentEnv,
-                AWS_LAMBDA_EXEC_WRAPPER: '/opt/bin/ldk_wrapper',
-                AWS_LAMBDA_DEBUG_ON_LATEST: shouldPublishVersion ? 'false' : 'true',
-                AWS_LDK_DESTINATION_TOKEN: destinationToken,
-            }
-            if (currentEnv['AWS_LAMBDA_EXEC_WRAPPER']) {
-                updatedEnv.ORIGINAL_AWS_LAMBDA_EXEC_WRAPPER = currentEnv['AWS_LAMBDA_EXEC_WRAPPER']
-            }
-
-            if (getLogger().logLevelEnabled('debug')) {
-                updatedEnv.RUST_LOG = 'debug'
-            }
-
-            // Create Lambda client using AWS SDK
-            const lambda = this.getLambdaClient(region)
-
-            // Update function configuration
-            if (!config.FunctionArn || !config.FunctionName) {
-                throw new Error('Function ARN is missing')
-            }
-
-            // Create a temporary config for the update
-            const updateConfig: FunctionConfiguration = {
-                FunctionName: config.FunctionName,
-                Timeout: lambdaTimeout ?? 900, // 15 minutes
-                Layers: updatedLayers.map((arn) => ({ Arn: arn })),
-                Environment: {
-                    Variables: updatedEnv,
-                },
-            }
-
-            await callUpdateFunctionConfiguration(lambda, updateConfig, true)
-
-            // publish version
-            let version = '$Latest'
-            if (shouldPublishVersion) {
-                // should somehow return version for debugging
-                const versionResp = await lambda.publishVersion(config.FunctionName, { waitForUpdate: true })
-                version = versionResp.Version ?? ''
-                // remove debug deployment in a non-blocking way
-                void Promise.resolve(
-                    callUpdateFunctionConfiguration(lambda, config, false).then(() => {
-                        progress.report({
-                            message: localize(
-                                'AWS.lambda.ldkClient.debugDeploymentCompleted',
-                                'Debug deployment completed successfully'
-                            ),
-                        })
-                    })
-                )
-            }
-            return version
-        } catch (error) {
-            getLogger().error(`Error creating debug deployment: ${error}`)
-            if (error instanceof Error) {
-                throw new ToolkitError(`Failed to create debug deployment: ${error.message}`)
-            }
-            return 'Failed'
-        }
-    }
-
-    // Remove debug deployment from the given lambda function
-    // use the snapshot we took before create debug deployment
-    // we are 1: reverting timeout to it's original snapshot
-    // 2: reverting layer status according to it's original snapshot
-    // 3: reverting environment back to it's original snapshot
-    async removeDebugDeployment(config: FunctionConfiguration, check: boolean = true): Promise {
-        try {
-            if (!config.FunctionArn || !config.FunctionName) {
-                throw new Error('Function ARN is missing')
-            }
-            const region = getRegionFromArn(config.FunctionArn ?? '')
-            if (!region) {
-                throw new Error('Could not determine region from Lambda ARN')
-            }
-
-            if (check) {
-                const currentConfig = await this.getFunctionDetail(config.FunctionArn)
-                if (
-                    currentConfig?.Timeout === config?.Timeout &&
-                    currentConfig?.Layers?.length === config?.Layers?.length
-                ) {
-                    // nothing to remove
-                    return true
-                }
-            }
-
-            // Create Lambda client using AWS SDK
-            const lambda = this.getLambdaClient(region)
-
-            // Update function configuration back to original values
-            await callUpdateFunctionConfiguration(lambda, config, false)
-
-            return true
-        } catch (error) {
-            // no need to raise, even this failed we want the following to execute
-            throw ToolkitError.chain(error, 'Error removing debug deployment')
-        }
-    }
-
-    async deleteDebugVersion(functionArn: string, qualifier: string) {
-        try {
-            const region = getRegionFromArn(functionArn)
-            if (!region) {
-                throw new Error('Could not determine region from Lambda ARN')
-            }
-            const lambda = this.getLambdaClient(region)
-            await lambda.deleteFunction(functionArn, qualifier)
-            return true
-        } catch (error) {
-            getLogger().error('Error deleting debug version: %O', error)
-            return false
-        }
-    }
-
-    // Start proxy with better resource management
-    async startProxy(region: string, sourceToken: string, port: number = 0): Promise {
-        try {
-            getLogger().info(`Starting direct proxy for region:${region}`)
-
-            // Clean up any existing proxy thoroughly
-            if (this.localProxy) {
-                getLogger().info('Stopping existing proxy before starting a new one')
-                this.localProxy.stop()
-                this.localProxy = undefined
-
-                // Small delay to ensure resources are released
-                await new Promise((resolve) => setTimeout(resolve, 100))
-            }
-
-            // Create and start a new local proxy
-            this.localProxy = new LocalProxy()
-
-            // Start the proxy and get the assigned port
-            const localPort = await this.localProxy.start(region, sourceToken, port)
-            getLogger().info(`Local proxy started successfully on port ${localPort}`)
-            return true
-        } catch (error) {
-            getLogger().error(`Failed to start proxy: ${error}`)
-            if (this.localProxy) {
-                this.localProxy.stop()
-                this.localProxy = undefined
-            }
-            throw ToolkitError.chain(error, 'Failed to start proxy')
-        }
-    }
-
-    // Stop proxy with proper cleanup and reference handling
-    async stopProxy(): Promise {
-        try {
-            getLogger().info(`Stopping proxy`)
-
-            if (this.localProxy) {
-                // Ensure proper resource cleanup
-                this.localProxy.stop()
-
-                // Force delete the reference to allow GC
-                this.localProxy = undefined
-
-                getLogger().info('Local proxy stopped successfully')
-            } else {
-                getLogger().info('No active local proxy to stop')
-            }
-
-            return true
-        } catch (error) {
-            throw ToolkitError.chain(error, 'Error stopping proxy')
-        }
-    }
-}
-
-// Helper function to extract region from ARN
-export function getRegionFromArn(arn: string | undefined): string | undefined {
-    if (!arn) {
-        return undefined
-    }
-    const parts = arn.split(':')
-    return parts.length >= 4 ? parts[3] : undefined
-}
diff --git a/packages/core/src/lambda/remoteDebugging/ldkController.ts b/packages/core/src/lambda/remoteDebugging/ldkController.ts
deleted file mode 100644
index dc04c9bd16..0000000000
--- a/packages/core/src/lambda/remoteDebugging/ldkController.ts
+++ /dev/null
@@ -1,871 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import globals from '../../shared/extensionGlobals'
-import { FunctionConfiguration, Runtime } from '@aws-sdk/client-lambda'
-import { getRegionFromArn, LdkClient } from './ldkClient'
-import { getFamily, mapFamilyToDebugType } from '../models/samLambdaRuntime'
-import { findJavaPath } from '../../shared/utilities/pathFind'
-import { ToolkitError } from '../../shared/errors'
-import { showConfirmationMessage, showMessage } from '../../shared/utilities/messages'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import * as nls from 'vscode-nls'
-import path from 'path'
-import { glob } from 'glob'
-import { Commands } from '../../shared/vscode/commands2'
-import { getLambdaSnapshot, persistLambdaSnapshot, type LambdaDebugger, type DebugConfig } from './lambdaDebugger'
-import { RemoteLambdaDebugger } from './remoteLambdaDebugger'
-import { LocalStackLambdaDebugger } from './localStackLambdaDebugger'
-import { fs } from '../../shared/fs/fs'
-import { detectCdkProjects } from '../../awsService/cdk/explorer/detectCdkProjects'
-
-const localize = nls.loadMessageBundle()
-const logger = getLogger()
-
-// Map debug types to their corresponding VS Code extension IDs
-const mapDebugTypeToExtensionId = new Map([
-    ['python', ['ms-python.python']],
-    ['java', ['redhat.java', 'vscjava.vscode-java-debug']],
-    ['node', ['ms-vscode.js-debug']],
-])
-
-const mapExtensionToBackup = new Map([['ms-vscode.js-debug', 'ms-vscode.js-debug-nightly']])
-
-// Helper function to create a human-readable diff message
-function createDiffMessage(
-    config: FunctionConfiguration,
-    currentConfig: FunctionConfiguration,
-    isRevert: boolean = true
-): string {
-    let message = isRevert ? 'The following changes will be reverted:\n\n' : 'The following changes will be made:\n\n'
-
-    message +=
-        '1. Timeout: ' +
-        (currentConfig.Timeout || 'default') +
-        ' seconds → ' +
-        (config.Timeout || 'default') +
-        ' seconds\n'
-
-    message += '2. Layers: '
-    const hasLdkLayer = currentConfig.Layers?.some(
-        (layer) => layer.Arn?.includes('LDKLayerX86') || layer.Arn?.includes('LDKLayerArm64')
-    )
-
-    message += hasLdkLayer ? 'Remove LDK layer\n' : 'No Change\n'
-
-    message += '3. Environment Variables: Remove AWS_LAMBDA_EXEC_WRAPPER and AWS_LDK_DESTINATION_TOKEN\n'
-
-    return message
-}
-
-/**
- * Attempts to revert an existing debug configuration if one exists
- * @returns true if revert was successful or no config exists, false if revert failed or user chose not to revert
- */
-export async function revertExistingConfig(): Promise {
-    try {
-        // Check if a debug context exists from a previous session
-        const savedConfig = getLambdaSnapshot()
-
-        if (!savedConfig) {
-            // No existing config to revert
-            return true
-        }
-
-        // clear the snapshot for it's corrupted
-        if (!savedConfig.FunctionArn || !savedConfig.FunctionName) {
-            logger.error('Function ARN or Function Name is missing, cannot revert')
-            void (await persistLambdaSnapshot(undefined))
-            return true
-        }
-
-        // compare with current config
-        const currentConfig = await LdkClient.instance.getFunctionDetail(savedConfig.FunctionArn)
-        // could be permission issues, or user has deleted previous function, we should remove the snapshot
-        if (!currentConfig) {
-            logger.error('Failed to get current function state, cannot revert')
-            void (await persistLambdaSnapshot(undefined))
-            return true
-        }
-
-        if (
-            currentConfig?.Timeout === savedConfig?.Timeout &&
-            currentConfig?.Layers?.length === savedConfig?.Layers?.length
-        ) {
-            // No changes needed, remove the snapshot
-            void (await persistLambdaSnapshot(undefined))
-            return true
-        }
-
-        // Create a diff message to show what will be changed
-        const diffMessage = currentConfig
-            ? createDiffMessage(savedConfig, currentConfig, true)
-            : 'Failed to get current function state'
-
-        const response = await showConfirmationMessage({
-            prompt: localize(
-                'AWS.lambda.remoteDebug.revertPreviousDeployment',
-                'A previous debug deployment was detected for {0}. Would you like to revert those changes before proceeding?\n\n{1}',
-                savedConfig.FunctionName,
-                diffMessage
-            ),
-            confirm: localize('AWS.lambda.remoteDebug.revert', 'Revert'),
-            cancel: localize('AWS.lambda.remoteDebug.dontShowAgain', "Don't show again"),
-            type: 'warning',
-        })
-
-        if (!response) {
-            // User chose not to revert, remove the snapshot
-            void (await persistLambdaSnapshot(undefined))
-            return true
-        }
-
-        await LdkClient.instance.removeDebugDeployment(savedConfig, false)
-        await persistLambdaSnapshot(undefined)
-        void showMessage(
-            'info',
-            localize(
-                'AWS.lambda.remoteDebug.successfullyReverted',
-                'Successfully reverted changes to {0}',
-                savedConfig.FunctionName
-            )
-        )
-
-        return true
-    } catch (error) {
-        throw ToolkitError.chain(error, `Error in revertExistingConfig`)
-    }
-}
-
-export async function activateRemoteDebugging(): Promise {
-    try {
-        globals.context.subscriptions.push(
-            Commands.register('aws.lambda.remoteDebugging.clearSnapshot', async () => {
-                void (await persistLambdaSnapshot(undefined))
-            })
-        )
-    } catch (error) {
-        logger.error(`Error in registering clearSnapshot command:${error}`)
-    }
-
-    try {
-        logger.info('Remote debugging is initiated')
-
-        // Use the revertExistingConfig function to handle any existing debug configurations
-        await revertExistingConfig()
-
-        // Initialize RemoteDebugController to ensure proper startup state
-        RemoteDebugController.instance.ensureCleanState()
-    } catch (error) {
-        // show warning
-        void vscode.window.showWarningMessage(`Error in activateRemoteDebugging: ${error}`)
-        logger.error(`Error in activateRemoteDebugging:${error}`)
-    }
-}
-
-/**
- * Try to auto-detect outFile for TypeScript debugging (SAM or CDK)
- * @param debugConfig Debug configuration
- * @param functionConfig Lambda function configuration
- * @returns The auto-detected outFile path or undefined
- */
-export async function tryAutoDetectOutFile(
-    debugConfig: DebugConfig,
-    functionConfig: FunctionConfiguration
-): Promise {
-    // Only works for TypeScript files
-    if (
-        !debugConfig.handlerFile ||
-        (!debugConfig.handlerFile.endsWith('.ts') && !debugConfig.handlerFile.endsWith('.tsx'))
-    ) {
-        return undefined
-    }
-
-    // Try SAM detection first using the provided parameters
-    if (debugConfig.samFunctionLogicalId && debugConfig.samProjectRoot) {
-        // if proj root is ..../sam-proj/
-        // build dir will be ..../sam-proj/.aws-sam/build/{LogicalID}/
-        const samBuildPath = vscode.Uri.joinPath(
-            debugConfig.samProjectRoot,
-            '.aws-sam',
-            'build',
-            debugConfig.samFunctionLogicalId
-        )
-
-        if (await fs.exists(samBuildPath)) {
-            getLogger().info(`SAM outFile auto-detected: ${samBuildPath.fsPath}`)
-            return samBuildPath.fsPath
-        }
-    }
-
-    // If SAM detection didn't work, try CDK detection using the function name
-    if (!functionConfig.FunctionName) {
-        return undefined
-    }
-
-    try {
-        // Find which workspace contains the handler file
-        const workspaceFolder = vscode.workspace.getWorkspaceFolder(vscode.Uri.file(debugConfig.handlerFile))
-        if (!workspaceFolder) {
-            return undefined
-        }
-
-        // Detect CDK projects in the workspace
-        const cdkProjects = await detectCdkProjects([workspaceFolder])
-
-        for (const project of cdkProjects) {
-            // Check if CDK project contains the handler file
-            const cdkProjectDir = vscode.Uri.joinPath(project.cdkJsonUri, '..')
-            // Normalize paths for comparison (handles Windows path separators and case)
-            const normalizedHandlerPath = path.normalize(debugConfig.handlerFile).toLowerCase()
-            const normalizedCdkPath = path.normalize(cdkProjectDir.fsPath).toLowerCase()
-            if (!normalizedHandlerPath.startsWith(normalizedCdkPath)) {
-                continue
-            }
-
-            // Get the cdk.out directory
-            const cdkOutDir = vscode.Uri.joinPath(project.treeUri, '..')
-
-            // Look for template.json files in cdk.out directory
-            const pattern = new vscode.RelativePattern(cdkOutDir.fsPath, '*.template.json')
-            const templateFiles = await vscode.workspace.findFiles(pattern)
-
-            for (const templateFile of templateFiles) {
-                try {
-                    // Read and parse the template.json file
-                    const templateContent = await fs.readFileText(templateFile)
-                    const template = JSON.parse(templateContent)
-
-                    // Search through resources for a Lambda function with matching FunctionName
-                    for (const [_, resource] of Object.entries(template.Resources || {})) {
-                        const res = resource as any
-                        if (
-                            res.Type === 'AWS::Lambda::Function' &&
-                            res.Properties?.FunctionName === functionConfig.FunctionName
-                        ) {
-                            // Found the matching function, extract the asset path from metadata
-                            const assetPath = res.Metadata?.['aws:asset:path']
-                            if (assetPath) {
-                                const assetDir = vscode.Uri.joinPath(cdkOutDir, assetPath)
-
-                                // Check if the asset directory exists
-                                if (await fs.exists(assetDir)) {
-                                    getLogger().info(`CDK outFile auto-detected from template.json: ${assetDir.fsPath}`)
-                                    return assetDir.fsPath
-                                }
-                            }
-                        }
-                    }
-                } catch (error) {
-                    getLogger().debug(`Failed to parse template file ${templateFile.fsPath}: ${error}`)
-                }
-            }
-        }
-    } catch (error) {
-        getLogger().warn(`Failed to auto-detect CDK outFile: ${error}`)
-    }
-
-    return undefined
-}
-
-/**
- * Helper function to check if a string is a valid VSCode glob pattern
- */
-function isVscodeGlob(pattern: string): boolean {
-    // Check for common glob patterns: *, **, ?, [], {}
-    return /[*?[\]{}]/.test(pattern)
-}
-
-/**
- * Extract temp directory patterns from source map files
- * @param mapFiles Array of source map file paths
- * @returns Set of temp directory patterns found in source maps
- */
-async function extractTempPatternsFromSourceMaps(mapFiles: string[]): Promise> {
-    const tempPatterns = new Set()
-
-    for (const mapFile of mapFiles) {
-        try {
-            const content = await fs.readFileText(mapFile)
-            const sourceMap = JSON.parse(content)
-
-            if (sourceMap.sources && Array.isArray(sourceMap.sources)) {
-                for (const source of sourceMap.sources) {
-                    // SAM uses Python's tempfile.mkdtemp() to create tmp dir which we want to detect
-                    // tempfile.mkdtemp() uses lowercase letters, digits, and underscores
-                    // The pattern is: tmp followed by 8 characters from [a-z0-9_]
-                    // see https://github.com/python/cpython/blob/20a677d75a95fa63be904f7ca4f8cb268aec95c1/Lib/tempfile.py#L132-L140
-                    const tempMatch = source.match(/\btmp[a-z0-9_]{8}\b/)
-                    if (tempMatch) {
-                        tempPatterns.add(tempMatch[0])
-                        getLogger().debug(`Found temp pattern in source map: ${tempMatch[0]}`)
-                    }
-                }
-            }
-        } catch (error) {
-            getLogger().debug(`Failed to read or parse source map ${mapFile}: ${error}`)
-        }
-    }
-
-    return tempPatterns
-}
-
-/**
- * Helper function to validate source map files exist for given outFiles patterns
- * @returns Object with validation result and temp patterns found in source maps
- */
-export async function validateSourceMapFiles(
-    outFiles: string[]
-): Promise<{ isValid: boolean; tempPatterns: Set }> {
-    getLogger().debug(`validating outFiles ${outFiles}`)
-    const allAreGlobs = outFiles.every((pattern) => isVscodeGlob(pattern))
-    if (!allAreGlobs) {
-        return { isValid: false, tempPatterns: new Set() }
-    }
-
-    try {
-        let jsfileCount = 0
-        let mapfileCount = 0
-        const mapFiles: string[] = []
-
-        // Convert Windows paths to use forward slashes for glob
-        const normalizedOutFiles = outFiles.map((pattern) => {
-            // Replace backslashes with forward slashes for glob compatibility
-            return pattern.replaceAll(/\\/g, '/')
-        })
-        getLogger().debug(`normalizedOutFiles ${normalizedOutFiles}`)
-        const jsFiles = await glob(normalizedOutFiles, { ignore: 'node_modules/**' })
-
-        for (const file of jsFiles) {
-            if (file.includes('js')) {
-                jsfileCount += 1
-            }
-            if (file.includes('.map')) {
-                mapfileCount += 1
-                mapFiles.push(file)
-            }
-        }
-
-        // Extract temp patterns from source map files
-        const tempPatterns = await extractTempPatternsFromSourceMaps(mapFiles)
-
-        return {
-            isValid: jsfileCount > 0 && mapfileCount > 0,
-            tempPatterns,
-        }
-    } catch (error) {
-        getLogger().warn(`Error validating source map files: ${error}`)
-        return { isValid: false, tempPatterns: new Set() }
-    }
-}
-
-function processOutFiles(outFiles: string[], localRoot: string): string[] {
-    const processedOutFiles: string[] = []
-
-    for (let outFile of outFiles) {
-        if (!outFile.includes('*')) {
-            // add * in the end
-            outFile = path.join(outFile, '*')
-        }
-        if (!path.isAbsolute(outFile)) {
-            // Find which workspace contains the localRoot path
-            const workspaceFolders = vscode.workspace.workspaceFolders
-            if (workspaceFolders) {
-                let matchingWorkspace: vscode.WorkspaceFolder | undefined
-
-                // Check if localRoot is within any workspace
-                for (const workspace of workspaceFolders) {
-                    const absoluteLocalRoot = path.resolve(localRoot)
-                    const workspacePath = workspace.uri.fsPath
-
-                    if (absoluteLocalRoot.startsWith(workspacePath)) {
-                        matchingWorkspace = workspace
-                        break
-                    }
-                }
-
-                if (matchingWorkspace) {
-                    // Join workspace folder with the relative outFile path
-                    processedOutFiles.push(path.join(matchingWorkspace.uri.fsPath, outFile))
-                } else {
-                    // If no matching workspace found, use the original outFile
-                    processedOutFiles.push(outFile)
-                }
-            } else {
-                // No workspace folders, use the original outFile
-                processedOutFiles.push(outFile)
-            }
-        } else {
-            // Already absolute path, use as is
-            processedOutFiles.push(outFile)
-        }
-    }
-    return processedOutFiles
-}
-
-async function getVscodeDebugConfig(
-    functionConfig: FunctionConfiguration,
-    debugConfig: DebugConfig
-): Promise {
-    // Parse and validate otherDebugParams if provided
-    let additionalParams: Record = {}
-    if (debugConfig.otherDebugParams) {
-        try {
-            const parsed = JSON.parse(debugConfig.otherDebugParams)
-            if (typeof parsed === 'object' && !Array.isArray(parsed)) {
-                additionalParams = parsed
-                getLogger().info('Additional debug parameters parsed successfully: %O ', additionalParams)
-            } else {
-                void vscode.window.showWarningMessage(
-                    localize(
-                        'AWS.lambda.remoteDebug.invalidDebugParams',
-                        'Other Debug Parameters must be a valid JSON object. The parameter will be ignored.'
-                    )
-                )
-                getLogger().warn(`Invalid otherDebugParams format: expected object, got ${typeof parsed}`)
-            }
-        } catch (error) {
-            void vscode.window.showWarningMessage(
-                localize(
-                    'AWS.lambda.remoteDebug.failedToParseDebugParams',
-                    'Failed to parse Other Debug Parameters as JSON: {0}. The parameter will be ignored.',
-                    error instanceof Error ? error.message : 'Invalid JSON'
-                )
-            )
-            getLogger().warn(`Failed to parse otherDebugParams as JSON: ${error}`)
-        }
-    }
-
-    const debugSessionName = `Debug ${functionConfig.FunctionArn!.split(':').pop()}`
-
-    // Define debugConfig before the try block
-    const debugType = mapFamilyToDebugType.get(getFamily(functionConfig.Runtime!), 'unknown')
-    let vsCodeDebugConfig: vscode.DebugConfiguration
-    switch (debugType) {
-        case 'node':
-            // Try to auto-detect outFiles for TypeScript if not provided
-            if (debugConfig.sourceMap && !debugConfig.outFiles && debugConfig.handlerFile) {
-                const autoDetectedOutFile = await tryAutoDetectOutFile(debugConfig, functionConfig)
-                if (autoDetectedOutFile) {
-                    debugConfig.outFiles = [autoDetectedOutFile]
-                    getLogger().info(`outFile auto-detected: ${autoDetectedOutFile}`)
-                }
-            }
-
-            // source map support
-            if (debugConfig.sourceMap && debugConfig.outFiles) {
-                // process outFiles first, if they are relative path (not starting with /),
-                // check local root path is located in which workspace. Then join workspace Folder with outFiles
-
-                // Update debugConfig with processed outFiles
-                debugConfig.outFiles = processOutFiles(debugConfig.outFiles, debugConfig.localRoot)
-
-                // Use glob to search if there are any matching js file or source map file
-                const sourceMapValidation = await validateSourceMapFiles(debugConfig.outFiles)
-
-                if (sourceMapValidation.isValid) {
-                    // Start with basic source map overrides
-                    const sourceMapOverrides: Record = {
-                        ...additionalParams['sourceMapPathOverrides'],
-                    }
-
-                    // Add specific temp directory patterns found in source maps
-                    for (const tempPattern of sourceMapValidation.tempPatterns) {
-                        sourceMapOverrides[`?:*/${tempPattern}/*`] = path.join(debugConfig.localRoot, '*')
-                        getLogger().info(`Added source map override for temp pattern: ${tempPattern}`)
-                    }
-
-                    additionalParams['sourceMapPathOverrides'] = sourceMapOverrides
-                    debugConfig.localRoot = debugConfig.outFiles[0].split('*')[0]
-                } else {
-                    debugConfig.sourceMap = false
-                    debugConfig.outFiles = undefined
-                    void showMessage(
-                        'warn',
-                        localize(
-                            'AWS.lambda.remoteDebug.outFileNotFound',
-                            'outFiles not valid or no js and map file found in outFiles, debug will continue without sourceMap support'
-                        )
-                    )
-                }
-            }
-            vsCodeDebugConfig = {
-                type: debugType,
-                request: 'attach',
-                name: debugSessionName,
-                address: 'localhost',
-                port: debugConfig.port,
-                localRoot: debugConfig.localRoot,
-                remoteRoot: debugConfig.remoteRoot,
-                skipFiles: debugConfig.skipFiles,
-                sourceMaps: debugConfig.sourceMap,
-                outFiles: debugConfig.outFiles,
-                continueOnAttach: debugConfig.outFiles ? false : true,
-                stopOnEntry: false,
-                timeout: 60000,
-                ...additionalParams, // Merge additional debug parameters
-            }
-            break
-        case 'python':
-            vsCodeDebugConfig = {
-                type: debugType,
-                request: 'attach',
-                name: debugSessionName,
-                port: debugConfig.port,
-                cwd: debugConfig.localRoot,
-                pathMappings: [
-                    {
-                        localRoot: debugConfig.localRoot,
-                        remoteRoot: debugConfig.remoteRoot,
-                    },
-                ],
-                justMyCode: debugConfig.justMyCode ?? true,
-                ...additionalParams, // Merge additional debug parameters
-            }
-            break
-        case 'java':
-            vsCodeDebugConfig = {
-                type: debugType,
-                request: 'attach',
-                name: debugSessionName,
-                hostName: 'localhost',
-                port: debugConfig.port,
-                sourcePaths: [debugConfig.localRoot],
-                projectName: debugConfig.projectName,
-                timeout: 60000,
-                ...additionalParams, // Merge additional debug parameters
-            }
-            break
-        default:
-            throw new ToolkitError(`Unsupported debug type: ${debugType}`)
-    }
-    getLogger().info('VS Code debug configuration: %O', vsCodeDebugConfig)
-    return vsCodeDebugConfig
-}
-
-export class RemoteDebugController {
-    static #instance: RemoteDebugController
-    isDebugging: boolean = false
-    qualifier: string | undefined = undefined
-    debugger: LambdaDebugger | undefined = undefined
-    private lastDebugStartTime: number = 0
-    // private debugSession: DebugSession | undefined
-    private debugSessionDisposables: Map = new Map()
-    private debugTypeSource: 'remoteDebug' | 'LocalStackDebug' = 'remoteDebug'
-
-    public static get instance() {
-        if (this.#instance !== undefined) {
-            return this.#instance
-        }
-
-        const self = (this.#instance = new this())
-        return self
-    }
-
-    constructor() {}
-
-    /**
-     * Ensures the controller is in a clean state at startup or before a new operation
-     */
-    public ensureCleanState(): void {
-        this.isDebugging = false
-        this.qualifier = undefined
-
-        // Clean up any leftover disposables
-        for (const [key, disposable] of this.debugSessionDisposables.entries()) {
-            try {
-                disposable.dispose()
-            } catch (e) {
-                // Ignore errors during startup cleanup
-            }
-            this.debugSessionDisposables.delete(key)
-        }
-    }
-
-    public supportCodeDownload(runtime: Runtime | undefined, codeSha256: string | undefined = ''): boolean {
-        if (!runtime) {
-            return false
-        }
-        // Incompatible with LocalStack hot-reloading
-        if (codeSha256?.startsWith('hot-reloading')) {
-            return false
-        }
-        try {
-            return ['node', 'python'].includes(mapFamilyToDebugType.get(getFamily(runtime)) ?? '')
-        } catch {
-            // deprecated runtime
-            return false
-        }
-    }
-
-    public supportRuntimeRemoteDebug(runtime: Runtime | undefined): boolean {
-        if (!runtime) {
-            return false
-        }
-        try {
-            return ['node', 'python', 'java'].includes(mapFamilyToDebugType.get(getFamily(runtime)) ?? '')
-        } catch {
-            return false
-        }
-    }
-
-    public async installDebugExtension(runtime: Runtime | undefined): Promise {
-        if (!runtime) {
-            throw new ToolkitError('Runtime is undefined')
-        }
-
-        const debugType = mapFamilyToDebugType.get(getFamily(runtime))
-        if (!debugType) {
-            throw new ToolkitError(`Debug type is undefined for runtime ${runtime}`)
-        }
-        // Install needed debug extension based on runtime
-        const extensions = mapDebugTypeToExtensionId.get(debugType)
-        if (extensions) {
-            for (const extension of extensions) {
-                const extensionObj = vscode.extensions.getExtension(extension)
-                const backupExtensionObj = vscode.extensions.getExtension(mapExtensionToBackup.get(extension) ?? '')
-
-                if (!extensionObj && !backupExtensionObj) {
-                    // Extension is not installed, install it
-                    const choice = await showConfirmationMessage({
-                        prompt: localize(
-                            'AWS.lambda.remoteDebug.extensionNotInstalled',
-                            'You need to install the {0} extension to debug {1} functions. Would you like to install it now?',
-                            extension,
-                            debugType
-                        ),
-                        confirm: localize('AWS.lambda.remoteDebug.install', 'Install'),
-                        cancel: localize('AWS.lambda.remoteDebug.cancel', 'Cancel'),
-                        type: 'warning',
-                    })
-                    if (!choice) {
-                        return false
-                    }
-                    await vscode.commands.executeCommand('workbench.extensions.installExtension', extension)
-                    if (vscode.extensions.getExtension(extension) === undefined) {
-                        return false
-                    }
-                }
-            }
-        }
-
-        if (debugType === 'java' && !(await findJavaPath())) {
-            // jvm not available
-            const choice = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.lambda.remoteDebug.jvmNotInstalled',
-                    'You need to install a JVM to debug Java functions. Would you like to install it now?'
-                ),
-                confirm: localize('AWS.lambda.remoteDebug.install', 'Install'),
-                cancel: localize('AWS.lambda.remoteDebug.continueAnyway', 'Continue Anyway'),
-                type: 'warning',
-            })
-            // open https://developers.redhat.com/products/openjdk/download
-            if (choice) {
-                await vscode.env.openExternal(
-                    vscode.Uri.parse('https://developers.redhat.com/products/openjdk/download')
-                )
-                return false
-            }
-        }
-        // passed all checks
-        return true
-    }
-
-    public async startDebugging(functionArn: string, runtime: string, debugConfig: DebugConfig): Promise {
-        if (debugConfig.isLambdaRemote) {
-            this.debugTypeSource = 'remoteDebug'
-            this.debugger = new RemoteLambdaDebugger(debugConfig, {
-                getQualifier: () => {
-                    return this.qualifier
-                },
-                setQualifier: (qualifier) => {
-                    this.qualifier = qualifier
-                },
-            })
-        } else {
-            this.debugTypeSource = 'LocalStackDebug'
-            this.debugger = new LocalStackLambdaDebugger(debugConfig)
-        }
-        if (this.isDebugging) {
-            getLogger().error('Debug already in progress, remove debug setup to restart')
-            return
-        }
-
-        await telemetry.lambda_remoteDebugStart.run(async (span) => {
-            // Create a copy of debugConfig without functionName and functionArn for telemetry
-            const debugConfigForTelemetry: Partial = { ...debugConfig }
-            debugConfigForTelemetry.functionName = undefined
-            debugConfigForTelemetry.functionArn = undefined
-            debugConfigForTelemetry.localRoot = undefined
-
-            span.record({
-                source: this.debugTypeSource,
-                passive: false,
-                action: JSON.stringify(debugConfigForTelemetry),
-            })
-            this.lastDebugStartTime = Date.now()
-            await vscode.window.withProgress(
-                {
-                    location: vscode.ProgressLocation.Notification,
-                    title: 'Setting up debug session',
-                    cancellable: false,
-                },
-                async (progress) => {
-                    // Reset state before starting
-                    this.ensureCleanState()
-
-                    getLogger().info(`Starting debugger for ${functionArn}`)
-
-                    const region = getRegionFromArn(functionArn)
-                    if (!region) {
-                        throw new ToolkitError('Could not determine region from Lambda ARN')
-                    }
-
-                    // Check if runtime / region is supported for remote debugging
-                    if (!this.supportRuntimeRemoteDebug(runtime as Runtime)) {
-                        throw new ToolkitError(
-                            `Runtime ${runtime} is not supported for remote debugging. ` +
-                                `Only Python, Node.js, and Java runtimes are supported.`
-                        )
-                    }
-
-                    // Ensure the remote connection is reachable before calling lambda.GetFunction in revertExistingConfig()
-                    await this.debugger?.checkHealth()
-
-                    // Check if a snapshot already exists and revert if needed
-                    // Use the revertExistingConfig function from ldkController
-                    progress.report({ message: 'Checking if snapshot exists...' })
-                    const revertResult = await revertExistingConfig()
-
-                    // If revert failed and user didn't choose to ignore, abort the deployment
-                    if (revertResult === false) {
-                        return
-                    }
-                    try {
-                        // Anything fails before this point doesn't requires reverting
-                        this.isDebugging = true
-
-                        // the following will contain changes that requires reverting.
-                        // Create a snapshot of lambda config before debug
-                        // let's preserve this config to a global variable at here
-                        // we will use this config to revert the changes back to it once was, once confirm it's success, update the global to undefined
-                        // if somehow the changes failed to revert, in init phase(activate remote debugging), we will detect this config and prompt user to revert the changes
-                        // get function config again in case anything changed
-                        const functionConfig = await LdkClient.instance.getFunctionDetail(functionArn)
-                        if (!functionConfig?.Runtime || !functionConfig?.FunctionArn) {
-                            throw new ToolkitError('Could not retrieve Lambda function configuration')
-                        }
-                        await persistLambdaSnapshot(functionConfig)
-
-                        // Record runtime in telemetry
-                        span.record({
-                            runtimeString: functionConfig.Runtime as any,
-                        })
-
-                        await this.debugger?.setup(progress, functionConfig, region)
-
-                        const vscodeDebugConfig = await getVscodeDebugConfig(functionConfig, debugConfig)
-                        // show every field in debugConfig
-                        // getLogger().info(`Debug configuration created successfully ${JSON.stringify(debugConfig)}`)
-
-                        await this.debugger?.waitForSetup(progress, functionConfig, region)
-
-                        progress.report({ message: 'Starting debugger...' })
-                        // Start debugging in a non-blocking way
-                        void Promise.resolve(vscode.debug.startDebugging(undefined, vscodeDebugConfig)).then(
-                            async (debugStarted) => {
-                                if (!debugStarted) {
-                                    // this could be triggered by another stop debugging, let's check state before stopping.
-                                    throw new ToolkitError('Failed to start debug session')
-                                }
-                            }
-                        )
-
-                        const debugSessionEndDisposable = vscode.debug.onDidTerminateDebugSession(async (session) => {
-                            if (session.name === vscodeDebugConfig.name) {
-                                void (await this.stopDebugging())
-                            }
-                        })
-
-                        await this.debugger?.waitForFunctionUpdates(progress)
-
-                        // Store the disposable
-                        this.debugSessionDisposables.set(functionConfig.FunctionArn, debugSessionEndDisposable)
-                        progress.report({
-                            message: `Debug session setup completed for ${functionConfig.FunctionArn.split(':').pop()}`,
-                        })
-                    } catch (error) {
-                        try {
-                            await this.stopDebugging()
-                        } catch (errStop) {
-                            getLogger().error(
-                                'encountered following error when stopping debug for failed debug session:'
-                            )
-                            getLogger().error(errStop as Error)
-                        }
-
-                        throw ToolkitError.chain(error, 'Error StartDebugging')
-                    }
-                }
-            )
-        })
-    }
-
-    public async stopDebugging(): Promise {
-        await telemetry.lambda_remoteDebugStop.run(async (span) => {
-            if (!this.isDebugging) {
-                void showMessage(
-                    'info',
-                    localize('AWS.lambda.remoteDebug.debugNotInProgress', 'Debug is not in progress')
-                )
-                return
-            }
-            // use sessionDuration to record debug duration
-            span.record({
-                sessionDuration: this.lastDebugStartTime === 0 ? 0 : Date.now() - this.lastDebugStartTime,
-                source: this.debugTypeSource,
-            })
-            try {
-                await vscode.window.withProgress(
-                    {
-                        location: vscode.ProgressLocation.Notification,
-                        title: 'Stopping debug session',
-                        cancellable: false,
-                    },
-                    async (progress) => {
-                        progress.report({ message: 'Stopping debugging...' })
-
-                        // First attempt to clean up resources from Lambda
-                        const savedConfig = getLambdaSnapshot()
-                        if (!savedConfig?.FunctionArn) {
-                            getLogger().error('No saved configuration found during cleanup')
-                            throw new ToolkitError('No saved configuration found during cleanup')
-                        }
-
-                        const disposable = this.debugSessionDisposables.get(savedConfig.FunctionArn)
-                        if (disposable) {
-                            disposable.dispose()
-                            this.debugSessionDisposables.delete(savedConfig.FunctionArn)
-                        }
-                        await this.debugger?.cleanup(savedConfig)
-
-                        progress.report({ message: `Debug session stopped` })
-                    }
-                )
-                void showMessage(
-                    'info',
-                    localize('AWS.lambda.remoteDebug.debugSessionStopped', 'Debug session stopped')
-                )
-            } catch (error) {
-                throw ToolkitError.chain(error, 'error when stopping remote debug')
-            } finally {
-                this.isDebugging = false
-            }
-        })
-    }
-}
diff --git a/packages/core/src/lambda/remoteDebugging/ldkLayers.ts b/packages/core/src/lambda/remoteDebugging/ldkLayers.ts
deleted file mode 100644
index cf955a6c01..0000000000
--- a/packages/core/src/lambda/remoteDebugging/ldkLayers.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-interface RegionAccountMapping {
-    [region: string]: string
-}
-
-// Map region to account ID
-export const regionToAccount: RegionAccountMapping = {
-    'us-east-1': '166855510987',
-    'ap-northeast-1': '435951944084',
-    'us-west-1': '397974708477',
-    'us-west-2': '116489046076',
-    'us-east-2': '372632330791',
-    'ca-central-1': '816313119386',
-    'eu-west-1': '020236748984',
-    'eu-west-2': '199003954714',
-    'eu-west-3': '490913546906',
-    'eu-central-1': '944487268028',
-    'eu-north-1': '351516301086',
-    'ap-southeast-1': '812073016575',
-    'ap-southeast-2': '185226997092',
-    'ap-northeast-2': '241511115815',
-    'ap-south-1': '926022987530',
-    'sa-east-1': '313162186107',
-    'ap-east-1': '416298298123',
-    'me-south-1': '511027370648',
-    'me-central-1': '766358817862',
-}
-
-// Global layer version
-const globalLayerVersion = 3
-
-export function getRemoteDebugLayerForArch(region: string, arch: string): string | undefined {
-    const account = regionToAccount[region]
-
-    if (!account) {
-        return undefined
-    }
-
-    const layerName = arch === 'x86_64' ? 'LDKLayerX86' : 'LDKLayerArm64'
-
-    return `arn:aws:lambda:${region}:${account}:layer:${layerName}:${globalLayerVersion}`
-}
diff --git a/packages/core/src/lambda/remoteDebugging/localProxy.ts b/packages/core/src/lambda/remoteDebugging/localProxy.ts
deleted file mode 100644
index 8b228deeb1..0000000000
--- a/packages/core/src/lambda/remoteDebugging/localProxy.ts
+++ /dev/null
@@ -1,901 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as net from 'net'
-import WebSocket from 'ws'
-import * as crypto from 'crypto'
-import { getLogger } from '../../shared/logger/logger'
-import { v4 as uuidv4 } from 'uuid'
-import * as protobuf from 'protobufjs'
-
-const logger = getLogger()
-
-// Define the message types from the protocol
-enum MessageType {
-    UNKNOWN = 0,
-    DATA = 1,
-    STREAM_START = 2,
-    STREAM_RESET = 3,
-    SESSION_RESET = 4,
-    SERVICE_IDS = 5,
-    CONNECTION_START = 6,
-    CONNECTION_RESET = 7,
-}
-
-// Interface for tunnel info
-export interface TunnelInfo {
-    tunnelId: string
-    sourceToken: string
-    destinationToken: string
-}
-
-// Interface for TCP connection
-interface TcpConnection {
-    socket: net.Socket
-    streamId: number
-    connectionId: number
-}
-
-/**
- * LocalProxy class that handles WebSocket connection to IoT secure tunneling
- * and sets up a TCP adapter as a local proxy
- */
-export class LocalProxy {
-    private ws: WebSocket.WebSocket | undefined = undefined
-    private tcpServer: net.Server | undefined = undefined
-    private tcpConnections: Map = new Map()
-    private isConnected: boolean = false
-    private reconnectAttempts: number = 0
-    private maxReconnectAttempts: number = 10
-    private reconnectInterval: number = 2500 // 2.5 seconds
-    private pingInterval: NodeJS.Timeout | undefined = undefined
-    private serviceId: string = 'WSS'
-    private currentStreamId: number = 1
-    private nextConnectionId: number = 1
-    private localPort: number = 0
-    private region: string = ''
-    private accessToken: string = ''
-    private Message: protobuf.Type | undefined = undefined
-    private clientToken: string = ''
-    private eventHandlers: { [key: string]: any[] } = {}
-    private isDisposed: boolean = false
-
-    constructor() {
-        void this.loadProtobufDefinition()
-    }
-
-    // Define the protobuf schema as a string constant
-    private static readonly protobufSchema = `
-    syntax = "proto3";
-
-    package com.amazonaws.iot.securedtunneling;
-
-    message Message {
-        Type    type         = 1;
-        int32   streamId     = 2;
-        bool    ignorable    = 3;
-        bytes   payload      = 4;
-        string  serviceId = 5;
-        repeated string availableServiceIds = 6;
-        uint32 connectionId = 7;
-
-        enum Type {
-            UNKNOWN = 0;
-            DATA = 1;
-            STREAM_START = 2;
-            STREAM_RESET = 3;
-            SESSION_RESET = 4;
-            SERVICE_IDS = 5;
-            CONNECTION_START = 6;
-            CONNECTION_RESET = 7;
-        }
-    }`
-
-    /**
-     * Load the protobuf definition from the embedded schema string
-     */
-    private async loadProtobufDefinition(): Promise {
-        try {
-            if (this.Message) {
-                // Already loaded, don't parse again
-                return
-            }
-
-            const root = protobuf.parse(LocalProxy.protobufSchema).root
-            this.Message = root.lookupType('com.amazonaws.iot.securedtunneling.Message')
-
-            if (!this.Message) {
-                throw new Error('Failed to load Message type from protobuf definition')
-            }
-
-            logger.debug('Protobuf definition loaded successfully')
-        } catch (error) {
-            logger.error(`Error loading protobuf definition:${error}`)
-            throw error
-        }
-    }
-
-    /**
-     * Start the local proxy
-     * @param region AWS region
-     * @param sourceToken Source token for the tunnel
-     * @param port Local port to listen on
-     */
-    public async start(region: string, sourceToken: string, port: number = 0): Promise {
-        // Reset disposal state when starting
-        this.isDisposed = false
-
-        this.region = region
-        this.accessToken = sourceToken
-
-        try {
-            // Start TCP server first
-            this.localPort = await this.startTcpServer(port)
-
-            // Then connect to WebSocket
-            await this.connectWebSocket()
-
-            return this.localPort
-        } catch (error) {
-            logger.error(`Failed to start local proxy:${error}`)
-            this.stop()
-            throw error
-        }
-    }
-
-    /**
-     * Stop the local proxy and clean up all resources
-     */
-    public stop(): void {
-        if (this.isDisposed) {
-            logger.debug('LocalProxy already stopped, skipping duplicate stop call')
-            return
-        }
-
-        logger.debug('Stopping LocalProxy and cleaning up resources')
-
-        // Cancel any pending reconnect timeouts
-        if (this.eventHandlers['reconnectTimeouts']) {
-            for (const timeoutId of this.eventHandlers['reconnectTimeouts']) {
-                clearTimeout(timeoutId as NodeJS.Timeout)
-            }
-        }
-
-        this.stopPingInterval()
-        this.closeWebSocket()
-        this.closeTcpServer()
-
-        // Reset all state
-        this.clientToken = ''
-        this.isConnected = false
-        this.reconnectAttempts = 0
-        this.currentStreamId = 1
-        this.nextConnectionId = 1
-        this.localPort = 0
-        this.region = ''
-        this.accessToken = ''
-
-        // Mark as disposed to prevent duplicate stop calls
-        this.isDisposed = true
-
-        // Clear any remaining event handlers reference
-        this.eventHandlers = {}
-    }
-
-    /**
-     * Start the TCP server
-     * @param port Port to listen on (0 for random port)
-     * @returns The port the server is listening on
-     */
-    private startTcpServer(port: number): Promise {
-        return new Promise((resolve, reject) => {
-            try {
-                this.tcpServer = net.createServer((socket) => {
-                    this.handleNewTcpConnection(socket)
-                })
-
-                this.tcpServer.on('error', (err) => {
-                    logger.error(`TCP server error:${err}`)
-                })
-
-                this.tcpServer.listen(port, '127.0.0.1', () => {
-                    const address = this.tcpServer?.address() as net.AddressInfo
-                    this.localPort = address.port
-                    logger.debug(`TCP server listening on port ${this.localPort}`)
-                    resolve(this.localPort)
-                })
-            } catch (error) {
-                logger.error(`Failed to start TCP server:${error}`)
-                reject(error)
-            }
-        })
-    }
-
-    /**
-     * Close the TCP server and all connections
-     */
-    private closeTcpServer(): void {
-        if (this.tcpServer) {
-            logger.debug('Closing TCP server and connections')
-
-            // Remove all listeners from the server
-            this.tcpServer.removeAllListeners('error')
-            this.tcpServer.removeAllListeners('connection')
-            this.tcpServer.removeAllListeners('listening')
-
-            // Close all TCP connections with proper error handling
-            for (const connection of this.tcpConnections.values()) {
-                try {
-                    // Remove all listeners before destroying
-                    connection.socket.removeAllListeners('data')
-                    connection.socket.removeAllListeners('error')
-                    connection.socket.removeAllListeners('close')
-                    connection.socket.destroy()
-                } catch (err) {
-                    logger.error(`Error closing TCP connection: ${err}`)
-                }
-            }
-            this.tcpConnections.clear()
-
-            // Close the server with proper error handling and timeout
-            try {
-                // Set a timeout in case server.close() hangs
-                const serverCloseTimeout = setTimeout(() => {
-                    logger.warn('TCP server close timed out, forcing closure')
-                    this.tcpServer = undefined
-                }, 5000)
-
-                this.tcpServer.close(() => {
-                    clearTimeout(serverCloseTimeout)
-                    logger.debug('TCP server closed successfully')
-                    this.tcpServer = undefined
-                })
-            } catch (err) {
-                logger.error(`Error closing TCP server: ${err}`)
-                this.tcpServer = undefined
-            }
-        }
-    }
-
-    /**
-     * Handle a new TCP connection with proper resource management
-     * @param socket The TCP socket
-     */
-    private handleNewTcpConnection(socket: net.Socket): void {
-        if (!this.isConnected || this.isDisposed) {
-            logger.warn('WebSocket not connected or proxy disposed, rejecting TCP connection')
-            socket.destroy()
-            return
-        }
-
-        const connectionId = this.nextConnectionId++
-        const streamId = this.currentStreamId
-
-        logger.debug(`New TCP connection: ${connectionId}`)
-
-        // Track event handlers for this connection
-        const handlers: { [event: string]: (...args: any[]) => void } = {}
-
-        // Data handler
-        const dataHandler = (data: Buffer) => {
-            this.sendData(streamId, connectionId, data)
-        }
-        socket.on('data', dataHandler)
-        handlers.data = dataHandler
-
-        // Error handler
-        const errorHandler = (err: Error) => {
-            logger.error(`TCP connection ${connectionId} error: ${err}`)
-            this.sendConnectionReset(streamId, connectionId)
-
-            // Cleanup handlers on error
-            this.cleanupSocketHandlers(socket, handlers)
-        }
-        socket.on('error', errorHandler)
-        handlers.error = errorHandler
-
-        // Close handler
-        const closeHandler = () => {
-            logger.debug(`TCP connection ${connectionId} closed`)
-
-            // Remove from connections map and send reset
-            this.tcpConnections.delete(connectionId)
-            this.sendConnectionReset(streamId, connectionId)
-
-            // Cleanup handlers on close
-            this.cleanupSocketHandlers(socket, handlers)
-        }
-        socket.on('close', closeHandler)
-        handlers.close = closeHandler
-
-        // Set a timeout to close idle connections after 10 minutes
-        const idleTimeout = setTimeout(
-            () => {
-                if (this.tcpConnections.has(connectionId)) {
-                    logger.debug(`Closing idle TCP connection ${connectionId}`)
-                    socket.destroy()
-                }
-            },
-            10 * 60 * 1000
-        )
-
-        // Clear timeout on socket close
-        socket.once('close', () => {
-            clearTimeout(idleTimeout)
-        })
-
-        // Store the connection
-        const connection: TcpConnection = {
-            socket,
-            streamId,
-            connectionId,
-        }
-        this.tcpConnections.set(connectionId, connection)
-
-        // Send StreamStart for the first connection, ConnectionStart for subsequent ones
-        if (connectionId === 1) {
-            this.sendStreamStart(streamId, connectionId)
-        } else {
-            this.sendConnectionStart(streamId, connectionId)
-        }
-    }
-
-    /**
-     * Helper method to clean up socket event handlers
-     * @param socket The socket to clean up
-     * @param handlers The handlers to remove
-     */
-    private cleanupSocketHandlers(socket: net.Socket, handlers: { [event: string]: (...args: any[]) => void }): void {
-        try {
-            if (handlers.data) {
-                socket.removeListener('data', handlers.data as (...args: any[]) => void)
-            }
-            if (handlers.error) {
-                socket.removeListener('error', handlers.error as (...args: any[]) => void)
-            }
-            if (handlers.close) {
-                socket.removeListener('close', handlers.close as (...args: any[]) => void)
-            }
-        } catch (error) {
-            logger.error(`Error cleaning up socket handlers: ${error}`)
-        }
-    }
-
-    /**
-     * Connect to the WebSocket server with proper event tracking
-     */
-    private async connectWebSocket(): Promise {
-        if (this.ws) {
-            this.closeWebSocket()
-        }
-
-        // Reset for new connection
-        this.isDisposed = false
-
-        return new Promise((resolve, reject) => {
-            try {
-                const url = `wss://data.tunneling.iot.${this.region}.amazonaws.com:443/tunnel?local-proxy-mode=source`
-
-                if (!this.clientToken) {
-                    this.clientToken = uuidv4().replace(/-/g, '')
-                }
-
-                this.ws = new WebSocket.WebSocket(url, ['aws.iot.securetunneling-3.0'], {
-                    headers: {
-                        'access-token': this.accessToken,
-                        'client-token': this.clientToken,
-                    },
-                    handshakeTimeout: 30000, // 30 seconds
-                })
-
-                // Track event listeners for proper cleanup
-                this.eventHandlers['wsOpen'] = []
-                this.eventHandlers['wsMessage'] = []
-                this.eventHandlers['wsClose'] = []
-                this.eventHandlers['wsError'] = []
-                this.eventHandlers['wsPing'] = []
-                this.eventHandlers['wsPong'] = []
-
-                // Open handler
-                const openHandler = () => {
-                    logger.debug('WebSocket connected')
-                    this.isConnected = true
-                    this.reconnectAttempts = 0
-                    this.startPingInterval()
-                    resolve()
-                }
-                this.ws.on('open', openHandler)
-                this.eventHandlers['wsOpen'].push(openHandler)
-
-                // Message handler
-                const messageHandler = (data: WebSocket.RawData) => {
-                    this.handleWebSocketMessage(data)
-                }
-                this.ws.on('message', messageHandler)
-                this.eventHandlers['wsMessage'].push(messageHandler)
-
-                // Close handler
-                const closeHandler = (code: number, reason: Buffer) => {
-                    logger.debug(`WebSocket closed: ${code} ${reason.toString()}`)
-                    this.isConnected = false
-                    this.stopPingInterval()
-
-                    // Only attempt reconnect if we haven't explicitly stopped
-                    if (!this.isDisposed) {
-                        void this.attemptReconnect()
-                    }
-                }
-                this.ws.on('close', closeHandler)
-                this.eventHandlers['wsClose'].push(closeHandler)
-
-                // Error handler
-                const errorHandler = (err: Error) => {
-                    logger.error(`WebSocket error: ${err}`)
-                    reject(err)
-                }
-                this.ws.on('error', errorHandler)
-                this.eventHandlers['wsError'].push(errorHandler)
-
-                // Ping handler
-                const pingHandler = (data: Buffer) => {
-                    // Respond to ping with pong
-                    if (this.ws && this.ws.readyState === WebSocket.OPEN) {
-                        this.ws.pong(data)
-                    }
-                }
-                this.ws.on('ping', pingHandler)
-                this.eventHandlers['wsPing'].push(pingHandler)
-
-                // Pong handler
-                const pongHandler = () => {
-                    logger.debug('Received pong')
-                }
-                this.ws.on('pong', pongHandler)
-                this.eventHandlers['wsPong'].push(pongHandler)
-
-                // Set connection timeout
-                const connectionTimeout = setTimeout(() => {
-                    if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
-                        logger.error('WebSocket connection timed out')
-                        this.closeWebSocket()
-                        reject(new Error('WebSocket connection timed out'))
-                    }
-                }, 35000) // 35 seconds (slightly longer than handshake timeout)
-
-                // Add a handler to clear the timeout on successful connection
-                this.ws.once('open', () => {
-                    clearTimeout(connectionTimeout)
-                })
-            } catch (error) {
-                logger.error(`Failed to connect WebSocket: ${error}`)
-                this.isConnected = false
-                reject(error)
-            }
-        })
-    }
-
-    /**
-     * Close the WebSocket connection with proper cleanup
-     */
-    private closeWebSocket(): void {
-        if (this.ws) {
-            try {
-                logger.debug('Closing WebSocket connection')
-
-                // Remove all event listeners before closing
-                this.ws.removeAllListeners('open')
-                this.ws.removeAllListeners('message')
-                this.ws.removeAllListeners('close')
-                this.ws.removeAllListeners('error')
-                this.ws.removeAllListeners('ping')
-                this.ws.removeAllListeners('pong')
-
-                // Try to close gracefully first
-                if (this.ws.readyState === WebSocket.OPEN) {
-                    // Set timeout in case close hangs
-                    const closeTimeout = setTimeout(() => {
-                        logger.warn('WebSocket close timed out, forcing termination')
-                        if (this.ws) {
-                            try {
-                                this.ws.terminate()
-                            } catch (e) {
-                                // Ignore errors on terminate after timeout
-                            }
-                            this.ws = undefined
-                        }
-                    }, 1000)
-
-                    // Try graceful closure first
-                    this.ws.close(1000, 'Normal Closure')
-
-                    // Set up a handler to clear the timeout if close works normally
-                    this.ws.once('close', () => {
-                        clearTimeout(closeTimeout)
-                    })
-                } else {
-                    // If not open, just terminate
-                    this.ws.terminate()
-                }
-            } catch (error) {
-                logger.error(`Error closing WebSocket: ${error}`)
-            } finally {
-                this.ws = undefined
-            }
-        }
-    }
-
-    /**
-     * Start the ping interval to keep the connection alive
-     */
-    private startPingInterval(): void {
-        this.stopPingInterval()
-
-        // Send ping every 30 seconds to keep the connection alive
-        this.pingInterval = setInterval(() => {
-            if (this.ws && this.ws.readyState === WebSocket.OPEN) {
-                logger.debug('Sending ping')
-                try {
-                    this.ws.ping(crypto.randomBytes(16))
-                } catch (error) {
-                    logger.error(`Error sending ping: ${error}`)
-                }
-            } else {
-                // If websocket is no longer open, stop the interval
-                this.stopPingInterval()
-            }
-        }, 30000)
-    }
-
-    /**
-     * Stop the ping interval with better error handling
-     */
-    private stopPingInterval(): void {
-        try {
-            if (this.pingInterval) {
-                clearInterval(this.pingInterval)
-                this.pingInterval = undefined
-                logger.debug('Ping interval stopped')
-            }
-        } catch (error) {
-            logger.error(`Error stopping ping interval: ${error}`)
-            this.pingInterval = undefined
-        }
-    }
-
-    /**
-     * Attempt to reconnect to the WebSocket server with better resource management
-     */
-    private async attemptReconnect(): Promise {
-        if (this.isDisposed) {
-            logger.debug('LocalProxy is disposed, not attempting reconnect')
-            return
-        }
-
-        if (!this.clientToken) {
-            logger.debug('stop retrying, ws closed manually')
-            return
-        }
-
-        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
-            logger.error('Max reconnect attempts reached')
-            // Clean up resources when max attempts reached
-            this.stop()
-            return
-        }
-
-        this.reconnectAttempts++
-        const delay = this.reconnectInterval * Math.pow(1.5, this.reconnectAttempts - 1)
-
-        logger.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`)
-
-        // Use a tracked timeout that we can clear if needed
-        const reconnectTimeoutId = setTimeout(() => {
-            if (!this.isDisposed) {
-                void this.connectWebSocket().catch((err) => {
-                    logger.error(`Reconnect failed: ${err}`)
-                })
-            } else {
-                logger.debug('Reconnect cancelled because LocalProxy was disposed')
-            }
-        }, delay)
-
-        // Store the timeout ID so it can be cleared if stop() is called
-        if (!this.eventHandlers['reconnectTimeouts']) {
-            this.eventHandlers['reconnectTimeouts'] = []
-        }
-        this.eventHandlers['reconnectTimeouts'].push(reconnectTimeoutId)
-    }
-
-    /**
-     * Handle a WebSocket message
-     * @param data The message data
-     */
-    private handleWebSocketMessage(data: WebSocket.RawData): void {
-        try {
-            // Handle binary data
-            if (Buffer.isBuffer(data)) {
-                let offset = 0
-
-                // Process all messages in the buffer
-                while (offset < data.length) {
-                    // Read the 2-byte length prefix
-                    if (offset + 2 > data.length) {
-                        logger.error('Incomplete message length prefix')
-                        break
-                    }
-
-                    const messageLength = data.readUInt16BE(offset)
-                    offset += 2
-
-                    // Check if we have the complete message
-                    if (offset + messageLength > data.length) {
-                        logger.error('Incomplete message data')
-                        break
-                    }
-
-                    // Extract the message data
-                    const messageData = data.slice(offset, offset + messageLength)
-                    offset += messageLength
-
-                    // Decode and process the message
-                    this.processMessage(messageData)
-                }
-            } else {
-                logger.warn('Received non-buffer WebSocket message')
-            }
-        } catch (error) {
-            logger.error(`Error handling WebSocket message:${error}`)
-        }
-    }
-
-    /**
-     * Process a decoded message
-     * @param messageData The message data
-     */
-    private processMessage(messageData: Buffer): void {
-        try {
-            if (!this.Message) {
-                logger.error('Protobuf Message type not loaded')
-                return
-            }
-
-            // Decode the message
-            const message = this.Message.decode(messageData)
-
-            // Process based on message type
-            const typedMessage = message as any
-            switch (typedMessage.type) {
-                case MessageType.DATA:
-                    this.handleDataMessage(message)
-                    break
-
-                case MessageType.STREAM_RESET:
-                    this.handleStreamReset(message)
-                    break
-
-                case MessageType.CONNECTION_RESET:
-                    this.handleConnectionReset(message)
-                    break
-
-                case MessageType.SESSION_RESET:
-                    this.handleSessionReset()
-                    break
-
-                case MessageType.SERVICE_IDS:
-                    this.handleServiceIds(message)
-                    break
-
-                default:
-                    logger.debug(`Received message of type ${typedMessage.type}`)
-                    break
-            }
-        } catch (error) {
-            logger.error(`Error processing message:${error}`)
-        }
-    }
-
-    /**
-     * Handle a DATA message
-     * @param message The message
-     */
-    private handleDataMessage(message: any): void {
-        const { streamId, connectionId, payload } = message
-
-        // Validate stream ID
-        if (streamId !== this.currentStreamId) {
-            logger.warn(`Received data for invalid stream ID: ${streamId}, current: ${this.currentStreamId}`)
-            return
-        }
-
-        // Find the connection
-        const connection = this.tcpConnections.get(connectionId || 1)
-        if (!connection) {
-            logger.warn(`Received data for unknown connection ID: ${connectionId}`)
-            return
-        }
-
-        logger.debug(`Received data for connection ${connectionId} in stream ${streamId}`)
-
-        // Write data to the TCP socket
-        if (connection.socket.writable) {
-            connection.socket.write(Buffer.from(payload))
-        }
-    }
-
-    /**
-     * Handle a STREAM_RESET message
-     * @param message The message
-     */
-    private handleStreamReset(message: any): void {
-        const { streamId } = message
-
-        logger.debug(`Received STREAM_RESET for stream ${streamId}`)
-
-        // Close all connections for this stream
-        for (const [connectionId, connection] of this.tcpConnections.entries()) {
-            if (connection.streamId === streamId) {
-                connection.socket.destroy()
-                this.tcpConnections.delete(connectionId)
-            }
-        }
-    }
-
-    /**
-     * Handle a CONNECTION_RESET message
-     * @param message The message
-     */
-    private handleConnectionReset(message: any): void {
-        const { streamId, connectionId } = message
-
-        logger.debug(`Received CONNECTION_RESET for connection ${connectionId} in stream ${streamId}`)
-
-        // Close the specific connection
-        const connection = this.tcpConnections.get(connectionId)
-        if (connection) {
-            connection.socket.destroy()
-            this.tcpConnections.delete(connectionId)
-        }
-    }
-
-    /**
-     * Handle a SESSION_RESET message
-     */
-    private handleSessionReset(): void {
-        logger.debug('Received SESSION_RESET')
-
-        // Close all connections
-        for (const connection of this.tcpConnections.values()) {
-            connection.socket.destroy()
-        }
-        this.tcpConnections.clear()
-
-        // Increment stream ID for new connections
-        this.currentStreamId++
-    }
-
-    /**
-     * Handle a SERVICE_IDS message
-     * @param message The message
-     */
-    private handleServiceIds(message: any): void {
-        const { availableServiceIds } = message
-
-        logger.debug(`Received SERVICE_IDS: ${availableServiceIds}`)
-
-        // Validate service IDs
-        if (Array.isArray(availableServiceIds) && availableServiceIds.length > 0) {
-            // Use the first service ID
-            this.serviceId = availableServiceIds[0]
-        }
-    }
-
-    /**
-     * Send a message over the WebSocket
-     * @param messageType The message type
-     * @param streamId The stream ID
-     * @param connectionId The connection ID
-     * @param payload The payload
-     */
-    private sendMessage(messageType: MessageType, streamId: number, connectionId: number, payload?: Buffer): void {
-        if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
-            logger.warn('WebSocket not connected, cannot send message')
-            return
-        }
-
-        if (!this.Message) {
-            logger.error('Protobuf Message type not loaded')
-            return
-        }
-
-        try {
-            // Create the message
-            const message = {
-                type: messageType,
-                streamId,
-                connectionId,
-                serviceId: this.serviceId,
-            }
-
-            // Add payload if provided
-            const typedMessage: any = message
-            if (payload) {
-                typedMessage.payload = payload
-            }
-
-            // Verify and encode the message
-            const err = this.Message.verify(message)
-            if (err) {
-                throw new Error(`Invalid message: ${err}`)
-            }
-
-            const encodedMessage = this.Message.encode(this.Message.create(message)).finish()
-
-            // Create the frame with 2-byte length prefix
-            const frameLength = encodedMessage.length
-            const frame = Buffer.alloc(2 + frameLength)
-
-            // Write the length prefix
-            frame.writeUInt16BE(frameLength, 0)
-
-            // Copy the encoded message
-            Buffer.from(encodedMessage).copy(frame, 2)
-
-            // Send the frame
-            if (this.ws?.readyState === WebSocket.OPEN) {
-                this.ws.send(frame)
-            } else {
-                logger.warn('WebSocket connection lost before sending message')
-            }
-        } catch (error) {
-            logger.error(`Error sending message: ${error}`)
-        }
-    }
-
-    /**
-     * Send a STREAM_START message
-     * @param streamId The stream ID
-     * @param connectionId The connection ID
-     */
-    private sendStreamStart(streamId: number, connectionId: number): void {
-        logger.debug(`Sending STREAM_START for stream ${streamId}, connection ${connectionId}`)
-        this.sendMessage(MessageType.STREAM_START, streamId, connectionId)
-    }
-
-    /**
-     * Send a CONNECTION_START message
-     * @param streamId The stream ID
-     * @param connectionId The connection ID
-     */
-    private sendConnectionStart(streamId: number, connectionId: number): void {
-        logger.debug(`Sending CONNECTION_START for stream ${streamId}, connection ${connectionId}`)
-        this.sendMessage(MessageType.CONNECTION_START, streamId, connectionId)
-    }
-
-    /**
-     * Send a CONNECTION_RESET message
-     * @param streamId The stream ID
-     * @param connectionId The connection ID
-     */
-    private sendConnectionReset(streamId: number, connectionId: number): void {
-        logger.debug(`Sending CONNECTION_RESET for stream ${streamId}, connection ${connectionId}`)
-        this.sendMessage(MessageType.CONNECTION_RESET, streamId, connectionId)
-    }
-
-    /**
-     * Send data over the WebSocket
-     * @param streamId The stream ID
-     * @param connectionId The connection ID
-     * @param data The data to send
-     */
-    private sendData(streamId: number, connectionId: number, data: Buffer): void {
-        // Split data into chunks if it exceeds the maximum payload size (63kb)
-        const maxChunkSize = 63 * 1024 // 63kb
-
-        for (let offset = 0; offset < data.length; offset += maxChunkSize) {
-            const chunk = data.slice(offset, offset + maxChunkSize)
-            this.sendMessage(MessageType.DATA, streamId, connectionId, chunk)
-        }
-    }
-}
diff --git a/packages/core/src/lambda/remoteDebugging/localStackLambdaDebugger.ts b/packages/core/src/lambda/remoteDebugging/localStackLambdaDebugger.ts
deleted file mode 100644
index d74b6ac347..0000000000
--- a/packages/core/src/lambda/remoteDebugging/localStackLambdaDebugger.ts
+++ /dev/null
@@ -1,164 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { FunctionConfiguration } from '@aws-sdk/client-lambda'
-import globals from '../../shared/extensionGlobals'
-import { persistLambdaSnapshot, type LambdaDebugger, type DebugConfig } from './lambdaDebugger'
-import { getLambdaClientWithAgent, getLambdaDebugUserAgent } from './utils'
-import { getLogger } from '../../shared/logger/logger'
-import { ToolkitError } from '../../shared/errors'
-
-export class LocalStackLambdaDebugger implements LambdaDebugger {
-    private debugConfig: DebugConfig
-
-    constructor(debugConfig: DebugConfig) {
-        this.debugConfig = debugConfig
-    }
-
-    public async checkHealth(): Promise {
-        const endpointUrl = globals.awsContext.getCredentialEndpointUrl()
-        const localStackHealthUrl = `${endpointUrl}/_localstack/health`
-        const localStackNotRunningMessage = 'LocalStack is not reachable. Ensure LocalStack is running!'
-        try {
-            const response = await fetch(localStackHealthUrl)
-            if (!response.ok) {
-                getLogger().error(`LocalStack health check failed with status ${response.status}`)
-                throw new ToolkitError(localStackNotRunningMessage)
-            }
-        } catch (error) {
-            throw ToolkitError.chain(error, localStackNotRunningMessage)
-        }
-    }
-
-    public async setup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise {
-        // No function update and version publishing needed for LocalStack
-        this.debugConfig.shouldPublishVersion = false
-
-        progress.report({ message: 'Creating LocalStack debug configuration...' })
-        const endpointUrl = globals.awsContext.getCredentialEndpointUrl()
-        const localStackLDMUrl = `${endpointUrl}/_aws/lambda/debug_configs/${functionConfig.FunctionArn}:$LATEST`
-        const response = await fetch(localStackLDMUrl, {
-            method: 'PUT',
-            body: JSON.stringify({
-                port: this.debugConfig.port,
-                user_agent: getLambdaDebugUserAgent(),
-            }),
-        })
-
-        if (!response.ok) {
-            const error = await this.errorFromResponse(response)
-            if (error.startsWith('UnsupportedLocalStackVersion')) {
-                void vscode.window.showErrorMessage(`${error}`, 'Update LocalStack Docker image').then((selection) => {
-                    if (selection) {
-                        const terminal = vscode.window.createTerminal('Update LocalStack Docker image')
-                        terminal.show()
-                        terminal.sendText('localstack update docker-images')
-                    }
-                })
-            } else {
-                void vscode.window.showErrorMessage(error)
-            }
-
-            throw ToolkitError.chain(
-                error,
-                `Failed to create LocalStack debug configuration for Lambda function ${functionConfig.FunctionName}.`
-            )
-        }
-
-        const json = await response.json()
-        this.debugConfig.port = json.port
-    }
-
-    private async errorFromResponse(response: Response): Promise {
-        const isXml = response.headers.get('content-type') === 'application/xml'
-        if (isXml) {
-            return 'UnsupportedLocalStackVersion: Your current LocalStack version does not support Lambda remote debugging. Update LocalStack and check your license.'
-        }
-
-        const isJson = response.headers.get('content-type') === 'application/json'
-        if (isJson) {
-            const json = await response.json()
-            if (json.error.type !== undefined && json.error.message !== undefined) {
-                return `${json.error.type}: ${json.error.message}`
-            }
-        }
-
-        return 'Unknown error'
-    }
-
-    public async waitForSetup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise {
-        if (!functionConfig?.FunctionArn) {
-            throw new ToolkitError('Could not retrieve Lambda function configuration')
-        }
-
-        progress.report({ message: 'Waiting for Lambda function to become Active...' })
-        getLogger().info(`Waiting for ${functionConfig.FunctionArn} to become Active...`)
-        try {
-            await getLambdaClientWithAgent(region).waitForActive(functionConfig.FunctionArn)
-        } catch (error) {
-            throw ToolkitError.chain(error, 'Lambda function failed to become Active.')
-        }
-
-        progress.report({ message: 'Waiting for startup of execution environment and debugger...' })
-        getLogger().info(`Waiting for ${functionConfig.FunctionArn} to startup execution environment and debugger...`)
-        const endpointUrl = globals.awsContext.getCredentialEndpointUrl()
-        const localStackLDMUrl = `${endpointUrl}/_aws/lambda/debug_configs/${functionConfig.FunctionArn}:$LATEST?debug_server_ready_timeout=300`
-        // Blocking call to wait for the Lambda function debug server to be running. LocalStack probes the debug server.
-        const response = await fetch(localStackLDMUrl, { method: 'GET' })
-        if (!response.ok) {
-            const error = await this.errorFromResponse(response)
-            throw ToolkitError.chain(
-                new Error(error),
-                `Failed to startup execution environment or debugger for Lambda function ${functionConfig.FunctionName}.`
-            )
-        }
-
-        const json = await response.json()
-        if (json.is_debug_server_running !== true) {
-            throw new ToolkitError(
-                `Debug server on port ${this.debugConfig.port} is not running for Lambda function ${functionConfig.FunctionName}.`
-            )
-        }
-
-        getLogger().info(`${functionConfig.FunctionArn} is ready for debugging on port ${this.debugConfig.port}.`)
-    }
-
-    public async waitForFunctionUpdates(
-        progress: vscode.Progress<{ message?: string; increment?: number }>
-    ): Promise {
-        // No additional steps needed for LocalStack:
-        // a) Port probing ensures the debug server is ready
-        // b) Invokes for debug-enabled await being served until the debugger is connected
-    }
-
-    public async cleanup(functionConfig: FunctionConfiguration): Promise {
-        await vscode.commands.executeCommand('workbench.action.debug.stop')
-
-        const endpointUrl = globals.awsContext.getCredentialEndpointUrl()
-        const localStackLDMUrl = `${endpointUrl}/_aws/lambda/debug_configs/${functionConfig.FunctionArn}:$LATEST`
-        const response = await fetch(localStackLDMUrl, { method: 'DELETE' })
-        if (!response.ok) {
-            const error = await this.errorFromResponse(response)
-            getLogger().warn(
-                `Failed to remove LocalStack debug configuration for ${functionConfig.FunctionArn}. ${error}`
-            )
-            throw new ToolkitError(
-                `Failed to remove LocalStack debug configuration for Lambda function ${functionConfig.FunctionName}.`
-            )
-        }
-
-        await persistLambdaSnapshot(undefined)
-        getLogger().info(`Removed LocalStack debug configuration for ${functionConfig.FunctionArn}`)
-    }
-}
diff --git a/packages/core/src/lambda/remoteDebugging/remoteLambdaDebugger.ts b/packages/core/src/lambda/remoteDebugging/remoteLambdaDebugger.ts
deleted file mode 100644
index afc9f83abc..0000000000
--- a/packages/core/src/lambda/remoteDebugging/remoteLambdaDebugger.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { Architecture, FunctionConfiguration } from '@aws-sdk/client-lambda'
-import { persistLambdaSnapshot, type LambdaDebugger, type DebugConfig } from './lambdaDebugger'
-import { getLogger } from '../../shared/logger/logger'
-import { isTunnelInfo, LdkClient } from './ldkClient'
-import type { TunnelInfo } from './ldkClient'
-import { ToolkitError } from '../../shared/errors'
-import { getRemoteDebugLayerForArch } from './ldkLayers'
-
-export function getRemoteDebugLayer(
-    region: string | undefined,
-    architectures: Architecture[] | undefined
-): string | undefined {
-    if (!region || !architectures) {
-        return undefined
-    }
-    if (architectures.includes('x86_64')) {
-        return getRemoteDebugLayerForArch(region, 'x86_64')
-    }
-    if (architectures.includes('arm64')) {
-        return getRemoteDebugLayerForArch(region, 'arm64')
-    }
-    return undefined
-}
-
-export interface QualifierProxy {
-    setQualifier(qualifier: string): void
-    getQualifier(): string | undefined
-}
-
-export class RemoteLambdaDebugger implements LambdaDebugger {
-    private debugConfig: DebugConfig
-    private debugDeployPromise: Promise | undefined
-    private tunnelInfo: TunnelInfo | undefined
-    private qualifierProxy: QualifierProxy
-
-    constructor(debugConfig: DebugConfig, qualifierProxy: QualifierProxy) {
-        this.debugConfig = debugConfig
-        this.qualifierProxy = qualifierProxy
-    }
-
-    public async checkHealth(): Promise {
-        // We assume AWS is always available
-    }
-
-    public async setup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise {
-        const ldkClient = LdkClient.instance
-        // Create or reuse tunnel
-        progress.report({ message: 'Creating secure tunnel...' })
-        getLogger().info('Creating secure tunnel...')
-        this.tunnelInfo = await ldkClient.createOrReuseTunnel(region)
-        if (!this.tunnelInfo) {
-            throw new ToolkitError(`Empty tunnel info response, please retry: ${this.tunnelInfo}`)
-        }
-
-        if (!isTunnelInfo(this.tunnelInfo)) {
-            throw new ToolkitError(`Invalid tunnel info response: ${this.tunnelInfo}`)
-        }
-        // start update lambda function, await in the end
-        // Create debug deployment
-        progress.report({ message: 'Configuring Lambda function for debugging...' })
-        getLogger().info('Configuring Lambda function for debugging...')
-
-        const layerArn = this.debugConfig.layerArn ?? getRemoteDebugLayer(region, functionConfig.Architectures)
-        if (!layerArn) {
-            throw new ToolkitError(`No Layer Arn is provided`)
-        }
-        // start this request and await in the end
-        this.debugDeployPromise = ldkClient.createDebugDeployment(
-            functionConfig,
-            this.tunnelInfo.destinationToken,
-            this.debugConfig.lambdaTimeout ?? 900,
-            this.debugConfig.shouldPublishVersion,
-            layerArn,
-            progress
-        )
-    }
-
-    public async waitForSetup(
-        progress: vscode.Progress<{ message?: string; increment?: number }>,
-        functionConfig: FunctionConfiguration,
-        region: string
-    ): Promise {
-        if (!this.tunnelInfo) {
-            throw new ToolkitError(`Empty tunnel info response, please retry: ${this.tunnelInfo}`)
-        }
-
-        // Start local proxy with timeout and better error handling
-        progress.report({ message: 'Starting local proxy...' })
-
-        const proxyStartTimeout = new Promise((_, reject) => {
-            setTimeout(() => reject(new Error('Local proxy start timed out')), 30000)
-        })
-
-        const proxyStartAttempt = LdkClient.instance.startProxy(
-            region,
-            this.tunnelInfo.sourceToken,
-            this.debugConfig.port
-        )
-
-        const proxyStarted = await Promise.race([proxyStartAttempt, proxyStartTimeout])
-
-        if (!proxyStarted) {
-            throw new ToolkitError('Failed to start local proxy')
-        }
-        getLogger().info('Local proxy started successfully')
-    }
-
-    public async waitForFunctionUpdates(
-        progress: vscode.Progress<{ message?: string; increment?: number }>
-    ): Promise {
-        // wait until lambda function update is completed
-        progress.report({ message: 'Waiting for function update...' })
-        const qualifier = await this.debugDeployPromise
-        if (!qualifier || qualifier === 'Failed') {
-            throw new ToolkitError('Failed to configure Lambda function for debugging')
-        }
-        // store the published version for debugging in version
-        if (this.debugConfig.shouldPublishVersion) {
-            // we already reverted
-            this.qualifierProxy.setQualifier(qualifier)
-        }
-    }
-
-    public async cleanup(functionConfig: FunctionConfiguration): Promise {
-        const ldkClient = LdkClient.instance
-        if (!functionConfig?.FunctionArn) {
-            throw new ToolkitError('No saved configuration found during cleanup')
-        }
-
-        getLogger().info(`Removing debug deployment for function: ${functionConfig.FunctionName}`)
-
-        await vscode.commands.executeCommand('workbench.action.debug.stop')
-        // Then stop the proxy (with more reliable error handling)
-        getLogger().info('Stopping proxy during cleanup')
-        await ldkClient.stopProxy()
-        // Ensure our resources are properly cleaned up
-        const qualifier = this.qualifierProxy.getQualifier()
-        if (qualifier) {
-            await ldkClient.deleteDebugVersion(functionConfig?.FunctionArn, qualifier)
-        }
-        if (await ldkClient.removeDebugDeployment(functionConfig, true)) {
-            await persistLambdaSnapshot(undefined)
-        }
-    }
-}
diff --git a/packages/core/src/lambda/remoteDebugging/utils.ts b/packages/core/src/lambda/remoteDebugging/utils.ts
deleted file mode 100644
index fade27c21c..0000000000
--- a/packages/core/src/lambda/remoteDebugging/utils.ts
+++ /dev/null
@@ -1,56 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { IoTSecureTunnelingClient } from '@aws-sdk/client-iotsecuretunneling'
-import { DefaultLambdaClient } from '../../shared/clients/lambdaClient'
-import { getUserAgentPairs, userAgentPairsToString } from '../../shared/telemetry/util'
-import globals from '../../shared/extensionGlobals'
-import type { UserAgent } from '@aws-sdk/types'
-
-const customUserAgentName = 'LAMBDA-DEBUG'
-const customUserAgentVersion = '1.0.0'
-
-export function getLambdaClientWithAgent(region: string, customUserAgent?: UserAgent): DefaultLambdaClient {
-    if (!customUserAgent) {
-        customUserAgent = getLambdaUserAgentPairs()
-    }
-    return new DefaultLambdaClient(region, customUserAgent)
-}
-
-/**
- * Returns properly formatted UserAgent pairs for AWS SDK v3
- */
-export function getLambdaDebugUserAgentPairs(): UserAgent {
-    return [
-        [customUserAgentName, customUserAgentVersion],
-        ...getUserAgentPairs({ includePlatform: true, includeClientId: true }),
-    ]
-}
-
-/**
- * Returns properly formatted UserAgent pairs for AWS SDK v3
- */
-export function getLambdaUserAgentPairs(): UserAgent {
-    return getUserAgentPairs({ includePlatform: true, includeClientId: true })
-}
-
-/**
- * Returns user agent string for Lambda debugging in traditional format.
- * Example: "LAMBDA-DEBUG/1.0.0 AWS-Toolkit-For-VSCode/testPluginVersion Visual-Studio-Code/1.105.1 ClientId/11111111-1111-1111-1111-111111111111"
- */
-export function getLambdaDebugUserAgent(): string {
-    return userAgentPairsToString(getLambdaDebugUserAgentPairs())
-}
-
-export function getIoTSTClientWithAgent(region: string): IoTSecureTunnelingClient {
-    return globals.sdkClientBuilderV3.createAwsService({
-        serviceClient: IoTSecureTunnelingClient,
-        clientOptions: {
-            customUserAgent: getLambdaDebugUserAgentPairs(),
-            region,
-        },
-        userAgent: false,
-    })
-}
diff --git a/packages/core/src/lambda/uriHandlers.ts b/packages/core/src/lambda/uriHandlers.ts
deleted file mode 100644
index 6d9129c37f..0000000000
--- a/packages/core/src/lambda/uriHandlers.ts
+++ /dev/null
@@ -1,58 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-
-import { SearchParams } from '../shared/vscode/uriHandler'
-import { openLambdaFolderForEdit } from './commands/editLambda'
-import { showConfirmationMessage } from '../shared/utilities/messages'
-import globals from '../shared/extensionGlobals'
-import { telemetry } from '../shared/telemetry/telemetry'
-import { ToolkitError } from '../shared/errors'
-
-const localize = nls.loadMessageBundle()
-
-export function registerLambdaUriHandler() {
-    async function openFunctionHandler(params: ReturnType) {
-        await telemetry.lambda_uriHandler.run(async () => {
-            try {
-                if (params.isCfn === 'true') {
-                    const response = await showConfirmationMessage({
-                        prompt: localize(
-                            'AWS.lambda.open.confirmInStack',
-                            'The function you are attempting to open is in a CloudFormation stack. Editing the function code could lead to stack drift.'
-                        ),
-                        confirm: localize('AWS.lambda.open.confirmStack', 'Confirm'),
-                        cancel: localize('AWS.lambda.open.cancelStack', 'Cancel'),
-                    })
-                    if (!response) {
-                        return
-                    }
-                }
-                await openLambdaFolderForEdit(params.functionName, params.region)
-            } catch (e) {
-                throw new ToolkitError(`Unable to get function ${params.functionName} in region ${params.region}: ${e}`)
-            }
-        })
-    }
-
-    return vscode.Disposable.from(
-        globals.uriHandler.onPath('/lambda/load-function', openFunctionHandler, parseOpenParams)
-    )
-}
-
-// Sample url:
-// vscode://AmazonWebServices.amazon-q-vscode/lambda/load-function?functionName=fnf-func-1®ion=us-east-1&isCfn=true
-export function parseOpenParams(query: SearchParams) {
-    return {
-        functionName: query.getOrThrow(
-            'functionName',
-            localize('AWS.lambda.open.missingName', 'A function name must be provided')
-        ),
-        region: query.getOrThrow('region', localize('AWS.lambda.open.missingRegion', 'A region must be provided')),
-        isCfn: query.get('isCfn'),
-    }
-}
diff --git a/packages/core/src/lambda/utils.ts b/packages/core/src/lambda/utils.ts
deleted file mode 100644
index 9b8ffcb884..0000000000
--- a/packages/core/src/lambda/utils.ts
+++ /dev/null
@@ -1,214 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-const localize = nls.loadMessageBundle()
-
-import path from 'path'
-import xml2js = require('xml2js')
-import { FunctionConfiguration, LayerVersionsListItem } from '@aws-sdk/client-lambda'
-import * as vscode from 'vscode'
-import { CloudFormationClient, StackSummary } from '../shared/clients/cloudFormation'
-import { DefaultLambdaClient, LambdaClient } from '../shared/clients/lambdaClient'
-import { getFamily, getNodeMajorVersion, RuntimeFamily } from './models/samLambdaRuntime'
-import { getLogger } from '../shared/logger/logger'
-import { HttpResourceFetcher } from '../shared/resourcefetcher/httpResourceFetcher'
-import { FileResourceFetcher } from '../shared/resourcefetcher/fileResourceFetcher'
-import { sampleRequestManifestPath } from './constants'
-import globals from '../shared/extensionGlobals'
-import { tempDirPath } from '../shared/filesystemUtilities'
-import { LambdaFunction } from './commands/uploadLambda'
-import { fs } from '../shared/fs/fs'
-
-export async function* listCloudFormationStacks(client: CloudFormationClient): AsyncIterableIterator {
-    // TODO: this 'loading' message needs to go under each regional entry
-    // in the explorer, and be removed when that region's query completes
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.loading.cloudFormation', 'Loading CloudFormation Stacks...')
-    )
-
-    try {
-        yield* client.listStacks()
-    } finally {
-        status.dispose()
-    }
-}
-
-export async function* listLambdaFunctions(client: LambdaClient): AsyncIterableIterator {
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.loading.lambda', 'Loading Lambdas...')
-    )
-
-    try {
-        yield* client.listFunctions()
-    } finally {
-        if (status) {
-            status.dispose()
-        }
-    }
-}
-
-export async function* listLayerVersions(
-    client: LambdaClient,
-    name: string
-): AsyncIterableIterator {
-    const status = vscode.window.setStatusBarMessage(
-        localize('AWS.message.statusBar.loading.lambda', 'Loading Lambda Layer Versions...')
-    )
-
-    try {
-        yield* client.listLayerVersions(name)
-    } finally {
-        if (status) {
-            status.dispose()
-        }
-    }
-}
-
-/**
- * Returns filename and function name corresponding to a Lambda.FunctionConfiguration
- * Only works for supported languages (Python/JS)
- * @param configuration Lambda configuration object from getFunction
- */
-export function getLambdaDetails(configuration: FunctionConfiguration): {
-    fileName: string
-    functionName: string
-} {
-    let runtimeExtension: string
-    switch (getFamily(configuration.Runtime!)) {
-        case RuntimeFamily.Python:
-            runtimeExtension = 'py'
-            break
-        case RuntimeFamily.NodeJS: {
-            const nodeVersion = getNodeMajorVersion(configuration.Runtime)
-            if (nodeVersion && nodeVersion >= 18) {
-                // node18+ defaults to using the .mjs extension
-                runtimeExtension = 'mjs'
-            } else {
-                runtimeExtension = 'js'
-            }
-            break
-        }
-        case RuntimeFamily.Ruby:
-            runtimeExtension = 'rb'
-            break
-        default:
-            throw new Error(`Toolkit does not currently support imports for runtime: ${configuration.Runtime}`)
-    }
-
-    const handlerArr = configuration.Handler!.split('.')
-
-    return {
-        fileName: `${handlerArr.slice(0, handlerArr.length - 1).join('.')}.${runtimeExtension}`,
-        functionName: handlerArr[handlerArr.length - 1]!,
-    }
-}
-
-export interface SampleRequest {
-    name: string | undefined
-    filename: string | undefined
-}
-
-interface SampleRequestManifest {
-    requests: {
-        request: SampleRequest[]
-    }
-}
-
-export async function getSampleLambdaPayloads(): Promise {
-    const logger = getLogger()
-    const sampleInput = await getSampleRequestManifest()
-
-    if (!sampleInput) {
-        throw new Error('Unable to retrieve Sample Request manifest')
-    }
-
-    logger.debug(`Loaded: ${sampleInput}`)
-
-    const inputs: SampleRequest[] = []
-
-    xml2js.parseString(sampleInput, { explicitArray: false }, (err: Error | null, result: SampleRequestManifest) => {
-        if (err) {
-            return
-        }
-
-        inputs.push(...result.requests.request)
-    })
-
-    return inputs
-}
-
-async function getSampleRequestManifest(): Promise {
-    const httpResp = await new HttpResourceFetcher(sampleRequestManifestPath, { showUrl: true }).get()
-    if (!httpResp) {
-        const fileResp = new FileResourceFetcher(globals.manifestPaths.lambdaSampleRequests)
-        return fileResp.get()
-    }
-    return httpResp.text()
-}
-
-function getInfoLocation(lambda: LambdaFunction): string {
-    return path.join(getTempRegionLocation(lambda.region), `.${lambda.name}`)
-}
-
-export async function getCodeShaLive(lambda: LambdaFunction): Promise {
-    const lambdaClient = new DefaultLambdaClient(lambda.region)
-    const func = await lambdaClient.getFunction(lambda.name)
-    return func.Configuration?.CodeSha256
-}
-
-export async function compareCodeSha(lambda: LambdaFunction): Promise {
-    const local = await getFunctionInfo(lambda, 'sha')
-    const remote = await getCodeShaLive(lambda)
-    getLogger().info(`local: ${local}, remote: ${remote}`)
-    return local === remote
-}
-
-export interface FunctionInfo {
-    lastDeployed?: number
-    undeployed?: boolean
-    sha?: string
-    handlerFile?: string
-}
-
-export async function getFunctionInfo(lambda: LambdaFunction, field?: K) {
-    try {
-        const data = JSON.parse(await fs.readFileText(getInfoLocation(lambda)))
-        getLogger().debug('Data returned from getFunctionInfo for %s: %O', lambda.name, data)
-        return field ? data[field] : data
-    } catch {
-        return field ? undefined : {}
-    }
-}
-
-export async function setFunctionInfo(lambda: LambdaFunction, info: Partial) {
-    try {
-        const existing = await getFunctionInfo(lambda)
-        const updated: FunctionInfo = {
-            lastDeployed: info.lastDeployed ?? existing.lastDeployed,
-            undeployed: info.undeployed ?? true,
-            sha: info.sha ?? (await getCodeShaLive(lambda)),
-            handlerFile: info.handlerFile ?? existing.handlerFile,
-        }
-        await fs.writeFile(getInfoLocation(lambda), JSON.stringify(updated))
-    } catch (err) {
-        getLogger().warn(`codesha: unable to save information at key "${lambda.name}: %s"`, err)
-    }
-}
-
-export const lambdaTempPath = path.join(tempDirPath, 'lambda')
-
-export function getTempRegionLocation(region: string) {
-    return path.join(lambdaTempPath, region)
-}
-
-export function getTempLocation(functionName: string, region: string) {
-    return path.join(getTempRegionLocation(region), functionName)
-}
-
-// LocalStack hot-reloading: https://docs.localstack.cloud/aws/tooling/lambda-tools/hot-reloading/
-export function isHotReloadingFunction(codeSha256: string | undefined): boolean {
-    return codeSha256?.startsWith('hot-reloading') ?? false
-}
diff --git a/packages/core/src/lambda/vue/configEditor/index.ts b/packages/core/src/lambda/vue/configEditor/index.ts
deleted file mode 100644
index 270392b1f6..0000000000
--- a/packages/core/src/lambda/vue/configEditor/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { createApp } from 'vue'
-import component from './samInvokeComponent.vue'
-
-const create = () => createApp(component)
-const app = create()
-app.mount('#vue-app')
-
-window.addEventListener('remount', () => {
-    app.unmount()
-    create().mount('#vue-app')
-})
diff --git a/packages/core/src/lambda/vue/configEditor/samInvoke.css b/packages/core/src/lambda/vue/configEditor/samInvoke.css
deleted file mode 100644
index 9ca2c8ef45..0000000000
--- a/packages/core/src/lambda/vue/configEditor/samInvoke.css
+++ /dev/null
@@ -1,177 +0,0 @@
-.section-header {
-    margin: 0px;
-    margin-bottom: 10px;
-    display: flex;
-    justify-content: flex-start;
-}
-
-textarea {
-    color: var(--vscode-settings-textInputForeground);
-    background: var(--vscode-settings-textInputBackground);
-    border: 1px solid var(--vscode-settings-textInputBorder);
-}
-
-.config-item {
-    border-bottom: 1px solid var(--vscode-menubar-selectionBackground);
-    display: grid;
-    grid-template-columns: 1fr 3fr;
-    padding: 8px 0px;
-}
-
-.col2 {
-    grid-column: 2;
-}
-
-.data-view {
-    display: none;
-    border: dashed rgb(218, 31, 31) 1px;
-    color: rgb(218, 31, 31);
-}
-
-.required {
-    color: red;
-}
-
-#form-title {
-    font-size: large;
-    font-weight: bold;
-}
-
-.form-buttons {
-    margin-left: 20px;
-}
-
-.margin-bottom-16 {
-    margin-bottom: 16px;
-}
-
-.header-buttons {
-    display: flex;
-    align-items: center;
-    margin-bottom: 20px;
-}
-
-#target-type-selector {
-    margin-bottom: 15px;
-    margin-left: 8px;
-}
-
-.form-row {
-    display: grid;
-    grid-template-columns: 150px 1fr;
-    margin-bottom: 10px;
-}
-
-.form-control {
-    min-width: 170%; /* Set a minimum width */
-    width: 100%; /* Allow the width to adjust based on content */
-    display: inline-block;
-    flex-grow: 1;
-    margin-right: 0.5rem;
-}
-
-.payload-options-button {
-    display: grid;
-    align-items: center;
-    border: none;
-    padding: 5px 10px;
-    cursor: pointer;
-    font-size: 0.9em;
-    margin-bottom: 10px;
-}
-
-.payload-options-buttons {
-    display: flex;
-    align-items: center;
-    margin-top: 10px;
-    margin-bottom: 10px;
-}
-
-.Icontainer {
-    margin-inline: auto;
-    margin-top: 5rem;
-}
-
-.container {
-    width: 574px;
-    height: 824px;
-    top: 18px;
-    gap: 20px;
-    margin: auto;
-    left: 688px;
-    background-color: var(--vscode-editor-background);
-}
-
-.container em {
-    display: block;
-    text-align: justify;
-}
-
-.button-theme-primary {
-    color: var(--vscode-button-foreground);
-    background: var(--vscode-button-background);
-    border: 1px solid var(--vscode-button-border);
-    padding: 8px 12px;
-}
-.button-theme-primary:hover:not(:disabled) {
-    background: var(--vscode-button-hoverBackground);
-    cursor: pointer;
-}
-.button-theme-secondary {
-    color: var(--vscode-button-secondaryForeground);
-    background: var(--vscode-button-secondaryBackground);
-    border: 1px solid var(--vscode-button-border);
-    padding: 8px 12px;
-}
-.button-theme-secondary:hover:not(:disabled) {
-    background: var(--vscode-button-secondaryHoverBackground);
-    cursor: pointer;
-}
-
-.formfield {
-    display: flex;
-    align-items: center;
-    margin-bottom: 0.5rem;
-}
-
-.payload-options-buttons {
-    display: flex;
-    align-items: center;
-    margin-top: 10px;
-    margin-bottom: 10px;
-}
-
-.radio-selector {
-    width: 15px;
-    height: 15px;
-    border-radius: 50%;
-}
-
-.label-selector {
-    padding-left: 7px;
-    font-weight: 500;
-    font-size: 13px;
-    line-height: 15.51px;
-    text-align: center;
-}
-
-.form-row-select {
-    width: 387px;
-    height: 28px;
-    border: 1px;
-    border-radius: 5px;
-    gap: 4px;
-    padding: 2px 8px;
-}
-
-.form-row-event-select {
-    width: 244px;
-    height: 28px;
-    margin-bottom: 15px;
-    margin-left: 8px;
-}
-
-.runtime-description {
-    font-size: 12px;
-    margin-top: 5px;
-}
diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts
deleted file mode 100644
index 2f00d8a437..0000000000
--- a/packages/core/src/lambda/vue/configEditor/samInvokeBackend.ts
+++ /dev/null
@@ -1,517 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as nls from 'vscode-nls'
-import { LaunchConfiguration } from '../../../shared/debug/launchConfiguration'
-import { getLogger } from '../../../shared/logger/logger'
-import { HttpResourceFetcher } from '../../../shared/resourcefetcher/httpResourceFetcher'
-import {
-    AwsSamDebuggerConfiguration,
-    isCodeTargetProperties,
-    isTemplateTargetProperties,
-    TemplateTargetProperties,
-} from '../../../shared/sam/debugger/awsSamDebugConfiguration'
-import {
-    DefaultAwsSamDebugConfigurationValidator,
-    resolveWorkspaceFolderVariable,
-} from '../../../shared/sam/debugger/awsSamDebugConfigurationValidator'
-import * as input from '../../../shared/ui/input'
-import * as picker from '../../../shared/ui/picker'
-import { addCodiconToString } from '../../../shared/utilities/textUtilities'
-import { sampleRequestPath } from '../../constants'
-import { tryGetAbsolutePath } from '../../../shared/utilities/workspaceUtils'
-import * as CloudFormation from '../../../shared/cloudformation/cloudformation'
-import { openLaunchJsonFile } from '../../../shared/sam/debugger/commands/addSamDebugConfiguration'
-import { getSampleLambdaPayloads } from '../../utils'
-import { samLambdaCreatableRuntimes } from '../../models/samLambdaRuntime'
-import { isFunctionResource } from '../../../awsService/appBuilder/explorer/samProject'
-import globals from '../../../shared/extensionGlobals'
-import { VueWebview } from '../../../webviews/main'
-import { Commands } from '../../../shared/vscode/commands2'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { fs } from '../../../shared/fs/fs'
-import { ToolkitError } from '../../../shared/errors'
-import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode'
-
-const localize = nls.loadMessageBundle()
-
-export interface ResourceData {
-    logicalId: string
-    region: string
-    arn: string
-    location: string
-    handler: string
-    runtime: string
-    stackName: string
-    source: string
-    environment?: {
-        Variables: Record
-    }
-}
-
-export type AwsSamDebuggerConfigurationLoose = AwsSamDebuggerConfiguration & {
-    invokeTarget: Omit<
-        AwsSamDebuggerConfiguration['invokeTarget'],
-        'templatePath' | 'logicalId' | 'lambdaHandler' | 'projectRoot'
-    > & {
-        templatePath: string
-        logicalId: string
-        lambdaHandler: string
-        projectRoot: string
-    }
-}
-
-interface SampleQuickPickItem extends vscode.QuickPickItem {
-    filename: string
-}
-
-export interface LaunchConfigPickItem extends vscode.QuickPickItem {
-    index: number
-    config?: AwsSamDebuggerConfiguration
-}
-
-export class SamInvokeWebview extends VueWebview {
-    public static readonly sourcePath: string = 'src/lambda/vue/configEditor/index.js'
-    public readonly id = 'createLambda'
-
-    public constructor(
-        private readonly config?: AwsSamDebuggerConfiguration,
-        private readonly data?: ResourceData
-    ) {
-        super(SamInvokeWebview.sourcePath)
-    }
-
-    public getRuntimes() {
-        return samLambdaCreatableRuntimes().toArray().sort()
-    }
-
-    public init() {
-        return this.config
-    }
-
-    public getResourceData() {
-        return this.data
-    }
-
-    public async getSamLaunchConfigs(): Promise {
-        // TODO: Find a better way to infer this. Might need another arg from the frontend (depends on the context in which the launch config is made?)
-        const workspaceFolder = vscode.workspace.workspaceFolders?.length
-            ? vscode.workspace.workspaceFolders[0]
-            : undefined
-        if (!workspaceFolder) {
-            void vscode.window.showErrorMessage(localize('AWS.lambda.form.noFolder', 'No workspace folder found.'))
-            return
-        }
-        const uri = workspaceFolder.uri
-        const launchConfig = new LaunchConfiguration(uri)
-        const pickerItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri)
-        return pickerItems
-    }
-
-    /**
-     * Open a quick pick containing the names of launch configs in the `launch.json` array.
-     * Filter out non-supported launch configs.
-     */
-    public async loadSamLaunchConfig(): Promise {
-        const pickerItems: LaunchConfigPickItem[] = (await this.getSamLaunchConfigs()) || []
-
-        if (pickerItems.length === 0) {
-            pickerItems.push({
-                index: -1,
-                label: localize('AWS.lambda.form.noDebugConfigs', 'No aws-sam debug configurations found.'),
-                detail: localize('AWS.picker.dynamic.noItemsFound.detail', 'Click here to go back'),
-            })
-        }
-        const qp = picker.createQuickPick({
-            items: pickerItems,
-            options: {
-                title: localize('AWS.lambda.form.selectDebugConfig', 'Select Debug Configuration'),
-            },
-        })
-
-        const choices = await picker.promptUser({
-            picker: qp,
-        })
-        const pickerResponse = picker.verifySinglePickerOutput(choices)
-
-        if (!pickerResponse || pickerResponse.index === -1) {
-            return
-        }
-        return pickerResponse.config!
-    }
-
-    /**
-     * Open a quick pick containing upstream sample payloads.
-     * Call back into the webview with the contents of the payload to add to the JSON field.
-     */
-    public async getSamplePayload(): Promise {
-        try {
-            const inputs: SampleQuickPickItem[] = (await getSampleLambdaPayloads()).map((entry) => {
-                return { label: entry.name ?? '', filename: entry.filename ?? '' }
-            })
-
-            const qp = picker.createQuickPick({
-                items: inputs,
-                options: {
-                    title: localize('AWS.lambda.form.pickSampleInput', 'Choose Sample Input'),
-                },
-            })
-
-            const choices = await picker.promptUser({
-                picker: qp,
-            })
-            const pickerResponse = picker.verifySinglePickerOutput(choices)
-
-            if (!pickerResponse) {
-                return
-            }
-            const sampleUrl = `${sampleRequestPath}${pickerResponse.filename}`
-            const resp = await new HttpResourceFetcher(sampleUrl, { showUrl: true }).get()
-            const sample = (await resp?.text()) ?? ''
-
-            return sample
-        } catch (err) {
-            getLogger().error('Error getting manifest data..: %O', err as Error)
-            throw ToolkitError.chain(err, 'getting manifest data')
-        }
-    }
-
-    protected getTemplateRegistry() {
-        return globals.templateRegistry
-    }
-
-    /**
-     * Get all templates in the registry.
-     * Call back into the webview with the registry contents.
-     */
-    public async getTemplate() {
-        const items: (vscode.QuickPickItem & { templatePath: string })[] = []
-        const noTemplate = 'NOTEMPLATEFOUND'
-        for (const template of (await this.getTemplateRegistry()).items) {
-            const resources = template.item.Resources
-            if (resources) {
-                for (const resource of Object.keys(resources)) {
-                    if (
-                        resources[resource]?.Type === CloudFormation.LAMBDA_FUNCTION_TYPE ||
-                        resources[resource]?.Type === CloudFormation.SERVERLESS_FUNCTION_TYPE ||
-                        resources[resource]?.Type === CloudFormation.SERVERLESS_API_TYPE
-                    ) {
-                        items.push({
-                            label: resource,
-                            detail: localize('AWS.lambda.form.selectResource.detail', 'Template: {0}', template.path),
-                            templatePath: template.path,
-                        })
-                    }
-                }
-            }
-        }
-
-        if (items.length === 0) {
-            items.push({
-                label: localize(
-                    'AWS.lambda.form.selectResource.noTemplates',
-                    'No templates with valid SAM functions found.'
-                ),
-                detail: localize('AWS.picker.dynamic.noItemsFound.detail', 'Click here to go back'),
-                templatePath: noTemplate,
-            })
-        }
-
-        const qp = picker.createQuickPick({
-            items,
-            options: {
-                title: localize('AWS.lambda.form.selectResource', 'Select Resource'),
-            },
-        })
-
-        const choices = await picker.promptUser({
-            picker: qp,
-        })
-        const selectedTemplate = picker.verifySinglePickerOutput(choices)
-
-        if (!selectedTemplate || selectedTemplate.templatePath === noTemplate) {
-            return
-        }
-
-        return {
-            logicalId: selectedTemplate.label,
-            template: selectedTemplate.templatePath,
-        }
-    }
-
-    // This method serves as a wrapper around the backend function `openLaunchJsonFile`.
-    // The frontend cannot directly import and invoke backend functions like `openLaunchJsonFile`
-    // because doing so would break the webview environment by introducing server-side logic
-    // into client-side code. Instead, this method acts as an interface or bridge, allowing
-    // the frontend to request the backend to open the launch configuration file without
-    // directly coupling the frontend to backend-specific implementations.
-    public async openLaunchConfig() {
-        await openLaunchJsonFile()
-    }
-
-    public async promptFile() {
-        const fileLocations = await vscode.window.showOpenDialog({
-            openLabel: 'Open',
-        })
-
-        if (!fileLocations || fileLocations.length === 0) {
-            return undefined
-        }
-
-        try {
-            const fileContent = await fs.readFileBytes(fileLocations[0].fsPath)
-            return {
-                sample: fileContent,
-                selectedFilePath: fileLocations[0].fsPath,
-                selectedFile: this.getFileName(fileLocations[0].fsPath),
-            }
-        } catch (e) {
-            getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocations[0].fsPath, e)
-            throw ToolkitError.chain(e, 'Failed to read selected file')
-        }
-    }
-
-    public getFileName(filePath: string): string {
-        return path.basename(filePath)
-    }
-    /**
-     * Open a quick pick containing the names of launch configs in the `launch.json` array, plus a "Create New Entry" entry.
-     * On selecting a name, overwrite the existing entry in the `launch.json` array and resave the file.
-     * On selecting "Create New Entry", prompt the user for a name and save the contents to the end of the `launch.json` array.
-     * @param config Config to save
-     */
-    public async saveLaunchConfig(config: AwsSamDebuggerConfiguration): Promise {
-        const uri = await this.getUriFromLaunchConfig(config)
-        if (!uri) {
-            // TODO Localize
-            void vscode.window.showErrorMessage(
-                'Toolkit requires a target resource in order to save a debug configuration'
-            )
-            return
-        }
-
-        const launchConfig = new LaunchConfiguration(uri)
-        const launchConfigItems = await this.getLaunchConfigQuickPickItems(launchConfig, uri)
-        const pickerItems = [
-            {
-                label: addCodiconToString(
-                    'add',
-                    localize('AWS.command.addSamDebugConfiguration', 'Add Local Invoke and Debug Configuration')
-                ),
-                index: -1,
-                alwaysShow: true,
-            },
-            ...launchConfigItems,
-        ]
-
-        const qp = picker.createQuickPick({
-            items: pickerItems,
-            options: {
-                title: localize('AWS.lambda.form.selectDebugConfig', 'Select Debug Configuration'),
-            },
-        })
-
-        const choices = await picker.promptUser({
-            picker: qp,
-        })
-        const pickerResponse = picker.verifySinglePickerOutput(choices)
-
-        if (!pickerResponse) {
-            return
-        }
-
-        if (pickerResponse.index === -1) {
-            const ib = input.createInputBox({
-                options: {
-                    prompt: localize('AWS.lambda.form.debugConfigName', 'Input Name For Debug Configuration'),
-                },
-            })
-            const response = await input.promptUser({ inputBox: ib })
-            if (response) {
-                await launchConfig.addDebugConfiguration(finalizeConfig(config, response))
-                await this.openLaunchConfig()
-            }
-        } else {
-            // use existing label
-            await launchConfig.editDebugConfiguration(
-                finalizeConfig(config, pickerResponse.label),
-                pickerResponse.index
-            )
-            await this.openLaunchConfig()
-        }
-    }
-
-    /**
-     * Validate and execute the provided launch config.
-     * TODO: Post validation failures back to webview?
-     * @param config Config to invoke
-     */
-    public async invokeLaunchConfig(config: AwsSamDebuggerConfiguration, source?: string): Promise {
-        const finalConfig = finalizeConfig(
-            resolveWorkspaceFolderVariable(undefined, config),
-            'Editor-Created Debug Config'
-        )
-        const targetUri = await this.getUriFromLaunchConfig(finalConfig)
-        const folder = targetUri ? vscode.workspace.getWorkspaceFolder(targetUri) : undefined
-
-        // startDebugging on VS Code goes through the whole resolution chain
-        await vscode.debug.startDebugging(folder, finalConfig)
-    }
-    public async getLaunchConfigQuickPickItems(
-        launchConfig: LaunchConfiguration,
-        uri: vscode.Uri
-    ): Promise {
-        const existingConfigs = launchConfig.getDebugConfigurations()
-        const samValidator = new DefaultAwsSamDebugConfigurationValidator(vscode.workspace.getWorkspaceFolder(uri))
-        const registry = await globals.templateRegistry
-        const mapped = existingConfigs.map((val, index) => {
-            return {
-                config: val as AwsSamDebuggerConfiguration,
-                index: index,
-                label: val.name,
-            }
-        })
-        // XXX: can't use filter() with async predicate.
-        const filtered: LaunchConfigPickItem[] = []
-        for (const c of mapped) {
-            const valid = await samValidator.validate(c.config, registry, true)
-            if (valid?.isValid) {
-                filtered.push(c)
-            }
-        }
-        return filtered
-    }
-
-    public async getUriFromLaunchConfig(config: AwsSamDebuggerConfiguration): Promise {
-        let targetPath: string
-        if (isTemplateTargetProperties(config.invokeTarget)) {
-            targetPath = config.invokeTarget.templatePath
-        } else if (isCodeTargetProperties(config.invokeTarget)) {
-            targetPath = config.invokeTarget.projectRoot
-        } else {
-            // error
-            return undefined
-        }
-        if (path.isAbsolute(targetPath)) {
-            return vscode.Uri.file(targetPath)
-        }
-        // TODO: rework this logic (and config variables in general)
-        // we have too many places where we try to resolve these paths when it realistically can be
-        // in a single place. Much less bug-prone when it's centralized.
-        // the following line is a quick-fix for a very narrow edge-case
-        targetPath = targetPath.replace('${workspaceFolder}/', '')
-        const workspaceFolders = vscode.workspace.workspaceFolders || []
-        for (const workspaceFolder of workspaceFolders) {
-            const absolutePath = tryGetAbsolutePath(workspaceFolder, targetPath)
-            if (await fs.exists(absolutePath)) {
-                return vscode.Uri.file(absolutePath)
-            }
-        }
-
-        return undefined
-    }
-}
-
-const WebviewPanel = VueWebview.compilePanel(SamInvokeWebview)
-
-export function registerSamInvokeVueCommand(context: vscode.ExtensionContext): vscode.Disposable {
-    return Commands.register('aws.launchConfigForm', async (launchConfig?: AwsSamDebuggerConfiguration) => {
-        const webview = new WebviewPanel(context, launchConfig)
-        await telemetry.sam_openConfigUi.run(async (span) => {
-            await webview.show({
-                title: localize('AWS.command.launchConfigForm.title', 'Local Invoke and Debug Configuration'),
-                // TODO: make this only open `Beside` when executed via CodeLens
-                viewColumn: vscode.ViewColumn.Beside,
-            })
-        })
-    })
-}
-
-export async function registerSamDebugInvokeVueCommand(
-    context: vscode.ExtensionContext,
-    params: { resource: ResourceNode }
-) {
-    const resource = params?.resource.resource
-    const source = 'AppBuilderLocalInvoke'
-    const launchConfigs = await new LaunchConfiguration(resource.location).getSamDebugConfigurations()
-    const launchConfig = launchConfigs.find(
-        (config) => (config.invokeTarget as TemplateTargetProperties).logicalId === resource.resource.Id
-    )
-
-    if (!isFunctionResource(resource.resource)) {
-        throw new ToolkitError('Resource is not a Lambda function')
-    }
-
-    const webview = new WebviewPanel(context, launchConfig, {
-        logicalId: resource.resource.Id ?? '',
-        region: resource.region ?? '',
-        location: resource.location.fsPath,
-        handler: resource.resource.Handler!,
-        runtime: launchConfig?.lambda?.runtime ?? resource.resource.Runtime!,
-        arn: resource.functionArn ?? '',
-        stackName: resource.stackName ?? '',
-        environment: resource.resource.Environment,
-        source: source,
-    })
-    await telemetry.sam_openConfigUi.run(async (span) => {
-        telemetry.record({ source: 'AppBuilderDebugger' }),
-            await webview.show({
-                title: localize('AWS.command.launchConfigForm.title', 'Local Invoke and Debug Configuration'),
-                // TODO: make this only open `Beside` when executed via CodeLens
-                viewColumn: vscode.ViewColumn.Beside,
-            })
-    })
-}
-
-export function finalizeConfig(config: AwsSamDebuggerConfiguration, name: string): AwsSamDebuggerConfiguration {
-    const newConfig = doTraverseAndPrune(config)
-    newConfig.name = name
-
-    if (isTemplateTargetProperties(config.invokeTarget)) {
-        newConfig.invokeTarget = {
-            target: config.invokeTarget.target,
-            logicalId: config.invokeTarget.logicalId,
-            templatePath: config.invokeTarget.templatePath,
-        }
-    } else if (isCodeTargetProperties(config.invokeTarget)) {
-        newConfig.invokeTarget = {
-            target: config.invokeTarget.target,
-            lambdaHandler: config.invokeTarget.lambdaHandler,
-            projectRoot: config.invokeTarget.projectRoot,
-        }
-    }
-
-    return newConfig
-}
-
-/**
- * Removes empty objects, strings, fields, and arrays from a given object.
- * Use when writing JSON to a file.
- * @param object
- * @returns Pruned object
- */
-function doTraverseAndPrune(object: { [key: string]: any }): any | undefined {
-    const keys = Object.keys(object)
-    const final = JSON.parse(JSON.stringify(object))
-    for (const key of keys) {
-        const val = object[key]
-        if (val === undefined || val === '' || (Array.isArray(val) && val.length === 0)) {
-            delete final[key]
-        } else if (typeof val === 'object') {
-            const pruned = doTraverseAndPrune(val)
-            if (pruned) {
-                final[key] = pruned
-            } else {
-                delete final[key]
-            }
-        }
-    }
-    if (Object.keys(final).length === 0) {
-        return undefined
-    }
-    return final
-}
diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue b/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue
deleted file mode 100644
index e7d09c584c..0000000000
--- a/packages/core/src/lambda/vue/configEditor/samInvokeComponent.vue
+++ /dev/null
@@ -1,400 +0,0 @@
-/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */
-
-
-
-
-
diff --git a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts b/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts
deleted file mode 100644
index 375e22f487..0000000000
--- a/packages/core/src/lambda/vue/configEditor/samInvokeFrontend.ts
+++ /dev/null
@@ -1,464 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Disable because it is a front-end file.
-/* eslint-disable aws-toolkits/no-console-log */
-
-import { defineComponent } from 'vue'
-import { AwsSamDebuggerConfiguration } from '../../../shared/sam/debugger/awsSamDebugConfiguration'
-import {
-    AwsSamDebuggerConfigurationLoose,
-    LaunchConfigPickItem,
-    ResourceData,
-    SamInvokeWebview,
-} from './samInvokeBackend'
-import settingsPanel from '../../../webviews/components/settingsPanel.vue'
-import { WebviewClientFactory } from '../../../webviews/client'
-import saveData from '../../../webviews/mixins/saveData'
-
-const client = WebviewClientFactory.create()
-
-interface VueDataLaunchPropertyObject {
-    value: string
-    errorMsg: string
-}
-interface SamInvokeVueData {
-    msg: any
-    targetTypes: { [k: string]: string }[]
-    runtimes: string[]
-    company: string
-    httpMethods: string[]
-    launchConfig: AwsSamDebuggerConfigurationLoose
-    payload: VueDataLaunchPropertyObject
-    apiPayload: VueDataLaunchPropertyObject
-    environmentVariables: VueDataLaunchPropertyObject
-    headers: VueDataLaunchPropertyObject
-    stageVariables: VueDataLaunchPropertyObject
-    parameters: VueDataLaunchPropertyObject
-    containerBuild: boolean
-    skipNewImageCheck: boolean
-    selectedConfig: LaunchConfigPickItem
-    payloadOption: string
-    selectedFile: string
-    selectedFilePath: string
-    selectedTestEvent: string
-    TestEvents: string[]
-    showNameInput: boolean
-    newTestEventName: string
-    resourceData: ResourceData | undefined
-    useDebugger: boolean
-}
-
-function newLaunchConfig(existingConfig?: AwsSamDebuggerConfiguration): AwsSamDebuggerConfigurationLoose {
-    return {
-        type: 'aws-sam',
-        request: 'direct-invoke',
-        name: '',
-        aws: {
-            credentials: '',
-            region: '',
-            ...(existingConfig?.aws ? existingConfig.aws : {}),
-        },
-        invokeTarget: {
-            target: 'template',
-            templatePath: '',
-            logicalId: '',
-            lambdaHandler: '',
-            projectRoot: '',
-            ...existingConfig?.invokeTarget,
-        },
-        lambda: {
-            runtime: existingConfig?.lambda?.runtime,
-            memoryMb: undefined,
-            timeoutSec: undefined,
-            environmentVariables: {},
-            ...existingConfig?.lambda,
-            payload: {
-                json: existingConfig?.lambda?.payload?.json ? existingConfig.lambda.payload.json : {},
-                path: existingConfig?.lambda?.payload?.path ? existingConfig.lambda.payload.path : '',
-            },
-
-            // pathMappings: undefined
-        },
-        sam: {
-            buildArguments: undefined,
-            containerBuild: false,
-            dockerNetwork: '',
-            localArguments: undefined,
-            skipNewImageCheck: false,
-            ...(existingConfig?.sam ? existingConfig.sam : {}),
-            template: {
-                parameters: existingConfig?.sam?.template?.parameters ? existingConfig.sam.template.parameters : {},
-            },
-        },
-        api: {
-            path: '',
-            httpMethod: 'get',
-            clientCertificateId: '',
-            querystring: '',
-            headers: {},
-            stageVariables: {},
-            ...(existingConfig?.api ? existingConfig.api : {}),
-            payload: {
-                json: existingConfig?.api?.payload?.json ? existingConfig.api.payload.json : {},
-                path: existingConfig?.api?.payload?.path ? existingConfig.api.payload.path : '',
-            },
-        },
-    }
-}
-
-function initData() {
-    return {
-        containerBuild: false,
-        skipNewImageCheck: false,
-        useDebugger: true,
-        launchConfig: newLaunchConfig(),
-        payload: { value: '', errorMsg: '' },
-        apiPayload: { value: '', errorMsg: '' },
-        environmentVariables: { value: '', errorMsg: '' },
-        parameters: { value: '', errorMsg: '' },
-        headers: { value: '', errorMsg: '' },
-        stageVariables: { value: '', errorMsg: '' },
-        selectedConfig: { index: 0, config: undefined, label: 'new-config' },
-        selectedTestEvent: '',
-        TestEvents: [],
-        showNameInput: false,
-        newTestEventName: '',
-    }
-}
-
-export default defineComponent({
-    name: 'sam-invoke',
-    components: {
-        settingsPanel,
-    },
-    created() {
-        this.setUpWebView()
-    },
-    mixins: [saveData],
-    data(): SamInvokeVueData {
-        return {
-            ...initData(),
-            msg: 'Hello',
-            company: '',
-            targetTypes: [
-                { name: 'Code', value: 'code' },
-                { name: 'Template', value: 'template' },
-                { name: 'API Gateway (Template)', value: 'api' },
-            ],
-            runtimes: [],
-            httpMethods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH'],
-            payloadOption: 'sampleEvents',
-            selectedFile: '',
-            selectedFilePath: '',
-            resourceData: undefined,
-        }
-    },
-    methods: {
-        resetJsonErrors() {
-            this.payload.errorMsg = ''
-            this.environmentVariables.errorMsg = ''
-            this.headers.errorMsg = ''
-            this.stageVariables.errorMsg = ''
-        },
-        async launch() {
-            const config = this.formatConfig()
-
-            if (!config) {
-                return // Exit early if config is not available
-            }
-
-            const source = this.resourceData?.source
-
-            client.invokeLaunchConfig(config, source).catch((e: Error) => {
-                console.error(`invokeLaunchConfig failed: ${e.message}`)
-            })
-        },
-        save() {
-            const config = this.formatConfig()
-            config &&
-                client.saveLaunchConfig(config).catch((e) => {
-                    console.error('saveLaunchConfig failed: %s', (e as Error).message)
-                })
-        },
-        loadConfig() {
-            client.loadSamLaunchConfig().then(
-                (config) => this.parseConfig(config),
-                (e) => {
-                    console.error('client.loadSamLaunchConfig failed: %s', (e as Error).message)
-                }
-            )
-        },
-        async parseConfig(config?: AwsSamDebuggerConfiguration) {
-            if (!config) {
-                return
-            }
-            const company = this.company
-            this.clearForm()
-            this.launchConfig = newLaunchConfig(config)
-
-            if (config.lambda?.payload) {
-                this.payload.value = JSON.stringify(config.lambda.payload.json, undefined, 4)
-            }
-
-            const localArgs = config.sam?.localArguments
-
-            if (!localArgs && this.payload.value) {
-                this.payloadOption = 'sampleEvents'
-                this.selectedFile = ''
-            }
-
-            if (localArgs?.includes('-e') || localArgs?.includes('--event')) {
-                const index = localArgs.findIndex((arg) => arg === '-e' || arg === '--event')
-
-                if (index !== -1 && localArgs[index + 1]) {
-                    this.payloadOption = 'localFile'
-                    this.selectedFile = await client.getFileName(localArgs[index + 1])
-                }
-            }
-
-            if (config.lambda?.environmentVariables) {
-                this.environmentVariables.value = JSON.stringify(config.lambda?.environmentVariables)
-            }
-            if (config.sam?.template?.parameters) {
-                this.parameters.value = JSON.stringify(config.sam?.template?.parameters)
-            }
-            if (config.api?.headers) {
-                this.headers.value = JSON.stringify(config.api?.headers)
-            }
-            if (config.api?.stageVariables) {
-                this.stageVariables.value = JSON.stringify(config.api?.stageVariables)
-            }
-            this.containerBuild = config.sam?.containerBuild ?? false
-            this.skipNewImageCheck = config.sam?.skipNewImageCheck ?? false
-            this.msg = `Loaded config: ${config.name}`
-            this.company = company
-        },
-        loadPayload() {
-            this.resetJsonErrors()
-            client.getSamplePayload().then(
-                (sample) => {
-                    if (!sample) {
-                        return
-                    }
-                    this.payload.value = JSON.stringify(JSON.parse(sample), undefined, 4)
-                },
-                (e) => {
-                    console.error('client.getSamplePayload failed: %s', (e as Error).message)
-                }
-            )
-        },
-        loadResource() {
-            this.resetJsonErrors()
-            client.getTemplate().then(
-                (template) => {
-                    if (!template) {
-                        return
-                    }
-                    this.launchConfig.invokeTarget.target = 'template'
-                    this.launchConfig.invokeTarget.logicalId = template.logicalId
-                    this.launchConfig.invokeTarget.templatePath = template.template
-                },
-                (e) => {
-                    console.error('client.getTemplate failed: %s', (e as Error).message)
-                }
-            )
-        },
-        formatFieldToStringArray(field: string | undefined) {
-            if (!field) {
-                return undefined
-            }
-            // Reg ex for a comma with 0 or more whitespace before and/or after
-            const re = /\s*,\s*/g
-            return field.trim().split(re)
-        },
-        formatStringtoJSON(field: VueDataLaunchPropertyObject) {
-            field.errorMsg = ''
-            if (field.value) {
-                try {
-                    return JSON.parse(field.value)
-                } catch (e) {
-                    field.errorMsg = (e as Error).message
-                    throw e
-                }
-            }
-        },
-        async openLaunchJson() {
-            await client.openLaunchConfig()
-        },
-        onFileChange(event: Event) {
-            const input = event.target as HTMLInputElement
-            if (input.files && input.files.length > 0) {
-                const file = input.files[0]
-                this.selectedFile = file.name
-
-                // Use Blob.text() to read the file as text
-                file.text()
-                    .then((text) => {
-                        this.payload.value = text
-                    })
-                    .catch((error) => {
-                        console.error('Error reading file:', error)
-                    })
-            }
-        },
-        async promptForFileLocation() {
-            const resp = await client.promptFile()
-
-            if (resp) {
-                this.selectedFile = resp.selectedFile
-                this.launchConfig.sam = this.launchConfig.sam || {}
-                this.launchConfig.sam.localArguments = this.launchConfig.sam.localArguments || []
-
-                // Ensure only one '-e ' or '--event ' exists
-                const eventArgIndex = this.launchConfig.sam.localArguments.findIndex(
-                    (arg) => arg === '-e' || arg === '--event'
-                )
-
-                if (eventArgIndex !== -1 && this.launchConfig.sam.localArguments[eventArgIndex + 1]) {
-                    // Replace the existing file path for either '-e' or '--event'
-                    this.launchConfig.sam.localArguments[eventArgIndex + 1] = resp.selectedFilePath
-                } else {
-                    // Add '-e ' if not already present
-                    this.launchConfig.sam.localArguments.push('-e', resp.selectedFilePath)
-                }
-            }
-        },
-        showNameField() {
-            this.showNameInput = true
-        },
-        setUpWebView() {
-            client.init().then(
-                (config) => this.parseConfig(config),
-                (e) => {
-                    console.error('client.init failed: %s', (e as Error).message)
-                }
-            )
-
-            if (this.launchConfig.invokeTarget.templatePath === '') {
-                client.getResourceData().then(
-                    (data) => {
-                        this.resourceData = data
-                        if (this.launchConfig && this.resourceData) {
-                            this.launchConfig.invokeTarget.logicalId = this.resourceData.logicalId
-                            this.launchConfig.invokeTarget.templatePath = this.resourceData.location
-                            this.launchConfig.invokeTarget.lambdaHandler = this.resourceData.handler
-                            if (this.launchConfig.lambda) {
-                                this.launchConfig.lambda.runtime = this.resourceData.runtime
-                                if (this.resourceData.environment?.Variables !== undefined) {
-                                    this.environmentVariables.value = JSON.stringify(
-                                        this.resourceData.environment?.Variables
-                                    )
-                                }
-                            }
-                        }
-                    },
-                    (e) => {
-                        console.error('client.getResourceData failed: %s', (e as Error).message)
-                    }
-                )
-            }
-
-            client.getRuntimes().then(
-                (runtimes) => {
-                    this.runtimes = runtimes
-                },
-                (e) => {
-                    console.error('client.getRuntimes failed: %s', (e as Error).message)
-                }
-            )
-
-            client.getCompanyName().then(
-                (o) => {
-                    this.company = o
-                },
-                (e) => {
-                    console.error('client.getCompanyName failed: %s', (e as Error).message)
-                }
-            )
-        },
-        formatConfig() {
-            this.resetJsonErrors()
-
-            let payloadJson, environmentVariablesJson, headersJson, stageVariablesJson, parametersJson, apiPayloadJson
-
-            try {
-                payloadJson = this.formatStringtoJSON(this.payload)
-                environmentVariablesJson = this.formatStringtoJSON(this.environmentVariables)
-                headersJson = this.formatStringtoJSON(this.headers)
-                stageVariablesJson = this.formatStringtoJSON(this.stageVariables)
-                parametersJson = this.formatStringtoJSON(this.parameters)
-                apiPayloadJson = this.formatStringtoJSON(this.apiPayload)
-            } catch {
-                return
-            }
-
-            // Vue internally stores a Proxy for all object-like fields, so the spread operator can
-            // propagate those through to the `postMessage` command, causing an error. We can stop
-            // this by recursively accessing all primitive fields (which is what this line does)
-            const launchConfig: AwsSamDebuggerConfigurationLoose = JSON.parse(JSON.stringify(this.launchConfig))
-            const localArgs = launchConfig.sam?.localArguments
-
-            const removeEventArg = () => {
-                if (localArgs) {
-                    const eventArgIndex = localArgs?.findIndex((arg) => arg === '-e' || arg === '--event')
-                    if (eventArgIndex !== -1) {
-                        // Remove the event argument and its value
-                        localArgs?.splice(eventArgIndex, 2)
-                    }
-                }
-            }
-
-            if (localArgs) {
-                if (this.payload && this.payloadOption !== 'localFile') {
-                    removeEventArg()
-                } else if (this.payloadOption === 'localFile' && this.selectedFile) {
-                    payloadJson = undefined
-                } else {
-                    removeEventArg()
-                }
-            }
-
-            return {
-                ...launchConfig,
-                lambda: {
-                    ...launchConfig.lambda,
-                    payload: {
-                        ...launchConfig.payload,
-                        json: payloadJson,
-                    },
-                    environmentVariables: environmentVariablesJson,
-                },
-                sam: {
-                    ...launchConfig.sam,
-                    buildArguments: this.formatFieldToStringArray(launchConfig.sam?.buildArguments?.toString()),
-                    localArguments: this.formatFieldToStringArray(launchConfig.sam?.localArguments?.toString()),
-                    containerBuild: this.containerBuild,
-                    skipNewImageCheck: this.skipNewImageCheck,
-                    template: {
-                        parameters: parametersJson,
-                    },
-                },
-                api: launchConfig.api
-                    ? {
-                          ...launchConfig.api,
-                          headers: headersJson,
-                          stageVariables: stageVariablesJson,
-                          payload: {
-                              json: apiPayloadJson,
-                          },
-                      }
-                    : undefined,
-                noDebug: !this.useDebugger,
-            }
-        },
-        clearForm() {
-            const init = initData()
-            for (const k of Object.keys(init)) {
-                ;(this as any)[k] = init[k as keyof typeof init]
-            }
-        },
-    },
-})
diff --git a/packages/core/src/lambda/vue/remoteInvoke/index.ts b/packages/core/src/lambda/vue/remoteInvoke/index.ts
deleted file mode 100644
index a44142fd52..0000000000
--- a/packages/core/src/lambda/vue/remoteInvoke/index.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { createApp } from 'vue'
-import component from './remoteInvoke.vue'
-
-const create = () => createApp(component)
-const app = create()
-app.mount('#vue-app')
-
-window.addEventListener('remount', () => {
-    app.unmount()
-    create().mount('#vue-app')
-})
diff --git a/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts b/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts
deleted file mode 100644
index 6c9348cb62..0000000000
--- a/packages/core/src/lambda/vue/remoteInvoke/invokeLambda.ts
+++ /dev/null
@@ -1,966 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { readFileSync } from 'fs' // eslint-disable-line no-restricted-imports
-import * as _ from 'lodash'
-import * as vscode from 'vscode'
-import { LambdaClient } from '../../../shared/clients/lambdaClient'
-import * as picker from '../../../shared/ui/picker'
-import { ExtContext } from '../../../shared/extensions'
-
-import { getLogger } from '../../../shared/logger/logger'
-import { HttpResourceFetcher } from '../../../shared/resourcefetcher/httpResourceFetcher'
-import { sampleRequestPath } from '../../constants'
-import { LambdaFunctionNode } from '../../explorer/lambdaFunctionNode'
-import { getSampleLambdaPayloads, SampleRequest, isHotReloadingFunction } from '../../utils'
-
-import * as nls from 'vscode-nls'
-import { VueWebview } from '../../../webviews/main'
-import { telemetry, Runtime as TelemetryRuntime } from '../../../shared/telemetry/telemetry'
-import { Runtime } from '@aws-sdk/client-lambda'
-import {
-    runSamCliRemoteTestEvents,
-    SamCliRemoteTestEventsParameters,
-    TestEventsOperation,
-} from '../../../shared/sam/cli/samCliRemoteTestEvent'
-import { getSamCliContext } from '../../../shared/sam/cli/samCliContext'
-import { ToolkitError } from '../../../shared/errors'
-import { basename } from 'path'
-import { decodeBase64 } from '../../../shared/utilities/textUtilities'
-import { RemoteDebugController, revertExistingConfig } from '../../remoteDebugging/ldkController'
-import type { DebugConfig } from '../../remoteDebugging/lambdaDebugger'
-import { getCachedLocalPath, openLambdaFile, runDownloadLambda } from '../../commands/downloadLambda'
-import { getLambdaHandlerFile } from '../../../awsService/appBuilder/utils'
-import { runUploadDirectory } from '../../commands/uploadLambda'
-import fs from '../../../shared/fs/fs'
-import { showConfirmationMessage, showMessage } from '../../../shared/utilities/messages'
-import { getLambdaClientWithAgent, getLambdaDebugUserAgentPairs } from '../../remoteDebugging/utils'
-import { isLocalStackConnection } from '../../../auth/utils'
-import { getRemoteDebugLayer } from '../../remoteDebugging/remoteLambdaDebugger'
-
-const localize = nls.loadMessageBundle()
-
-type Event = {
-    name: string
-    region: string
-    arn: string
-    event?: string
-}
-
-export interface InitialData {
-    FunctionName: string
-    FunctionArn: string
-    FunctionRegion: string
-    InputSamples: SampleRequest[]
-    TestEvents?: string[]
-    Source?: string
-    StackName?: string
-    LogicalId?: string
-    Runtime?: Runtime
-    LocalRootPath?: string
-    LambdaFunctionNode?: LambdaFunctionNode
-    supportCodeDownload?: boolean
-    runtimeSupportsRemoteDebug?: boolean
-    remoteDebugLayer?: string | undefined
-    isLambdaRemote?: boolean
-}
-
-// Debug configuration sub-interface
-export interface DebugConfiguration {
-    debugPort: number | undefined
-    localRootPath: string
-    remoteRootPath: string
-    shouldPublishVersion: boolean
-    lambdaTimeout: number
-    otherDebugParams: string
-}
-
-// Debug state sub-interface
-export interface DebugState {
-    isDebugging: boolean
-    debugTimer: number | undefined
-    debugTimeRemaining: number
-    showDebugTimer: boolean
-    handlerFileAvailable: boolean
-    remoteDebuggingEnabled: boolean
-}
-
-// Runtime-specific debug settings sub-interface
-export interface RuntimeDebugSettings {
-    // Node.js specific
-    sourceMapEnabled: boolean
-    skipFiles: string
-    outFiles: string | undefined
-    // Python specific
-    justMyCode: boolean
-    // Java specific
-    projectName: string
-}
-
-// UI state sub-interface
-export interface UIState {
-    isCollapsed: boolean
-    extraRegionInfo: string
-}
-
-// Payload/Event handling sub-interface
-export interface PayloadData {
-    sampleText: string
-}
-
-export interface RemoteInvokeData {
-    initialData: InitialData
-    debugConfig: DebugConfiguration
-    debugState: DebugState
-    runtimeSettings: RuntimeDebugSettings
-    uiState: UIState
-    payloadData: PayloadData
-}
-
-// Event types for communicating state between backend and frontend
-export type StateChangeEvent = {
-    isDebugging?: boolean
-}
-interface SampleQuickPickItem extends vscode.QuickPickItem {
-    filename: string
-}
-
-export class RemoteInvokeWebview extends VueWebview {
-    public static readonly sourcePath: string = 'src/lambda/vue/remoteInvoke/index.js'
-    public readonly id = 'remoteInvoke'
-
-    // Event emitter for state changes that need to be synchronized with the frontend
-    public readonly onStateChange = new vscode.EventEmitter()
-
-    // Backend timer that will continue running even when the webview loses focus
-    private debugTimerHandle: NodeJS.Timeout | undefined
-    private debugTimeRemaining: number = 0
-    private isInvoking: boolean = false
-    private debugging: boolean = false
-    private watcherDisposable: vscode.Disposable | undefined
-    private fileWatcherDisposable: vscode.Disposable | undefined
-    private handlerFileAvailable: boolean = false
-    private isStartingDebug: boolean = false
-    private handlerFile: string | undefined
-    public constructor(
-        private readonly channel: vscode.OutputChannel,
-        private readonly client: LambdaClient,
-        private readonly clientDebug: LambdaClient,
-        private readonly data: InitialData
-    ) {
-        super(RemoteInvokeWebview.sourcePath)
-    }
-
-    public init() {
-        this.watcherDisposable = vscode.debug.onDidTerminateDebugSession(async (session: vscode.DebugSession) => {
-            this.resetServerState()
-        })
-        return this.data
-    }
-
-    public resetServerState() {
-        this.stopDebugTimer()
-        this.debugging = false
-        this.isInvoking = false
-        this.isStartingDebug = false
-        this.onStateChange.fire({
-            isDebugging: false,
-        })
-    }
-
-    public async disposeServer() {
-        this.watcherDisposable?.dispose()
-        this.fileWatcherDisposable?.dispose()
-        if (this.debugging && RemoteDebugController.instance.isDebugging) {
-            await this.stopDebugging()
-        }
-        this.dispose()
-    }
-
-    private setupFileWatcher() {
-        // Dispose existing watcher if any
-        this.fileWatcherDisposable?.dispose()
-
-        if (!this.data.LocalRootPath) {
-            return
-        }
-
-        // Create a file system watcher for the local root path
-        const pattern = new vscode.RelativePattern(this.data.LocalRootPath, '**/*')
-        const watcher = vscode.workspace.createFileSystemWatcher(pattern)
-
-        // Set up event handlers for file changes
-        const handleFileChange = async () => {
-            const result = await showMessage(
-                'info',
-                localize(
-                    'AWS.lambda.remoteInvoke.codeChangesDetected',
-                    'Code changes detected in the local directory. Would you like to update the Lambda function {0}@{1}?',
-                    this.data.FunctionName,
-                    this.data.FunctionRegion
-                ),
-                ['Yes', 'No']
-            )
-
-            if (result === 'Yes') {
-                try {
-                    if (this.data.LambdaFunctionNode && this.data.LocalRootPath) {
-                        const lambdaFunction = {
-                            name: this.data.FunctionName,
-                            region: this.data.FunctionRegion,
-                            configuration: this.data.LambdaFunctionNode.configuration,
-                        }
-                        await runUploadDirectory(lambdaFunction, 'zip', vscode.Uri.file(this.data.LocalRootPath))
-                    }
-                } catch (error) {
-                    throw ToolkitError.chain(
-                        error,
-                        localize('AWS.lambda.remoteInvoke.updateFailed', 'Failed to update Lambda function')
-                    )
-                }
-            }
-        }
-
-        // Listen for file changes, creations, and deletions
-        watcher.onDidChange(handleFileChange)
-        watcher.onDidCreate(handleFileChange)
-        watcher.onDidDelete(handleFileChange)
-
-        // Store the disposable so we can clean it up later
-        this.fileWatcherDisposable = watcher
-    }
-
-    // Method to start the backend timer
-    public startDebugTimer() {
-        // Clear any existing timer
-        this.stopDebugTimer()
-
-        this.debugTimeRemaining = 60
-
-        // Create a new timer that ticks every second
-        this.debugTimerHandle = setInterval(async () => {
-            this.debugTimeRemaining--
-
-            // When timer reaches zero, stop debugging
-            if (this.debugTimeRemaining <= 0) {
-                await this.handleTimerExpired()
-            }
-        }, 1000)
-    }
-
-    // Method to stop the timer
-    public stopDebugTimer() {
-        if (this.debugTimerHandle) {
-            clearInterval(this.debugTimerHandle)
-            this.debugTimerHandle = undefined
-            this.debugTimeRemaining = 0
-        }
-    }
-
-    // Handler for timer expiration
-    private async handleTimerExpired() {
-        await this.stopDebugging()
-    }
-
-    public async invokeLambda(input: string, source?: string, remoteDebugEnabled: boolean = false): Promise {
-        let qualifier: string | undefined = undefined
-        // if debugging, focus on the first editor
-        if (remoteDebugEnabled && RemoteDebugController.instance.isDebugging) {
-            await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
-            qualifier = RemoteDebugController.instance.qualifier
-        }
-
-        const isLMI = (this.data.LambdaFunctionNode?.configuration as any)?.CapacityProviderConfig
-        const isDurable = (this.data.LambdaFunctionNode?.configuration as any)?.DurableConfig
-        if (isDurable && !qualifier) {
-            // Make sure to invoke with qualifier for Durable Function, invoking unqualified will fail
-            qualifier = isLMI ? '$LATEST.PUBLISHED' : '$LATEST'
-        }
-
-        this.isInvoking = true
-
-        // If debugging is active, reset the timer during invoke
-        if (RemoteDebugController.instance.isDebugging) {
-            this.stopDebugTimer()
-        }
-
-        this.channel.show()
-        this.channel.appendLine('Loading response...')
-        await telemetry.lambda_invokeRemote.run(async (span) => {
-            try {
-                let funcResponse
-
-                if (remoteDebugEnabled) {
-                    funcResponse = await this.clientDebug.invoke(this.data.FunctionArn, input, qualifier)
-                } else if (isLMI) {
-                    funcResponse = await this.client.invoke(this.data.FunctionArn, input, qualifier, 'None')
-                } else {
-                    funcResponse = await this.client.invoke(this.data.FunctionArn, input, qualifier, 'Tail')
-                }
-
-                const logs = funcResponse.LogResult ? decodeBase64(funcResponse.LogResult) : ''
-                const decodedPayload = funcResponse.Payload ? new TextDecoder().decode(funcResponse.Payload) : ''
-                const payload = decodedPayload || JSON.stringify({})
-
-                this.channel.appendLine(`Invocation result for ${this.data.FunctionArn}`)
-                if (!isLMI) {
-                    this.channel.appendLine('Logs:')
-                    this.channel.appendLine(logs)
-                    this.channel.appendLine('')
-                }
-                this.channel.appendLine('Payload:')
-                this.channel.appendLine(String(payload))
-                this.channel.appendLine('')
-            } catch (e) {
-                const error = e as Error
-                this.channel.appendLine(`There was an error invoking ${this.data.FunctionArn}`)
-                this.channel.appendLine(error.toString())
-                this.channel.appendLine('')
-            } finally {
-                let action = remoteDebugEnabled ? 'debug' : 'invoke'
-                action = `${action}${isDurable ? '-durable' : ''}${isLMI ? '-lmi' : ''}`
-                if (!this.data.isLambdaRemote) {
-                    action = `${action}LocalStack`
-                }
-                span.record({
-                    passive: false,
-                    source: source,
-                    runtimeString: this.data.Runtime,
-                    action: action,
-                })
-
-                // Update the session state to indicate we've finished invoking
-                this.isInvoking = false
-
-                // If debugging is active, restart the timer
-                if (RemoteDebugController.instance.isDebugging) {
-                    this.startDebugTimer()
-                }
-                this.channel.show()
-            }
-        })
-    }
-
-    public async promptFile() {
-        const fileLocations = await vscode.window.showOpenDialog({
-            openLabel: localize('AWS.lambda.remoteInvoke.open', 'Open'),
-        })
-
-        if (!fileLocations || fileLocations.length === 0) {
-            return undefined
-        }
-
-        try {
-            const fileContent = readFileSync(fileLocations[0].fsPath, { encoding: 'utf8' })
-            return {
-                sample: fileContent,
-                selectedFilePath: fileLocations[0].fsPath,
-                selectedFile: this.getFileName(fileLocations[0].fsPath),
-            }
-        } catch (e) {
-            getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocations[0].fsPath, e)
-            throw ToolkitError.chain(
-                e,
-                localize('AWS.lambda.remoteInvoke.failedToReadFile', 'Failed to read selected file')
-            )
-        }
-    }
-
-    public async promptFolder(): Promise {
-        const fileLocations = await vscode.window.showOpenDialog({
-            openLabel: localize('AWS.lambda.remoteInvoke.open', 'Open'),
-            canSelectFolders: true,
-            canSelectFiles: false,
-            canSelectMany: false,
-        })
-
-        if (!fileLocations || fileLocations.length === 0) {
-            return undefined
-        }
-        this.data.LocalRootPath = fileLocations[0].fsPath
-        // try to find the handler file in this folder, open it if not opened already
-        if (!(await this.tryOpenHandlerFile())) {
-            const warning = localize(
-                'AWS.lambda.remoteInvoke.handlerFileNotFound',
-                'Handler {0} not found in selected location. Please select the folder that contains the copy of your handler file',
-                this.data.LambdaFunctionNode?.configuration.Handler
-            )
-            getLogger().warn(warning)
-            void showMessage('warn', warning)
-        }
-        return fileLocations[0].fsPath
-    }
-
-    public async tryOpenHandlerFile(path?: string, watchForUpdates: boolean = true): Promise {
-        this.handlerFile = undefined
-        if (this.data.LocalRootPath) {
-            // don't watch in appbuilder
-            watchForUpdates = false
-        }
-        if (path) {
-            // path is provided, override init path
-            this.data.LocalRootPath = path
-        }
-        // init path or node not available
-        if (!this.data.LocalRootPath || !this.data.LambdaFunctionNode) {
-            return false
-        }
-
-        const handlerFile = this.data.Runtime
-            ? await getLambdaHandlerFile(
-                  vscode.Uri.file(this.data.LocalRootPath),
-                  '',
-                  this.data.LambdaFunctionNode?.configuration.Handler ?? '',
-                  this.data.Runtime
-              )
-            : undefined
-        if (!handlerFile || !(await fs.exists(handlerFile))) {
-            this.handlerFileAvailable = false
-            return false
-        }
-        this.handlerFileAvailable = true
-        if (watchForUpdates && !isHotReloadingFunction(this.data.LambdaFunctionNode?.configuration.CodeSha256)) {
-            this.setupFileWatcher()
-        }
-        await openLambdaFile(handlerFile.fsPath)
-        this.handlerFile = handlerFile.fsPath
-        return true
-    }
-
-    public async loadFile(fileLocations: string) {
-        return await this.readFile(fileLocations)
-    }
-
-    private async readFile(filePath: string) {
-        if (!filePath) {
-            return undefined
-        }
-        const fileLocation = vscode.Uri.file(filePath)
-        try {
-            const fileContent = readFileSync(fileLocation.fsPath, { encoding: 'utf8' })
-
-            return {
-                sample: fileContent,
-                selectedFilePath: fileLocation.fsPath,
-                selectedFile: this.getFileName(fileLocation.fsPath),
-            }
-        } catch (e) {
-            getLogger().error('readFileSync: Failed to read file at path %s %O', fileLocation.fsPath, e)
-            throw ToolkitError.chain(
-                e,
-                localize('AWS.lambda.remoteInvoke.failedToReadFile', 'Failed to read selected file')
-            )
-        }
-    }
-
-    private getFileName(filePath: string): string {
-        return basename(filePath)
-    }
-
-    public async listRemoteTestEvents(functionArn: string, region: string): Promise {
-        try {
-            const params: SamCliRemoteTestEventsParameters = {
-                functionArn: functionArn,
-                operation: TestEventsOperation.List,
-                region: region,
-            }
-            const result = await this.remoteTestEvents(params)
-            return result.split('\n').filter((event) => event.trim() !== '')
-        } catch (error) {
-            // Suppress "lambda-testevent-schemas registry not found" error - this is normal when no test events exist
-            const errorMessage = error instanceof Error ? error.message : String(error)
-            if (
-                errorMessage.includes('lambda-testevent-schemas registry not found') ||
-                errorMessage.includes('There are no saved events')
-            ) {
-                getLogger().debug('No remote test events found for function: %s', functionArn)
-                return []
-            }
-            // Re-throw other errors
-            throw error
-        }
-    }
-
-    public async selectRemoteTestEvent(functionArn: string, region: string): Promise {
-        let events: string[] = []
-
-        try {
-            events = await this.listRemoteTestEvents(functionArn, region)
-        } catch (error) {
-            getLogger().error('Failed to list remote test events: %O', error)
-            void showMessage(
-                'error',
-                localize('AWS.lambda.remoteInvoke.failedToListEvents', 'Failed to list remote test events')
-            )
-            return undefined
-        }
-
-        if (events.length === 0) {
-            void showMessage(
-                'info',
-                localize(
-                    'AWS.lambda.remoteInvoke.noRemoteEvents',
-                    'No remote test events found. You can create one using "Save as remote event".'
-                )
-            )
-            return undefined
-        }
-
-        const selected = await vscode.window.showQuickPick(events, {
-            placeHolder: localize('AWS.lambda.remoteInvoke.selectRemoteEvent', 'Select a remote test event'),
-            title: localize('AWS.lambda.remoteInvoke.loadRemoteEvent', 'Load Remote Test Event'),
-        })
-
-        if (selected) {
-            const eventData = {
-                name: selected,
-                region: region,
-                arn: functionArn,
-            }
-            const resp = await this.getRemoteTestEvents(eventData)
-            return resp
-        }
-
-        return undefined
-    }
-
-    public async saveRemoteTestEvent(
-        functionArn: string,
-        region: string,
-        eventContent: string
-    ): Promise {
-        let events: string[] = []
-
-        try {
-            events = await this.listRemoteTestEvents(functionArn, region)
-        } catch (error) {
-            // Log error but continue - user can still create new events
-            getLogger().debug('Failed to list existing remote test events (may not exist yet): %O', error)
-        }
-
-        // Create options for quickpick
-        const createNewOption = '$(add) Create new test event'
-        const options = events.length > 0 ? [createNewOption, ...events] : [createNewOption]
-
-        const selected = await vscode.window.showQuickPick(options, {
-            placeHolder: localize(
-                'AWS.lambda.remoteInvoke.saveEventChoice',
-                'Create new or overwrite existing test event'
-            ),
-            title: localize('AWS.lambda.remoteInvoke.saveRemoteEvent', 'Save as Remote Event'),
-        })
-
-        if (!selected) {
-            return undefined
-        }
-
-        let eventName: string | undefined
-
-        if (selected === createNewOption) {
-            // Prompt for new event name
-            eventName = await vscode.window.showInputBox({
-                prompt: localize('AWS.lambda.remoteInvoke.enterEventName', 'Enter a name for the test event'),
-                placeHolder: localize('AWS.lambda.remoteInvoke.eventNamePlaceholder', 'MyTestEvent'),
-                validateInput: (value) => {
-                    if (!value || value.trim() === '') {
-                        return localize('AWS.lambda.remoteInvoke.eventNameRequired', 'Event name is required')
-                    }
-                    if (events.includes(value)) {
-                        return localize(
-                            'AWS.lambda.remoteInvoke.eventNameExists',
-                            'An event with this name already exists'
-                        )
-                    }
-                    return undefined
-                },
-            })
-        } else {
-            // Use selected existing event name
-            const confirm = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.lambda.remoteInvoke.overwriteEvent',
-                    'Overwrite existing test event "{0}"?',
-                    selected
-                ),
-                confirm: localize('AWS.lambda.remoteInvoke.overwrite', 'Overwrite'),
-                cancel: 'Cancel',
-                type: 'warning',
-            })
-
-            if (confirm) {
-                eventName = selected
-            }
-        }
-
-        if (eventName) {
-            // Use force flag when overwriting existing events
-            const isOverwriting = selected !== createNewOption
-            const params: SamCliRemoteTestEventsParameters = {
-                functionArn: functionArn,
-                operation: TestEventsOperation.Put,
-                name: eventName,
-                eventSample: eventContent,
-                region: region,
-                force: isOverwriting,
-            }
-            await this.remoteTestEvents(params)
-            return eventName
-        }
-
-        return undefined
-    }
-
-    public async createRemoteTestEvents(putEvent: Event, force: boolean = false) {
-        const params: SamCliRemoteTestEventsParameters = {
-            functionArn: putEvent.arn,
-            operation: TestEventsOperation.Put,
-            name: putEvent.name,
-            eventSample: putEvent.event,
-            region: putEvent.region,
-            force: force,
-        }
-        return await this.remoteTestEvents(params)
-    }
-    public async getRemoteTestEvents(getEvents: Event) {
-        const params: SamCliRemoteTestEventsParameters = {
-            name: getEvents.name,
-            operation: TestEventsOperation.Get,
-            functionArn: getEvents.arn,
-            region: getEvents.region,
-        }
-        return await this.remoteTestEvents(params)
-    }
-
-    private async remoteTestEvents(params: SamCliRemoteTestEventsParameters) {
-        return await runSamCliRemoteTestEvents(params, getSamCliContext().invoker)
-    }
-
-    public async getSamplePayload(): Promise {
-        try {
-            const inputs: SampleQuickPickItem[] = (await getSampleLambdaPayloads()).map((entry) => {
-                return { label: entry.name ?? '', filename: entry.filename ?? '' }
-            })
-
-            const qp = picker.createQuickPick({
-                items: inputs,
-                options: {
-                    title: localize(
-                        'AWS.lambda.form.pickSampleInput',
-                        'Enter keywords to filter the list of sample events'
-                    ),
-                },
-            })
-
-            const choices = await picker.promptUser({
-                picker: qp,
-            })
-            const pickerResponse = picker.verifySinglePickerOutput(choices)
-
-            if (!pickerResponse) {
-                return
-            }
-            const sampleUrl = `${sampleRequestPath}${pickerResponse.filename}`
-            const resp = await new HttpResourceFetcher(sampleUrl, { showUrl: true }).get()
-            const sample = (await resp?.text()) ?? ''
-
-            return sample
-        } catch (err) {
-            getLogger().error('Error getting manifest data..: %O', err as Error)
-            throw ToolkitError.chain(
-                err,
-                localize('AWS.lambda.remoteInvoke.gettingManifestData', 'getting manifest data')
-            )
-        }
-    }
-
-    // Download lambda code and update the local root path
-    public async downloadRemoteCode(): Promise {
-        return await telemetry.lambda_import.run(async (span) => {
-            span.record({ runtime: this.data.Runtime as TelemetryRuntime | undefined, source: 'RemoteDebug' })
-            try {
-                if (this.data.LambdaFunctionNode) {
-                    const output = await runDownloadLambda(this.data.LambdaFunctionNode, true)
-                    if (output instanceof vscode.Uri) {
-                        this.data.LocalRootPath = output.fsPath
-                        this.handlerFileAvailable = true
-                        this.setupFileWatcher()
-
-                        return output.fsPath
-                    }
-                } else {
-                    getLogger().error(
-                        localize(
-                            'AWS.lambda.remoteInvoke.lambdaFunctionNodeUndefined',
-                            'LambdaFunctionNode is undefined'
-                        )
-                    )
-                }
-                return undefined
-            } catch (error) {
-                throw ToolkitError.chain(
-                    error,
-                    localize('AWS.lambda.remoteInvoke.failedToDownloadCode', 'Failed to download remote code')
-                )
-            }
-        })
-    }
-
-    // this serves as a lock for invoke
-    public checkReadyToInvoke(): boolean {
-        if (this.isInvoking) {
-            void showMessage(
-                'warn',
-                localize(
-                    'AWS.lambda.remoteInvoke.invokeInProgress',
-                    'A remote invoke is already in progress, please wait for previous invoke, or remove debug setup'
-                )
-            )
-            return false
-        }
-        if (this.isStartingDebug) {
-            void showMessage(
-                'warn',
-                localize(
-                    'AWS.lambda.remoteInvoke.debugSetupInProgress',
-                    'A debugger setup is already in progress, please wait for previous setup to complete, or remove debug setup'
-                )
-            )
-            return false
-        }
-        return true
-    }
-
-    // this check is run when user click remote invoke with remote debugging checked
-    public async checkReadyToDebug(config: DebugConfig): Promise {
-        if (!this.data.LambdaFunctionNode) {
-            return false
-        }
-
-        if (!this.handlerFileAvailable) {
-            const result = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.lambda.remoteInvoke.handlerFileNotLocated',
-                    'The handler file cannot be located in the specified Local Root Path. As a result, remote debugging will not pause at breakpoints.'
-                ),
-                confirm: 'Continue Anyway',
-                cancel: 'Cancel',
-                type: 'warning',
-            })
-            if (!result) {
-                return false
-            }
-        }
-        // check if snapstart is on and we are publishing a version
-        if (
-            config.shouldPublishVersion &&
-            this.data.LambdaFunctionNode.configuration.SnapStart?.ApplyOn === 'PublishedVersions'
-        ) {
-            const result = await showConfirmationMessage({
-                prompt: localize(
-                    'AWS.lambda.remoteInvoke.snapstartWarning',
-                    "This function has Snapstart enabled. If you use Remote Debug with the 'publish version' setting, you'll experience notable delays. For faster debugging, consider disabling the 'publish version' option."
-                ),
-                confirm: 'Continue Anyway',
-                cancel: 'Cancel',
-                type: 'warning',
-            })
-            if (!result) {
-                // didn't confirm
-                getLogger().warn(
-                    localize('AWS.lambda.remoteInvoke.userCanceledSnapstart', 'User canceled Snapstart confirm')
-                )
-                return false
-            }
-        }
-
-        // ready to debug
-        return true
-    }
-
-    public async startDebugging(config: DebugConfig): Promise {
-        if (!this.data.LambdaFunctionNode) {
-            return false
-        }
-        if (!(await this.checkReadyToDebug(config))) {
-            return false
-        }
-        this.isStartingDebug = true
-        try {
-            await RemoteDebugController.instance.startDebugging(this.data.FunctionArn, this.data.Runtime ?? 'unknown', {
-                ...config,
-                handlerFile: this.handlerFile,
-                samFunctionLogicalId: this.data.LambdaFunctionNode.logicalId,
-                samProjectRoot: this.data.LambdaFunctionNode.projectRoot,
-            })
-        } catch (e) {
-            throw ToolkitError.chain(
-                e,
-                localize('AWS.lambda.remoteInvoke.failedToStartDebugging', 'Failed to start debugging')
-            )
-        } finally {
-            this.isStartingDebug = false
-        }
-
-        this.startDebugTimer()
-        this.debugging = this.isLDKDebugging()
-        return this.debugging
-    }
-
-    public async stopDebugging(): Promise {
-        if (this.isLDKDebugging()) {
-            this.resetServerState()
-            await RemoteDebugController.instance.stopDebugging()
-        }
-        this.debugging = this.isLDKDebugging()
-        return this.debugging
-    }
-
-    public isLDKDebugging(): boolean {
-        return RemoteDebugController.instance.isDebugging
-    }
-
-    public isWebViewDebugging(): boolean {
-        return this.debugging
-    }
-
-    public getIsInvoking(): boolean {
-        return this.isInvoking
-    }
-
-    public getDebugTimeRemaining(): number {
-        return this.debugTimeRemaining
-    }
-
-    public getLocalPath(): string {
-        return this.data.LocalRootPath ?? ''
-    }
-
-    public getHandlerAvailable(): boolean {
-        return this.handlerFileAvailable
-    }
-
-    // prestatus check run at checkbox click
-    public async debugPreCheck(): Promise {
-        return await telemetry.lambda_remoteDebugPrecheck.run(async (span) => {
-            span.record({
-                runtimeString: this.data.Runtime,
-                source: this.data.isLambdaRemote ? 'webview' : 'webviewLocalStack',
-            })
-            if (!this.debugging && RemoteDebugController.instance.isDebugging) {
-                // another debug session in progress
-                const result = await showConfirmationMessage({
-                    prompt: localize(
-                        'AWS.lambda.remoteInvoke.debugSessionActive',
-                        'A remote debug session is already active. Stop that for this new session?'
-                    ),
-                    confirm: 'Stop Previous Session',
-                    cancel: 'Cancel',
-                    type: 'warning',
-                })
-
-                if (result) {
-                    // Stop the previous session
-                    if (await this.stopDebugging()) {
-                        getLogger().error(
-                            localize(
-                                'AWS.lambda.remoteInvoke.failedToStopPreviousSession',
-                                'Failed to stop previous debug session.'
-                            )
-                        )
-                        return false
-                    }
-                } else {
-                    // user canceled, Do nothing
-                    return false
-                }
-            }
-
-            const result = await RemoteDebugController.instance.installDebugExtension(this.data.Runtime)
-            if (!result && result === false) {
-                // install failed
-                return false
-            }
-
-            await revertExistingConfig()
-
-            // Check if the function ARN is in the cache and try to open handler file
-            const cachedPath = getCachedLocalPath(this.data.FunctionArn)
-            // only check cache if not comming from appbuilder
-            if (cachedPath && !this.data.LambdaFunctionNode?.localDir) {
-                getLogger().debug(
-                    `lambda: found cached local path for function ARN: ${this.data.FunctionArn} -> ${cachedPath}`
-                )
-                await this.tryOpenHandlerFile(cachedPath, true)
-            }
-
-            // this is comming from appbuilder
-            if (this.data.LambdaFunctionNode?.localDir) {
-                await this.tryOpenHandlerFile(undefined, false)
-            }
-
-            return true
-        })
-    }
-}
-
-export async function invokeRemoteLambda(
-    context: ExtContext,
-    params: {
-        /* TODO: Instead of vague scope-leaking objects: awsContext & element, it would be cleaner if this took:
-         *  {
-         *      lambdaClient: LambdaClient,         // or just invoke/invokeAsync interface of AWS.Lambda (see: lambda.d.ts)
-         *      invokeParams: {functionArn: string} // or Lambda.Types.InvocationRequest (see: lambda.d.ts)
-         *  }
-         */
-        outputChannel: vscode.OutputChannel
-        functionNode: LambdaFunctionNode
-        source?: string
-    }
-) {
-    const inputs = await getSampleLambdaPayloads()
-    const resource: LambdaFunctionNode = params.functionNode
-    const source: string = params.source || 'AwsExplorerRemoteInvoke'
-    const client = getLambdaClientWithAgent(resource.regionCode)
-    const clientDebug = getLambdaClientWithAgent(resource.regionCode, getLambdaDebugUserAgentPairs())
-
-    const Panel = VueWebview.compilePanel(RemoteInvokeWebview)
-
-    // Initialize support and debugging capabilities
-    const runtime = resource.configuration.Runtime
-    const region = resource.regionCode
-    const supportCodeDownload = RemoteDebugController.instance.supportCodeDownload(
-        runtime,
-        resource.configuration.CodeSha256
-    )
-    const runtimeSupportsRemoteDebug = RemoteDebugController.instance.supportRuntimeRemoteDebug(runtime)
-    const remoteDebugLayer = getRemoteDebugLayer(region, resource.configuration.Architectures)
-
-    const wv = new Panel(context.extensionContext, context.outputChannel, client, clientDebug, {
-        FunctionName: resource.configuration.FunctionName ?? '',
-        FunctionArn: resource.configuration.FunctionArn ?? '',
-        FunctionRegion: resource.regionCode,
-        InputSamples: inputs,
-        TestEvents: [],
-        Source: source,
-        Runtime: runtime,
-        LocalRootPath: params.functionNode.localDir,
-        LambdaFunctionNode: params.functionNode,
-        supportCodeDownload: supportCodeDownload,
-        runtimeSupportsRemoteDebug: runtimeSupportsRemoteDebug,
-        remoteDebugLayer: remoteDebugLayer,
-        isLambdaRemote: !isLocalStackConnection(),
-    })
-    // focus on first group so wv will show up in the side
-    await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup')
-
-    const activePanel = await wv.show({
-        title: localize('AWS.invokeLambda.title', 'Invoke Lambda {0}', resource.configuration.FunctionName),
-        viewColumn: vscode.ViewColumn.Beside,
-    })
-
-    activePanel.onDidDispose(async () => {
-        await wv.server.disposeServer()
-    })
-}
diff --git a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css
deleted file mode 100644
index c96291b26a..0000000000
--- a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.css
+++ /dev/null
@@ -1,238 +0,0 @@
-/* Container and Layout */
-.Icontainer {
-    margin-inline: auto;
-    margin-top: 2rem;
-    width: 100%;
-    min-width: 650px;
-}
-
-h1 {
-    text-align: left;
-    margin-bottom: 20px;
-}
-
-/* Remove fixed width for divs to allow responsive behavior */
-div {
-    width: 100%;
-}
-
-/* VSCode Settings Style Layout */
-.vscode-setting-item {
-    margin-bottom: 10px;
-    padding: 5px 0;
-}
-
-.setting-header {
-    display: flex;
-    align-items: center;
-    margin-bottom: 8px;
-}
-
-.setting-title {
-    font-weight: 600;
-    font-size: 14px;
-    margin: 0;
-}
-
-.setting-body {
-    display: flex;
-    align-items: flex-start;
-    gap: 8px;
-}
-
-.setting-description {
-    flex: 1;
-}
-
-.setting-description info-wrap,
-.setting-description info {
-    display: block;
-    margin-bottom: 4px;
-}
-
-.setting-description-full {
-    margin-bottom: 8px;
-}
-
-.setting-description-full info-wrap {
-    display: block;
-    margin-bottom: 4px;
-}
-
-.setting-input-group-full {
-    display: flex;
-    align-items: center;
-    gap: 5px;
-}
-
-.setting-input {
-    flex-grow: 1;
-    margin-right: 2px;
-}
-
-/* Form Layout Classes - Base grid layout shared by multiple classes */
-.form-row,
-.form-row-no-align {
-    display: grid;
-    grid-template-columns: 150px 1fr;
-    margin-bottom: 10px;
-}
-
-.form-row {
-    align-items: center;
-}
-
-.form-double-row {
-    display: grid;
-    grid-template-rows: 20px 1fr;
-    align-items: center;
-}
-
-/* Typography and Text Elements */
-label {
-    font-weight: 500;
-    font-size: 14px;
-    margin-right: 10px;
-}
-
-/* Merge info and info-wrap as they share most properties */
-info,
-info-wrap {
-    color: var(--vscode-descriptionForeground);
-    font-weight: 500;
-    font-size: 13px;
-    margin-right: 10px;
-}
-
-info {
-    text-wrap-mode: nowrap;
-}
-
-/* Form Elements */
-span,
-select {
-    display: block;
-}
-
-textarea {
-    color: var(--vscode-settings-textInputForeground);
-    background: var(--vscode-settings-textInputBackground);
-    border: 1px solid var(--vscode-settings-textInputBorder);
-    width: 100%;
-    box-sizing: border-box;
-    resize: none;
-}
-
-/* Button Styles */
-.button-theme-primary,
-.button-theme-inline {
-    border: 1px solid var(--vscode-button-border);
-}
-
-.button-theme-primary {
-    padding: 8px 12px;
-    color: var(--vscode-button-foreground);
-    background: var(--vscode-button-background);
-}
-
-.button-theme-primary:hover:not(:disabled) {
-    background: var(--vscode-button-hoverBackground);
-    cursor: pointer;
-}
-
-.button-theme-inline {
-    padding: 4px 6px;
-    color: var(--vscode-button-secondaryForeground);
-    background: var(--vscode-button-secondaryBackground);
-}
-
-.button-theme-inline:hover:not(:disabled) {
-    background: var(--vscode-button-secondaryHoverBackground);
-    cursor: pointer;
-}
-
-button:disabled {
-    opacity: 0.5;
-    cursor: not-allowed;
-}
-
-/* Payload Section Styles */
-.payload-button-group {
-    display: flex;
-    gap: 5px;
-    margin-bottom: 10px;
-}
-
-.payload-textarea {
-    width: 100%;
-    min-height: 200px;
-    font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
-    font-size: 13px;
-    line-height: 1.5;
-}
-
-/* Collapsible Section */
-.collapsible-section {
-    margin: 15px 0;
-    border: 1px solid var(--vscode-widget-border);
-    border-radius: 4px;
-}
-
-.collapsible-header,
-.collapsible-content {
-    max-width: 96%;
-}
-
-.collapsible-header {
-    padding: 8px 12px;
-    background-color: var(--vscode-sideBarSectionHeader-background);
-    cursor: pointer;
-    font-weight: 500;
-}
-
-.collapsible-content {
-    padding: 10px;
-    border-top: 1px solid var(--vscode-widget-border);
-}
-
-/* Validation and Error Styles */
-.input-error {
-    border: 1px solid var(--vscode-inputValidation-errorBorder) !important;
-    background-color: var(--vscode-inputValidation-errorBackground) !important;
-}
-
-.error-message {
-    color: var(--vscode-inputValidation-errorForeground);
-    font-size: 12px;
-    margin-top: 4px;
-    font-weight: 400;
-    line-height: 1.2;
-}
-
-/* Checkbox and Status Styles */
-.remote-debug-checkbox {
-    width: 18px !important;
-    height: 18px !important;
-    accent-color: var(--vscode-checkbox-foreground);
-    border: 2px solid var(--vscode-checkbox-border) !important;
-    border-radius: 3px !important;
-    background-color: var(--vscode-checkbox-background) !important;
-    cursor: pointer;
-}
-
-.remote-debug-checkbox:checked {
-    background-color: var(--vscode-checkbox-selectBackground) !important;
-    border-color: var(--vscode-checkbox-selectBorder) !important;
-}
-
-.remote-debug-checkbox:disabled {
-    opacity: 0.6;
-    cursor: not-allowed;
-    border-color: var(--vscode-checkbox-border) !important;
-    background-color: var(--vscode-input-background) !important;
-}
-
-.remote-debug-checkbox:focus {
-    outline: 2px solid var(--vscode-focusBorder);
-    outline-offset: 2px;
-}
diff --git a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue b/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue
deleted file mode 100644
index 36c1a997d5..0000000000
--- a/packages/core/src/lambda/vue/remoteInvoke/remoteInvoke.vue
+++ /dev/null
@@ -1,354 +0,0 @@
-/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */
-
-
-
-
-
diff --git a/packages/core/src/lambda/vue/remoteInvoke/remoteInvokeFrontend.ts b/packages/core/src/lambda/vue/remoteInvoke/remoteInvokeFrontend.ts
deleted file mode 100644
index a99b6ac075..0000000000
--- a/packages/core/src/lambda/vue/remoteInvoke/remoteInvokeFrontend.ts
+++ /dev/null
@@ -1,397 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Disable because it is a front-end file.
-/* eslint-disable aws-toolkits/no-console-log */
-
-import { defineComponent } from 'vue'
-import { WebviewClientFactory } from '../../../webviews/client'
-import saveData from '../../../webviews/mixins/saveData'
-import type { RemoteInvokeData, RemoteInvokeWebview } from './invokeLambda'
-
-const client = WebviewClientFactory.create()
-const defaultInitialData = {
-    FunctionName: '',
-    FunctionArn: '',
-    FunctionRegion: '',
-    InputSamples: [],
-    FunctionMap: [],
-    TestEvents: [],
-    FunctionStack: '',
-    Source: '',
-    LambdaFunctionNode: undefined,
-    supportCodeDownload: true,
-    runtimeSupportsRemoteDebug: true,
-    remoteDebugLayer: '',
-    isLambdaRemote: true,
-}
-
-export default defineComponent({
-    data(): RemoteInvokeData {
-        return {
-            initialData: { ...defaultInitialData },
-            debugConfig: {
-                debugPort: undefined,
-                localRootPath: '',
-                remoteRootPath: '/var/task',
-                shouldPublishVersion: true,
-                lambdaTimeout: 900,
-                otherDebugParams: '',
-            },
-            debugState: {
-                isDebugging: false,
-                debugTimer: undefined,
-                debugTimeRemaining: 60,
-                showDebugTimer: false,
-                handlerFileAvailable: false,
-                remoteDebuggingEnabled: false,
-            },
-            runtimeSettings: {
-                sourceMapEnabled: true,
-                skipFiles: '/var/runtime/node_modules/**/*.js,/**',
-                justMyCode: true,
-                projectName: '',
-                outFiles: undefined,
-            },
-            uiState: {
-                isCollapsed: true,
-                extraRegionInfo: '',
-            },
-            payloadData: {
-                sampleText: '{}',
-            },
-        }
-    },
-
-    async created() {
-        // Initialize data from backend
-        this.initialData = (await client.init()) ?? this.initialData
-        this.debugConfig.localRootPath = this.initialData.LocalRootPath ?? ''
-
-        // Register for state change events from the backend
-        void client.onStateChange(async () => {
-            await this.syncStateFromWorkspace()
-        })
-
-        // Check for existing session state and load it
-        await this.syncStateFromWorkspace()
-    },
-
-    computed: {
-        // Auto-adjust textarea rows based on content
-        textareaRows(): number {
-            if (!this.payloadData.sampleText) {
-                return 5 // Default minimum rows
-            }
-
-            // Count line breaks to determine basic row count
-            const lineCount = this.payloadData.sampleText.split('\n').length
-            let additionalLine = 0
-            for (const line of this.payloadData.sampleText.split('\n')) {
-                if (line.length > 60) {
-                    additionalLine += Math.floor(line.length / 60)
-                }
-            }
-
-            // Use the larger of line count or estimated lines, with min 5 and max 20
-            const calculatedRows = lineCount + additionalLine
-            return Math.max(5, Math.min(50, calculatedRows))
-        },
-
-        // Validation computed properties
-        debugPortError(): string {
-            if (this.debugConfig.debugPort !== null && this.debugConfig.debugPort !== undefined) {
-                const port = Number(this.debugConfig.debugPort)
-                if (isNaN(port) || port < 1 || port > 65535) {
-                    return 'Debug port must be between 1 and 65535'
-                }
-            }
-            return ''
-        },
-
-        otherDebugParamsError(): string {
-            if (this.debugConfig.otherDebugParams && this.debugConfig.otherDebugParams.trim() !== '') {
-                try {
-                    JSON.parse(this.debugConfig.otherDebugParams)
-                } catch (error) {
-                    return 'Other Debug Params must be a valid JSON object'
-                }
-            }
-            return ''
-        },
-
-        lambdaTimeoutError(): string {
-            if (this.debugConfig.lambdaTimeout !== undefined) {
-                const timeout = Number(this.debugConfig.lambdaTimeout)
-                if (isNaN(timeout) || timeout < 1 || timeout > 900) {
-                    return 'Timeout override must be between 1 and 900 seconds'
-                }
-            }
-            return ''
-        },
-
-        // user can override the default provided layer and bring their own layer
-        // this is useful to support function with code signing config
-        lambdaLayerError(): string {
-            if (this.initialData.remoteDebugLayer && this.initialData.remoteDebugLayer.trim() !== '') {
-                const layerArn = this.initialData.remoteDebugLayer.trim()
-
-                // Validate Lambda layer ARN format
-                // Expected format: arn:aws:lambda:region:account-id:layer:layer-name:version
-                const layerArnRegex = /^arn:aws:lambda:[a-z0-9-]+:\d{12}:layer:[a-zA-Z0-9-_]+:\d+$/
-
-                if (!layerArnRegex.test(layerArn)) {
-                    return 'Layer ARN must be in the format: arn:aws:lambda:::layer::'
-                }
-
-                // Extract region from ARN to validate it matches the function region
-                const arnParts = layerArn.split(':')
-                if (arnParts.length >= 4) {
-                    const layerRegion = arnParts[3]
-                    if (this.initialData.FunctionRegion && layerRegion !== this.initialData.FunctionRegion) {
-                        return `Layer region (${layerRegion}) must match function region (${this.initialData.FunctionRegion})`
-                    }
-                }
-            }
-            return ''
-        },
-    },
-
-    methods: {
-        // Runtime detection computed properties based on the runtime string
-        hasRuntimePrefix(prefix: string): boolean {
-            const runtime = this.initialData.Runtime || ''
-            return runtime.startsWith(prefix)
-        },
-        // Sync state from workspace storage
-        async syncStateFromWorkspace() {
-            try {
-                // Detect Lambda remote debugging connection
-                this.uiState.extraRegionInfo = this.initialData.isLambdaRemote ? '' : '(LocalStack running)'
-
-                // Update debugging state
-                this.debugState.isDebugging = await client.isWebViewDebugging()
-                this.debugConfig.localRootPath = await client.getLocalPath()
-                this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
-                // Get current session state
-
-                if (this.debugState.isDebugging) {
-                    // Update invoke button state based on session
-                    const isInvoking = await client.getIsInvoking()
-
-                    // If debugging is active and we're not showing the timer,
-                    // calculate and show remaining time
-                    this.clearDebugTimer()
-                    if (this.debugState.isDebugging && !isInvoking) {
-                        await this.startDebugTimer()
-                    }
-                } else {
-                    this.clearDebugTimer()
-                    // no debug session
-                }
-            } catch (error) {
-                console.error('Failed to sync state from workspace:', error)
-            }
-        },
-        async saveEvent() {
-            if (this.initialData.FunctionArn && this.initialData.FunctionRegion) {
-                // Use the backend method that shows a quickpick for save
-                await client.saveRemoteTestEvent(
-                    this.initialData.FunctionArn,
-                    this.initialData.FunctionRegion,
-                    this.payloadData.sampleText
-                )
-            }
-        },
-        async promptForFileLocation() {
-            const resp = await client.promptFile()
-            if (resp) {
-                // Populate the textarea with file content
-                this.payloadData.sampleText = resp.sample
-            }
-        },
-        async promptForFolderLocation() {
-            const resp = await client.promptFolder()
-            if (resp) {
-                this.debugConfig.localRootPath = resp
-                this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
-            }
-        },
-        async debugPreCheck() {
-            if (!this.debugState.remoteDebuggingEnabled) {
-                // don't check if unchecking
-                this.debugState.remoteDebuggingEnabled = false
-                if (this.debugState.isDebugging) {
-                    await client.stopDebugging()
-                }
-            } else {
-                // check when user is checking box
-                this.debugState.remoteDebuggingEnabled = await client.debugPreCheck()
-                this.debugConfig.localRootPath = await client.getLocalPath()
-                this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
-            }
-        },
-
-        async sendInput() {
-            // Tell the backend to set the button state. This state is maintained even if webview loses focus
-            if (this.debugState.remoteDebuggingEnabled) {
-                // check few outof bound issue
-                if (
-                    this.debugConfig.lambdaTimeout &&
-                    (this.debugConfig.lambdaTimeout > 900 || this.debugConfig.lambdaTimeout < 0)
-                ) {
-                    this.debugConfig.lambdaTimeout = 900
-                }
-                if (
-                    this.debugConfig.debugPort &&
-                    (this.debugConfig.debugPort > 65535 || this.debugConfig.debugPort <= 0)
-                ) {
-                    this.debugConfig.debugPort = 9229
-                }
-
-                // acquire invoke lock
-                if (this.debugState.remoteDebuggingEnabled && !(await client.checkReadyToInvoke())) {
-                    return
-                }
-
-                const defaultPort = this.initialData.isLambdaRemote ? 9229 : undefined
-
-                if (!this.debugState.isDebugging) {
-                    this.debugState.isDebugging = await client.startDebugging({
-                        functionArn: this.initialData.FunctionArn,
-                        functionName: this.initialData.FunctionName,
-                        port: this.debugConfig.debugPort ?? defaultPort,
-                        sourceMap: this.runtimeSettings.sourceMapEnabled,
-                        localRoot: this.debugConfig.localRootPath,
-                        shouldPublishVersion: this.debugConfig.shouldPublishVersion,
-                        remoteRoot:
-                            this.debugConfig.remoteRootPath !== '' ? this.debugConfig.remoteRootPath : '/var/task',
-                        skipFiles: (this.runtimeSettings.skipFiles !== ''
-                            ? this.runtimeSettings.skipFiles
-                            : '/**'
-                        ).split(','),
-                        justMyCode: this.runtimeSettings.justMyCode,
-                        projectName: this.runtimeSettings.projectName,
-                        otherDebugParams: this.debugConfig.otherDebugParams,
-                        layerArn: this.initialData.remoteDebugLayer,
-                        lambdaTimeout: this.debugConfig.lambdaTimeout ?? 900,
-                        outFiles: this.runtimeSettings.outFiles?.split(','),
-                        isLambdaRemote: this.initialData.isLambdaRemote ?? true,
-                    })
-                    if (!this.debugState.isDebugging) {
-                        // user cancel or failed to start debugging
-                        return
-                    }
-                }
-                this.debugState.showDebugTimer = false
-            }
-
-            await client.invokeLambda(
-                this.payloadData.sampleText,
-                this.initialData.Source,
-                this.debugState.remoteDebuggingEnabled
-            )
-            await this.syncStateFromWorkspace()
-        },
-
-        async removeDebugSetup() {
-            this.debugState.isDebugging = await client.stopDebugging()
-        },
-
-        async startDebugTimer() {
-            this.debugState.debugTimeRemaining = await client.getDebugTimeRemaining()
-            if (this.debugState.debugTimeRemaining <= 0) {
-                return
-            }
-            this.debugState.showDebugTimer = true
-            this.debugState.debugTimer = window.setInterval(() => {
-                this.debugState.debugTimeRemaining--
-                if (this.debugState.debugTimeRemaining <= 0) {
-                    this.clearDebugTimer()
-                }
-            }, 1000) as number | undefined
-        },
-
-        clearDebugTimer() {
-            if (this.debugState.debugTimer) {
-                window.clearInterval(this.debugState.debugTimer)
-                this.debugState.debugTimeRemaining = 0
-                this.debugState.debugTimer = undefined
-                this.debugState.showDebugTimer = false
-            }
-        },
-
-        toggleCollapsible() {
-            this.uiState.isCollapsed = !this.uiState.isCollapsed
-        },
-
-        async openHandler() {
-            await client.tryOpenHandlerFile()
-        },
-
-        async openHandlerWithDelay() {
-            const preValue = this.debugConfig.localRootPath
-            // user is inputting the dir, only try to open dir if user stopped typing for 1 second
-            await new Promise((resolve) => setTimeout(resolve, 1000))
-            if (preValue !== this.debugConfig.localRootPath) {
-                return
-            }
-            // try open if user stop input for 1 second
-            await client.tryOpenHandlerFile(this.debugConfig.localRootPath)
-            this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
-        },
-
-        async downloadRemoteCode() {
-            try {
-                const path = await client.downloadRemoteCode()
-                if (path) {
-                    this.debugConfig.localRootPath = path
-                    this.debugState.handlerFileAvailable = await client.getHandlerAvailable()
-                }
-            } catch (error) {
-                console.error('Failed to download remote code:', error)
-            }
-        },
-
-        loadSampleEvent() {
-            client.getSamplePayload().then(
-                (sample) => {
-                    if (!sample) {
-                        return
-                    }
-                    this.payloadData.sampleText = JSON.stringify(JSON.parse(sample), undefined, 4)
-                },
-                (e) => {
-                    console.error('client.getSamplePayload failed: %s', (e as Error).message)
-                }
-            )
-        },
-
-        async loadRemoteTestEvents() {
-            if (this.initialData.FunctionArn && this.initialData.FunctionRegion) {
-                // Use the backend method that shows a quickpick
-                const eventContent = await client.selectRemoteTestEvent(
-                    this.initialData.FunctionArn,
-                    this.initialData.FunctionRegion
-                )
-
-                if (eventContent) {
-                    // Populate the textarea with the selected event
-                    this.payloadData.sampleText = JSON.stringify(JSON.parse(eventContent), undefined, 4)
-                }
-            }
-        },
-        onDebugPortChange(event: Event) {
-            const value = (event.target as HTMLInputElement).value
-            if (value === '') {
-                this.debugConfig.debugPort = undefined
-            } else {
-                this.debugConfig.debugPort = Number(value)
-            }
-        },
-    },
-
-    mixins: [saveData],
-})
diff --git a/packages/core/src/lambda/wizards/samDeployWizard.ts b/packages/core/src/lambda/wizards/samDeployWizard.ts
deleted file mode 100644
index 365d395e53..0000000000
--- a/packages/core/src/lambda/wizards/samDeployWizard.ts
+++ /dev/null
@@ -1,1054 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-import * as _ from 'lodash'
-
-const localize = nls.loadMessageBundle()
-
-import * as path from 'path'
-import * as vscode from 'vscode'
-import * as localizedText from '../../shared/localizedText'
-import { getLogger } from '../../shared/logger/logger'
-import { createHelpButton } from '../../shared/ui/buttons'
-import * as input from '../../shared/ui/input'
-import * as picker from '../../shared/ui/picker'
-import { difference, filter, IteratorTransformer } from '../../shared/utilities/collectionUtils'
-import {
-    MultiStepWizard,
-    WIZARD_GOBACK,
-    WIZARD_TERMINATE,
-    wizardContinue,
-    WizardStep,
-    WIZARD_RETRY,
-} from '../../shared/wizards/multiStepWizard'
-import { configureParameterOverrides } from '../config/configureParameterOverrides'
-import { getOverriddenParameters, getParameters } from '../config/parameterUtils'
-
-import { DefaultEcrClient, EcrRepository } from '../../shared/clients/ecrClient'
-import { getSamCliVersion } from '../../shared/sam/cli/samCliContext'
-import * as semver from 'semver'
-import { minSamCliVersionForImageSupport } from '../../shared/sam/cli/samCliValidator'
-import { ExtContext } from '../../shared/extensions'
-import { validateBucketName } from '../../awsService/s3/util'
-import { showViewLogsMessage } from '../../shared/utilities/messages'
-import { getIdeProperties, getSamDeployDocUrl, isCloud9 } from '../../shared/extensionUtilities'
-import { recentlyUsed } from '../../shared/localizedText'
-import globals from '../../shared/extensionGlobals'
-import { SamCliSettings } from '../../shared/sam/cli/samCliSettings'
-import { getIcon } from '../../shared/icons'
-import { S3Client } from '../../shared/clients/s3'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { openUrl } from '../../shared/utilities/vsCodeUtils'
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-const CREATE_NEW_BUCKET = localize('AWS.command.s3.createBucket', 'Create Bucket...')
-// eslint-disable-next-line @typescript-eslint/naming-convention
-const ENTER_BUCKET = localize('AWS.samcli.deploy.bucket.existingLabel', 'Enter Existing Bucket Name...')
-
-export interface SamDeployWizardResponse {
-    parameterOverrides: Map
-    region: string
-    template: vscode.Uri
-    s3Bucket: string
-    ecrRepo?: EcrRepository
-    stackName: string
-}
-
-export const enum ParameterPromptResult {
-    Cancel,
-    Continue,
-}
-
-export interface SamDeployWizardContext {
-    readonly extContext: ExtContext
-    readonly workspaceFolders: vscode.Uri[] | undefined
-    additionalSteps: number
-
-    /**
-     * Returns the parameters in the specified template, or `undefined`
-     * if the template does not include a `Parameters` section. `required`
-     * is set to `true` if the parameter does not have a default value.
-     *
-     * @param templateUri The URL of the SAM template to inspect.
-     */
-    getParameters: typeof getParameters
-
-    /**
-     * Returns true if the teamplate has images and needs an ECR repo to upload them to
-     * TODO refactor this to not be needed by making getTemplate also return the template in addition
-     * to the URI
-     */
-    determineIfTemplateHasImages(templatePath: vscode.Uri): Promise
-
-    /**
-     * Returns the names and values of parameters from the specified template
-     * that have been overridden in `templates.json`, or `undefined` if `templates.json`
-     * does not include a `parameterOverrides` section for the specified template.
-     *
-     * @param templateUri
-     */
-    getOverriddenParameters: typeof getOverriddenParameters
-
-    /**
-     * Retrieves the URI of a Sam template to deploy from the user
-     *
-     * @returns vscode.Uri of a Sam Template. undefined represents cancel.
-     */
-    promptUserForSamTemplate(initialValue?: vscode.Uri): Promise
-
-    /**
-     * Prompts the user to configure parameter overrides, then either pre-fills and opens
-     * `templates.json`, or returns true.
-     *
-     * @param options.templateUri The URL of the SAM template to inspect.
-     * @param options.missingParameters The names of required parameters that are not yet overridden.
-     * @returns A value indicating whether the wizard should proceed. `false` if `missingParameters` was
-     *          non-empty, or if it was empty and the user opted to configure overrides instead of continuing.
-     */
-    promptUserForParametersIfApplicable(options: {
-        templateUri: vscode.Uri
-        missingParameters?: Set
-    }): Promise
-
-    promptUserForRegion(step: number, initialValue?: string): Promise
-
-    /**
-     * Retrieves an S3 Bucket to deploy to from the user.
-     *
-     * @param initialValue Optional, Initial value to prompt with
-     *
-     * @returns S3 Bucket name. Undefined represents cancel.
-     */
-    promptUserForS3Bucket(
-        step: number,
-        selectedRegion: string,
-        profile?: string,
-        accountId?: string,
-        initialValue?: string
-    ): Promise
-
-    /**
-     * Prompts user to enter a bucket name
-     *
-     * @returns S3 Bucket name. Undefined represents cancel.
-     */
-    promptUserForS3BucketName(
-        step: number,
-        bucketProps: {
-            title: string
-            prompt?: string
-            placeHolder?: string
-            value?: string
-            buttons?: vscode.QuickInputButton[]
-            buttonHandler?: (
-                button: vscode.QuickInputButton,
-                inputBox: vscode.InputBox,
-                resolve: (value: string | PromiseLike | undefined) => void,
-                reject: (value: string | PromiseLike | undefined) => void
-            ) => void
-        }
-    ): Promise
-
-    /**
-     * Retrieves an ECR Repo to deploy to from the user.
-     *
-     * @param initialValue Optional, Initial value to prompt with
-     *
-     * @returns ECR Repo URI. Undefined represents cancel.
-     */
-    promptUserForEcrRepo(
-        step: number,
-        selectedRegion?: string,
-        initialValue?: EcrRepository
-    ): Promise
-
-    /**
-     * Retrieves a Stack Name to deploy to from the user.
-     *
-     * @param initialValue Optional, Initial value to prompt with
-     * @param validateInput Optional, validates input as it is entered
-     *
-     * @returns Stack name. Undefined represents cancel.
-     */
-    promptUserForStackName({
-        initialValue,
-        validateInput,
-    }: {
-        initialValue?: string
-        validateInput(value: string): string | undefined
-    }): Promise
-}
-
-function getSingleResponse(responses: vscode.QuickPickItem[] | undefined): string | undefined {
-    if (!responses) {
-        return undefined
-    }
-
-    if (responses.length !== 1) {
-        throw new Error(`Expected a single response, but got ${responses.length}`)
-    }
-
-    return responses[0].label
-}
-
-export class DefaultSamDeployWizardContext implements SamDeployWizardContext {
-    public readonly getParameters = getParameters
-    public readonly getOverriddenParameters = getOverriddenParameters
-    private readonly helpButton = createHelpButton()
-
-    private readonly totalSteps: number = 4
-    public additionalSteps: number = 0
-    public newBucketCalled = false
-
-    public constructor(readonly extContext: ExtContext) {}
-
-    public get workspaceFolders(): vscode.Uri[] | undefined {
-        return (vscode.workspace.workspaceFolders || []).map((f) => f.uri)
-    }
-
-    public async determineIfTemplateHasImages(templatePath: vscode.Uri): Promise {
-        const template = (await globals.templateRegistry).getItem(templatePath.fsPath)
-        const resources = template?.item?.Resources
-        if (resources === undefined) {
-            return false
-        } else {
-            return Object.keys(resources)
-                .filter((key) => resources[key]?.Type === 'AWS::Serverless::Function')
-                .map((key) => resources[key]?.Properties?.PackageType)
-                .includes('Image')
-        }
-    }
-
-    /**
-     * Retrieves the URI of a Sam template to deploy from the user
-     *
-     * @returns vscode.Uri of a Sam Template. undefined represents cancel.
-     */
-    public async promptUserForSamTemplate(initialValue?: vscode.Uri): Promise {
-        const workspaceFolders = this.workspaceFolders || []
-
-        const quickPick = picker.createQuickPick({
-            options: {
-                ignoreFocusOut: true,
-                title: localize(
-                    'AWS.samcli.deploy.template.prompt',
-                    'Which SAM Template would you like to deploy to {0}?',
-                    getIdeProperties().company
-                ),
-                step: 1,
-                totalSteps: this.totalSteps,
-            },
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-            items: await getTemplateChoices(...workspaceFolders),
-        })
-
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                }
-            },
-        })
-        const val = picker.verifySinglePickerOutput(choices)
-
-        return val ? val.uri : undefined
-    }
-
-    public async promptUserForParametersIfApplicable({
-        templateUri,
-        missingParameters = new Set(),
-    }: {
-        templateUri: vscode.Uri
-        missingParameters?: Set
-    }): Promise {
-        if (missingParameters.size < 1) {
-            const prompt = localize(
-                'AWS.samcli.deploy.parameters.optionalPrompt.message',
-                // prettier-ignore
-                'Template "{0}" contains parameters. Do you want to override their default values?',
-                templateUri.fsPath
-            )
-            const quickPick = picker.createQuickPick({
-                options: {
-                    ignoreFocusOut: true,
-                    title: prompt,
-                    step: 2,
-                    totalSteps: this.totalSteps + this.additionalSteps,
-                },
-                buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-                items: [{ label: localizedText.yes }, { label: localizedText.no }],
-            })
-            const response = getSingleResponse(
-                await picker.promptUser({
-                    picker: quickPick,
-                    onDidTriggerButton: (button, resolve, reject) => {
-                        if (button === vscode.QuickInputButtons.Back) {
-                            resolve(undefined)
-                        } else if (button === this.helpButton) {
-                            void openUrl(getSamDeployDocUrl())
-                        }
-                    },
-                })
-            )
-            if (response !== localizedText.yes) {
-                return ParameterPromptResult.Continue
-            }
-
-            await configureParameterOverrides({
-                templateUri,
-                requiredParameterNames: missingParameters.keys(),
-            })
-
-            return ParameterPromptResult.Cancel
-        } else {
-            const prompt = localize(
-                'AWS.samcli.deploy.parameters.mandatoryPrompt.message',
-                // prettier-ignore
-                'The template {0} contains parameters without default values. In order to deploy, you must provide values for these parameters. Configure them now?',
-                templateUri.fsPath
-            )
-            const responseConfigure = localize(
-                'AWS.samcli.deploy.parameters.mandatoryPrompt.responseConfigure',
-                'Configure'
-            )
-            const responseCancel = localizedText.cancel
-
-            // no step number needed since this is a dead end?
-            const quickPick = picker.createQuickPick({
-                options: {
-                    ignoreFocusOut: true,
-                    title: prompt,
-                },
-                buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-                items: [{ label: responseConfigure }, { label: responseCancel }],
-            })
-            const response = getSingleResponse(
-                await picker.promptUser({
-                    picker: quickPick,
-                    onDidTriggerButton: (button, resolve, reject) => {
-                        if (button === vscode.QuickInputButtons.Back) {
-                            resolve(undefined)
-                        } else if (button === this.helpButton) {
-                            void openUrl(getSamDeployDocUrl())
-                        }
-                    },
-                })
-            )
-            if (response === responseConfigure) {
-                await configureParameterOverrides({
-                    templateUri,
-                    requiredParameterNames: missingParameters.keys(),
-                })
-            }
-
-            return ParameterPromptResult.Cancel
-        }
-    }
-
-    public async promptUserForRegion(step: number, initialRegionCode?: string): Promise {
-        const partitionRegions = this.extContext.regionProvider.getRegions()
-
-        const quickPick = picker.createQuickPick({
-            options: {
-                title: localize(
-                    'AWS.samcli.deploy.region.prompt',
-                    'Which {0} Region would you like to deploy to?',
-                    getIdeProperties().company
-                ),
-                value: initialRegionCode,
-                matchOnDetail: true,
-                ignoreFocusOut: true,
-                step: step,
-                totalSteps: this.totalSteps + this.additionalSteps,
-            },
-            items: partitionRegions.map((region) => ({
-                label: region.name,
-                detail: region.id,
-                // this is the only way to get this to show on going back
-                // this will make it so it always shows even when searching for something else
-                alwaysShow: region.id === initialRegionCode,
-                description: region.id === initialRegionCode ? localizedText.recentlyUsed : '',
-            })),
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-        })
-
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                }
-            },
-        })
-        const val = picker.verifySinglePickerOutput(choices)
-
-        return val?.detail
-    }
-
-    /**
-     * Retrieves an S3 Bucket to deploy to from the user.
-     *
-     * @param selectedRegion Selected region for S3 client usage
-     * @param initialValue Optional, Initial value to prompt with
-     * @param messages Passthrough strings for testing
-     *
-     * @returns S3 Bucket name. Undefined represents cancel.
-     */
-    public async promptUserForS3Bucket(
-        step: number,
-        selectedRegion: string,
-        profile?: string,
-        accountId?: string,
-        initialValue?: string,
-        messages: {
-            noBuckets: string
-            bucketError: string
-        } = {
-            noBuckets: localize('AWS.samcli.deploy.s3bucket.picker.noBuckets', 'No buckets found.'),
-            bucketError: localize('AWS.samcli.deploy.s3bucket.picker.error', 'There was an error loading S3 buckets.'),
-        }
-    ): Promise {
-        const createBucket = {
-            iconPath: getIcon('vscode-add'),
-            tooltip: CREATE_NEW_BUCKET,
-        }
-        const enterBucket = {
-            iconPath: getIcon('vscode-edit'),
-            tooltip: ENTER_BUCKET,
-        }
-        const quickPick = picker.createQuickPick({
-            buttons: [enterBucket, createBucket, this.helpButton, vscode.QuickInputButtons.Back],
-            options: {
-                title: localize(
-                    'AWS.samcli.deploy.s3Bucket.prompt',
-                    'Select an {0} S3 Bucket to deploy code to',
-                    getIdeProperties().company
-                ),
-                value: initialValue,
-                matchOnDetail: true,
-                ignoreFocusOut: true,
-                step: step,
-                totalSteps: this.totalSteps + this.additionalSteps,
-            },
-        })
-
-        quickPick.busy = true
-
-        // NOTE: Do not await this promise.
-        // This will background load the S3 buckets and load them all (in one chunk) when the operation completes.
-        // Not awaiting lets us display a "loading" quick pick for immediate feedback.
-        // Does not use an IteratingQuickPick because listing S3 buckets by region is not a paginated operation.
-        populateS3QuickPick(quickPick, selectedRegion, SamCliSettings.instance, messages, profile, accountId).catch(
-            (e) => {
-                getLogger().error('populateS3QuickPick: %s', (e as Error).message)
-            }
-        )
-
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                } else if (button === createBucket) {
-                    resolve([{ label: CREATE_NEW_BUCKET }])
-                } else if (button === enterBucket) {
-                    resolve([{ label: ENTER_BUCKET }])
-                }
-            },
-        })
-        const val = picker.verifySinglePickerOutput(choices)
-
-        return val?.label && ![messages.noBuckets, messages.bucketError].includes(val.label) ? val.label : undefined
-    }
-
-    public async promptUserForS3BucketName(
-        step: number,
-        bucketProps: {
-            title: string
-            prompt?: string
-            placeHolder?: string
-            value?: string
-            buttons?: vscode.QuickInputButton[]
-            buttonHandler?: (
-                button: vscode.QuickInputButton,
-                inputBox: vscode.InputBox,
-                resolve: (value: string | PromiseLike | undefined) => void,
-                reject: (value: string | PromiseLike | undefined) => void
-            ) => void
-        }
-    ): Promise {
-        if (!this.newBucketCalled) {
-            this.additionalSteps++
-            this.newBucketCalled = true
-        }
-        const inputBox = input.createInputBox({
-            buttons: [
-                this.helpButton,
-                vscode.QuickInputButtons.Back,
-                ...(bucketProps.buttons ? bucketProps.buttons : []),
-            ],
-            options: {
-                title: bucketProps.title,
-                ignoreFocusOut: true,
-                step: step + 1,
-                totalSteps: this.totalSteps + this.additionalSteps,
-                value: bucketProps.value,
-                prompt: bucketProps.prompt,
-                placeHolder: bucketProps.placeHolder,
-            },
-        })
-
-        const response = await input.promptUser({
-            inputBox: inputBox,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                } else if (bucketProps.buttonHandler) {
-                    bucketProps.buttonHandler(button, inputBox, resolve, reject)
-                }
-            },
-            onValidateInput: validateBucketName,
-        })
-
-        if (!response) {
-            return undefined
-        } else {
-            return response
-        }
-    }
-
-    public async promptUserForEcrRepo(
-        step: number,
-        selectedRegion: string,
-        initialValue?: EcrRepository
-    ): Promise {
-        const quickPick = picker.createQuickPick({
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-            options: {
-                title: localize('AWS.samcli.deploy.ecrRepo.prompt', 'Select a ECR repo to deploy images to'),
-                value: initialValue?.repositoryName,
-                matchOnDetail: true,
-                ignoreFocusOut: true,
-                step: step,
-                totalSteps: this.totalSteps + this.additionalSteps,
-            },
-        })
-
-        const populator = new IteratorTransformer(
-            () => new DefaultEcrClient(selectedRegion).describeRepositories(),
-            (response) => (response === undefined ? [] : [{ label: response.repositoryName, repository: response }])
-        )
-        const controller = new picker.IteratingQuickPickController(quickPick, populator)
-        controller.startRequests()
-        const choices = await picker.promptUser({
-            picker: quickPick,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                }
-            },
-        })
-
-        const result = picker.verifySinglePickerOutput(choices)
-        const repository: EcrRepository | undefined = (result as any)?.repository
-        const label = result?.label
-
-        if (!repository || label === picker.IteratingQuickPickController.NO_ITEMS_ITEM.label) {
-            return undefined
-        }
-
-        return repository
-    }
-
-    /**
-     * Retrieves a Stack Name to deploy to from the user.
-     *
-     * @param initialValue Optional, Initial value to prompt with
-     * @param validateInput Optional, validates input as it is entered
-     *
-     * @returns Stack name. Undefined represents cancel.
-     */
-    public async promptUserForStackName({
-        initialValue,
-        validateInput,
-    }: {
-        initialValue?: string
-        validateInput(value: string): string | undefined
-    }): Promise {
-        const inputBox = input.createInputBox({
-            buttons: [this.helpButton, vscode.QuickInputButtons.Back],
-            options: {
-                title: localize('AWS.samcli.deploy.stackName.prompt', 'Enter the name to use for the deployed stack'),
-                ignoreFocusOut: true,
-                step: 4 + this.additionalSteps,
-                totalSteps: this.totalSteps + this.additionalSteps,
-            },
-        })
-
-        // Pre-populate the value if it was already set
-        if (initialValue) {
-            inputBox.value = initialValue
-        }
-
-        return await input.promptUser({
-            inputBox: inputBox,
-            onValidateInput: validateInput,
-            onDidTriggerButton: (button, resolve, reject) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    resolve(undefined)
-                } else if (button === this.helpButton) {
-                    void openUrl(getSamDeployDocUrl())
-                }
-            },
-        })
-    }
-}
-
-export class SamDeployWizard extends MultiStepWizard {
-    private readonly response: Partial = {}
-    /**
-     * If the selected template has Image based lambdas. If it does, we also need to prompt for
-     * an ECR repo to push the images to
-     */
-    private hasImages: boolean = false
-
-    /**
-     * Initial treenode passed as a command arg to the "Deploy" command
-     * (typically, when invoked from the File Explorer context-menu).
-     */
-    private regionNode: any | undefined
-    /**
-     * Initial template.yaml path passed as a command arg to the "Deploy"
-     * command (typically, when invoked from the File Explorer context-menu).
-     */
-    private samTemplateFile: vscode.Uri | undefined
-
-    /**
-     *
-     * @param context
-     * @param commandArg Argument given by VSCode when the "Deploy" command was invoked from a context-menu.
-     */
-    public constructor(
-        private readonly context: SamDeployWizardContext,
-        commandArg?: any
-    ) {
-        super()
-        if (commandArg && commandArg.path) {
-            // "Deploy" command was invoked on a template.yaml file.
-            // The promptUserForSamTemplate() call will be skipped.
-            this.samTemplateFile = commandArg as vscode.Uri
-        } else if (commandArg && commandArg.regionCode) {
-            this.regionNode = commandArg
-            this.response.region = this.regionNode.regionCode
-        }
-    }
-
-    protected get startStep() {
-        return this.TEMPLATE
-    }
-
-    protected getResult(): SamDeployWizardResponse | undefined {
-        if (
-            !this.response.parameterOverrides ||
-            !this.response.template ||
-            !this.response.region ||
-            !this.response.s3Bucket ||
-            !this.response.stackName
-        ) {
-            return undefined
-        }
-
-        return {
-            parameterOverrides: this.response.parameterOverrides,
-            template: this.response.template,
-            region: this.response.region,
-            s3Bucket: this.response.s3Bucket,
-            ecrRepo: this.response.ecrRepo,
-            stackName: this.response.stackName,
-        }
-    }
-
-    private readonly TEMPLATE: WizardStep = async () => {
-        // set steps back to 0 since the next step determines if additional steps are needed
-        this.context.additionalSteps = 0
-        if (this.samTemplateFile && this.response.template) {
-            // This state means:
-            //   1. wizard was started from a context-menu (`this.samTemplateFile` was set)
-            //   2. user canceled the REGION step.
-            // => User wants to exit the wizard.
-            return WIZARD_TERMINATE
-        } else if (this.samTemplateFile) {
-            this.response.template = this.samTemplateFile
-        } else {
-            this.response.template = await this.context.promptUserForSamTemplate(this.response.template)
-        }
-
-        if (!this.response.template) {
-            return WIZARD_TERMINATE
-        }
-
-        // also ask user to setup CFN parameters if they haven't already done so
-        const getNextStep = (result: ParameterPromptResult) => {
-            switch (result) {
-                case ParameterPromptResult.Cancel:
-                    return WIZARD_TERMINATE
-                case ParameterPromptResult.Continue:
-                    return wizardContinue(this.skipOrPromptRegion(this.S3_BUCKET))
-            }
-        }
-
-        this.hasImages = await this.context.determineIfTemplateHasImages(this.response.template)
-        if (this.hasImages) {
-            // TODO: remove check when min version is high enough
-            const samCliVersion = await getSamCliVersion(this.context.extContext.samCliContext())
-            if (semver.lt(samCliVersion, minSamCliVersionForImageSupport)) {
-                void vscode.window.showErrorMessage(
-                    localize(
-                        'AWS.output.sam.no.image.support',
-                        'Support for Image-based Lambdas requires a minimum SAM CLI version of 1.13.0.'
-                    )
-                )
-                return WIZARD_TERMINATE
-            }
-
-            this.context.additionalSteps++
-        }
-
-        const parameters = await this.context.getParameters(this.response.template)
-        if (parameters.size < 1) {
-            this.response.parameterOverrides = new Map()
-
-            return wizardContinue(this.skipOrPromptRegion(this.S3_BUCKET))
-        }
-
-        const requiredParameterNames = new Set(
-            filter(parameters.keys(), (name) => parameters.get(name)!.required)
-        )
-        const overriddenParameters = await this.context.getOverriddenParameters(this.response.template)
-        if (!overriddenParameters) {
-            // In there are no missing required parameters case, it isn't mandatory to override any parameters,
-            // but we still want to inform users of the option to override. Once we have prompted (i.e., if the
-            // parameter overrides section is empty instead of undefined), don't prompt again unless required.
-            this.context.additionalSteps++
-
-            const options = {
-                templateUri: this.response.template,
-                missingParameters: requiredParameterNames.size > 0 ? requiredParameterNames : undefined,
-            }
-
-            this.response.parameterOverrides = new Map()
-
-            return getNextStep(await this.context.promptUserForParametersIfApplicable(options))
-        }
-
-        const missingParameters = difference(requiredParameterNames, overriddenParameters.keys())
-        if (missingParameters.size > 0) {
-            this.context.additionalSteps++
-
-            return getNextStep(
-                await this.context.promptUserForParametersIfApplicable({
-                    templateUri: this.response.template,
-                    missingParameters,
-                })
-            )
-        }
-
-        this.response.parameterOverrides = overriddenParameters
-
-        return wizardContinue(this.skipOrPromptRegion(this.S3_BUCKET))
-    }
-
-    private readonly REGION: WizardStep = async (step) => {
-        this.response.region = await this.context.promptUserForRegion(step, this.response.region)
-
-        return this.response.region ? wizardContinue(this.S3_BUCKET) : WIZARD_GOBACK
-    }
-
-    private readonly S3_BUCKET: WizardStep = async (step) => {
-        const profile = this.context.extContext.awsContext.getCredentialProfileName() || ''
-        const accountId = this.context.extContext.awsContext.getCredentialAccountId() || ''
-        const response = await this.context.promptUserForS3Bucket(
-            step,
-            this.response.region!,
-            profile,
-            accountId,
-            this.response.s3Bucket
-        )
-
-        if (!response) {
-            return WIZARD_GOBACK
-        }
-
-        if (response === CREATE_NEW_BUCKET) {
-            const newBucketRequest = await this.context.promptUserForS3BucketName(step, {
-                title: localize('AWS.s3.createBucket.prompt', 'Enter a new bucket name'),
-            })
-            if (!newBucketRequest) {
-                return WIZARD_RETRY
-            }
-
-            try {
-                const s3Client = new S3Client(this.response.region!)
-                const newBucketName = (await s3Client.createBucket({ bucketName: newBucketRequest })).bucket.Name
-                this.response.s3Bucket = newBucketName
-                getLogger().info('Created bucket: %O', newBucketName)
-                void vscode.window.showInformationMessage(
-                    localize('AWS.s3.createBucket.success', 'Created bucket: {0}', newBucketName)
-                )
-                telemetry.s3_createBucket.emit({ result: 'Succeeded' })
-            } catch (e) {
-                void showViewLogsMessage(
-                    localize('AWS.s3.createBucket.error.general', 'Failed to create bucket: {0}', newBucketRequest)
-                )
-                telemetry.s3_createBucket.emit({ result: 'Failed' })
-                return WIZARD_RETRY
-            }
-        } else if (response === ENTER_BUCKET) {
-            const bucket = await this.context.promptUserForS3BucketName(step, {
-                title: localize('AWS.samcli.deploy.bucket.existingTitle', 'Enter Existing Bucket Name'),
-                value: this.response.s3Bucket,
-            })
-
-            if (!bucket) {
-                return WIZARD_RETRY
-            }
-
-            this.response.s3Bucket = bucket
-        } else {
-            this.response.s3Bucket = response
-        }
-
-        return this.hasImages ? wizardContinue(this.ECR_REPO) : wizardContinue(this.STACK_NAME)
-    }
-
-    private readonly ECR_REPO: WizardStep = async (step) => {
-        const response = await this.context.promptUserForEcrRepo(step, this.response.region, this.response.ecrRepo)
-
-        this.response.ecrRepo = response
-
-        return response ? wizardContinue(this.STACK_NAME) : WIZARD_GOBACK
-    }
-
-    private readonly STACK_NAME: WizardStep = async () => {
-        this.response.stackName = await this.context.promptUserForStackName({
-            initialValue: this.response.stackName,
-            validateInput: validateStackName,
-        })
-
-        return this.response.stackName ? WIZARD_TERMINATE : WIZARD_GOBACK
-    }
-
-    private skipOrPromptRegion(skipToStep: WizardStep): WizardStep {
-        return this.regionNode && Object.prototype.hasOwnProperty.call(this.regionNode, 'regionCode')
-            ? skipToStep
-            : this.REGION
-    }
-}
-
-class SamTemplateQuickPickItem implements vscode.QuickPickItem {
-    public readonly label: string
-
-    public description?: string
-    public detail?: string
-
-    public constructor(
-        public readonly uri: vscode.Uri,
-        showWorkspaceFolderDetails: boolean
-    ) {
-        this.label = SamTemplateQuickPickItem.getLabel(uri)
-
-        if (showWorkspaceFolderDetails) {
-            const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri)
-
-            if (workspaceFolder) {
-                this.description = `in ${workspaceFolder.uri.fsPath}`
-            }
-        }
-    }
-
-    public compareTo(rhs: SamTemplateQuickPickItem): number {
-        const labelComp = this.label.localeCompare(rhs.label)
-        if (labelComp !== 0) {
-            return labelComp
-        }
-
-        const descriptionComp = (this.description || '').localeCompare(rhs.description || '')
-        if (descriptionComp !== 0) {
-            return descriptionComp
-        }
-
-        return (this.detail || '').localeCompare(rhs.detail || '')
-    }
-
-    public static getLabel(uri: vscode.Uri): string {
-        const logger = getLogger()
-        const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri)
-
-        if (workspaceFolder) {
-            // If workspace is /usr/foo/code and uri is /usr/foo/code/processor/template.yaml,
-            // show "processor/template.yaml"
-            return path.relative(workspaceFolder.uri.fsPath, uri.fsPath)
-        }
-
-        // We shouldn't find sam templates outside of a workspace folder. If we do, show the full path.
-        logger.warn(`Unexpected situation: detected SAM Template ${uri.fsPath} not found within a workspace folder.`)
-
-        return uri.fsPath
-    }
-}
-
-// https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateStack.html
-// A stack name can contain only alphanumeric characters (case sensitive) and hyphens.
-// It must start with an alphabetic character and cannot be longer than 128 characters.
-function validateStackName(value: string): string | undefined {
-    if (!/^[a-zA-Z\d\-]+$/.test(value)) {
-        return localize(
-            'AWS.samcli.deploy.stackName.error.invalidCharacters',
-            'A stack name may contain only alphanumeric characters (case sensitive) and hyphens'
-        )
-    }
-
-    if (!/^[a-zA-Z]/.test(value)) {
-        return localize(
-            'AWS.samcli.deploy.stackName.error.firstCharacter',
-            'A stack name must begin with an alphabetic character'
-        )
-    }
-
-    if (value.length > 128) {
-        return localize(
-            'AWS.samcli.deploy.stackName.error.length',
-            'A stack name must not be longer than 128 characters'
-        )
-    }
-
-    // TODO: Validate that a stack with this name does not already exist.
-
-    return undefined
-}
-
-async function getTemplateChoices(...workspaceFolders: vscode.Uri[]): Promise {
-    const templateUris = (await globals.templateRegistry).items.map((o) => vscode.Uri.file(o.path))
-    const uriToLabel: Map = new Map()
-    const labelCounts: Map = new Map()
-
-    for (const uri of templateUris) {
-        const label = SamTemplateQuickPickItem.getLabel(uri)
-        uriToLabel.set(uri, label)
-        labelCounts.set(label, 1 + (labelCounts.get(label) || 0))
-    }
-
-    return Array.from(uriToLabel, ([uri, label]) => {
-        const showWorkspaceFolderDetails: boolean = (labelCounts.get(label) || 0) > 1
-
-        return new SamTemplateQuickPickItem(uri, showWorkspaceFolderDetails)
-    }).sort((a, b) => a.compareTo(b))
-}
-
-/**
- * Loads S3 buckets into a quick pick.
- * Fully replaces the quick pick's `items` field on loading S3 buckets.
- * Operation is not paginated as S3 does not offer paginated listing of regionalized buckets.
- * @param quickPick Quick pick to modify the items and busy/enabled state of.
- * @param selectedRegion AWS region to display buckets for
- * @param settings Settings object to get stored settings
- * @param messages Messages to denote no available buckets and errors.
- */
-async function populateS3QuickPick(
-    quickPick: vscode.QuickPick,
-    selectedRegion: string,
-    settings: SamCliSettings,
-    messages: { noBuckets: string; bucketError: string },
-    profile?: string,
-    accountId?: string
-): Promise {
-    return new Promise(async (resolve) => {
-        const goBack: string = localize('AWS.picker.dynamic.noItemsFound.detail', 'Click here to go back')
-        const baseItems: vscode.QuickPickItem[] = []
-        const cloud9Bucket = `cloud9-${accountId}-sam-deployments-${selectedRegion}`
-
-        let recent: string = ''
-        try {
-            const existingBuckets = settings.getSavedBuckets()
-            if (existingBuckets && profile && existingBuckets[profile] && existingBuckets[profile][selectedRegion]) {
-                recent = existingBuckets[profile][selectedRegion]
-                baseItems.push({
-                    label: recent,
-                    description: recentlyUsed,
-                })
-            }
-        } catch (e) {
-            getLogger().error('Recent bucket JSON not parseable: %O', e)
-        }
-
-        if (isCloud9() && recent !== cloud9Bucket) {
-            baseItems.push({
-                label: cloud9Bucket,
-                detail: localize(
-                    'AWS.samcli.deploy.bucket.cloud9name',
-                    'Default {0} Cloud9 Bucket',
-                    getIdeProperties().company
-                ),
-            })
-        }
-
-        try {
-            const s3Client = new S3Client(selectedRegion)
-
-            quickPick.items = [...baseItems]
-
-            const buckets = (await s3Client.listBuckets()).buckets
-
-            if (buckets.length === 0) {
-                quickPick.items = [
-                    ...baseItems,
-                    { label: CREATE_NEW_BUCKET },
-                    { label: ENTER_BUCKET },
-                    {
-                        label: messages.noBuckets,
-                        description: goBack,
-                    },
-                ]
-            } else {
-                const bucketItems = buckets
-                    .filter((bucket) => bucket.Name !== recent && !(isCloud9() && bucket.Name === cloud9Bucket))
-                    .map((bucket) => {
-                        return {
-                            label: bucket.Name,
-                        }
-                    })
-
-                quickPick.items = [...baseItems, ...bucketItems]
-            }
-        } catch (e) {
-            const err = e as Error
-            quickPick.items = [
-                ...baseItems,
-                { label: CREATE_NEW_BUCKET },
-                { label: ENTER_BUCKET },
-                {
-                    label: messages.bucketError,
-                    description: goBack,
-                    detail: err.message,
-                },
-            ]
-        } finally {
-            quickPick.busy = false
-            resolve()
-        }
-    })
-}
diff --git a/packages/core/src/lambda/wizards/samInitWizard.ts b/packages/core/src/lambda/wizards/samInitWizard.ts
deleted file mode 100644
index 9bd3a1b72f..0000000000
--- a/packages/core/src/lambda/wizards/samInitWizard.ts
+++ /dev/null
@@ -1,289 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as nls from 'vscode-nls'
-import * as AWS from '@aws-sdk/types'
-import { Runtime } from '@aws-sdk/client-lambda'
-import * as path from 'path'
-import * as vscode from 'vscode'
-import { SchemasDataProvider } from '../../eventSchemas/providers/schemasDataProvider'
-import { DefaultSchemaClient } from '../../shared/clients/schemaClient'
-import { eventBridgeSchemasDocUrl } from '../../shared/constants'
-import {
-    Architecture,
-    createRuntimeQuickPick,
-    DependencyManager,
-    getDependencyManager,
-    RuntimeAndPackage,
-    RuntimePackageType,
-    samArmLambdaRuntimes,
-} from '../models/samLambdaRuntime'
-import {
-    eventBridgeStarterAppTemplate,
-    getSamTemplateWizardOption,
-    getTemplateDescription,
-    SamTemplate,
-} from '../models/samTemplates'
-import * as semver from 'semver'
-import { minSamCliVersionForArmSupport, minSamCliVersionForImageSupport } from '../../shared/sam/cli/samCliValidator'
-import { Wizard } from '../../shared/wizards/wizard'
-import { createFolderPrompt } from '../../shared/ui/common/location'
-import { createInputBox, InputBoxPrompter } from '../../shared/ui/inputPrompter'
-import { createLabelQuickPick, createQuickPick, QuickPickPrompter } from '../../shared/ui/pickerPrompter'
-import { createRegionPrompter } from '../../shared/ui/common/region'
-import { Region } from '../../shared/regions/endpoints'
-import { createCommonButtons } from '../../shared/ui/buttons'
-import { createExitPrompter } from '../../shared/ui/common/exitPrompter'
-import { getNonexistentFilename } from '../../shared/filesystemUtilities'
-import { getSamInitDocUrl } from '../../shared/extensionUtilities'
-
-const localize = nls.loadMessageBundle()
-
-export interface CreateNewSamAppWizardForm {
-    runtimeAndPackage: RuntimeAndPackage
-    dependencyManager: DependencyManager
-    architecture?: Architecture
-    template: SamTemplate
-    region?: string
-    registryName?: string
-    schemaName?: string
-    location: vscode.Uri
-    name: string
-}
-
-function createRuntimePrompter(samCliVersion: string): QuickPickPrompter {
-    return createRuntimeQuickPick({
-        showImageRuntimes: semver.gte(samCliVersion, minSamCliVersionForImageSupport),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-    })
-}
-
-function createSamTemplatePrompter(
-    currRuntime: Runtime,
-    packageType: RuntimePackageType,
-    samCliVersion: string
-): QuickPickPrompter {
-    const templates = getSamTemplateWizardOption(currRuntime, packageType, samCliVersion)
-    const items = templates.toArray().map((template) => ({
-        label: template,
-        data: template,
-        detail: getTemplateDescription(template),
-    }))
-
-    return createQuickPick(items, {
-        title: localize('AWS.samcli.initWizard.template.prompt', 'Select a SAM Application Template'),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-    })
-}
-
-function createSchemaRegionPrompter(regions: Region[], defaultRegion?: string): QuickPickPrompter {
-    return createRegionPrompter(regions, {
-        title: localize('AWS.samcli.initWizard.schemas.region.prompt', 'Select an EventBridge Schemas Region'),
-        buttons: createCommonButtons(eventBridgeSchemasDocUrl),
-        defaultRegion,
-    }).transform((r) => r.id)
-}
-
-function createDependencyPrompter(currRuntime: Runtime): QuickPickPrompter {
-    const dependencyManagers = getDependencyManager(currRuntime)
-    const items = dependencyManagers.map((dependencyManager) => ({ label: dependencyManager }))
-
-    return createLabelQuickPick(items, {
-        title: localize('AWS.samcli.initWizard.dependencyManager.prompt', 'Select a Dependency Manager'),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-    })
-}
-
-function createRegistryPrompter(region: string, credentials?: AWS.Credentials): QuickPickPrompter {
-    const client = new DefaultSchemaClient(region)
-    const items = SchemasDataProvider.getInstance()
-        .getRegistries(region, client, credentials!)
-        .then((registryNames) => {
-            if (!registryNames) {
-                void vscode.window.showInformationMessage(
-                    localize(
-                        'AWS.samcli.initWizard.schemas.registry.failed_to_load_resources',
-                        'Error loading registries.'
-                    )
-                )
-                return []
-            }
-
-            return registryNames.map((registry) => ({
-                label: registry,
-            }))
-        })
-
-    return createLabelQuickPick(items, {
-        title: localize('AWS.samcli.initWizard.schemas.registry.prompt', 'Select a Registry'),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-    })
-}
-
-function createSchemaPrompter(
-    region: string,
-    registry: string,
-    credentials?: AWS.Credentials
-): QuickPickPrompter {
-    const client = new DefaultSchemaClient(region)
-    const items = SchemasDataProvider.getInstance()
-        .getSchemas(region, registry, client, credentials!)
-        .then((schemas) => {
-            if (!schemas) {
-                void vscode.window.showInformationMessage(
-                    localize(
-                        'AWS.samcli.initWizard.schemas.failed_to_load_resources',
-                        'Error loading schemas in registry {0}.',
-                        registry
-                    )
-                )
-                return []
-            }
-
-            if (schemas.length === 0) {
-                void vscode.window.showInformationMessage(
-                    localize('AWS.samcli.initWizard.schemas.notFound"', 'No schemas found in registry {0}.', registry)
-                )
-                return []
-            }
-
-            return schemas.map((schema) => ({
-                label: schema.SchemaName!,
-            }))
-        })
-
-    return createLabelQuickPick(items, {
-        title: localize('AWS.samcli.initWizard.schemas.schema.prompt', 'Select a Schema'),
-        buttons: createCommonButtons(eventBridgeSchemasDocUrl),
-    })
-}
-
-function createNamePrompter(defaultValue: string): InputBoxPrompter {
-    function validateName(value: string): string | undefined {
-        if (!value) {
-            return localize('AWS.samcli.initWizard.name.error.empty', 'Application name cannot be empty')
-        }
-
-        if (value.includes(path.sep)) {
-            return localize(
-                'AWS.samcli.initWizard.name.error.pathSep',
-                'The path separator ({0}) is not allowed in application names',
-                path.sep
-            )
-        }
-
-        return undefined
-    }
-
-    return createInputBox({
-        value: defaultValue,
-        title: localize('AWS.samcli.initWizard.name.prompt', 'Enter a name for your new application'),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-        validateInput: validateName,
-    })
-}
-
-function createArchitecturePrompter(): QuickPickPrompter {
-    return createLabelQuickPick([{ label: 'x86_64' }, { label: 'arm64' }], {
-        title: localize('AWS.samcli.initWizard.architecture.prompt', 'Select an Architecture'),
-        buttons: createCommonButtons(getSamInitDocUrl()),
-    })
-}
-
-export class CreateNewSamAppWizard extends Wizard {
-    public constructor(context: {
-        samCliVersion: string
-        schemaRegions: Region[]
-        defaultRegion?: string
-        credentials?: AWS.Credentials
-    }) {
-        super({
-            exitPrompterProvider: createExitPrompter,
-        })
-
-        this.form.runtimeAndPackage.bindPrompter(() => createRuntimePrompter(context.samCliVersion))
-
-        this.form.dependencyManager.bindPrompter(
-            (state) => createDependencyPrompter(state.runtimeAndPackage!.runtime!),
-            {
-                showWhen: (state) =>
-                    state.runtimeAndPackage?.runtime !== undefined &&
-                    getDependencyManager(state.runtimeAndPackage.runtime).length > 1,
-                setDefault: (state) =>
-                    state.runtimeAndPackage?.runtime !== undefined
-                        ? getDependencyManager(state.runtimeAndPackage.runtime)[0]
-                        : undefined,
-            }
-        )
-
-        // TODO: remove `partial` when updated wizard types gets merged (need to make the PR first of course...)
-        function canShowArchitecture(
-            state: Partial>
-        ): boolean {
-            // TODO: Remove Image Maven check if/when Hello World app supports multiarch Maven builds
-            if (state.dependencyManager === 'maven' && state.runtimeAndPackage?.packageType === 'Image') {
-                return false
-            }
-
-            if (semver.lt(context.samCliVersion, minSamCliVersionForArmSupport)) {
-                return false
-            }
-
-            return state.runtimeAndPackage ? samArmLambdaRuntimes.has(state.runtimeAndPackage.runtime) : false
-        }
-
-        this.form.architecture.bindPrompter(createArchitecturePrompter, {
-            showWhen: canShowArchitecture,
-        })
-
-        this.form.template.bindPrompter((state) =>
-            createSamTemplatePrompter(
-                state.runtimeAndPackage!.runtime!,
-                state.runtimeAndPackage!.packageType!,
-                context.samCliVersion
-            )
-        )
-
-        function isStarterTemplate(state: { template?: string }): boolean {
-            return state.template === eventBridgeStarterAppTemplate
-        }
-
-        this.form.region.bindPrompter(() => createSchemaRegionPrompter(context.schemaRegions, context.defaultRegion), {
-            showWhen: isStarterTemplate,
-        })
-
-        this.form.registryName.bindPrompter((form) => createRegistryPrompter(form.region!, context.credentials), {
-            showWhen: isStarterTemplate,
-        })
-
-        this.form.schemaName.bindPrompter(
-            (state) => createSchemaPrompter(state.region!, state.registryName!, context.credentials),
-            {
-                showWhen: isStarterTemplate,
-            }
-        )
-
-        this.form.location.bindPrompter(() =>
-            createFolderPrompt(vscode.workspace.workspaceFolders ?? [], {
-                buttons: createCommonButtons(getSamInitDocUrl()),
-                title: localize('AWS.samInit.location.title', 'Select the folder for your new SAM application'),
-                browseFolderDetail: localize(
-                    'AWS.samInit.location.detail',
-                    'The selected folder will be added to the workspace.'
-                ),
-            })
-        )
-
-        this.form.name.bindPrompter(async (state) => {
-            const fname = await getNonexistentFilename(
-                state.location!.fsPath,
-                `lambda-${state.runtimeAndPackage!.runtime}`,
-                '',
-                99
-            )
-            return createNamePrompter(fname)
-        })
-    }
-}
diff --git a/packages/core/src/login/webview/commonAuthViewProvider.ts b/packages/core/src/login/webview/commonAuthViewProvider.ts
index 8ad5c71bcd..d4aa5cf1bb 100644
--- a/packages/core/src/login/webview/commonAuthViewProvider.ts
+++ b/packages/core/src/login/webview/commonAuthViewProvider.ts
@@ -39,8 +39,6 @@ import {
 import { registerAssetsHttpsFileSystem } from '../../amazonq/webview/assets/assetsHandler'
 import { VueWebview, VueWebviewPanel } from '../../webviews/main'
 import { AmazonQLoginWebview } from './vue/amazonq/backend_amazonq'
-import { ToolkitLoginWebview } from './vue/toolkit/backend_toolkit'
-import { CodeCatalystAuthenticationProvider } from '../../codecatalyst/auth'
 import { telemetry } from '../../shared/telemetry/telemetry'
 import { AuthSources } from './util'
 import { AuthFlowStates } from './vue/types'
@@ -51,7 +49,7 @@ import { ExtensionUse } from '../../auth/utils'
 export class CommonAuthViewProvider implements WebviewViewProvider {
     public readonly viewType: string
 
-    webView: VueWebviewPanel | undefined
+    webView: VueWebviewPanel | undefined
     source: string = ''
 
     constructor(
@@ -62,13 +60,7 @@ export class CommonAuthViewProvider implements WebviewViewProvider {
         this.viewType = `aws.${app}.AmazonCommonAuth`
 
         registerAssetsHttpsFileSystem(extensionContext)
-        if (app === 'toolkit') {
-            // Create panel bindings using our class
-            const Panel = VueWebview.compilePanel(ToolkitLoginWebview)
-            // `context` is `ExtContext` provided on activation
-            this.webView = new Panel(extensionContext, CodeCatalystAuthenticationProvider.fromContext(extensionContext))
-            this.source = ToolkitLoginWebview.sourcePath
-        } else if (app === 'amazonq') {
+        if (app === 'amazonq') {
             const Panel = VueWebview.compilePanel(AmazonQLoginWebview)
             this.webView = new Panel(extensionContext)
             this.source = AmazonQLoginWebview.sourcePath
diff --git a/packages/core/src/login/webview/vue/backend.ts b/packages/core/src/login/webview/vue/backend.ts
index 7bfa115c2e..f058dd5d4f 100644
--- a/packages/core/src/login/webview/vue/backend.ts
+++ b/packages/core/src/login/webview/vue/backend.ts
@@ -16,7 +16,6 @@ import {
     AwsConnection,
     Connection,
     hasScopes,
-    scopesCodeCatalyst,
     scopesCodeWhispererChat,
     scopesSsoAccountAccess,
     SsoConnection,
@@ -96,8 +95,6 @@ export abstract class CommonAuthWebview extends VueWebview {
                 if (e instanceof ToolkitError && e.code === 'NotOnboarded') {
                     /**
                      * Connection is fine, they just skipped onboarding so not an actual error.
-                     *
-                     * The error comes from user cancelling prompt by {@link CodeCatalystAuthenticationProvider.promptOnboarding()}
                      */
                     return
                 }
@@ -282,9 +279,6 @@ export abstract class CommonAuthWebview extends VueWebview {
         if (hasScopes(conn.scopes!, scopesCodeWhispererChat)) {
             authEnabledFeatures.push('codewhisperer')
         }
-        if (hasScopes(conn.scopes!, scopesCodeCatalyst)) {
-            authEnabledFeatures.push('codecatalyst')
-        }
         if (hasScopes(conn.scopes!, scopesSsoAccountAccess)) {
             authEnabledFeatures.push('awsExplorer')
         }
diff --git a/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts b/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts
deleted file mode 100644
index 10a6c98a3c..0000000000
--- a/packages/core/src/login/webview/vue/toolkit/backend_toolkit.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { tryAddCredentials } from '../../../../auth/utils'
-import { getLogger } from '../../../../shared/logger/logger'
-import { CommonAuthWebview } from '../backend'
-import {
-    AwsConnection,
-    Connection,
-    SsoConnection,
-    TelemetryMetadata,
-    createSsoProfile,
-    getTelemetryMetadataForConn,
-    isSsoConnection,
-} from '../../../../auth/connection'
-import { Auth } from '../../../../auth/auth'
-import { CodeCatalystAuthenticationProvider } from '../../../../codecatalyst/auth'
-import { AuthError, AuthFlowState } from '../types'
-import { setContext } from '../../../../shared/vscode/setContext'
-import { builderIdStartUrl } from '../../../../auth/sso/constants'
-import { RegionProfile } from '../../../../codewhisperer/models/model'
-import { ProfileSwitchIntent } from '../../../../codewhisperer/region/regionProfileManager'
-import globals from '../../../../shared/extensionGlobals'
-
-export class ToolkitLoginWebview extends CommonAuthWebview {
-    public override id: string = 'aws.toolkit.AmazonCommonAuth'
-    public static sourcePath: string = 'vue/src/login/webview/vue/toolkit/index.js'
-    public override supportsLoadTelemetry: boolean = true
-    private isCodeCatalystLogin = false
-
-    override onActiveConnectionModified: vscode.EventEmitter = new vscode.EventEmitter()
-
-    constructor(private readonly codeCatalystAuth: CodeCatalystAuthenticationProvider) {
-        super(ToolkitLoginWebview.sourcePath)
-    }
-
-    setLoginService(serviceToShow?: string) {
-        this.isCodeCatalystLogin = serviceToShow === 'codecatalyst'
-    }
-
-    async startEnterpriseSetup(startUrl: string, region: string): Promise {
-        getLogger().debug(`called startEnterpriseSetup() with startUrl: '${startUrl}', region: '${region}'`)
-        const metadata: TelemetryMetadata = {
-            credentialSourceId: 'iamIdentityCenter',
-            credentialStartUrl: startUrl,
-            isReAuth: false,
-        }
-        await globals.globalState.update('recentSso', {
-            startUrl: startUrl,
-            region: region,
-        })
-
-        if (this.isCodeCatalystLogin) {
-            return this.ssoSetup('startCodeCatalystSSOSetup', async () => {
-                this.storeMetricMetadata({ ...metadata })
-
-                const conn = await this.codeCatalystAuth.connectToEnterpriseSso(startUrl, region)
-                this.storeMetricMetadata({
-                    authEnabledFeatures: this.getAuthEnabledFeatures(conn),
-                    ...(await getTelemetryMetadataForConn(conn)),
-                })
-
-                await setContext('aws.explorer.showAuthView', false)
-                await this.showResourceExplorer()
-            })
-        }
-
-        return this.ssoSetup('createIdentityCenterConnection', async () => {
-            this.storeMetricMetadata({ ...metadata })
-
-            const ssoProfile = createSsoProfile(startUrl, region)
-            const conn = await Auth.instance.createConnection(ssoProfile)
-            await Auth.instance.useConnection(conn)
-
-            this.storeMetricMetadata({
-                authEnabledFeatures: this.getAuthEnabledFeatures(conn),
-                ...(await getTelemetryMetadataForConn(conn)),
-            })
-
-            await setContext('aws.explorer.showAuthView', false)
-            void vscode.window.showInformationMessage('Toolkit: Successfully connected to AWS IAM Identity Center')
-            void this.showResourceExplorer()
-        })
-    }
-
-    async startConsoleCredentialSetup(profileName: string, region: string): Promise {
-        getLogger().debug(`called startConsoleCredentialSetup()`)
-        const runAuth = async () => {
-            try {
-                // Execute AWS CLI login command
-                await vscode.commands.executeCommand('aws.toolkit.auth.consoleLogin', profileName, region)
-
-                // Hide auth view and show resource explorer
-                await setContext('aws.explorer.showAuthView', false)
-                await this.showResourceExplorer()
-
-                return undefined // Success case returns undefined
-            } catch (err) {
-                getLogger().error('Failed setting up authentication with console credentials : %O', err)
-                return {
-                    id: this.id,
-                    text: err instanceof Error ? err.message : String(err),
-                }
-            }
-        }
-        const result = await runAuth()
-        this.storeMetricMetadata({
-            credentialSourceId: 'consoleCredentials',
-            authEnabledFeatures: 'awsExplorer',
-            ...this.getResultForMetrics(result),
-        })
-        this.emitAuthMetric()
-        return result
-    }
-
-    async startIamCredentialSetup(
-        profileName: string,
-        accessKey: string,
-        secretKey: string
-    ): Promise {
-        getLogger().debug(`called startIamCredentialSetup()`)
-        // See submitData() in manageCredentials.vue
-        const runAuth = async () => {
-            const data = { aws_access_key_id: accessKey, aws_secret_access_key: secretKey }
-            const error = await this.getAuthenticatedCredentialsError(data)
-            if (error) {
-                return { id: this.id, text: error.error }
-            }
-            try {
-                await tryAddCredentials(profileName, data, true)
-                await setContext('aws.explorer.showAuthView', false)
-                await this.showResourceExplorer()
-            } catch (e) {
-                getLogger().error('Failed submitting credentials %O', e)
-                return { id: this.id, text: e as string }
-            }
-        }
-
-        const result = await runAuth()
-        this.storeMetricMetadata({
-            credentialSourceId: 'sharedCredentials',
-            authEnabledFeatures: 'awsExplorer',
-            ...this.getResultForMetrics(result),
-        })
-        this.emitAuthMetric()
-
-        return result
-    }
-
-    async startBuilderIdSetup(): Promise {
-        getLogger().debug(`called startBuilderIdSetup()`)
-        return this.ssoSetup('startCodeCatalystBuilderIdSetup', async () => {
-            this.storeMetricMetadata({
-                credentialSourceId: 'awsId',
-                authEnabledFeatures: 'codecatalyst',
-                isReAuth: false,
-            })
-
-            const conn = await this.codeCatalystAuth.connectToAwsBuilderId()
-            this.storeMetricMetadata(await getTelemetryMetadataForConn(conn))
-
-            await setContext('aws.explorer.showAuthView', false)
-            await this.showResourceExplorer()
-        })
-    }
-
-    /**
-     * Returns list of connections that are pushed from other extensions to Toolkit
-     */
-    async fetchConnections(): Promise {
-        const connections: AwsConnection[] = []
-        for (const conn of Auth.instance.declaredConnections) {
-            // No need to display Builder ID as an existing connection,
-            // users can just select the Builder ID login option and it would have the same effect.
-            if (conn.startUrl !== builderIdStartUrl) {
-                connections.push({
-                    ssoRegion: conn.ssoRegion,
-                    startUrl: conn.startUrl,
-                } as AwsConnection)
-            }
-        }
-        return connections
-    }
-
-    async listSsoConnections(): Promise {
-        return (await Auth.instance.listConnections()).filter((conn) => isSsoConnection(conn)) as SsoConnection[]
-    }
-
-    override reauthenticateConnection(): Promise {
-        throw new Error('Method not implemented.')
-    }
-    override getActiveConnection(): Promise {
-        throw new Error('Method not implemented.')
-    }
-
-    override async refreshAuthState(): Promise {}
-    override async getAuthState(): Promise {
-        // No need for a reauth page yet, so always show login
-        return 'LOGIN'
-    }
-
-    override signout(): Promise {
-        throw new Error('Method not implemented.')
-    }
-
-    override getReauthError(): Promise {
-        throw new Error('Method not implemented.')
-    }
-
-    async quitLoginScreen() {
-        await setContext('aws.explorer.showAuthView', false)
-        await this.showResourceExplorer()
-    }
-
-    override listRegionProfiles(): Promise {
-        throw new Error('Method not implemented')
-    }
-
-    override selectRegionProfile(profile: RegionProfile, source: ProfileSwitchIntent): Promise {
-        throw new Error('Method not implemented')
-    }
-}
diff --git a/packages/core/src/login/webview/vue/toolkit/index.ts b/packages/core/src/login/webview/vue/toolkit/index.ts
deleted file mode 100644
index 34fbbedb8c..0000000000
--- a/packages/core/src/login/webview/vue/toolkit/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- *
- * This module is run within the webview, and will mount the Vue app.
- */
-
-import { createApp } from 'vue'
-import component from '../root.vue'
-
-const create = () =>
-    createApp(component, {
-        app: 'TOOLKIT',
-    })
-const app = create()
-
-app.mount('#vue-app')
-window.addEventListener('remount', () => {
-    app.unmount()
-    create().mount('#vue-app')
-})
diff --git a/packages/core/src/notifications/panelNode.ts b/packages/core/src/notifications/panelNode.ts
index 70a69873ee..aee6ed6e6a 100644
--- a/packages/core/src/notifications/panelNode.ts
+++ b/packages/core/src/notifications/panelNode.ts
@@ -13,7 +13,6 @@ import { NotificationType, OnReceiveType, ToolkitNotification, getNotificationTe
 import { ToolkitError } from '../shared/errors'
 import { isAmazonQ } from '../shared/extensionUtilities'
 import { getLogger } from '../shared/logger/logger'
-import { registerToolView } from '../awsexplorer/activationShared'
 import { readonlyDocument } from '../shared/utilities/textDocumentUtilities'
 import { openUrl } from '../shared/utilities/vsCodeUtils'
 import { telemetry } from '../shared/telemetry/telemetry'
@@ -309,13 +308,22 @@ export class NotificationsNode implements TreeNode {
     }
 
     registerView(context: vscode.ExtensionContext) {
-        this.view = registerToolView(
-            {
-                nodes: [this],
-                view: isAmazonQ() ? 'aws.amazonq.notifications' : 'aws.toolkit.notifications',
-                refreshCommands: [(provider: ResourceTreeDataProvider) => this.registerProvider(provider)],
+        const nodes: TreeNode[] = [this]
+        const viewId = isAmazonQ() ? 'aws.amazonq.notifications' : 'aws.toolkit.notifications'
+        const treeDataProvider = new ResourceTreeDataProvider({
+            getChildren: async () => {
+                const children: TreeNode[] = []
+                for (const node of nodes) {
+                    if (node.getChildren) {
+                        children.push(...(await node.getChildren()))
+                    }
+                }
+                return children
             },
-            context
-        )
+        })
+        this.registerProvider(treeDataProvider)
+        const treeView = vscode.window.createTreeView(viewId, { treeDataProvider })
+        context.subscriptions.push(treeView)
+        this.view = treeView
     }
 }
diff --git a/packages/core/src/sagemakerunifiedstudio/activation.ts b/packages/core/src/sagemakerunifiedstudio/activation.ts
deleted file mode 100644
index 9c47137d6d..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/activation.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { activate as activateConnectionMagicsSelector } from './connectionMagicsSelector/activation'
-import { activate as activateExplorer } from './explorer/activation'
-import { isSageMaker } from '../shared/extensionUtilities'
-import { initializeResourceMetadata } from './shared/utils/resourceMetadataUtils'
-import { setContext } from '../shared/vscode/setContext'
-import { SmusUtils } from './shared/smusUtils'
-import * as smusUriHandlers from './uriHandlers'
-import { ExtContext } from '../shared/extensions'
-
-export async function activate(ctx: ExtContext): Promise {
-    // Only run when environment is a SageMaker Unified Studio space
-    if (isSageMaker('SMUS') || isSageMaker('SMUS-SPACE-REMOTE-ACCESS')) {
-        await initializeResourceMetadata()
-        // Setting context before any getContext calls to avoid potential race conditions.
-        await setContext('aws.smus.inSmusSpaceEnvironment', SmusUtils.isInSmusSpaceEnvironment())
-        await activateConnectionMagicsSelector(ctx.extensionContext)
-    }
-    await activateExplorer(ctx.extensionContext)
-
-    // Register SMUS URI handler for deeplink connections
-    ctx.extensionContext.subscriptions.push(smusUriHandlers.register(ctx))
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/authenticationOrchestrator.ts b/packages/core/src/sagemakerunifiedstudio/auth/authenticationOrchestrator.ts
deleted file mode 100644
index 208f7ccd95..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/authenticationOrchestrator.ts
+++ /dev/null
@@ -1,330 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { ToolkitError } from '../../shared/errors'
-import { SmusErrorCodes } from '../shared/smusUtils'
-import { SmusAuthenticationProvider } from './providers/smusAuthenticationProvider'
-
-import { SmusSsoAuthenticationUI } from './ui/ssoAuthentication'
-import {
-    SmusIamProfileSelector,
-    IamProfileSelection,
-    IamProfileEditingInProgress,
-    IamProfileBackNavigation,
-} from './ui/iamProfileSelection'
-import { SmusAuthenticationPreferencesManager } from './preferences/authenticationPreferences'
-import { DataZoneCustomClientHelper } from '../shared/client/datazoneCustomClientHelper'
-import { recordAuthTelemetry } from '../shared/telemetry'
-
-export type SmusAuthenticationMethod = 'sso' | 'iam'
-
-export type SmusAuthenticationResult =
-    | { status: 'SUCCESS' }
-    | { status: 'BACK' }
-    | { status: 'EDITING' }
-    | { status: 'INVALID_PROFILE'; error: string }
-
-/**
- * Orchestrates SMUS authentication flows
- */
-export class SmusAuthenticationOrchestrator {
-    private static readonly logger = getLogger('smus')
-
-    /**
-     * Handles IAM authentication flow
-     * @param authProvider The SMUS authentication provider
-     * @param span Telemetry span
-     * @param context Extension context
-     * @param existingProfileName Optional profile name to re-authenticate with (skips profile selection)
-     * @param existingRegion Optional region to use (skips region selection)
-     */
-    public static async handleIamAuthentication(
-        authProvider: SmusAuthenticationProvider,
-        span: any,
-        context: vscode.ExtensionContext,
-        existingProfileName?: string,
-        existingRegion?: string
-    ): Promise {
-        const logger = this.logger
-
-        try {
-            let profileSelection: IamProfileSelection | IamProfileEditingInProgress | IamProfileBackNavigation
-
-            // If profile and region are provided, skip profile selection (re-authentication case)
-            if (existingProfileName && existingRegion) {
-                logger.debug(
-                    `Auth: Re-authenticating with existing profile: ${existingProfileName}, region: ${existingRegion}`
-                )
-                profileSelection = {
-                    profileName: existingProfileName,
-                    region: existingRegion,
-                }
-            } else {
-                // Show IAM profile selection dialog
-                profileSelection = await SmusIamProfileSelector.showIamProfileSelection()
-            }
-
-            // Handle different result types
-            if ('isBack' in profileSelection) {
-                // User chose to go back to authentication method selection
-                logger.debug('User chose to go back to authentication method selection')
-                return { status: 'BACK' }
-            }
-
-            if ('isEditing' in profileSelection) {
-                // User chose to edit credentials or is in editing mode
-                logger.debug('User is editing credentials')
-                return { status: 'EDITING' }
-            }
-
-            // At this point, we have a profile selected
-            logger.debug(`Selected profile: ${profileSelection.profileName}, region: ${profileSelection.region}`)
-
-            // Validate the selected profile
-            const validation = await authProvider.validateIamProfile(profileSelection.profileName)
-            if (!validation.isValid) {
-                logger.debug(`Profile validation failed: ${validation.error}`)
-                return { status: 'INVALID_PROFILE', error: validation.error || 'Profile validation failed' }
-            }
-
-            // Discover IAM-based domain using IAM credential. If IAM-based domain is not present, we should throw an appropriate error
-            // and exit
-            logger.debug('Discovering IAM-based domain using IAM credentials')
-
-            const domainUrl = await this.findSmusIamDomain(
-                authProvider,
-                profileSelection.profileName,
-                profileSelection.region
-            )
-            if (!domainUrl) {
-                throw new ToolkitError('No IAM-based domains found in the specified region', {
-                    code: SmusErrorCodes.IamDomainNotFound,
-                    cancelled: true,
-                })
-            }
-
-            // Connect using IAM profile with IAM-based domain flag
-            const connection = await authProvider.connectWithIamProfile(
-                profileSelection.profileName,
-                profileSelection.region,
-                domainUrl,
-                true // isIamDomain - we found an IAM-based domain
-            )
-
-            if (!connection) {
-                throw new ToolkitError('Failed to establish IAM connection', {
-                    code: SmusErrorCodes.FailedAuthConnecton,
-                })
-            }
-
-            logger.info(
-                `Successfully connected with IAM profile ${profileSelection.profileName} in region ${profileSelection.region} to IAM-based domain`
-            )
-
-            // Extract domain ID and region for telemetry logging
-            const domainId = connection.domainId
-            const region = authProvider.getDomainRegion()
-
-            logger.info(`Connected to SageMaker Unified Studio domain: ${domainId} in region ${region}`)
-            await this.recordAuthTelemetry(span, authProvider, domainId, region)
-
-            // Refresh the tree view to show authenticated state
-            try {
-                await vscode.commands.executeCommand('aws.smus.rootView.refresh')
-            } catch (refreshErr) {
-                logger.debug(`Failed to refresh views after login: ${(refreshErr as Error).message}`)
-            }
-
-            // After successful IAM authentication (IAM mode), automatically open project picker
-            logger.debug('IAM authentication successful, opening project picker')
-            try {
-                await vscode.commands.executeCommand('aws.smus.switchProject')
-            } catch (pickerErr) {
-                logger.debug(`Failed to open project picker: ${(pickerErr as Error).message}`)
-            }
-
-            // Ask to remember authentication method preference (non-blocking)
-            void this.askToRememberAuthMethod(context, 'iam')
-
-            // Return success to complete the authentication flow gracefully
-            return { status: 'SUCCESS' }
-        } catch (error) {
-            // Handle user cancellation (including editing mode)
-            if (
-                error instanceof ToolkitError &&
-                (error.code === SmusErrorCodes.UserCancelled || error.code === SmusErrorCodes.IamDomainNotFound)
-            ) {
-                logger.debug('IAM authentication cancelled by user or failed due to customer error')
-                throw error // Re-throw to be handled by the main loop
-            } else {
-                // Log the error for actual failures
-                logger.error('IAM authentication failed: %s', (error as Error).message)
-                throw error
-            }
-        }
-    }
-
-    /**
-     * Handles SSO authentication flow
-     */
-    public static async handleSsoAuthentication(
-        authProvider: SmusAuthenticationProvider,
-        span: any,
-        context: vscode.ExtensionContext
-    ): Promise {
-        const logger = this.logger
-        logger.debug('Starting SSO authentication flow')
-
-        // Show domain URL input dialog with back button support
-        const domainUrl = await SmusSsoAuthenticationUI.showDomainUrlInput()
-
-        logger.debug(`Domain URL input result: ${domainUrl ? 'provided' : 'cancelled or back'}`)
-
-        if (domainUrl === 'BACK') {
-            // User wants to go back to authentication method selection
-            logger.debug('User chose to go back from domain URL input')
-            return { status: 'BACK' }
-        }
-
-        if (!domainUrl) {
-            // User cancelled
-            logger.debug('User cancelled domain URL input')
-            throw new ToolkitError('User cancelled domain URL input', {
-                cancelled: true,
-                code: SmusErrorCodes.UserCancelled,
-            })
-        }
-
-        try {
-            // Connect to SMUS using the authentication provider
-            const connection = await authProvider.connectToSmusWithSso(domainUrl)
-
-            if (!connection) {
-                throw new ToolkitError('Failed to establish connection', {
-                    code: SmusErrorCodes.FailedAuthConnecton,
-                })
-            }
-
-            // Extract domain account ID, domain ID, and region for logging
-            const domainId = connection.domainId
-            const region = authProvider.getDomainRegion() // Use the auth provider method that handles both connection types
-
-            logger.info(`Connected to SageMaker Unified Studio domain: ${domainId} in region ${region}`)
-            await this.recordAuthTelemetry(span, authProvider, domainId, region)
-
-            // Ask to remember authentication method preference
-            await this.askToRememberAuthMethod(context, 'sso')
-
-            // Immediately refresh the tree view to show authenticated state
-            try {
-                await vscode.commands.executeCommand('aws.smus.rootView.refresh')
-            } catch (refreshErr) {
-                logger.debug(`Failed to refresh views after login: ${(refreshErr as Error).message}`)
-            }
-
-            return { status: 'SUCCESS' }
-        } catch (connectionErr) {
-            // Clear the status bar message
-            vscode.window.setStatusBarMessage('Connection to SageMaker Unified Studio Failed')
-
-            // Log the error and re-throw to be handled by the outer catch block
-            logger.error('Connection failed: %s', (connectionErr as Error).message)
-            throw new ToolkitError('Connection failed.', {
-                cause: connectionErr as Error,
-                code: (connectionErr as Error).name,
-            })
-        }
-    }
-
-    /**
-     * Asks the user if they want to remember their authentication method choice after successful login
-     */
-    private static async askToRememberAuthMethod(
-        context: vscode.ExtensionContext,
-        method: SmusAuthenticationMethod
-    ): Promise {
-        const logger = this.logger
-
-        try {
-            const methodName = method === 'sso' ? 'SSO Authentication' : 'IAM Credential Profile'
-
-            const result = await vscode.window.showInformationMessage(
-                `Remember ${methodName} as your preferred authentication method for SageMaker Unified Studio?`,
-                'Yes',
-                'No'
-            )
-
-            if (result === 'Yes') {
-                logger.debug(`Saving user preference: ${method}`)
-                await SmusAuthenticationPreferencesManager.setPreferredMethod(context, method, true)
-                logger.debug(`Preference saved successfully`)
-            }
-        } catch (error) {
-            // Not a hard failure, so not throwing error
-            logger.warn('Error asking to remember auth method: %s', error)
-        }
-    }
-
-    /**
-     * Finds SMUS IAM-based domain using IAM credentials
-     * @param authProvider The SMUS authentication provider
-     * @param profileName The AWS credential profile name
-     * @param region The AWS region
-     * @returns Promise resolving to domain URL or undefined if no IAM-based domain found
-     */
-    private static async findSmusIamDomain(
-        authProvider: SmusAuthenticationProvider,
-        profileName: string,
-        region: string
-    ): Promise {
-        const logger = this.logger
-
-        try {
-            logger.debug(`Finding IAM-based domain in region ${region} using profile ${profileName}`)
-
-            // Get DataZoneCustomClientHelper instance
-            const datazoneCustomClientHelper = DataZoneCustomClientHelper.getInstance(
-                await authProvider.getCredentialsProviderForIamProfile(profileName),
-                region
-            )
-
-            // Find the IAM-based domain using the client
-            const iamDomain = await datazoneCustomClientHelper.getIamDomain()
-
-            if (!iamDomain) {
-                logger.warn(`No IAM-based domain found in region ${region}`)
-                return undefined
-            }
-
-            logger.debug(`Found IAM-based domain: ${iamDomain.name} (${iamDomain.id})`)
-
-            // Construct domain URL from the IAM-based domain
-            const domainUrl = iamDomain.portalUrl || `https://${iamDomain.id}.sagemaker.${region}.on.aws/`
-            logger.info(`Discovered IAM-based domain URL: ${domainUrl}`)
-
-            return domainUrl
-        } catch (error) {
-            logger.error(`Failed to find IAM-based domain: %s`, error)
-            throw new ToolkitError(`Failed to find IAM-based domain: ${(error as Error).message}`, {
-                code: SmusErrorCodes.ApiTimeout,
-                cause: error instanceof Error ? error : undefined,
-            })
-        }
-    }
-
-    /**
-     * Records authentication telemetry
-     */
-    private static async recordAuthTelemetry(
-        span: any,
-        authProvider: SmusAuthenticationProvider,
-        domainId: string,
-        region: string
-    ): Promise {
-        await recordAuthTelemetry(span, authProvider, domainId, region)
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/credentialExpiryHandler.ts b/packages/core/src/sagemakerunifiedstudio/auth/credentialExpiryHandler.ts
deleted file mode 100644
index 3c03b2875e..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/credentialExpiryHandler.ts
+++ /dev/null
@@ -1,241 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../shared/logger/logger'
-import { ToolkitError } from '../../shared/errors'
-import { SmusErrorCodes } from '../shared/smusUtils'
-import { SmusIamProfileSelector } from './ui/iamProfileSelection'
-import { getCredentialsFilename, getConfigFilename } from '../../auth/credentials/sharedCredentialsFile'
-import type { SmusAuthenticationProvider } from './providers/smusAuthenticationProvider'
-
-export enum IamCredentialExpiryAction {
-    Reauthenticate = 'reauthenticate',
-    EditCredentials = 'editCredentials',
-    SwitchProfile = 'switchProfile',
-    SignOut = 'signOut',
-    Cancelled = 'cancelled',
-}
-
-export type IamCredentialExpiryResult =
-    | { action: IamCredentialExpiryAction.Reauthenticate }
-    | { action: IamCredentialExpiryAction.EditCredentials }
-    | { action: IamCredentialExpiryAction.SwitchProfile }
-    | { action: IamCredentialExpiryAction.SignOut }
-    | { action: IamCredentialExpiryAction.Cancelled }
-
-/**
- * Shows credential expiry options for IAM connections
- * Provides options to re-authenticate, edit credentials, switch profiles, or sign out
- * @param authProvider The SMUS authentication provider
- * @param connection The expired IAM connection
- * @param extensionContext The extension context
- * @returns Promise that resolves with the action taken
- */
-export async function showIamCredentialExpiryOptions(
-    authProvider: SmusAuthenticationProvider,
-    connection: any,
-    extensionContext: vscode.ExtensionContext
-): Promise {
-    const logger = getLogger('smus')
-
-    type QuickPickItemWithAction = vscode.QuickPickItem & { action: IamCredentialExpiryAction }
-    const options: QuickPickItemWithAction[] = [
-        {
-            label: '$(sync) Re-authenticate with current profile',
-            description: `Profile: ${connection.profileName}`,
-            detail: 'Refresh credentials using the same IAM profile',
-            action: IamCredentialExpiryAction.Reauthenticate,
-        },
-        {
-            label: '$(file-text) Edit credentials file',
-            description: 'Open ~/.aws/credentials and ~/.aws/config',
-            detail: 'Manually update your AWS credentials',
-            action: IamCredentialExpiryAction.EditCredentials,
-        },
-        {
-            label: '$(arrow-swap) Switch to another profile',
-            description: 'Select a different IAM profile',
-            detail: 'Choose from available credential profiles',
-            action: IamCredentialExpiryAction.SwitchProfile,
-        },
-        {
-            label: '$(trash) Sign out',
-            description: 'Sign out from this connection',
-            detail: 'Remove the expired connection',
-            action: IamCredentialExpiryAction.SignOut,
-        },
-    ]
-
-    const quickPick = vscode.window.createQuickPick()
-    quickPick.title = 'IAM Credentials Expired'
-    quickPick.placeholder = 'Choose how to fix your expired credentials'
-    quickPick.items = options
-    quickPick.canSelectMany = false
-    quickPick.ignoreFocusOut = true
-
-    return new Promise((resolve, reject) => {
-        let isCompleted = false
-
-        quickPick.onDidAccept(async () => {
-            const selectedItem = quickPick.selectedItems[0]
-            if (!selectedItem) {
-                quickPick.dispose()
-                reject(new ToolkitError('No option selected', { code: SmusErrorCodes.UserCancelled, cancelled: true }))
-                return
-            }
-
-            isCompleted = true
-            quickPick.dispose()
-
-            const itemWithAction = selectedItem as QuickPickItemWithAction
-
-            try {
-                switch (itemWithAction.action) {
-                    case IamCredentialExpiryAction.Reauthenticate: {
-                        logger.debug(
-                            `SMUS: Re-authenticating with current IAM profile: ${connection.profileName} in region ${connection.region}`
-                        )
-                        // For IAM connections, just validate the credentials are still valid
-                        // The auth system will handle refreshing them automatically
-                        const validation = await authProvider.validateIamProfile(connection.profileName)
-                        if (validation.isValid) {
-                            // Credentials are valid, refresh the connection state
-                            await authProvider.auth.refreshConnectionState(connection)
-                            void vscode.window.showInformationMessage(
-                                'Successfully reauthenticated with SageMaker Unified Studio'
-                            )
-                            resolve({ action: IamCredentialExpiryAction.Reauthenticate })
-                        } else {
-                            const errorMsg = validation.error || 'Unknown validation error'
-                            // Throw error for telemetry - activation.ts will show the notification
-                            throw new ToolkitError(
-                                `Failed to re-authenticate, ensure credential has been updated: ${errorMsg}`,
-                                { code: SmusErrorCodes.IamValidationFailed }
-                            )
-                        }
-                        break
-                    }
-                    case IamCredentialExpiryAction.EditCredentials: {
-                        logger.debug('Opening AWS credentials and config files for editing')
-                        // Open both credentials and config files like AWS Explorer does
-                        const credentialsPath = getCredentialsFilename()
-                        const configPath = getConfigFilename()
-
-                        // Open both files
-                        const [credentialsDoc, configDoc] = await Promise.all([
-                            vscode.workspace.openTextDocument(credentialsPath),
-                            vscode.workspace.openTextDocument(configPath),
-                        ])
-
-                        // Show both documents
-                        await vscode.window.showTextDocument(credentialsDoc, { preview: false })
-                        await vscode.window.showTextDocument(configDoc, {
-                            preview: false,
-                            viewColumn: vscode.ViewColumn.Beside,
-                        })
-
-                        void vscode.window.showInformationMessage(
-                            'AWS credentials and config files opened. Please update your credentials and try reconnecting.'
-                        )
-                        resolve({ action: IamCredentialExpiryAction.EditCredentials })
-                        break
-                    }
-                    case IamCredentialExpiryAction.SwitchProfile: {
-                        logger.debug('Switching to another IAM profile')
-                        try {
-                            const profileSelection = await SmusIamProfileSelector.showIamProfileSelection()
-
-                            // Handle back navigation - show the credential expiry menu again
-                            if ('isBack' in profileSelection) {
-                                logger.debug('User clicked back, showing credential expiry options again')
-                                // Recursively show the credential expiry options menu
-                                const result = await showIamCredentialExpiryOptions(
-                                    authProvider,
-                                    connection,
-                                    extensionContext
-                                )
-                                resolve(result)
-                                return
-                            }
-
-                            // Handle editing mode - This is if user picks edit during the profile selection
-                            if ('isEditing' in profileSelection) {
-                                logger.debug('User is editing credentials')
-                                resolve({ action: IamCredentialExpiryAction.EditCredentials })
-                                return
-                            }
-
-                            // User selected a new profile, authenticate with it using the selected profile
-                            // Use dynamic import to avoid circular dependency
-                            const { SmusAuthenticationOrchestrator } = await import('./authenticationOrchestrator.js')
-                            const result = await SmusAuthenticationOrchestrator.handleIamAuthentication(
-                                authProvider,
-                                { record: () => {} }, // Minimal span object
-                                extensionContext,
-                                profileSelection.profileName,
-                                profileSelection.region
-                            )
-
-                            if (result.status === 'SUCCESS') {
-                                void vscode.window.showInformationMessage(
-                                    `Successfully switched to profile: ${profileSelection.profileName}`
-                                )
-                                resolve({ action: IamCredentialExpiryAction.SwitchProfile })
-                            } else if (result.status === 'INVALID_PROFILE') {
-                                void vscode.window.showErrorMessage(`Failed to switch profile: ${result.error}`)
-                                resolve({ action: IamCredentialExpiryAction.SwitchProfile })
-                            } else {
-                                // BACK or EDITING - shouldn't happen here but handle gracefully
-                                resolve({ action: IamCredentialExpiryAction.Cancelled })
-                            }
-                        } catch (switchError) {
-                            // Handle user cancellation gracefully
-                            if (
-                                switchError instanceof ToolkitError &&
-                                switchError.code === SmusErrorCodes.UserCancelled
-                            ) {
-                                logger.debug('Profile switch cancelled by user')
-                                resolve({ action: IamCredentialExpiryAction.Cancelled })
-                            } else {
-                                // Show error message for actual failures
-                                const errorMsg = (switchError as Error).message
-                                void vscode.window.showErrorMessage(`Failed to switch profile: ${errorMsg}`)
-                                logger.error('Profile switch failed: %s', switchError)
-                                resolve({ action: IamCredentialExpiryAction.SwitchProfile })
-                            }
-                        }
-                        break
-                    }
-                    case IamCredentialExpiryAction.SignOut: {
-                        logger.debug('Signing out from connection')
-                        // Use the provider's signOut method which properly handles metadata cleanup
-                        await authProvider.signOut()
-                        void vscode.window.showInformationMessage('Successfully signed out')
-                        resolve({ action: IamCredentialExpiryAction.SignOut })
-                        break
-                    }
-                }
-            } catch (error) {
-                logger.error('Failed to handle credential expiry action: %s', error)
-                // Only show error for non-reauthenticate cases (reauthenticate handles its own errors)
-                if (itemWithAction.action !== IamCredentialExpiryAction.Reauthenticate) {
-                    void vscode.window.showErrorMessage(`Failed to complete action: ${(error as Error).message}`)
-                }
-                reject(error)
-            }
-        })
-
-        quickPick.onDidHide(() => {
-            if (!isCompleted) {
-                quickPick.dispose()
-                logger.debug('Credential expiry options cancelled by user')
-                resolve({ action: IamCredentialExpiryAction.Cancelled })
-            }
-        })
-
-        quickPick.show()
-    })
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/model.ts b/packages/core/src/sagemakerunifiedstudio/auth/model.ts
deleted file mode 100644
index adc6fd2945..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/model.ts
+++ /dev/null
@@ -1,147 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { SsoProfile, SsoConnection, Connection, IamConnection } from '../../auth/connection'
-import { DevSettings } from '../../shared/settings'
-
-/**
- * Default scope for SageMaker Unified Studio authentication
- */
-export const scopeSmus = 'datazone:domain:access'
-
-/**
- * Gets the DataZone SSO scope from user settings or returns the default
- */
-export function getDataZoneSsoScope(): string {
-    const devSettings = DevSettings.instance
-    return devSettings.get('datazoneScope', scopeSmus)
-}
-
-/**
- * SageMaker Unified Studio profile extending the base SSO profile
- */
-export interface SmusSsoProfile extends SsoProfile {
-    readonly domainUrl: string
-    readonly domainId: string
-}
-
-/**
- * SageMaker Unified Studio SSO connection extending the base SSO connection
- */
-export interface SmusSsoConnection extends SmusSsoProfile, SsoConnection {
-    readonly id: string
-    readonly label: string
-}
-
-/**
- * SageMaker Unified Studio IAM connection for credential profile authentication
- */
-export interface SmusIamConnection extends IamConnection {
-    readonly profileName: string
-    readonly region: string
-    readonly domainUrl: string
-    readonly domainId: string
-}
-
-/**
- * Union type for all SMUS connection types (SSO and IAM)
- */
-export type SmusConnection = SmusSsoConnection | SmusIamConnection
-
-/**
- * Creates a SageMaker Unified Studio profile
- * @param domainUrl The SageMaker Unified Studio domain URL
- * @param domainId The SageMaker Unified Studio domain ID
- * @param startUrl The SSO start URL (issuer URL)
- * @param region The AWS region
- * @returns A SageMaker Unified Studio profile
- */
-export function createSmusProfile(
-    domainUrl: string,
-    domainId: string,
-    startUrl: string,
-    region: string,
-    scopes = [getDataZoneSsoScope()]
-): SmusSsoProfile & { readonly scopes: string[] } {
-    return {
-        scopes,
-        type: 'sso',
-        startUrl,
-        ssoRegion: region,
-        domainUrl,
-        domainId,
-    }
-}
-
-/**
- * Type guard to check if a connection is a SMUS IAM connection
- * @param conn Connection to check
- * @returns True if the connection is a SMUS IAM connection
- */
-export function isSmusIamConnection(conn?: Connection): conn is SmusIamConnection {
-    return !!(
-        conn &&
-        conn.type === 'iam' &&
-        'profileName' in conn &&
-        'region' in conn &&
-        'domainId' in conn &&
-        typeof conn.profileName === 'string' &&
-        typeof conn.region === 'string' &&
-        typeof conn.domainId === 'string'
-    )
-}
-
-/**
- * Type guard to check if a connection is a SMUS SSO connection
- * @param conn Connection to check
- * @returns True if the connection is a SMUS SSO connection
- */
-export function isSmusSsoConnection(conn?: Connection): conn is SmusSsoConnection {
-    if (!conn || conn.type !== 'sso') {
-        return false
-    }
-    // Check if the connection has the required SMUS scope (check both default and custom scope)
-    const configuredScope = getDataZoneSsoScope()
-    const hasScope =
-        Array.isArray((conn as any).scopes) &&
-        ((conn as any).scopes.includes(scopeSmus) || (conn as any).scopes.includes(configuredScope))
-    // Check if the connection has the required SMUS properties
-    const hasSmusProps = 'domainUrl' in conn && 'domainId' in conn
-    return !!hasScope && !!hasSmusProps
-}
-
-/**
- * Checks if a connection is a valid SageMaker Unified Studio connection (either SSO or IAM)
- * @param conn Connection to check
- * @param smusMetadata Optional SMUS metadata for IAM connections
- * @returns True if the connection is a valid SMUS connection
- */
-export function isValidSmusConnection(conn?: any, smusMetadata?: any): conn is SmusConnection | IamConnection {
-    // Accept SMUS SSO connections
-    if (isSmusSsoConnection(conn)) {
-        return true
-    }
-
-    // For IAM connections, check if they have SMUS metadata either in the connection or separately
-    if (conn && conn.type === 'iam') {
-        // Check if connection already has SMUS properties
-        if (isSmusIamConnection(conn)) {
-            return true
-        }
-
-        // Check if we have separate SMUS metadata for this IAM connection
-        if (
-            smusMetadata &&
-            typeof smusMetadata.profileName === 'string' &&
-            typeof smusMetadata.region === 'string' &&
-            typeof smusMetadata.domainUrl === 'string' &&
-            typeof smusMetadata.domainId === 'string'
-        ) {
-            return true
-        }
-    }
-
-    return false
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.ts b/packages/core/src/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.ts
deleted file mode 100644
index 47416e7325..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.ts
+++ /dev/null
@@ -1,191 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../../shared/logger/logger'
-import globals from '../../../shared/extensionGlobals.js'
-import { SmusAuthenticationMethod } from '../ui/authenticationMethodSelection.js'
-
-/**
- * Configuration for IAM profile preferences
- */
-export interface SmusIamProfileConfig {
-    profileName: string
-    region: string
-    lastUsed?: Date
-    isDefault?: boolean
-}
-
-/**
- * SMUS authentication preferences
- */
-export interface SmusAuthenticationPreferences {
-    preferredMethod?: SmusAuthenticationMethod
-    lastUsedSsoConnection?: string
-    lastUsedIamProfile?: SmusIamProfileConfig
-    rememberChoice: boolean
-}
-
-/**
- * Manager for SMUS authentication preferences
- */
-export class SmusAuthenticationPreferencesManager {
-    private static readonly logger = getLogger('smus')
-    // eslint-disable-next-line @typescript-eslint/naming-convention
-    private static readonly PREFERENCES_KEY = 'aws.smus.authenticationPreferences'
-
-    /**
-     * Gets the current authentication preferences
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     * @returns Current authentication preferences
-     */
-    public static getPreferences(context?: vscode.ExtensionContext): SmusAuthenticationPreferences {
-        const stored = globals.globalState.get(this.PREFERENCES_KEY)
-
-        return {
-            rememberChoice: false,
-            ...stored,
-        }
-    }
-
-    /**
-     * Updates authentication preferences
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     * @param preferences Preferences to update
-     */
-    public static async updatePreferences(
-        context: vscode.ExtensionContext,
-        preferences: Partial
-    ): Promise {
-        const logger = this.logger
-
-        const current = this.getPreferences()
-        const updated = { ...current, ...preferences }
-
-        logger.debug(
-            `SMUS Auth: Updating authentication preferences - preferredMethod: ${updated.preferredMethod}, rememberChoice: ${updated.rememberChoice}`
-        )
-
-        await globals.globalState.update(this.PREFERENCES_KEY, updated)
-    }
-
-    /**
-     * Sets the preferred authentication method
-     * @param context VS Code extension context
-     * @param method Preferred authentication method
-     * @param rememberChoice Whether to remember this choice
-     */
-    public static async setPreferredMethod(
-        context: vscode.ExtensionContext,
-        method: SmusAuthenticationMethod,
-        rememberChoice: boolean
-    ): Promise {
-        await this.updatePreferences(context, {
-            preferredMethod: method,
-            rememberChoice,
-        })
-    }
-
-    /**
-     * Gets the preferred authentication method
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     * @returns Preferred authentication method or undefined if not set
-     */
-    public static getPreferredMethod(context?: vscode.ExtensionContext): SmusAuthenticationMethod | undefined {
-        const preferences = this.getPreferences()
-        return preferences.rememberChoice ? preferences.preferredMethod : undefined
-    }
-
-    /**
-     * Sets the last used SSO connection
-     * @param context VS Code extension context
-     * @param connectionId Connection ID
-     */
-    public static async setLastUsedSsoConnection(
-        context: vscode.ExtensionContext,
-        connectionId: string
-    ): Promise {
-        await this.updatePreferences(context, {
-            lastUsedSsoConnection: connectionId,
-        })
-    }
-
-    /**
-     * Sets the last used IAM profile configuration
-     * @param context VS Code extension context
-     * @param profileConfig IAM profile configuration
-     */
-    public static async setLastUsedIamProfile(
-        context: vscode.ExtensionContext,
-        profileConfig: SmusIamProfileConfig
-    ): Promise {
-        await this.updatePreferences(context, {
-            lastUsedIamProfile: {
-                ...profileConfig,
-                lastUsed: new Date(),
-            },
-        })
-    }
-
-    /**
-     * Gets the last used IAM profile configuration
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     * @returns Last used IAM profile configuration or undefined
-     */
-    public static getLastUsedIamProfile(context?: vscode.ExtensionContext): SmusIamProfileConfig | undefined {
-        const preferences = this.getPreferences()
-        return preferences.lastUsedIamProfile
-    }
-
-    /**
-     * Clears all authentication preferences
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     */
-    public static async clearPreferences(context?: vscode.ExtensionContext): Promise {
-        const logger = this.logger
-        logger.debug('Clearing authentication preferences')
-
-        await globals.globalState.update(this.PREFERENCES_KEY, undefined)
-    }
-
-    /**
-     * Clears only connection-specific preferences, preserving authentication method preference
-     * @param context VS Code extension context (unused, kept for API compatibility)
-     */
-    public static async clearConnectionPreferences(context?: vscode.ExtensionContext): Promise {
-        const logger = this.logger
-        logger.debug('Clearing connection-specific preferences (preserving auth method preference)')
-
-        const currentPrefs = this.getPreferences()
-
-        // Keep only the authentication method preference and rememberChoice flag
-        const preservedPrefs: SmusAuthenticationPreferences = {
-            preferredMethod: currentPrefs.preferredMethod,
-            rememberChoice: currentPrefs.rememberChoice,
-            // Clear connection-specific data
-            lastUsedSsoConnection: undefined,
-            lastUsedIamProfile: undefined,
-        }
-
-        await globals.globalState.update(this.PREFERENCES_KEY, preservedPrefs)
-    }
-
-    /**
-     * Switches the authentication method preference
-     * @param context VS Code extension context
-     * @param newMethod New authentication method to switch to
-     */
-    public static async switchAuthenticationMethod(
-        context: vscode.ExtensionContext,
-        newMethod: SmusAuthenticationMethod
-    ): Promise {
-        const logger = this.logger
-        logger.debug(`Switching authentication method to: ${newMethod}`)
-
-        await this.updatePreferences(context, {
-            preferredMethod: newMethod,
-        })
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider.ts b/packages/core/src/sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider.ts
deleted file mode 100644
index 30b8477f6c..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider.ts
+++ /dev/null
@@ -1,252 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import { ToolkitError } from '../../../shared/errors'
-import * as AWS from '@aws-sdk/types'
-import { CredentialsId, CredentialsProvider, CredentialsProviderType } from '../../../auth/providers/credentials'
-
-import { DataZoneClient } from '../../shared/client/datazoneClient'
-import { SmusAuthenticationProvider } from './smusAuthenticationProvider'
-import { CredentialType } from '../../../shared/telemetry/telemetry'
-import { SmusCredentialExpiry, validateCredentialFields } from '../../shared/smusUtils'
-import { getContext } from '../../../shared/vscode/setContext'
-
-/**
- * Credentials provider for SageMaker Unified Studio Connection credentials
- * Uses DataZone API to get connection credentials for a specific connection *
- * This provider implements independent caching with 10-minute expiry
- */
-export class ConnectionCredentialsProvider implements CredentialsProvider {
-    private readonly logger = getLogger('smus')
-    private credentialCache?: {
-        credentials: AWS.Credentials
-        expiresAt: Date
-    }
-
-    constructor(
-        private readonly smusAuthProvider: SmusAuthenticationProvider,
-        private readonly connectionId: string,
-        private readonly projectId: string
-    ) {}
-
-    /**
-     * Gets the connection ID
-     * @returns Connection ID
-     */
-    public getConnectionId(): string {
-        return this.connectionId
-    }
-
-    /**
-     * Gets the credentials ID
-     * @returns Credentials ID
-     */
-    public getCredentialsId(): CredentialsId {
-        return {
-            credentialSource: 'temp',
-            credentialTypeId: `${this.smusAuthProvider.getDomainId()}:${this.connectionId}`,
-        }
-    }
-
-    /**
-     * Gets the provider type
-     * @returns Provider type
-     */
-    public getProviderType(): CredentialsProviderType {
-        return 'temp'
-    }
-
-    /**
-     * Gets the telemetry type
-     * @returns Telemetry type
-     */
-    public getTelemetryType(): CredentialType {
-        return 'other'
-    }
-
-    /**
-     * Gets the default region
-     * @returns Default region
-     */
-    public getDefaultRegion(): string | undefined {
-        return this.smusAuthProvider.getDomainRegion()
-    }
-
-    /**
-     * Gets the domain AWS account ID
-     * @returns Promise resolving to the domain account ID
-     */
-    public async getDomainAccountId(): Promise {
-        return this.smusAuthProvider.getDomainAccountId()
-    }
-
-    /**
-     * Gets the hash code
-     * @returns Hash code
-     */
-    public getHashCode(): string {
-        const hashCode = `smus-connection:${this.smusAuthProvider.getDomainId()}:${this.connectionId}`
-        return hashCode
-    }
-
-    /**
-     * Determines if the provider can auto-connect
-     * @returns Promise resolving to boolean
-     */
-    public async canAutoConnect(): Promise {
-        return false // SMUS requires manual authentication
-    }
-
-    /**
-     * Determines if the provider is available
-     * @returns Promise resolving to boolean
-     */
-    public async isAvailable(): Promise {
-        try {
-            return this.smusAuthProvider.isConnected()
-        } catch (err) {
-            this.logger.error('Error checking if auth provider is connected: %s', err)
-            return false
-        }
-    }
-
-    /**
-     * Gets Connection credentials with independent caching
-     * @returns Promise resolving to credentials
-     */
-    public async getCredentials(): Promise {
-        this.logger.debug(`Getting credentials for connection ${this.connectionId}`)
-
-        // Check cache first (10-minute expiry)
-        if (this.credentialCache && this.credentialCache.expiresAt > new Date()) {
-            this.logger.debug(
-                `SMUS Connection: Using cached connection credentials for connection ${this.connectionId}`
-            )
-            return this.credentialCache.credentials
-        }
-
-        this.logger.debug(
-            `SMUS Connection: Calling GetConnection to fetch credentials for connection ${this.connectionId}`
-        )
-
-        try {
-            if (getContext('aws.smus.isIamMode') && this.projectId) {
-                return (await this.smusAuthProvider.getProjectCredentialProvider(this.projectId)).getCredentials()
-            }
-            const datazoneClient = DataZoneClient.createWithCredentials(
-                this.smusAuthProvider.getDomainRegion(),
-                this.smusAuthProvider.getDomainId(),
-                await this.smusAuthProvider.getDerCredentialsProvider()
-            )
-            const getConnectionResponse = await datazoneClient.getConnection({
-                domainIdentifier: this.smusAuthProvider.getDomainId(),
-                identifier: this.connectionId,
-                withSecret: true,
-            })
-
-            this.logger.debug(`Successfully retrieved connection details for ${this.connectionId}`)
-
-            // Extract connection credentials
-            const connectionCredentials = getConnectionResponse.connectionCredentials
-            if (!connectionCredentials) {
-                throw new ToolkitError(
-                    `No connection credentials available in response for connection ${this.connectionId}`,
-                    {
-                        code: 'NoConnectionCredentials',
-                    }
-                )
-            }
-
-            // Validate credential fields
-            validateCredentialFields(
-                connectionCredentials,
-                'InvalidConnectionCredentials',
-                'connection credential response',
-                true
-            )
-
-            // Create AWS credentials with expiration
-            // Use the expiration from the response if available, otherwise default to 10 minutes
-            let expiresAt: Date
-            if (connectionCredentials.expiration) {
-                // The API returns expiration as a string or Date, handle both cases
-                expiresAt =
-                    connectionCredentials.expiration instanceof Date
-                        ? connectionCredentials.expiration
-                        : new Date(connectionCredentials.expiration)
-            } else {
-                expiresAt = new Date(Date.now() + SmusCredentialExpiry.connectionExpiryMs)
-            }
-
-            const awsCredentials: AWS.Credentials = {
-                accessKeyId: connectionCredentials.accessKeyId as string,
-                secretAccessKey: connectionCredentials.secretAccessKey as string,
-                sessionToken: connectionCredentials.sessionToken as string,
-                expiration: expiresAt,
-            }
-
-            // Cache connection credentials (10-minute expiry)
-            const cacheExpiresAt = new Date(Date.now() + SmusCredentialExpiry.connectionExpiryMs)
-            this.credentialCache = {
-                credentials: awsCredentials,
-                expiresAt: cacheExpiresAt,
-            }
-
-            this.logger.debug(
-                `SMUS Connection: Successfully cached connection credentials for connection ${this.connectionId}, expires in %s minutes`,
-                Math.round((cacheExpiresAt.getTime() - Date.now()) / 60000)
-            )
-
-            return awsCredentials
-        } catch (err) {
-            this.logger.error(
-                `SMUS Connection: Failed to get connection credentials for connection ${this.connectionId}: %s`,
-                err
-            )
-
-            // Re-throw ToolkitErrors with specific codes (NoConnectionCredentials, InvalidConnectionCredentials)
-            if (
-                err instanceof ToolkitError &&
-                (err.code === 'NoConnectionCredentials' || err.code === 'InvalidConnectionCredentials')
-            ) {
-                throw err
-            }
-
-            // Wrap other errors in ConnectionCredentialsFetchFailed
-            throw new ToolkitError(`Failed to get connection credentials for ${this.connectionId}: ${err}`, {
-                code: 'ConnectionCredentialsFetchFailed',
-                cause: err instanceof Error ? err : undefined,
-            })
-        }
-    }
-
-    /**
-     * Invalidates cached connection credentials
-     * Clears the internal cache without fetching new credentials
-     */
-    public invalidate(): void {
-        this.logger.debug(`Invalidating cached credentials for connection ${this.connectionId}`)
-        // Clear cache to force fresh fetch on next getCredentials() call
-        this.credentialCache = undefined
-        this.logger.debug(
-            `SMUS Connection: Successfully invalidated connection credentials cache for connection ${this.connectionId}`
-        )
-    }
-
-    /**
-     * Disposes of the provider and cleans up resources
-     */
-    public dispose(): void {
-        this.logger.debug(
-            `SMUS Connection: Disposing connection credentials provider for connection ${this.connectionId}`
-        )
-        // Clear cache to clean up resources
-        this.invalidate()
-        this.logger.debug(
-            `SMUS Connection: Successfully disposed connection credentials provider for connection ${this.connectionId}`
-        )
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/providers/domainExecRoleCredentialsProvider.ts b/packages/core/src/sagemakerunifiedstudio/auth/providers/domainExecRoleCredentialsProvider.ts
deleted file mode 100644
index 85da4901a9..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/providers/domainExecRoleCredentialsProvider.ts
+++ /dev/null
@@ -1,325 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import { ToolkitError } from '../../../shared/errors'
-import * as AWS from '@aws-sdk/types'
-import { CredentialsId, CredentialsProvider, CredentialsProviderType } from '../../../auth/providers/credentials'
-import fetch from 'node-fetch'
-import globals from '../../../shared/extensionGlobals'
-import { CredentialType } from '../../../shared/telemetry/telemetry'
-import { SmusCredentialExpiry, SmusTimeouts, SmusErrorCodes, validateCredentialFields } from '../../shared/smusUtils'
-
-/**
- * Credentials provider for SageMaker Unified Studio Domain Execution Role (DER)
- * Uses SSO tokens to get DER credentials via the /sso/redeem-token endpoint
- *
- * This provider implements internal caching with 10-minute expiry and handles
- * its own credential lifecycle independently
- */
-export class DomainExecRoleCredentialsProvider implements CredentialsProvider {
-    private readonly logger = getLogger('smus')
-    private credentialCache?: {
-        credentials: AWS.Credentials
-        expiresAt: Date
-    }
-
-    constructor(
-        private readonly domainUrl: string,
-        private readonly domainId: string,
-        private readonly ssoRegion: string,
-        private readonly getAccessToken: () => Promise // Function to get SSO access token for the Connection
-    ) {}
-
-    /**
-     * Gets the domain ID
-     * @returns Domain ID
-     */
-    public getDomainId(): string {
-        return this.domainId
-    }
-
-    /**
-     * Gets the domain URL
-     * @returns Domain URL
-     */
-    public getDomainUrl(): string {
-        return this.domainUrl
-    }
-
-    /**
-     * Gets the credentials ID
-     * @returns Credentials ID
-     */
-    public getCredentialsId(): CredentialsId {
-        return {
-            credentialSource: 'sso',
-            credentialTypeId: this.domainId,
-        }
-    }
-
-    /**
-     * Gets the provider type
-     * @returns Provider type
-     */
-    public getProviderType(): CredentialsProviderType {
-        return 'sso'
-    }
-
-    /**
-     * Gets the telemetry type
-     * @returns Telemetry type
-     */
-    public getTelemetryType(): CredentialType {
-        return 'ssoProfile'
-    }
-
-    /**
-     * Gets the default region
-     * @returns Default region
-     */
-    public getDefaultRegion(): string | undefined {
-        return this.ssoRegion
-    }
-
-    /**
-     * Gets the hash code
-     * @returns Hash code
-     */
-    public getHashCode(): string {
-        const hashCode = `smus-der:${this.domainId}:${this.ssoRegion}`
-        return hashCode
-    }
-
-    /**
-     * Determines if the provider can auto-connect
-     * @returns Promise resolving to boolean
-     */
-    public async canAutoConnect(): Promise {
-        return false // SMUS requires manual authentication
-    }
-
-    /**
-     * Determines if the provider is available
-     * @returns Promise resolving to boolean
-     */
-    public async isAvailable(): Promise {
-        try {
-            // Check if we can get an access token
-            await this.getAccessToken()
-            return true
-        } catch {
-            return false
-        }
-    }
-
-    /**
-     * Gets Domain Execution Role (DER) credentials with internal caching
-     * @returns Promise resolving to credentials
-     */
-    public async getCredentials(): Promise {
-        this.logger.debug(`Getting DER credentials for domain ${this.domainId}`)
-
-        // Check cache first (10-minute expiry with 5-minute buffer for proactive refresh)
-        if (this.credentialCache && this.credentialCache.expiresAt > new Date()) {
-            this.logger.debug(`Using cached DER credentials for domain ${this.domainId}`)
-            return this.credentialCache.credentials
-        }
-
-        this.logger.debug(`Fetching credentials from API for domain ${this.domainId}`)
-
-        try {
-            // Get current SSO access token
-            const accessToken = await this.getAccessToken()
-            if (!accessToken) {
-                throw new ToolkitError('No access token available for DER credential refresh', {
-                    code: 'NoTokenAvailable',
-                })
-            }
-
-            this.logger.debug(`Got access token for refresh for domain ${this.domainId}`)
-
-            // Call SMUS redeem token API to get DER credentials
-            const redeemUrl = new URL('/sso/redeem-token', this.domainUrl)
-            this.logger.debug(`Calling redeem token endpoint: ${redeemUrl.toString()}`)
-
-            const requestBody = {
-                domainId: this.domainId,
-                accessToken,
-            }
-
-            const requestHeaders = {
-                'Content-Type': 'application/json',
-                Accept: 'application/json',
-                'User-Agent': 'aws-toolkit-vscode',
-            }
-
-            let response
-            try {
-                response = await fetch(redeemUrl.toString(), {
-                    method: 'POST',
-                    headers: requestHeaders,
-                    body: JSON.stringify(requestBody),
-                    timeout: SmusTimeouts.apiCallTimeoutMs,
-                })
-            } catch (fetchError) {
-                // Handle timeout errors specifically
-                if (
-                    fetchError instanceof Error &&
-                    (fetchError.name === 'AbortError' || fetchError.message.includes('timeout'))
-                ) {
-                    throw new ToolkitError(
-                        `Redeem token request timed out after ${SmusTimeouts.apiCallTimeoutMs / 1000} seconds`,
-                        {
-                            code: SmusErrorCodes.ApiTimeout,
-                            cause: fetchError,
-                        }
-                    )
-                }
-                // Re-throw other fetch errors
-                throw fetchError
-            }
-
-            this.logger.debug(`Redeem token response status: ${response.status} for domain ${this.domainId}`)
-
-            if (!response.ok) {
-                // Try to get response body for more details
-                let responseBody = ''
-                try {
-                    responseBody = await response.text()
-                    this.logger.debug(`Error response body for domain ${this.domainId}: ${responseBody}`)
-                } catch (bodyErr) {
-                    this.logger.debug(
-                        `SMUS DER: Could not read error response body for domain ${this.domainId}: ${bodyErr}`
-                    )
-                }
-
-                throw new ToolkitError(
-                    `Failed to redeem access token: ${response.status} ${response.statusText}${responseBody ? ` - ${responseBody}` : ''}`,
-                    { code: SmusErrorCodes.RedeemAccessTokenFailed }
-                )
-            }
-
-            const responseText = await response.text()
-
-            const data = JSON.parse(responseText) as {
-                credentials: {
-                    accessKeyId: string
-                    secretAccessKey: string
-                    sessionToken: string
-                    expiration: string
-                }
-            }
-            this.logger.debug(`Successfully received credentials from API for domain ${this.domainId}`)
-
-            // Validate the response data structure
-            if (!data.credentials) {
-                throw new ToolkitError('Missing credentials object in API response', {
-                    code: 'InvalidCredentialResponse',
-                })
-            }
-
-            const credentials = data.credentials
-
-            // Validate the credential fields
-            validateCredentialFields(credentials, 'InvalidCredentialResponse', 'API response')
-
-            // Create credentials with expiration
-            let credentialExpiresAt: Date
-            if (credentials.expiration) {
-                // Handle both epoch timestamps and ISO date strings
-                let parsedExpiration: Date
-
-                // Check if expiration is a numeric string (epoch timestamp)
-                const expirationNum = Number(credentials.expiration)
-                if (!isNaN(expirationNum) && expirationNum > 0) {
-                    // Treat as epoch timestamp in seconds and convert to milliseconds
-                    const timestampMs = expirationNum * 1000
-                    parsedExpiration = new Date(timestampMs)
-                    this.logger.debug(
-                        `SMUS DER: Parsed epoch timestamp ${credentials.expiration} (seconds) as ${parsedExpiration.toISOString()}`
-                    )
-                } else {
-                    // Treat as ISO date string
-                    parsedExpiration = new Date(credentials.expiration)
-                    if (!isNaN(parsedExpiration.getTime())) {
-                        this.logger.debug(
-                            `SMUS DER: Parsed ISO date string ${credentials.expiration} as ${parsedExpiration.toISOString()}`
-                        )
-                    } else {
-                        this.logger.debug(
-                            `SMUS DER: Failed to parse ISO date string ${credentials.expiration} - invalid date format`
-                        )
-                    }
-                }
-
-                // Check if the parsed date is valid
-                if (isNaN(parsedExpiration.getTime())) {
-                    this.logger.warn(
-                        `SMUS DER: Invalid expiration value: ${credentials.expiration}, using default expiration`
-                    )
-                    credentialExpiresAt = new Date(Date.now() + SmusCredentialExpiry.derExpiryMs)
-                } else {
-                    credentialExpiresAt = parsedExpiration
-                }
-                if (!isNaN(credentialExpiresAt.getTime())) {
-                    this.logger.debug(`Credential expires at ${credentialExpiresAt.toISOString()}`)
-                } else {
-                    this.logger.debug(`Invalid credential expiration date, using default`)
-                }
-            } else {
-                this.logger.debug(`No expiration provided, using default`)
-                credentialExpiresAt = new Date(Date.now() + SmusCredentialExpiry.derExpiryMs)
-            }
-
-            const awsCredentials: AWS.Credentials = {
-                accessKeyId: credentials.accessKeyId as string,
-                secretAccessKey: credentials.secretAccessKey as string,
-                sessionToken: credentials.sessionToken as string,
-                expiration: credentialExpiresAt,
-            }
-
-            // Cache DER credentials with 10-minute expiry (5-minute buffer for proactive refresh)
-            const cacheExpiresAt = new globals.clock.Date(Date.now() + SmusCredentialExpiry.derExpiryMs)
-            this.credentialCache = {
-                credentials: awsCredentials,
-                expiresAt: cacheExpiresAt,
-            }
-
-            this.logger.debug(
-                'SMUS DER: Successfully cached DER credentials for domain %s, cache expires in %s minutes',
-                this.domainId,
-                Math.round((cacheExpiresAt.getTime() - Date.now()) / 60000)
-            )
-
-            return awsCredentials
-        } catch (err) {
-            this.logger.error('Failed to fetch credentials for domain %s: %s', this.domainId, err)
-            throw new ToolkitError(`Failed to fetch DER credentials for domain ${this.domainId}: ${err}`, {
-                code: 'DerCredentialsFetchFailed',
-                cause: err instanceof Error ? err : undefined,
-            })
-        }
-    }
-
-    /**
-     * Invalidates cached DER credentials
-     * Clears the internal cache without fetching new credentials
-     */
-    public invalidate(): void {
-        this.logger.debug(`Invalidating cached DER credentials for domain ${this.domainId}`)
-        // Clear cache to force fresh fetch on next getCredentials() call
-        this.credentialCache = undefined
-        this.logger.debug(`Successfully invalidated DER credentials cache for domain ${this.domainId}`)
-    }
-    /**
-     * Disposes of the provider and cleans up resources
-     */
-    public dispose(): void {
-        this.logger.debug(`Disposing DER credentials provider for domain ${this.domainId}`)
-        this.invalidate()
-        this.logger.debug(`Successfully disposed DER credentials provider for domain ${this.domainId}`)
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/providers/projectRoleCredentialsProvider.ts b/packages/core/src/sagemakerunifiedstudio/auth/providers/projectRoleCredentialsProvider.ts
deleted file mode 100644
index 7f67d0a97a..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/providers/projectRoleCredentialsProvider.ts
+++ /dev/null
@@ -1,363 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import { ToolkitError } from '../../../shared/errors'
-import * as AWS from '@aws-sdk/types'
-import { CredentialsId, CredentialsProvider, CredentialsProviderType } from '../../../auth/providers/credentials'
-
-import { SmusAuthenticationProvider } from './smusAuthenticationProvider'
-import { CredentialType } from '../../../shared/telemetry/telemetry'
-import { SmusCredentialExpiry, validateCredentialFields } from '../../shared/smusUtils'
-import { loadMappings, saveMappings } from '../../../awsService/sagemaker/credentialMapping'
-import { createDZClientBaseOnDomainMode } from '../../explorer/nodes/utils'
-
-/**
- * Credentials provider for SageMaker Unified Studio Project Role credentials
- * Uses Domain Execution Role (DER) credentials to get project-scoped credentials
- * via the DataZone GetEnvironmentCredentials API
- *
- * This provider implements independent caching with 10-minute expiry and can be used
- * with any AWS SDK client (S3Client, LambdaClient, etc.)
- */
-export class ProjectRoleCredentialsProvider implements CredentialsProvider {
-    private readonly logger = getLogger('smus')
-    private credentialCache?: {
-        credentials: AWS.Credentials
-        expiresAt: Date
-    }
-    private refreshTimer?: NodeJS.Timeout
-    private readonly refreshInterval = 10 * 60 * 1000 // 10 minutes
-    private readonly checkInterval = 10 * 1000 // 10 seconds - check frequently, refresh based on actual time
-    private sshRefreshActive = false
-    private lastRefreshTime?: Date
-
-    constructor(
-        private readonly smusAuthProvider: SmusAuthenticationProvider,
-        private readonly projectId: string
-    ) {}
-
-    /**
-     * Gets the project ID
-     * @returns Project ID
-     */
-    public getProjectId(): string {
-        return this.projectId
-    }
-
-    /**
-     * Gets the credentials ID
-     * @returns Credentials ID
-     */
-    public getCredentialsId(): CredentialsId {
-        return {
-            credentialSource: 'temp',
-            credentialTypeId: `${this.smusAuthProvider.getDomainId()}:${this.projectId}`,
-        }
-    }
-
-    /**
-     * Gets the provider type
-     * @returns Provider type
-     */
-    public getProviderType(): CredentialsProviderType {
-        return 'temp'
-    }
-
-    /**
-     * Gets the telemetry type
-     * @returns Telemetry type
-     */
-    public getTelemetryType(): CredentialType {
-        return 'other'
-    }
-
-    /**
-     * Gets the default region
-     * @returns Default region
-     */
-    public getDefaultRegion(): string | undefined {
-        return this.smusAuthProvider.getDomainRegion()
-    }
-
-    /**
-     * Gets the hash code
-     * @returns Hash code
-     */
-    public getHashCode(): string {
-        const hashCode = `smus-project:${this.smusAuthProvider.getDomainId()}:${this.projectId}`
-        return hashCode
-    }
-
-    /**
-     * Determines if the provider can auto-connect
-     * @returns Promise resolving to boolean
-     */
-    public async canAutoConnect(): Promise {
-        return false // SMUS requires manual authentication
-    }
-
-    /**
-     * Determines if the provider is available
-     * @returns Promise resolving to boolean
-     */
-    public async isAvailable(): Promise {
-        return this.smusAuthProvider.isConnected()
-    }
-
-    /**
-     * Gets Project Role credentials with independent caching
-     * @returns Promise resolving to credentials
-     */
-    public async getCredentials(): Promise {
-        this.logger.debug(`Getting credentials for project ${this.projectId}`)
-
-        // Check cache first (10-minute expiry)
-        if (this.credentialCache && this.credentialCache.expiresAt > new Date()) {
-            this.logger.debug(`Using cached project credentials for project ${this.projectId}`)
-            return this.credentialCache.credentials
-        }
-
-        this.logger.debug(`Fetching project credentials from API for project ${this.projectId}`)
-
-        try {
-            const dataZoneClient = await createDZClientBaseOnDomainMode(this.smusAuthProvider)
-            const response = await dataZoneClient.getProjectDefaultEnvironmentCreds(this.projectId)
-
-            this.logger.debug(
-                `SMUS Project: Successfully received response from GetEnvironmentCredentials API for project ${this.projectId}`
-            )
-
-            // Validate credential fields - credentials are returned directly in the response
-            validateCredentialFields(response, 'InvalidProjectCredentialResponse', 'project credential response')
-
-            // Create AWS credentials with expiration
-            // Use the expiration from the response if available, otherwise default to 10 minutes
-            let expiresAt: Date
-            if (response.expiration) {
-                // The API returns expiration as a string, parse it to Date
-                expiresAt = new Date(response.expiration)
-            } else {
-                expiresAt = new Date(Date.now() + SmusCredentialExpiry.projectExpiryMs)
-            }
-
-            const awsCredentials: AWS.Credentials = {
-                accessKeyId: response.accessKeyId as string,
-                secretAccessKey: response.secretAccessKey as string,
-                sessionToken: response.sessionToken as string,
-                expiration: expiresAt,
-            }
-
-            // Cache project credentials
-            this.credentialCache = {
-                credentials: awsCredentials,
-                expiresAt: expiresAt,
-            }
-
-            this.logger.debug(
-                'SMUS Project: Successfully cached project credentials for project %s, expires in %s minutes',
-                this.projectId,
-                Math.round((expiresAt.getTime() - Date.now()) / 60000)
-            )
-
-            // Write project credentials to mapping file to be used by Sagemaker local server for remote connections
-            await this.writeCredentialsToMapping(awsCredentials)
-
-            return awsCredentials
-        } catch (err) {
-            this.logger.error('Failed to get project credentials for project %s: %s', this.projectId, err)
-
-            // Handle InvalidGrantException specially - indicates need for reauthentication
-            if (err instanceof Error && err.name === 'InvalidGrantException') {
-                // Invalidate cache when authentication fails
-                this.invalidate()
-                throw new ToolkitError(
-                    `Failed to get project credentials for project ${this.projectId}: ${err.message}. Reauthentication required.`,
-                    {
-                        code: 'InvalidRefreshToken',
-                        cause: err,
-                    }
-                )
-            }
-
-            throw new ToolkitError(`Failed to get project credentials for project ${this.projectId}: ${err}`, {
-                code: 'ProjectCredentialsFetchFailed',
-                cause: err instanceof Error ? err : undefined,
-            })
-        }
-    }
-
-    /**
-     * Writes project credentials to mapping file for local server usage
-     */
-    private async writeCredentialsToMapping(awsCredentials: AWS.Credentials): Promise {
-        try {
-            const mapping = await loadMappings()
-            mapping.smusProjects ??= {}
-            mapping.smusProjects[this.projectId] = {
-                accessKey: awsCredentials.accessKeyId,
-                secret: awsCredentials.secretAccessKey,
-                token: awsCredentials.sessionToken || '',
-            }
-            await saveMappings(mapping)
-        } catch (err) {
-            this.logger.warn('Failed to write project credentials to mapping file: %s', err)
-        }
-    }
-
-    /**
-     * Starts proactive credential refresh for SSH connections
-     *
-     * Uses an expiry-based approach with safety buffer:
-     * - Checks every 10 seconds using setTimeout
-     * - Refreshes when credentials expire within 5 minutes (safety buffer)
-     * - Falls back to 10-minute time-based refresh if no expiry information available
-     * - Handles sleep/resume because it uses wall-clock time for expiry checks
-     *
-     * This means credentials are refreshed just before they expire, reducing
-     * unnecessary API calls while ensuring credentials remain valid.
-     */
-    public startProactiveCredentialRefresh(): void {
-        if (this.sshRefreshActive) {
-            this.logger.debug(`SSH refresh already active for project ${this.projectId}`)
-            return
-        }
-
-        this.logger.info(`Starting SSH credential refresh for project ${this.projectId}`)
-        this.sshRefreshActive = true
-        this.lastRefreshTime = new Date() // Initialize refresh time
-
-        // Start the check timer (checks every 10 seconds, refreshes every 10 minutes based on actual time)
-        this.scheduleNextCheck()
-    }
-
-    /**
-     * Stops proactive credential refresh
-     * Called when SSH connection ends or SMUS disconnects
-     */
-    public stopProactiveCredentialRefresh(): void {
-        if (!this.sshRefreshActive) {
-            return
-        }
-
-        this.logger.info(`Stopping SSH credential refresh for project ${this.projectId}`)
-        this.sshRefreshActive = false
-        this.lastRefreshTime = undefined
-
-        // Clean up timer
-        if (this.refreshTimer) {
-            clearTimeout(this.refreshTimer)
-            this.refreshTimer = undefined
-        }
-    }
-
-    /**
-     * Schedules the next credential check (every 10 seconds)
-     * Refreshes credentials when they expire within 5 minutes (safety buffer)
-     * Falls back to 10-minute time-based refresh if no expiry information available
-     * This handles sleep/resume scenarios correctly
-     */
-    private scheduleNextCheck(): void {
-        if (!this.sshRefreshActive) {
-            return
-        }
-        // Check every 10 seconds, but only refresh every 10 minutes based on actual time elapsed
-        this.refreshTimer = setTimeout(async () => {
-            try {
-                const now = new Date()
-                // Check if we need to refresh based on actual time elapsed
-                if (this.shouldPerformRefresh(now)) {
-                    await this.refresh()
-                }
-                // Schedule next check if still active
-                if (this.sshRefreshActive) {
-                    this.scheduleNextCheck()
-                }
-            } catch (error) {
-                this.logger.error(
-                    `SMUS Project: Failed to refresh credentials for project ${this.projectId}: %O`,
-                    error
-                )
-                // Continue trying even if refresh fails. Dispose will handle stopping the refresh.
-                if (this.sshRefreshActive) {
-                    this.scheduleNextCheck()
-                }
-            }
-        }, this.checkInterval)
-    }
-
-    /**
-     * Determines if a credential refresh should be performed based on credential expiration
-     * This handles sleep/resume scenarios properly and is more efficient than time-based refresh
-     */
-    private shouldPerformRefresh(now: Date): boolean {
-        if (!this.lastRefreshTime || !this.credentialCache) {
-            // First refresh or no cached credentials
-            this.logger.debug(`First refresh - no previous credentials for ${this.projectId}`)
-            return true
-        }
-
-        // Check if credentials expire soon (with 5-minute safety buffer)
-        const safetyBufferMs = 5 * 60 * 1000 // 5 minutes before expiry
-        const expiryTime = this.credentialCache.credentials.expiration?.getTime()
-
-        if (!expiryTime) {
-            // No expiry info - fall back to time-based refresh as safety net
-            const timeSinceLastRefresh = now.getTime() - this.lastRefreshTime.getTime()
-            const shouldRefresh = timeSinceLastRefresh >= this.refreshInterval
-            return shouldRefresh
-        }
-
-        const timeUntilExpiry = expiryTime - now.getTime()
-        const shouldRefresh = timeUntilExpiry < safetyBufferMs
-        return shouldRefresh
-    }
-
-    /**
-     * Performs credential refresh by invalidating cache and fetching fresh credentials
-     */
-    private async refresh(): Promise {
-        const now = new Date()
-        const expiryTime = this.credentialCache?.credentials.expiration?.getTime()
-
-        if (expiryTime) {
-            const minutesUntilExpiry = Math.round((expiryTime - now.getTime()) / 60000)
-            this.logger.debug(
-                `SMUS Project: Refreshing credentials for project ${this.projectId} - expires in ${minutesUntilExpiry} minutes`
-            )
-        } else {
-            const minutesSinceLastRefresh = this.lastRefreshTime
-                ? Math.round((now.getTime() - this.lastRefreshTime.getTime()) / 60000)
-                : 0
-            this.logger.debug(
-                `SMUS Project: Refreshing credentials for project ${this.projectId} - time-based refresh after ${minutesSinceLastRefresh} minutes`
-            )
-        }
-
-        await this.getCredentials()
-        this.lastRefreshTime = new Date()
-    }
-
-    /**
-     * Invalidates cached project credentials
-     * Clears the internal cache without fetching new credentials
-     */
-    public invalidate(): void {
-        this.logger.debug(`Invalidating cached credentials for project ${this.projectId}`)
-        // Clear cache to force fresh fetch on next getCredentials() call
-        this.credentialCache = undefined
-        this.logger.debug(
-            `SMUS Project: Successfully invalidated project credentials cache for project ${this.projectId}`
-        )
-    }
-
-    /**
-     * Disposes of the provider and cleans up resources
-     */
-    public dispose(): void {
-        this.stopProactiveCredentialRefresh()
-        this.invalidate()
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider.ts b/packages/core/src/sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider.ts
deleted file mode 100644
index 11b312faf9..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider.ts
+++ /dev/null
@@ -1,1542 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { AwsCredentialIdentity } from '@aws-sdk/types'
-import { Auth } from '../../../auth/auth'
-import { getSecondaryAuth } from '../../../auth/secondaryAuth'
-import { ToolkitError } from '../../../shared/errors'
-import { withTelemetryContext } from '../../../shared/telemetry/util'
-import { SsoConnection } from '../../../auth/connection'
-import { showReauthenticateMessage } from '../../../shared/utilities/messages'
-import * as localizedText from '../../../shared/localizedText'
-import { ToolkitPromptSettings } from '../../../shared/settings'
-import { setContext, getContext } from '../../../shared/vscode/setContext'
-import { getLogger } from '../../../shared/logger/logger'
-import {
-    SmusUtils,
-    SmusErrorCodes,
-    extractAccountIdFromResourceMetadata,
-    convertToToolkitCredentialProvider,
-    isIamDomain,
-} from '../../shared/smusUtils'
-import {
-    createSmusProfile,
-    isValidSmusConnection,
-    SmusConnection,
-    SmusIamConnection,
-    isSmusSsoConnection,
-    isSmusIamConnection,
-} from '../model'
-import { IamCredentialExpiryAction, showIamCredentialExpiryOptions } from '../credentialExpiryHandler'
-
-import { DomainExecRoleCredentialsProvider } from './domainExecRoleCredentialsProvider'
-import { ProjectRoleCredentialsProvider } from './projectRoleCredentialsProvider'
-import { ConnectionCredentialsProvider } from './connectionCredentialsProvider'
-import { ConnectionClientStore } from '../../shared/client/connectionClientStore'
-import { getResourceMetadata } from '../../shared/utils/resourceMetadataUtils'
-import { CredentialsProviderManager } from '../../../auth/providers/credentialsProviderManager'
-import { SharedCredentialsProvider } from '../../../auth/providers/sharedCredentialsProvider'
-import { CredentialsId, CredentialsProvider } from '../../../auth/providers/credentials'
-import globals from '../../../shared/extensionGlobals'
-import { fromContainerMetadata, fromIni, fromNodeProviderChain } from '@aws-sdk/credential-providers'
-import { randomUUID } from '../../../shared/crypto'
-import { DefaultStsClient } from '../../../shared/clients/stsClient'
-import { DataZoneCustomClientHelper } from '../../shared/client/datazoneCustomClientHelper'
-import { createDZClientBaseOnDomainMode } from '../../explorer/nodes/utils'
-import { DataZoneClient } from '../../shared/client/datazoneClient'
-import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader'
-import { loadSharedCredentialsProfiles } from '../../../auth/credentials/sharedCredentials'
-
-/**
- * Sets the context variable for SageMaker Unified Studio connection state
- * @param isConnected Whether SMUS is connected
- */
-export function setSmusConnectedContext(isConnected: boolean): Promise {
-    return setContext('aws.smus.connected', isConnected)
-}
-
-/**
- * Sets the context variable for SMUS space environment state
- * @param inSmusSpace Whether we're in SMUS space environment
- */
-export function setSmusSpaceEnvironmentContext(inSmusSpace: boolean): Promise {
-    return setContext('aws.smus.inSmusSpaceEnvironment', inSmusSpace)
-}
-
-/**
- * Sets the context variable for SMUS IAM mode state
- * @param isIamMode Whether the current domain is in IAM mode
- */
-export function setSmusIamModeContext(isIamMode: boolean): Promise {
-    return setContext('aws.smus.isIamMode', isIamMode)
-}
-const authClassName = 'SmusAuthenticationProvider'
-
-/**
- * Authentication provider for SageMaker Unified Studio
- * Manages authentication state and credentials for SMUS
- */
-export class SmusAuthenticationProvider {
-    private readonly logger = getLogger('smus')
-    public readonly onDidChangeActiveConnection: vscode.Event
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChange = this.onDidChangeEmitter.event
-    private credentialsProviderCache = new Map()
-    private projectCredentialProvidersCache = new Map()
-    private connectionCredentialProvidersCache = new Map()
-    private cachedDomainAccountId: string | undefined
-    private cachedProjectAccountIds = new Map()
-    private iamCallerIdentityCache: { arn: string; connectionId: string } | undefined
-
-    public readonly secondaryAuth: ReturnType
-
-    public constructor(public readonly auth = Auth.instance) {
-        // Create secondaryAuth after the class is constructed so we can reference instance methods
-        this.secondaryAuth = getSecondaryAuth(
-            auth,
-            'smus',
-            'SageMaker Unified Studio',
-            (conn): conn is SmusConnection => {
-                // Use auth's state directly since secondaryAuth isn't available yet during initialization
-                const state = auth.getStateMemento()
-                const smusConnections = state.get('smus.connections') as any
-                const savedConnectionId = state.get('smus.savedConnectionId') as string
-
-                // Only accept IAM connections that are currently saved for SMUS
-                if (conn && conn.type === 'iam') {
-                    // Must be the exact connection that SMUS has saved AND have metadata
-                    return (
-                        conn.id === savedConnectionId &&
-                        smusConnections &&
-                        smusConnections[conn.id] &&
-                        isValidSmusConnection(conn, smusConnections[conn.id])
-                    )
-                }
-
-                // SSO connections: Check if they have SMUS scope (always SMUS-specific)
-                if (conn && conn.type === 'sso') {
-                    return isValidSmusConnection(conn) // Checks for SMUS scope
-                }
-
-                // Reject everything else
-                return false
-            }
-        )
-
-        // Initialize the event property
-        this.onDidChangeActiveConnection = this.secondaryAuth.onDidChangeActiveConnection as vscode.Event<
-            SmusConnection | undefined
-        >
-
-        // Set up event listeners
-        this.secondaryAuth.onDidChangeActiveConnection(async () => {
-            // Stop SSH credential refresh for all projects when connection changes
-            this.stopAllSshCredentialRefresh()
-
-            // Invalidate any cached credentials for the previous connection
-            await this.invalidateAllCredentialsInCache()
-            // Clear credentials provider cache when connection changes
-            this.credentialsProviderCache.clear()
-            // Clear project provider cache when connection changes
-            this.projectCredentialProvidersCache.clear()
-            // Clear connection provider cache when connection changes
-            this.connectionCredentialProvidersCache.clear()
-            // Clear cached domain account ID when connection changes
-            this.cachedDomainAccountId = undefined
-            // Clear cached project account IDs when connection changes
-            this.cachedProjectAccountIds.clear()
-            // Clear cached IAM caller identity when connection changes
-            this.clearIamCallerIdentityCache()
-            // Clear all clients in client store when connection changes
-            ConnectionClientStore.getInstance().clearAll()
-            await setSmusConnectedContext(this.isConnected())
-            await setSmusSpaceEnvironmentContext(SmusUtils.isInSmusSpaceEnvironment())
-
-            // Set IAM mode context based on connection metadata
-            const activeConn = this.activeConnection
-            if (activeConn && 'type' in activeConn && activeConn.type === 'iam') {
-                const smusConnections = (this.secondaryAuth.state.get('smus.connections') as any) || {}
-                const connectionMetadata = smusConnections[activeConn.id]
-                const isIamDomain = connectionMetadata?.isIamDomain || false
-                await setSmusIamModeContext(isIamDomain)
-            } else {
-                // Clear IAM mode context for non-IAM connections or no connection
-                await setSmusIamModeContext(false)
-            }
-            // Update IAM mode context in SMUS space environment
-            if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-                await this.initIamModeContextInSpaceEnvironment()
-            }
-
-            this.onDidChangeEmitter.fire()
-        })
-
-        // Set initial context in case event does not trigger
-        void setSmusConnectedContext(this.isConnectionValid())
-        void setSmusSpaceEnvironmentContext(SmusUtils.isInSmusSpaceEnvironment())
-
-        // Set initial IAM mode context
-        void (async () => {
-            // Update IAM mode context in SMUS space environment
-            if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-                await this.initIamModeContextInSpaceEnvironment()
-            } else {
-                const activeConn = this.activeConnection
-                if (activeConn && 'type' in activeConn && activeConn.type === 'iam') {
-                    const state = this.auth.getStateMemento()
-                    const smusConnections = (state.get('smus.connections') as any) || {}
-                    const connectionMetadata = smusConnections[activeConn.id]
-                    const isIamDomain = connectionMetadata?.isIamDomain || false
-                    await setSmusIamModeContext(isIamDomain)
-                } else {
-                    await setSmusIamModeContext(false)
-                }
-            }
-        })()
-    }
-
-    /**
-     * Initializes IAM mode context in SMUS space environment
-     */
-    private async initIamModeContextInSpaceEnvironment(): Promise {
-        try {
-            const resourceMetadata = getResourceMetadata()
-            if (
-                resourceMetadata?.AdditionalMetadata?.DataZoneDomainId &&
-                resourceMetadata?.AdditionalMetadata?.DataZoneDomainRegion
-            ) {
-                const domainId = resourceMetadata.AdditionalMetadata.DataZoneDomainId
-                const region = resourceMetadata.AdditionalMetadata.DataZoneDomainRegion
-
-                const credentialsProvider = (await this.getDerCredentialsProvider()) as CredentialsProvider
-
-                // Get DataZoneCustomClientHelper instance and fetch domain details to check if it's IAM mode
-                const datazoneCustomClientHelper = DataZoneCustomClientHelper.getInstance(credentialsProvider, region)
-                const domain = await datazoneCustomClientHelper.getDomain(domainId)
-                const isIamMode = isIamDomain({
-                    domainVersion: domain.domainVersion,
-                    iamSignIns: domain.iamSignIns,
-                    domainId: domainId,
-                })
-                this.logger.debug(`Domain ${domainId} is in IAM mode: ${isIamMode}`)
-                await setSmusIamModeContext(isIamMode)
-            }
-        } catch (error) {
-            this.logger.error('Failed to check IAM mode in SMUS space environment:  %s', error)
-            await setSmusIamModeContext(false)
-        }
-    }
-
-    /**
-     * Stops SSH credential refresh for all projects
-     * Called when SMUS connection changes or extension deactivates
-     */
-    public stopAllSshCredentialRefresh(): void {
-        this.logger.debug('Stopping SSH credential refresh for all projects')
-        for (const provider of this.projectCredentialProvidersCache.values()) {
-            provider.stopProactiveCredentialRefresh()
-        }
-    }
-
-    /**
-     * Gets the active connection
-     */
-    public get activeConnection(): SmusConnection | undefined {
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            const resourceMetadata = getResourceMetadata()!
-            if (resourceMetadata.AdditionalMetadata!.DataZoneDomainRegion) {
-                // Return a mock connection object for SMUS space environment
-                // Include type property based on IAM mode context for telemetry
-                // Note: type will be undefined initially until mode is detected
-                const isIamMode = getContext('aws.smus.isIamMode')
-                return {
-                    domainId: resourceMetadata.AdditionalMetadata!.DataZoneDomainId!,
-                    ssoRegion: resourceMetadata.AdditionalMetadata!.DataZoneDomainRegion!,
-                    domainUrl: `https://${resourceMetadata.AdditionalMetadata!.DataZoneDomainId!}.sagemaker.${resourceMetadata.AdditionalMetadata!.DataZoneDomainRegion!}.on.aws/`,
-                    id: randomUUID(),
-                    type: isIamMode !== undefined ? (isIamMode ? 'iam' : 'sso') : undefined,
-                } as any as SmusConnection
-            } else {
-                throw new ToolkitError('Domain region not found in metadata file.')
-            }
-        }
-        const baseConnection = this.secondaryAuth.activeConnection
-
-        // If we have a connection, wrap it with SMUS metadata if available
-        if (baseConnection) {
-            const smusConnections = this.secondaryAuth.state.get('smus.connections') as any
-            const connectionMetadata = smusConnections?.[baseConnection.id]
-
-            if (connectionMetadata) {
-                // For IAM connections, add the profile-specific metadata
-                if (baseConnection.type === 'iam') {
-                    return {
-                        ...baseConnection,
-                        profileName: connectionMetadata.profileName,
-                        region: connectionMetadata.region,
-                        domainUrl: connectionMetadata.domainUrl,
-                        domainId: connectionMetadata.domainId,
-                    } as SmusIamConnection
-                }
-                // For SSO connections, the metadata is already in the connection object
-                // but we can ensure consistency by adding any missing properties
-                else if (baseConnection.type === 'sso') {
-                    return {
-                        ...baseConnection,
-                        domainUrl: connectionMetadata.domainUrl || (baseConnection as any).domainUrl,
-                        domainId: connectionMetadata.domainId || (baseConnection as any).domainId,
-                    } as SmusConnection
-                }
-            }
-        }
-
-        return baseConnection as SmusConnection | undefined
-    }
-
-    /**
-     * Checks if using a saved connection
-     */
-    public get isUsingSavedConnection() {
-        return this.secondaryAuth.hasSavedConnection
-    }
-
-    /**
-     * Checks if the connection is valid
-     */
-    public isConnectionValid(): boolean {
-        // When in SMUS space, the extension is already running in projet context and sign in is not needed
-        // Set isConnectionValid to always true
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            return true
-        }
-        return this.activeConnection !== undefined && !this.secondaryAuth.isConnectionExpired
-    }
-
-    /**
-     * Checks if connected to SMUS
-     */
-    public isConnected(): boolean {
-        // When in SMUS space, the extension is already running in projet context and sign in is not needed
-        // Set isConnected to always true
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            return true
-        }
-        return this.activeConnection !== undefined
-    }
-
-    /**
-     * Restores the previous connection
-     * Validates domain metadata against profile and updates if needed before using saved connection
-     */
-    public async restore() {
-        const logger = getLogger('smus')
-
-        // Get the saved connection ID before restoring
-        const savedConnectionId = this.secondaryAuth.state.get('smus.savedConnectionId') as string
-        if (!savedConnectionId) {
-            logger.debug('No saved connection ID found, proceeding with normal restore')
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-
-        // Get the saved connection metadata
-        const smusConnections = (this.secondaryAuth.state.get('smus.connections') as any) || {}
-        const connectionMetadata = smusConnections[savedConnectionId]
-
-        // If no connection metadata exists, proceed with normal restore
-        if (!connectionMetadata) {
-            logger.debug('No connection metadata found, proceeding with normal restore')
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-
-        const savedProfileName = connectionMetadata.profileName
-
-        // If no profile name in metadata, proceed with normal restore
-        if (!savedProfileName) {
-            logger.debug('No profile name in metadata, proceeding with normal restore')
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-
-        const profiles = await loadSharedCredentialsProfiles()
-        const profile = profiles[savedProfileName]
-        if (!profile) {
-            logger.debug(`No profile found with name: ${savedProfileName}`)
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-        const region = profile.region || 'not-set'
-
-        const validation = await this.validateIamProfile(savedProfileName)
-        if (!validation.isValid) {
-            logger.debug(`Profile validation failed: ${validation.error}, proceeding with normal restore`)
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-
-        let domainUrl
-        try {
-            logger.debug(`Finding IAM-based domain in region using profile ${savedProfileName}`)
-
-            // Get DataZoneCustomClientHelper instance
-            const datazoneCustomClientHelper = DataZoneCustomClientHelper.getInstance(
-                await this.getCredentialsProviderForIamProfile(savedProfileName),
-                region
-            )
-
-            // Find the IAM-based domain using the client
-            const iamDomain = await datazoneCustomClientHelper.getIamDomain()
-
-            if (!iamDomain) {
-                logger.warn(`No IAM-based domain found in region ${region}, proceeding with normal restore`)
-                await this.secondaryAuth.restoreConnection()
-                return
-            }
-
-            logger.debug(`Found IAM-based domain: ${iamDomain.name} (${iamDomain.id})`)
-
-            // Construct domain URL from the IAM-based domain
-            domainUrl = iamDomain.portalUrl || `https://${iamDomain.id}.sagemaker.${region}.on.aws/`
-            logger.debug(`Discovered IAM-based domain URL: ${domainUrl}`)
-        } catch (error) {
-            logger.error(`Failed to find IAM-based domain: ${error} , proceeding with normal restore`)
-            await this.secondaryAuth.restoreConnection()
-            return
-        }
-
-        try {
-            logger.debug(`Validating domain metadata for saved connection ${savedConnectionId}`)
-
-            if (!domainUrl) {
-                logger.info('No domain URL constructed, proceeding with normal restore')
-                await this.secondaryAuth.restoreConnection()
-                return
-            }
-
-            const { domainId } = SmusUtils.extractDomainInfoFromUrl(domainUrl)
-
-            // Compare with saved metadata
-            const savedDomainId = connectionMetadata.domainId
-            const savedRegion = connectionMetadata.region
-
-            if (domainId === savedDomainId && region === savedRegion) {
-                logger.debug('Domain metadata matches, proceeding with normal restore')
-            } else {
-                logger.debug(
-                    `SMUS: Domain metadata mismatch detected. Saved: ${savedDomainId}@${savedRegion}, Profile: ${domainId}@${region}. Updating metadata.`
-                )
-
-                // Update the metadata with API values
-                connectionMetadata.domainId = domainId
-                connectionMetadata.region = region
-
-                // Save updated metadata
-                smusConnections[savedConnectionId] = connectionMetadata
-                await this.secondaryAuth.state.update('smus.connections', smusConnections)
-
-                logger.debug('Successfully updated domain metadata')
-            }
-        } catch (error) {
-            logger.warn(`Failed to validate domain metadata: ${error}. Proceeding with normal restore.`)
-        }
-
-        // Proceed with normal restore
-        await this.secondaryAuth.restoreConnection()
-    }
-
-    /**
-     * Signs out from SMUS with different behavior based on connection type:
-     * - SSO connections: Deletes the connection (old behavior)
-     * - IAM connections: Forgets the connection without affecting the underlying IAM profile
-     */
-    @withTelemetryContext({ name: 'signOut', class: authClassName })
-    public async signOut() {
-        const logger = getLogger('smus')
-
-        const activeConnection = this.activeConnection
-        if (!activeConnection) {
-            logger.debug('No active connection to sign out from')
-            return
-        }
-
-        const connectionId = activeConnection.id
-        logger.info(`Signing out from connection ${connectionId}`)
-
-        try {
-            // Clear SMUS-specific metadata from connections registry
-            const smusConnections = (this.secondaryAuth.state.get('smus.connections') as any) || {}
-            if (smusConnections[connectionId]) {
-                delete smusConnections[connectionId]
-                await this.secondaryAuth.state.update('smus.connections', smusConnections)
-            }
-
-            // Handle sign-out based on connection type
-            // Check if this is a real connection (has 'type' property) vs mock connection in SMUS space
-            if ('type' in activeConnection && isSmusSsoConnection(activeConnection)) {
-                // For SSO connections, delete the connection (old behavior)
-                await this.secondaryAuth.deleteConnection()
-                logger.info(`Deleted SSO connection ${connectionId}`)
-            } else if ('type' in activeConnection) {
-                // For IAM connections, forget the connection without affecting the underlying IAM profile
-                await this.secondaryAuth.forgetConnection()
-                logger.info(`Forgot IAM connection ${connectionId} (preserved for other services)`)
-
-                // Clear IAM mode context for IAM connections
-                await setSmusIamModeContext(false)
-                logger.debug('Cleared IAM mode context')
-            } else {
-                // Mock connection in SMUS space environment - no action needed
-                logger.info(`Sign out completed for mock connection ${connectionId}`)
-            }
-
-            logger.info(`Successfully signed out from connection ${connectionId}`)
-        } catch (error) {
-            logger.error(`Failed to sign out from connection ${connectionId}:`, error)
-            throw new ToolkitError('Failed to sign out from SageMaker Unified Studio', {
-                code: SmusErrorCodes.SignOutFailed,
-                cause: error instanceof Error ? error : undefined,
-            })
-        }
-    }
-
-    /**
-     * Authenticates with SageMaker Unified Studio using SSO and a domain URL
-     * @param domainUrl The SageMaker Unified Studio domain URL
-     * @returns Promise resolving to the SSO connection
-     */
-    @withTelemetryContext({ name: 'connectToSmusWithSso', class: authClassName })
-    public async connectToSmusWithSso(domainUrl: string): Promise {
-        const logger = getLogger('smus')
-
-        try {
-            // Extract domain info using SmusUtils
-            const { domainId, region } = SmusUtils.extractDomainInfoFromUrl(domainUrl)
-
-            // Validate domain ID
-            if (!domainId) {
-                throw new ToolkitError('Invalid domain URL format', { code: SmusErrorCodes.InvalidDomainUrl })
-            }
-
-            logger.info(`Connecting to domain ${domainId} in region ${region}`)
-
-            // Check if we already have a connection for this domain
-            const existingConn = (await this.auth.listConnections()).find(
-                (c): c is SmusConnection =>
-                    isValidSmusConnection(c) && (c as any).domainUrl?.toLowerCase() === domainUrl.toLowerCase()
-            )
-
-            if (existingConn) {
-                const connectionState = this.auth.getConnectionState(existingConn)
-                logger.info(`Found existing connection ${existingConn.id} with state: ${connectionState}`)
-
-                // If connection is valid, use it directly without triggering new auth flow
-                if (connectionState === 'valid') {
-                    logger.info('Using existing valid connection')
-
-                    // Only SSO connections can be used with connectToSmusWithSso
-                    if (isSmusSsoConnection(existingConn)) {
-                        // Use the existing SSO connection
-                        const result = await this.secondaryAuth.useNewConnection(existingConn)
-
-                        // Auto-invoke project selection after successful sign-in (but not in SMUS space environment)
-                        if (!SmusUtils.isInSmusSpaceEnvironment()) {
-                            void vscode.commands.executeCommand('aws.smus.switchProject')
-                        }
-
-                        return result as SmusConnection
-                    }
-                }
-
-                // If connection is invalid or expired, handle based on connection type
-                if (connectionState === 'invalid') {
-                    // Only SSO connections can be reauthenticated
-                    if (isSmusSsoConnection(existingConn)) {
-                        logger.info('Existing SSO connection is invalid, reauthenticating')
-                        const reauthenticatedConn = await this.reauthenticate(existingConn)
-
-                        // Create the SMUS connection wrapper
-                        const smusConn: SmusConnection = {
-                            ...reauthenticatedConn,
-                            domainUrl,
-                            domainId,
-                        }
-
-                        const result = await this.secondaryAuth.useNewConnection(smusConn)
-                        logger.debug(`Reauthenticated connection successfully, id=${result.id}`)
-
-                        // Auto-invoke project selection after successful reauthentication (but not in SMUS space environment)
-                        if (!SmusUtils.isInSmusSpaceEnvironment()) {
-                            void vscode.commands.executeCommand('aws.smus.switchProject')
-                        }
-
-                        return result as SmusConnection
-                    }
-                }
-            }
-
-            // No existing connection found, create a new one
-            logger.info('No existing connection found, creating new connection')
-
-            // Get SSO instance info from DataZone
-            const ssoInstanceInfo = await SmusUtils.getSsoInstanceInfo(domainUrl)
-
-            // Create a new connection with appropriate scope based on domain URL
-            const profile = createSmusProfile(domainUrl, domainId, ssoInstanceInfo.issuerUrl, ssoInstanceInfo.region)
-            const newConn = await this.auth.createConnection(profile)
-            logger.debug(`Created new connection ${newConn.id}`)
-
-            const smusConn: SmusConnection = {
-                ...newConn,
-                domainUrl,
-                domainId,
-            }
-
-            const result = await this.secondaryAuth.useNewConnection(smusConn)
-
-            // Auto-invoke project selection after successful sign-in (but not in SMUS space environment)
-            if (!SmusUtils.isInSmusSpaceEnvironment()) {
-                void vscode.commands.executeCommand('aws.smus.switchProject')
-            }
-
-            return result as SmusConnection
-        } catch (e) {
-            throw ToolkitError.chain(e, 'Failed to connect to SageMaker Unified Studio', {
-                code: SmusErrorCodes.FailedToConnect,
-            })
-        }
-    }
-
-    /**
-     * Authenticates with SageMaker Unified Studio using IAM credential profile
-     * @param profileName The AWS credential profile name
-     * @param region The AWS region
-     * @param domainUrl The SageMaker Unified Studio domain URL
-     * @param isIamDomain Whether the domain is an IAM-based domain
-     * @returns Promise resolving to the IAM connection
-     */
-    @withTelemetryContext({ name: 'connectWithIamProfile', class: authClassName })
-    public async connectWithIamProfile(
-        profileName: string,
-        region: string,
-        domainUrl: string,
-        isIamDomain: boolean = false
-    ): Promise {
-        const logger = getLogger('smus')
-
-        try {
-            // Extract domain info using SmusUtils
-            const { domainId } = SmusUtils.extractDomainInfoFromUrl(domainUrl)
-
-            // Validate domain ID
-            if (!domainId) {
-                throw new ToolkitError('Invalid domain URL format', { code: SmusErrorCodes.InvalidDomainUrl })
-            }
-
-            logger.info(`Connecting with IAM profile ${profileName} to domain ${domainId} in region ${region}`)
-
-            // Note: Credential validation is already done in the orchestrator via validateIamProfile()
-            // No need for redundant validation here
-
-            // Check if we already have a basic IAM connection for this profile
-            const profileId = `profile:${profileName}`
-            const existingConn = await this.auth.getConnection({ id: profileId })
-
-            if (existingConn && existingConn.type === 'iam') {
-                logger.info(`Found existing IAM profile connection ${profileId}`)
-
-                // Store SMUS metadata in the connections registry
-                const smusConnections = (this.secondaryAuth.state.get('smus.connections') as any) || {}
-                smusConnections[existingConn.id] = {
-                    profileName,
-                    region,
-                    domainUrl,
-                    domainId,
-                    isIamDomain,
-                }
-                await this.secondaryAuth.state.update('smus.connections', smusConnections)
-
-                // Use the basic IAM connection with secondaryAuth
-                await this.secondaryAuth.useNewConnection(existingConn)
-
-                // Ensure the connection state is validated
-                await this.auth.refreshConnectionState(existingConn)
-                logger.debug(
-                    `SMUS: Using existing IAM connection as SMUS connection successfully, id=${existingConn.id}`
-                )
-
-                // Set IAM mode context if this is an IAM-based domain
-                if (isIamDomain) {
-                    await setSmusIamModeContext(true)
-                    logger.debug('Set IAM mode context to true')
-                }
-
-                // Return a SMUS IAM connection wrapper for the caller
-                const smusIamConn: SmusIamConnection = {
-                    ...existingConn,
-                    profileName,
-                    region,
-                    domainUrl,
-                    domainId,
-                }
-
-                return smusIamConn
-            }
-
-            // If no existing connection, the auth system should have created one during profile validation
-            // This shouldn't happen if credentials are valid, but let's handle it gracefully
-            throw new ToolkitError(
-                `IAM profile connection not found for '${profileName}'. Please check your AWS credentials configuration.`,
-                {
-                    code: SmusErrorCodes.ConnectionNotFound,
-                }
-            )
-        } catch (e) {
-            throw ToolkitError.chain(e, 'Failed to connect to SageMaker Unified Studio with IAM profile', {
-                code: SmusErrorCodes.FailedToConnect,
-            })
-        }
-    }
-
-    /**
-     * Validates an IAM credential profile using the existing Toolkit validation infrastructure
-     * @param profileName Profile name to validate
-     * @returns Promise resolving to validation result
-     */
-    public async validateIamProfile(profileName: string): Promise<{ isValid: boolean; error?: string }> {
-        const logger = getLogger('smus')
-
-        try {
-            logger.debug(`Validating IAM profile: ${profileName}`)
-
-            // Create credentials ID for the profile using the existing Toolkit pattern
-            const credentialsId: CredentialsId = {
-                credentialSource: SharedCredentialsProvider.getProviderType(),
-                credentialTypeId: profileName,
-            }
-
-            // Get the provider using the existing manager
-            const provider = await CredentialsProviderManager.getInstance().getCredentialsProvider(credentialsId)
-            if (!provider) {
-                return {
-                    isValid: false,
-                    error: `Profile '${profileName}' not found or not available`,
-                }
-            }
-
-            // Get credentials and validate using the existing Toolkit validation logic
-            // This includes proper telemetry and error handling
-            const credentials = await provider.getCredentials()
-            await globals.loginManager.validateCredentials(
-                credentials,
-                provider.getEndpointUrl?.(),
-                provider.getDefaultRegion() // Use the region from the profile, not hardcoded
-            )
-
-            logger.debug(`Profile validation successful: ${profileName}`)
-            return { isValid: true }
-        } catch (error) {
-            logger.error(`Profile validation failed: ${profileName}`, error)
-            return {
-                isValid: false,
-                error: `Invalid profile '${profileName}' - ${(error as Error).message}`,
-            }
-        }
-    }
-
-    /**
-     * Gets credentials for an IAM profile using Toolkit providers
-     * @param profileName AWS profile name
-     * @returns Promise resolving to credentials
-     */
-    public async getCredentialsForIamProfile(profileName: string): Promise {
-        const logger = getLogger('smus')
-
-        try {
-            logger.debug(`Getting credentials for IAM profile: ${profileName}`)
-
-            // Create credentials ID for the profile using the existing Toolkit pattern
-            const credentialsId: CredentialsId = {
-                credentialSource: SharedCredentialsProvider.getProviderType(),
-                credentialTypeId: profileName,
-            }
-
-            // Get the provider using the existing manager
-            const provider = await CredentialsProviderManager.getInstance().getCredentialsProvider(credentialsId)
-            if (!provider) {
-                throw new ToolkitError(`Profile '${profileName}' not found or not available`, {
-                    code: SmusErrorCodes.ProfileNotFound,
-                })
-            }
-
-            // Get credentials using the existing Toolkit provider
-            const credentials = await provider.getCredentials()
-
-            logger.debug(`Successfully retrieved credentials for IAM profile: ${profileName}`)
-            return credentials
-        } catch (error) {
-            logger.error(`Failed to get credentials for IAM profile ${profileName}: %s`, error)
-            throw new ToolkitError(
-                `Failed to get credentials for profile '${profileName}': ${(error as Error).message}`,
-                {
-                    code: SmusErrorCodes.CredentialRetrievalFailed,
-                    cause: error instanceof Error ? error : undefined,
-                }
-            )
-        }
-    }
-
-    /**
-     * Gets the underlying credentials provider for an IAM profile
-     * @param profileName AWS profile name
-     * @returns Promise resolving to the credentials provider
-     */
-    public async getCredentialsProviderForIamProfile(profileName: string): Promise {
-        const logger = getLogger('smus')
-        logger.debug(`Getting credentials provider for IAM profile: ${profileName}`)
-
-        // Create credentials ID for the profile using the existing Toolkit pattern
-        const credentialsId: CredentialsId = {
-            credentialSource: SharedCredentialsProvider.getProviderType(),
-            credentialTypeId: profileName,
-        }
-
-        // Get the provider using the existing manager
-        const provider = await CredentialsProviderManager.getInstance().getCredentialsProvider(credentialsId)
-        if (!provider) {
-            throw new ToolkitError(`Profile '${profileName}' not found or not available`, {
-                code: SmusErrorCodes.ProfileNotFound,
-            })
-        }
-
-        // Return the underlying provider directly
-        // This allows callers to use the provider's full interface including caching and refresh
-        return provider
-    }
-
-    /**
-     * Gets the cached caller identity ARN for the active IAM connection
-     * Fetches from STS if not cached or if connection has changed
-     * Only works for IAM connections - returns undefined for SSO connections
-     * @returns Promise resolving to the ARN, or undefined if not available or not an IAM connection
-     */
-    public async getCachedIamCallerIdentityArn(): Promise {
-        const logger = getLogger('smus')
-        try {
-            const activeConn = this.activeConnection
-            // Only cache for IAM connections
-            if (!activeConn || activeConn.type !== 'iam') {
-                return undefined
-            }
-
-            // Check if we have a cached ARN for this connection
-            if (this.iamCallerIdentityCache && this.iamCallerIdentityCache.connectionId === activeConn.id) {
-                logger.debug('Using cached IAM caller identity ARN')
-                return this.iamCallerIdentityCache.arn
-            }
-
-            // Fetch fresh caller identity
-            logger.debug('Fetching IAM caller identity from STS')
-            const smusConnections = (this.secondaryAuth.state.get('smus.connections') as any) || {}
-            const connectionMetadata = smusConnections[activeConn.id]
-
-            if (!connectionMetadata?.profileName || !connectionMetadata?.region) {
-                logger.debug('Missing profile name or region in connection metadata')
-                return undefined
-            }
-
-            const credentials = await this.getCredentialsForIamProfile(connectionMetadata.profileName)
-            const stsClient = new DefaultStsClient(connectionMetadata.region, credentials)
-            const callerIdentity = await stsClient.getCallerIdentity()
-
-            if (!callerIdentity.Arn) {
-                logger.debug('No ARN found in caller identity')
-                return undefined
-            }
-
-            // Cache the result
-            this.iamCallerIdentityCache = {
-                arn: callerIdentity.Arn,
-                connectionId: activeConn.id,
-            }
-            logger.debug(`Cached IAM caller identity ARN for connection ${activeConn.id}`)
-
-            return callerIdentity.Arn
-        } catch (error) {
-            logger.warn(`Failed to get IAM caller identity: %s`, error)
-            return undefined
-        }
-    }
-
-    /**
-     * Gets the session name from the cached IAM caller identity
-     * Only works for IAM connections - returns undefined for SSO connections
-     * @returns Promise resolving to the session name, or undefined if not available or not an IAM connection
-     */
-    public async getSessionName(): Promise {
-        const arn = await this.getCachedIamCallerIdentityArn()
-        if (!arn) {
-            return undefined
-        }
-
-        const sessionName = SmusUtils.extractSessionNameFromArn(arn)
-        this.logger.debug(`Extracted session name: ${sessionName || 'none'}`)
-        return sessionName
-    }
-
-    /**
-     * Gets the role ARN from the cached IAM caller identity
-     * Converts assumed role ARN to IAM role ARN format
-     * Only works for IAM connections - returns undefined for SSO connections
-     * @returns Promise resolving to the IAM role ARN, or undefined if not available or not an IAM connection
-     */
-    public async getIamPrincipalArn(): Promise {
-        const arn = await this.getCachedIamCallerIdentityArn()
-        if (!arn) {
-            return undefined
-        }
-
-        // Convert assumed role ARN to IAM role ARN
-        const roleArn = SmusUtils.convertAssumedRoleArnToIamRoleArn(arn)
-        this.logger.debug(`Extracted role ARN: ${roleArn || 'none'}`)
-        return roleArn
-    }
-
-    /**
-     * Clears the cached IAM caller identity
-     * Should be called when connection changes or credentials are refreshed
-     */
-    private clearIamCallerIdentityCache(): void {
-        this.iamCallerIdentityCache = undefined
-        this.logger.debug('Cleared IAM caller identity cache')
-    }
-
-    /**
-     * Reauthenticates an existing connection
-     * @param conn Connection to reauthenticate
-     * @returns Promise resolving to the reauthenticated connection
-     */
-    @withTelemetryContext({ name: 'reauthenticate', class: authClassName })
-    public async reauthenticate(conn: SmusConnection): Promise {
-        try {
-            // Check if this is an IAM connection
-            if (isSmusIamConnection(conn)) {
-                // For IAM connections, show options menu
-                this.logger.debug('Showing IAM credential expiry options for reauthentication')
-                const result = await showIamCredentialExpiryOptions(this, conn, globals.context)
-
-                // Handle the result - for most actions, return the original connection
-                // The actions have already been performed (sign out, edit credentials, etc.)
-                if (result.action === IamCredentialExpiryAction.SignOut) {
-                    throw new ToolkitError('User signed out from connection', { cancelled: true })
-                } else if (result.action === IamCredentialExpiryAction.Cancelled) {
-                    throw new ToolkitError('Reauthentication cancelled by user', { cancelled: true })
-                }
-
-                // For Reauthenticate, EditCredentials, and SwitchProfile, return the connection
-                return conn
-            } else {
-                // For SSO connections, use existing re-auth flow
-                const reauthenticatedConn = await this.auth.reauthenticate(conn)
-
-                // Re-add SMUS-specific properties that aren't preserved by the base auth system
-                return {
-                    ...reauthenticatedConn,
-                    domainUrl: conn.domainUrl,
-                    domainId: conn.domainId,
-                } as SmusConnection
-            }
-        } catch (err) {
-            throw ToolkitError.chain(err, 'Unable to reauthenticate SageMaker Unified Studio connection.')
-        }
-    }
-
-    /**
-     * Shows a reauthentication prompt to the user
-     * @param conn Connection to reauthenticate
-     */
-    public async showReauthenticationPrompt(conn: SmusConnection): Promise {
-        await showReauthenticateMessage({
-            message: localizedText.connectionExpired('SageMaker Unified Studio'),
-            connect: localizedText.reauthenticate,
-            suppressId: 'smusConnectionExpired',
-            settings: ToolkitPromptSettings.instance,
-            source: 'SageMaker Unified Studio',
-            reauthFunc: async () => {
-                await this.reauthenticate(conn)
-            },
-        })
-    }
-
-    /**
-     * Gets the current SSO access token for the active connection
-     * @returns Promise resolving to the access token string
-     * @throws ToolkitError if unable to retrieve access token
-     */
-    public async getAccessToken(): Promise {
-        const logger = getLogger('smus')
-
-        const connection = this.activeConnection
-        if (!connection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // Only SSO connections have access tokens
-        if (!isSmusSsoConnection(connection)) {
-            throw new ToolkitError('Access tokens are only available for SSO connections', {
-                code: SmusErrorCodes.InvalidConnectionType,
-            })
-        }
-
-        try {
-            // Type assertion is safe here because we've already checked with isSmusSsoConnection
-            const accessToken = await this.auth.getSsoAccessToken(connection as SsoConnection)
-            logger.debug(`Successfully retrieved SSO access token for connection ${connection.id}`)
-
-            return accessToken
-        } catch (err) {
-            logger.error(`Failed to retrieve SSO access token for connection ${connection.id}: %s`, err)
-
-            // Check if this is a reauth error that should be handled by showing SMUS-specific prompt
-            if (err instanceof ToolkitError && err.code === 'InvalidConnection') {
-                // Re-throw the error to maintain the error flow
-                logger.debug(
-                    `SMUS: Auth connection has been marked invalid - Likely due to expiry. Reauthentication flow will be triggered, ignoring error`
-                )
-            }
-
-            throw new ToolkitError(`Failed to retrieve SSO access token for connection ${connection.id}`, {
-                code: SmusErrorCodes.RedeemAccessTokenFailed,
-                cause: err instanceof Error ? err : undefined,
-            })
-        }
-    }
-
-    /**
-     * Gets or creates a project credentials provider for the specified project
-     * @param projectId The project ID to get credentials for
-     * @returns Promise resolving to the project credentials provider
-     */
-    public async getProjectCredentialProvider(projectId: string): Promise {
-        const logger = getLogger('smus')
-
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        logger.debug(`Getting project provider for project ${projectId}`)
-
-        // Check if we already have a cached provider for this project
-        if (this.projectCredentialProvidersCache.has(projectId)) {
-            logger.debug('Using cached project provider')
-            return this.projectCredentialProvidersCache.get(projectId)!
-        }
-
-        logger.debug('Creating new project provider')
-        // Create a new project provider and cache it
-        const projectProvider = new ProjectRoleCredentialsProvider(this, projectId)
-        this.projectCredentialProvidersCache.set(projectId, projectProvider)
-
-        logger.debug('Cached new project provider')
-
-        return projectProvider
-    }
-
-    /**
-     * Gets or creates a connection credentials provider for the specified connection
-     * @param connectionId The connection ID to get credentials for
-     * @param projectId The project ID that owns the connection
-     * @param region The region for the connection
-     * @returns Promise resolving to the connection credentials provider
-     */
-    public async getConnectionCredentialsProvider(
-        connectionId: string,
-        projectId: string,
-        region: string
-    ): Promise {
-        const logger = getLogger('smus')
-
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        const cacheKey = `${this.getDomainId()}:${projectId}:${connectionId}`
-        logger.debug(`Getting connection provider for connection ${connectionId}`)
-
-        // Check if we already have a cached provider for this connection
-        if (this.connectionCredentialProvidersCache.has(cacheKey)) {
-            logger.debug('Using cached connection provider')
-            return this.connectionCredentialProvidersCache.get(cacheKey)!
-        }
-
-        logger.debug('Creating new connection provider')
-        // Create a new connection provider and cache it
-        const connectionProvider = new ConnectionCredentialsProvider(this, connectionId, projectId)
-        this.connectionCredentialProvidersCache.set(cacheKey, connectionProvider)
-
-        logger.debug('Cached new connection provider')
-
-        return connectionProvider
-    }
-
-    /**
-     * Gets the domain ID from the active connection
-     * @returns Domain ID
-     */
-    public getDomainId(): string {
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            return getResourceMetadata()!.AdditionalMetadata!.DataZoneDomainId!
-        }
-
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // For SMUS connections (both SSO and IAM) with domainId property
-        if ('domainId' in this.activeConnection) {
-            return (this.activeConnection as any).domainId
-        }
-
-        throw new ToolkitError('Domain ID not available. Please reconnect to SMUS.', {
-            code: SmusErrorCodes.NoActiveConnection,
-        })
-    }
-
-    /**
-     * Gets the domain URL from the active connection
-     * @returns Domain URL
-     */
-    public getDomainUrl(): string {
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // For SMUS connections (both SSO and IAM) with domainUrl property
-        if ('domainUrl' in this.activeConnection) {
-            return (this.activeConnection as any).domainUrl
-        }
-
-        throw new ToolkitError('Domain URL not available. Please reconnect to SMUS.', {
-            code: SmusErrorCodes.NoActiveConnection,
-        })
-    }
-
-    /**
-     * Gets the AWS account ID for the active domain connection
-     * In SMUS space environment, extracts from ResourceArn in metadata
-     * Otherwise, makes an STS GetCallerIdentity call using DER credentials and caches the result
-     * @returns Promise resolving to the domain's AWS account ID
-     * @throws ToolkitError if unable to retrieve account ID
-     */
-    public async getDomainAccountId(): Promise {
-        const logger = getLogger('smus')
-
-        // Return cached value if available
-        if (this.cachedDomainAccountId) {
-            logger.debug('Using cached domain account ID')
-            return this.cachedDomainAccountId
-        }
-
-        // If in SMUS space environment, extract account ID from resource-metadata file
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            const accountId = await extractAccountIdFromResourceMetadata()
-
-            // Cache the account ID
-            this.cachedDomainAccountId = accountId
-            logger.debug(`Successfully cached domain account ID: ${accountId}`)
-
-            return accountId
-        }
-
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // Use existing STS GetCallerIdentity implementation for non-SMUS space environments
-        try {
-            logger.debug('Fetching domain account ID via STS GetCallerIdentity')
-
-            let credentialsProvider
-            if (getContext('aws.smus.isIamMode')) {
-                credentialsProvider = await this.getCredentialsProviderForIamProfile(
-                    (this.activeConnection as SmusIamConnection).profileName
-                )
-            } else {
-                credentialsProvider = await this.getDerCredentialsProvider()
-            }
-            // Get the region for STS client
-            const region = this.getDomainRegion()
-
-            // Create STS client with DER credentials
-            const stsClient = new DefaultStsClient(region, await credentialsProvider.getCredentials())
-
-            // Make GetCallerIdentity call
-            const callerIdentity = await stsClient.getCallerIdentity()
-
-            if (!callerIdentity.Account) {
-                throw new ToolkitError('Account ID not found in STS GetCallerIdentity response', {
-                    code: SmusErrorCodes.AccountIdNotFound,
-                })
-            }
-
-            // Cache the account ID
-            this.cachedDomainAccountId = callerIdentity.Account
-
-            logger.debug(`Successfully retrieved and cached domain account ID: ${callerIdentity.Account}`)
-
-            return callerIdentity.Account
-        } catch (err) {
-            logger.error(`Failed to retrieve domain account ID: %s`, err)
-
-            throw new ToolkitError('Failed to retrieve AWS account ID for active domain connection', {
-                code: SmusErrorCodes.GetDomainAccountIdFailed,
-                cause: err instanceof Error ? err : undefined,
-            })
-        }
-    }
-
-    /**
-     * Gets the AWS account ID for a specific project using project credentials
-     * In SMUS space environment, extracts from ResourceArn in metadata (same as domain account)
-     * Otherwise, makes an STS GetCallerIdentity call using project credentials
-     * @param projectId The DataZone project ID
-     * @returns Promise resolving to the project's AWS account ID
-     */
-    public async getProjectAccountId(projectId: string): Promise {
-        const logger = getLogger('smus')
-
-        // Return cached value if available
-        if (this.cachedProjectAccountIds.has(projectId)) {
-            logger.debug(`Using cached project account ID for project ${projectId}`)
-            return this.cachedProjectAccountIds.get(projectId)!
-        }
-
-        // If in SMUS space environment, extract account ID from resource-metadata file
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            const accountId = await extractAccountIdFromResourceMetadata()
-
-            // Cache the account ID
-            this.cachedProjectAccountIds.set(projectId, accountId)
-            logger.debug(`Successfully cached project account ID for project ${projectId}: ${accountId}`)
-
-            return accountId
-        }
-
-        if (!this.activeConnection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // For non-SMUS space environments, use project credentials with STS
-        try {
-            logger.debug('Fetching project account ID via STS GetCallerIdentity with project credentials')
-
-            // Get project credentials
-            const projectCredProvider = await this.getProjectCredentialProvider(projectId)
-            const projectCreds = await projectCredProvider.getCredentials()
-
-            // Get project region from tooling environment
-            const dzClient = await createDZClientBaseOnDomainMode(this)
-            const toolingEnv = await dzClient.getToolingEnvironment(projectId)
-            const projectRegion = toolingEnv.awsAccountRegion
-
-            if (!projectRegion) {
-                throw new ToolkitError('No AWS account region found in tooling environment', {
-                    code: SmusErrorCodes.RegionNotFound,
-                })
-            }
-
-            // Use STS to get account ID from project credentials
-            const stsClient = new DefaultStsClient(projectRegion, projectCreds)
-            const callerIdentity = await stsClient.getCallerIdentity()
-
-            if (!callerIdentity.Account) {
-                throw new ToolkitError('Account ID not found in STS GetCallerIdentity response', {
-                    code: SmusErrorCodes.AccountIdNotFound,
-                })
-            }
-
-            // Cache the account ID
-            this.cachedProjectAccountIds.set(projectId, callerIdentity.Account)
-            logger.debug(
-                `Successfully retrieved and cached project account ID for project ${projectId}: ${callerIdentity.Account}`
-            )
-
-            return callerIdentity.Account
-        } catch (err) {
-            logger.error('Failed to get project account ID: %s', err as Error)
-            throw new ToolkitError(`Failed to get project account ID: ${(err as Error).message}`, {
-                code: SmusErrorCodes.GetProjectAccountIdFailed,
-            })
-        }
-    }
-
-    public getDomainRegion(): string {
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            const resourceMetadata = getResourceMetadata()!
-            if (resourceMetadata.AdditionalMetadata!.DataZoneDomainRegion) {
-                return resourceMetadata.AdditionalMetadata!.DataZoneDomainRegion
-            } else {
-                throw new ToolkitError('Domain region not found in metadata file.')
-            }
-        }
-
-        const connection = this.activeConnection
-        if (!connection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // Handle different connection types
-        if (isSmusSsoConnection(connection)) {
-            return connection.ssoRegion
-        }
-
-        // For SMUS connections (both SSO and IAM) with region property
-        if ('region' in connection) {
-            return (connection as any).region
-        }
-
-        throw new ToolkitError('Domain region not available. Please reconnect to SMUS.', {
-            code: SmusErrorCodes.NoActiveConnection,
-        })
-    }
-
-    /**
-     * Gets or creates a cached credentials provider for the active connection
-     * @returns Promise resolving to the credentials provider
-     */
-    public async getDerCredentialsProvider(): Promise {
-        const logger = getLogger('smus')
-
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            // When in SMUS space, DomainExecutionRoleCreds can be found in config file
-            // Read the credentials from credential profile DomainExecutionRoleCreds
-            try {
-                // Load AWS config file to check profile configuration
-                const { configFile } = await loadSharedConfigFiles()
-                const profileConfig = configFile['DomainExecutionRoleCreds']
-
-                if (profileConfig?.credential_process) {
-                    // Normal SMUS domain: Use the profile with credential_process
-                    logger.debug('Using DomainExecutionRoleCreds profile with credential_process')
-                    const credentials = fromIni({ profile: 'DomainExecutionRoleCreds' })
-                    return convertToToolkitCredentialProvider(
-                        async () => await credentials(),
-                        'DomainExecutionRoleCreds',
-                        `smus-der-profile:${this.getDomainId()}:${this.getDomainRegion()}`,
-                        this.getDomainRegion()
-                    )
-                } else if (profileConfig?.credential_source === 'EcsContainer') {
-                    // IAM-based domain with EcsContainer: Use ECS container credentials directly
-                    // The environment has AWS_CONTAINER_CREDENTIALS_RELATIVE_URI set, so use fromContainerMetadata
-                    // which properly handles the ECS credential endpoint
-                    logger.debug('IAM-based domain detected, using ECS container credentials')
-                    const credentials = fromContainerMetadata({
-                        timeout: 5000,
-                        maxRetries: 3,
-                    })
-                    return convertToToolkitCredentialProvider(
-                        async () => await credentials(),
-                        'EcsContainer',
-                        `smus-ecs-container:${this.getDomainId()}:${this.getDomainRegion()}`,
-                        this.getDomainRegion()
-                    )
-                } else {
-                    // Fallback: try the profile anyway
-                    logger.debug(
-                        'SMUS: Unknown profile configuration, attempting to use DomainExecutionRoleCreds profile'
-                    )
-                    const credentials = fromIni({ profile: 'DomainExecutionRoleCreds' })
-                    return convertToToolkitCredentialProvider(
-                        async () => await credentials(),
-                        'DomainExecutionRoleCreds-fallback',
-                        `smus-der-fallback:${this.getDomainId()}:${this.getDomainRegion()}`,
-                        this.getDomainRegion()
-                    )
-                }
-            } catch (error) {
-                logger.error('Failed to load config file, falling back to default credential chain: %s', error)
-                const credentials = fromNodeProviderChain()
-                return convertToToolkitCredentialProvider(
-                    async () => await credentials(),
-                    'NodeProviderChain',
-                    `smus-node-provider-chain:${this.getDomainId()}:${this.getDomainRegion()}`,
-                    this.getDomainRegion()
-                )
-            }
-        }
-
-        const connection = this.activeConnection
-        if (!connection) {
-            throw new ToolkitError('No active SMUS connection available', { code: SmusErrorCodes.NoActiveConnection })
-        }
-
-        // Domain Execution Role credentials are only available for SSO connections
-        if (!isSmusSsoConnection(connection)) {
-            throw new ToolkitError('Domain Execution Role credentials are only available for SSO connections', {
-                code: SmusErrorCodes.InvalidConnectionType,
-            })
-        }
-
-        // Create a cache key based on the connection details
-        const cacheKey = `${connection.ssoRegion}:${connection.domainId}`
-
-        logger.debug(`Getting credentials provider for cache key: ${cacheKey}`)
-
-        // Check if we already have a cached provider
-        if (this.credentialsProviderCache.has(cacheKey)) {
-            logger.debug('Using cached credentials provider')
-            return this.credentialsProviderCache.get(cacheKey)
-        }
-
-        logger.debug('Creating new credentials provider')
-
-        // Create a new provider and cache it
-        const provider = new DomainExecRoleCredentialsProvider(
-            connection.domainUrl,
-            connection.domainId,
-            connection.ssoRegion,
-            async () => await this.getAccessToken()
-        )
-
-        this.credentialsProviderCache.set(cacheKey, provider)
-        logger.debug('Cached new credentials provider')
-
-        return provider
-    }
-
-    /**
-     * Invalidates all cached credentials (for all connections)
-     * Used during connection changes or logout
-     */
-    private async invalidateAllCredentialsInCache(): Promise {
-        const logger = getLogger('smus')
-        logger.debug('Invalidating all cached credentials')
-
-        // Clear all cached DER providers and their internal credentials
-        for (const [cacheKey, provider] of this.credentialsProviderCache.entries()) {
-            try {
-                provider.invalidate() // This will clear the provider's internal cache
-                logger.debug(`Invalidated credentials for cache key: ${cacheKey}`)
-            } catch (err) {
-                logger.warn(`Failed to invalidate credentials for cache key ${cacheKey}: %s`, err)
-            }
-        }
-
-        // Clear all cached project providers and their internal credentials
-
-        await this.invalidateAllProjectCredentialsInCache()
-        // Clear all cached connection providers and their internal credentials
-        for (const [cacheKey, connectionProvider] of this.connectionCredentialProvidersCache.entries()) {
-            try {
-                connectionProvider.invalidate() // This will clear the connection provider's internal cache
-                logger.debug(`Invalidated connection credentials for cache key: ${cacheKey}`)
-            } catch (err) {
-                logger.warn(`Failed to invalidate connection credentials for cache key ${cacheKey}: %s`, err)
-            }
-        }
-
-        // Clear cached domain account ID
-        this.cachedDomainAccountId = undefined
-        logger.debug('Cleared cached domain account ID')
-
-        // Clear cached project account IDs
-        this.cachedProjectAccountIds.clear()
-        logger.debug('Cleared cached project account IDs')
-    }
-
-    /**
-     * Invalidates all project cached credentials
-     */
-    public async invalidateAllProjectCredentialsInCache(): Promise {
-        const logger = getLogger('smus')
-        logger.debug('Invalidating all cached project credentials')
-
-        for (const [projectId, projectProvider] of this.projectCredentialProvidersCache.entries()) {
-            try {
-                projectProvider.invalidate() // This will clear the project provider's internal cache
-                logger.debug(`Invalidated project credentials for project: ${projectId}`)
-            } catch (err) {
-                logger.warn(`Failed to invalidate project credentials for project ${projectId}: %s`, err)
-            }
-        }
-    }
-
-    /**
-     * Stops SSH credential refresh and cleans up resources
-     */
-    public dispose(): void {
-        this.logger.debug('Disposing authentication provider and all cached providers')
-
-        // Dispose all project providers
-        for (const provider of this.projectCredentialProvidersCache.values()) {
-            provider.dispose()
-        }
-        this.projectCredentialProvidersCache.clear()
-
-        // Dispose all connection providers
-        for (const provider of this.connectionCredentialProvidersCache.values()) {
-            provider.dispose()
-        }
-        this.connectionCredentialProvidersCache.clear()
-
-        // Dispose all DER providers in the general cache
-        for (const provider of this.credentialsProviderCache.values()) {
-            if (provider && typeof provider.dispose === 'function') {
-                provider.dispose()
-            }
-        }
-        this.credentialsProviderCache.clear()
-
-        // Clear cached domain account ID
-        this.cachedDomainAccountId = undefined
-
-        // Clear cached project account IDs
-        this.cachedProjectAccountIds.clear()
-
-        // Clear cached IAM caller identity
-        this.clearIamCallerIdentityCache()
-
-        DataZoneClient.dispose()
-        DataZoneCustomClientHelper.dispose()
-
-        this.logger.debug('Successfully disposed authentication provider')
-    }
-
-    static #instance: SmusAuthenticationProvider | undefined
-
-    public static get instance(): SmusAuthenticationProvider | undefined {
-        return SmusAuthenticationProvider.#instance
-    }
-
-    public static fromContext() {
-        return (this.#instance ??= new this())
-    }
-
-    public async invalidateConnection(): Promise {
-        // When in SMUS space, the extension is already running in projet context and sign in is not needed
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            return
-        }
-
-        if (!this.activeConnection) {
-            return
-        }
-
-        // For IAM connections, actively validate credentials
-        // No action needed for SSO as the connection is automatically updated
-        if (isSmusIamConnection(this.activeConnection)) {
-            try {
-                const validation = await this.validateIamProfile(this.activeConnection.profileName)
-                await this.auth.updateConnectionState(
-                    this.activeConnection.id,
-                    validation.isValid ? 'valid' : 'invalid'
-                )
-            } catch {
-                await this.auth.updateConnectionState(this.activeConnection.id, 'invalid')
-            }
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.ts b/packages/core/src/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.ts
deleted file mode 100644
index e209a15b1c..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.ts
+++ /dev/null
@@ -1,111 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../../shared/logger/logger'
-import { ToolkitError } from '../../../shared/errors'
-import { SmusErrorCodes } from '../../shared/smusUtils'
-
-/**
- * Authentication method types supported by SMUS
- */
-export type SmusAuthenticationMethod = 'sso' | 'iam'
-
-/**
- * Result of authentication method selection
- */
-export interface AuthenticationMethodSelection {
-    method: SmusAuthenticationMethod
-}
-
-/**
- * Authentication method selection dialog for SMUS
- */
-export class SmusAuthenticationMethodSelector {
-    private static readonly logger = getLogger('smus')
-
-    /**
-     * Shows the authentication method selection dialog matching the Figma design
-     * @param defaultMethod Optional default method to pre-select
-     * @returns Promise resolving to the selected authentication method
-     */
-    public static async showAuthenticationMethodSelection(
-        defaultMethod?: SmusAuthenticationMethod
-    ): Promise {
-        const logger = this.logger
-
-        const iamOption: vscode.QuickPickItem = {
-            label: '$(key) IAM Credential',
-            detail: 'Use IAM credentials to access resources in SageMaker Unified Studio IAM-based domains.',
-        }
-
-        const ssoOption: vscode.QuickPickItem = {
-            label: '$(organization) IAM Identity Center',
-            detail: 'Use Identity Center to access resources in SageMaker Unified Studio IdC-based domains.',
-        }
-
-        const options = [iamOption, ssoOption]
-
-        // Set default selection based on preference
-        let defaultIndex = 0
-        if (defaultMethod === 'sso') {
-            defaultIndex = 1
-        }
-
-        const quickPick = vscode.window.createQuickPick()
-        quickPick.title = 'Select a sign in method'
-        quickPick.placeholder = 'Choose how you want to authenticate with SageMaker Unified Studio'
-        quickPick.items = options
-        quickPick.canSelectMany = false
-        quickPick.ignoreFocusOut = true
-
-        // Pre-select the default method
-        if (options[defaultIndex]) {
-            quickPick.activeItems = [options[defaultIndex]]
-        }
-
-        return new Promise((resolve, reject) => {
-            let isCompleted = false
-
-            quickPick.onDidAccept(() => {
-                const selectedItem = quickPick.selectedItems[0]
-                if (!selectedItem) {
-                    quickPick.dispose()
-                    reject(
-                        new ToolkitError('No authentication method selected', {
-                            code: SmusErrorCodes.UserCancelled,
-                            cancelled: true,
-                        })
-                    )
-                    return
-                }
-
-                const method: SmusAuthenticationMethod = selectedItem === iamOption ? 'iam' : 'sso'
-
-                logger.debug(`User selected authentication method: ${method}`)
-
-                isCompleted = true
-                quickPick.dispose()
-
-                // Return the selected method without asking about preferences
-                resolve({ method })
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    reject(
-                        new ToolkitError('Authentication method selection cancelled', {
-                            code: SmusErrorCodes.UserCancelled,
-                            cancelled: true,
-                        })
-                    )
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/ui/iamProfileSelection.ts b/packages/core/src/sagemakerunifiedstudio/auth/ui/iamProfileSelection.ts
deleted file mode 100644
index fd61fc70f5..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/ui/iamProfileSelection.ts
+++ /dev/null
@@ -1,1311 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import * as path from 'path'
-import { getLogger } from '../../../shared/logger/logger'
-import { ToolkitError } from '../../../shared/errors'
-import { loadSharedCredentialsProfiles } from '../../../auth/credentials/sharedCredentials'
-import { getCredentialsFilename, getConfigFilename } from '../../../auth/credentials/sharedCredentialsFile'
-import { SmusErrorCodes, DataZoneServiceId } from '../../shared/smusUtils'
-import globals from '../../../shared/extensionGlobals'
-import fs from '../../../shared/fs/fs'
-
-/**
- * Actions available in the credential management dialog
- */
-enum CredentialManagementAction {
-    EditCredentialsFile = 'EDIT_CREDENTIALS_FILE',
-    EditConfigFile = 'EDIT_CONFIG_FILE',
-    AddNewProfile = 'ADD_NEW_PROFILE',
-}
-
-/**
- * Actions available in the profile selection dialog
- */
-enum ProfileSelectionAction {
-    SelectProfile = 'SELECT_PROFILE',
-    ManageCredentials = 'MANAGE_CREDENTIALS',
-}
-
-/**
- * Actions available in the session token input dialog
- */
-enum SessionTokenAction {
-    Skip = 'SKIP',
-    UseToken = 'USE_TOKEN',
-    Warning = 'WARNING',
-}
-
-/**
- * Result of IAM profile selection
- */
-export interface IamProfileSelection {
-    profileName: string
-    region: string
-}
-
-/**
- * Result indicating user chose to edit credential files
- */
-export interface IamProfileEditingInProgress {
-    isEditing: true
-    message: string
-}
-
-/**
- * Result indicating user chose to go back
- */
-export interface IamProfileBackNavigation {
-    isBack: true
-    message: string
-}
-
-/**
- * IAM profile selection interface for SMUS
- */
-export class SmusIamProfileSelector {
-    private static readonly logger = getLogger('smus')
-
-    // Validation regex patterns (based on AWS STS API specifications)
-    // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-    private static readonly profileNamePattern = /^[a-zA-Z0-9_-]+$/
-    // AWS AccessKeyId: 16-128 chars, pattern [\w]* (alphanumeric + underscore)
-    private static readonly accessKeyIdPattern = /^[a-zA-Z0-9_]*$/
-    // AWS SecretAccessKey and SessionToken: Required per STS API, but no pattern/length constraints specified
-    private static readonly regionLinePattern = /^region\s*=.*$/m
-
-    /**
-     * Creates a QuickPick with common settings for input dialogs
-     * @param title Title for the QuickPick
-     * @param placeholder Placeholder text
-     * @returns Configured QuickPick instance
-     */
-    private static createInputQuickPick(title: string, placeholder: string): vscode.QuickPick {
-        const quickPick = vscode.window.createQuickPick()
-        quickPick.title = title
-        quickPick.placeholder = placeholder
-        quickPick.canSelectMany = false
-        quickPick.ignoreFocusOut = true
-        quickPick.buttons = [vscode.QuickInputButtons.Back]
-        return quickPick
-    }
-
-    /**
-     * Shows the IAM profile selection dialog matching the Figma design
-     * @returns Promise resolving to the selected profile and region, editing status, or back navigation
-     */
-    public static async showIamProfileSelection(): Promise<
-        IamProfileSelection | IamProfileEditingInProgress | IamProfileBackNavigation
-    > {
-        const logger = this.logger
-
-        try {
-            // Load available credential profiles
-            const profiles = await loadSharedCredentialsProfiles()
-            const profileNames = Object.keys(profiles)
-
-            // Create QuickPick items for profiles
-            const profileItems: (vscode.QuickPickItem & {
-                action: ProfileSelectionAction
-                profileName: string
-                region: string
-            })[] = profileNames.map((profileName) => {
-                const profile = profiles[profileName]
-                const region = profile.region || 'not-set'
-
-                return {
-                    label: `$(key) ${profileName}`,
-                    description: `IAM Credentials, configured locally (${region})`,
-                    detail: `Profile: ${profileName} | Region: ${region}`,
-                    action: ProfileSelectionAction.SelectProfile,
-                    profileName,
-                    region,
-                }
-            })
-
-            // Add "Add or edit credentials" option
-            const addCredentialsItem: vscode.QuickPickItem & { action: ProfileSelectionAction } = {
-                label: '$(add) Add or edit credentials',
-                description: 'Manage AWS credential profiles',
-                detail: 'Add new profiles or edit existing credential files',
-                action: ProfileSelectionAction.ManageCredentials,
-            }
-
-            const options = [...profileItems, addCredentialsItem]
-
-            const quickPick = vscode.window.createQuickPick()
-            quickPick.title = 'Select an IAM Profile'
-            quickPick.placeholder = 'Choose an AWS credential profile to authenticate with SageMaker Unified Studio'
-            quickPick.items = options
-            quickPick.canSelectMany = false
-            quickPick.ignoreFocusOut = true
-
-            // Add back button
-            const backButton = vscode.QuickInputButtons.Back
-            quickPick.buttons = [backButton]
-
-            return new Promise((resolve, reject) => {
-                let isCompleted = false
-
-                quickPick.onDidAccept(() => {
-                    const selectedItem = quickPick.selectedItems[0]
-                    if (!selectedItem) {
-                        quickPick.dispose()
-                        reject(
-                            new ToolkitError('No profile selected', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        )
-                        return
-                    }
-
-                    isCompleted = true
-                    quickPick.dispose()
-
-                    const itemWithAction = selectedItem as vscode.QuickPickItem & {
-                        action: ProfileSelectionAction
-                        profileName?: string
-                        region?: string
-                    }
-
-                    // Check if user selected "Add or edit credentials"
-                    if (itemWithAction.action === ProfileSelectionAction.ManageCredentials) {
-                        // Handle the async credential management flow
-                        void (async () => {
-                            try {
-                                const managementResult = await SmusIamProfileSelector.showCredentialManagement()
-
-                                // Check if a new profile was created (returns IamProfileSelection)
-                                if (typeof managementResult === 'object' && 'profileName' in managementResult) {
-                                    // User created a new profile, use it directly
-                                    logger.debug(
-                                        `SMUS Auth: Using newly created profile: ${managementResult.profileName}`
-                                    )
-                                    resolve(managementResult)
-                                } else if (managementResult === true) {
-                                    // User wants to restart profile selection (e.g., clicked back)
-                                    const result = await SmusIamProfileSelector.showIamProfileSelection()
-                                    resolve(result)
-                                } else {
-                                    // User chose to edit files, return a special result indicating this
-                                    resolve({
-                                        isEditing: true,
-                                        message:
-                                            'User chose to edit credential files. Please complete setup and try again.',
-                                    })
-                                }
-                            } catch (error) {
-                                // Handle user cancellation gracefully
-                                if (error instanceof ToolkitError && error.code === SmusErrorCodes.UserCancelled) {
-                                    resolve({
-                                        isEditing: true,
-                                        message: 'User cancelled credential management.',
-                                    })
-                                } else {
-                                    reject(error)
-                                }
-                            }
-                        })()
-                        return
-                    }
-
-                    // User selected an existing profile
-                    // Ensure we have profile data (should always be present for SelectProfile action)
-                    if (!itemWithAction.profileName || !itemWithAction.region) {
-                        reject(new ToolkitError('Invalid profile selection', { code: 'InvalidProfileSelection' }))
-                        return
-                    }
-
-                    const profileName = itemWithAction.profileName
-                    const profileRegion = itemWithAction.region
-
-                    logger.debug(`User selected profile: ${profileName}`)
-
-                    // Check if region is not set and prompt for region selection
-                    if (profileRegion === 'not-set') {
-                        void (async () => {
-                            try {
-                                const selectedRegion = await SmusIamProfileSelector.showRegionSelection()
-
-                                // Check if user clicked back on region selection
-                                if (selectedRegion === 'BACK') {
-                                    resolve({
-                                        isBack: true,
-                                        message: 'User chose to go back from region selection.',
-                                    })
-                                    return
-                                }
-
-                                // Update the profile with the selected region
-                                await SmusIamProfileSelector.updateProfileRegion(profileName, selectedRegion)
-
-                                resolve({
-                                    profileName: profileName,
-                                    region: selectedRegion,
-                                })
-                            } catch (error) {
-                                reject(error)
-                            }
-                        })()
-                    } else {
-                        resolve({
-                            profileName: profileName,
-                            region: profileRegion,
-                        })
-                    }
-                })
-
-                quickPick.onDidTriggerButton((button) => {
-                    if (button === vscode.QuickInputButtons.Back) {
-                        isCompleted = true
-                        quickPick.dispose()
-                        resolve({
-                            isBack: true,
-                            message: 'User chose to go back to authentication method selection.',
-                        })
-                    }
-                })
-
-                quickPick.onDidHide(() => {
-                    if (!isCompleted) {
-                        quickPick.dispose()
-                        reject(
-                            new ToolkitError('Profile selection cancelled', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        )
-                    }
-                })
-
-                quickPick.show()
-            })
-        } catch (error) {
-            // Don't log or chain user cancellation as an error
-            if (error instanceof ToolkitError && error.code === SmusErrorCodes.UserCancelled) {
-                throw error
-            }
-            logger.error('Failed to show IAM profile selection: %s', error)
-            throw ToolkitError.chain(error, 'Failed to show IAM profile selection')
-        }
-    }
-
-    /**
-     * Shows region selection dialog for IAM authentication
-     * @param options Configuration options for the region selection dialog
-     * @returns Promise resolving to the selected region or 'BACK' if user wants to go back
-     */
-    public static async showRegionSelection(options?: {
-        defaultRegion?: string
-        title?: string
-        placeholder?: string
-        returnBackOnCancel?: boolean
-    }): Promise {
-        const logger = this.logger
-
-        // Get regions where DataZone service is available
-        const allRegions = globals.regionProvider.getRegions()
-        const dataZoneRegions = allRegions.filter((region) =>
-            globals.regionProvider.isServiceInRegion(DataZoneServiceId, region.id)
-        )
-
-        // If no regions found with DataZone service, fall back to all regions
-        const regions = dataZoneRegions.length > 0 ? dataZoneRegions : allRegions
-
-        const regionItems: vscode.QuickPickItem[] = regions.map(
-            (region) =>
-                ({
-                    label: region.name,
-                    description: region.id,
-                    detail: `AWS Region: ${region.id}`,
-                    regionCode: region.id,
-                }) as vscode.QuickPickItem & { regionCode: string }
-        )
-
-        const quickPick = this.createInputQuickPick(
-            options?.title ?? 'Select AWS Region',
-            options?.placeholder ?? 'Choose the AWS region for SageMaker Unified Studio'
-        )
-        quickPick.items = regionItems
-
-        // Allow users to find matches by typing in the region code (e.g., us-east-1)
-        quickPick.matchOnDescription = true
-
-        // Pre-select default region if provided
-        if (options?.defaultRegion) {
-            const defaultItem = regionItems.find((item) => (item as any).regionCode === options.defaultRegion)
-            if (defaultItem) {
-                quickPick.activeItems = [defaultItem]
-            }
-        }
-
-        return new Promise((resolve, reject) => {
-            let isCompleted = false
-
-            quickPick.onDidAccept(() => {
-                const selectedItem = quickPick.selectedItems[0]
-                if (!selectedItem) {
-                    if (options?.returnBackOnCancel) {
-                        quickPick.dispose()
-                        resolve('BACK')
-                    } else {
-                        quickPick.dispose()
-                        reject(
-                            new ToolkitError('No region selected', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        )
-                    }
-                    return
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-
-                const regionItem = selectedItem as vscode.QuickPickItem & { regionCode: string }
-
-                logger.debug(`User selected region: ${regionItem.regionCode}`)
-
-                resolve(regionItem.regionCode)
-            })
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    if (options?.returnBackOnCancel) {
-                        resolve('BACK')
-                    } else {
-                        reject(
-                            new ToolkitError('Region selection cancelled', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        )
-                    }
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Shows credential management options (Add/Edit credentials)
-     * @returns Promise resolving to boolean indicating if profile selection should restart, or profile data if a new profile was created
-     */
-    public static async showCredentialManagement(): Promise {
-        const logger = this.logger
-
-        logger.debug('Showing credential management options')
-
-        const options: (vscode.QuickPickItem & { action: CredentialManagementAction })[] = [
-            {
-                label: '$(file-text) Edit AWS Credentials File',
-                description: 'Open ~/.aws/credentials file for editing',
-                detail: 'Edit existing credential profiles or add new ones',
-                action: CredentialManagementAction.EditCredentialsFile,
-            },
-            {
-                label: '$(file-text) Edit AWS Config File',
-                description: 'Open ~/.aws/config file for editing',
-                detail: 'Edit AWS configuration settings and profiles',
-                action: CredentialManagementAction.EditConfigFile,
-            },
-            {
-                label: '$(add) Add New Profile',
-                description: 'Create a new AWS credential profile',
-                detail: 'Interactive setup for a new credential profile',
-                action: CredentialManagementAction.AddNewProfile,
-            },
-        ]
-
-        const quickPick = vscode.window.createQuickPick()
-        quickPick.title = 'Manage AWS Credentials'
-        quickPick.placeholder = 'Choose how you want to manage your AWS credentials'
-        quickPick.items = options
-        quickPick.canSelectMany = false
-        quickPick.ignoreFocusOut = true
-
-        // Add back button
-        const backButton = vscode.QuickInputButtons.Back
-        quickPick.buttons = [backButton]
-
-        return new Promise((resolve, reject) => {
-            let isCompleted = false
-
-            quickPick.onDidAccept(() => {
-                const selectedItem = quickPick.selectedItems[0]
-                if (!selectedItem) {
-                    quickPick.dispose()
-                    reject(
-                        new ToolkitError('No option selected', { code: SmusErrorCodes.UserCancelled, cancelled: true })
-                    )
-                    return
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-
-                // Handle the async operations after disposing the quick pick
-                void (async () => {
-                    try {
-                        const itemWithAction = selectedItem as vscode.QuickPickItem & {
-                            action: CredentialManagementAction
-                        }
-
-                        switch (itemWithAction.action) {
-                            case CredentialManagementAction.EditCredentialsFile: {
-                                const result = await this.openAwsFile('credentials')
-                                // If user clicked "Select Profile", restart profile selection
-                                resolve(result === 'RESTART_PROFILE_SELECTION')
-                                break
-                            }
-                            case CredentialManagementAction.EditConfigFile: {
-                                const result = await this.openAwsFile('config')
-                                // If user clicked "Select Profile", restart profile selection
-                                resolve(result === 'RESTART_PROFILE_SELECTION')
-                                break
-                            }
-                            case CredentialManagementAction.AddNewProfile: {
-                                const newProfile = await this.addNewProfile()
-                                // Return the newly created profile data to use it directly
-                                resolve(newProfile)
-                                break
-                            }
-                        }
-                    } catch (error) {
-                        if (error instanceof ToolkitError && error.code === SmusErrorCodes.UserCancelled) {
-                            // User cancelled, don't treat as error
-                            reject(error)
-                        } else {
-                            reject(error)
-                        }
-                    }
-                })()
-            })
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    // User wants to go back to profile selection
-                    resolve(true)
-                }
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    reject(
-                        new ToolkitError('Credential management cancelled', {
-                            code: SmusErrorCodes.UserCancelled,
-                            cancelled: true,
-                        })
-                    )
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Opens the AWS credentials file in VS Code editor
-     */
-    /**
-     * Opens an AWS configuration file in VS Code editor
-     * @param fileType Type of file to open ('credentials' or 'config')
-     */
-    private static async openAwsFile(fileType: 'credentials' | 'config'): Promise {
-        const logger = this.logger
-        const isCredentials = fileType === 'credentials'
-
-        try {
-            const filePath = isCredentials ? getCredentialsFilename() : getConfigFilename()
-            const fileLabel = isCredentials ? 'credentials' : 'config'
-
-            logger.debug(`Opening ${fileLabel} file: ${filePath}`)
-
-            // Ensure the .aws directory exists
-            await this.ensureAwsDirectoryExists()
-
-            // Create the file if it doesn't exist
-            if (!(await fs.existsFile(filePath))) {
-                await fs.writeFile(filePath, '')
-                logger.debug(`Created new ${fileLabel} file`)
-            }
-
-            // Open the file in VS Code
-            const document = await vscode.workspace.openTextDocument(filePath)
-            await vscode.window.showTextDocument(document)
-
-            logger.debug(`${fileLabel} file opened successfully`)
-        } catch (error) {
-            const fileLabel = isCredentials ? 'credentials' : 'config'
-            logger.error(`Failed to open ${fileLabel} file: %s`, error)
-            throw new ToolkitError(`Failed to open AWS ${fileLabel} file: ${(error as Error).message}`, {
-                code: isCredentials ? 'CredentialsFileError' : 'ConfigFileError',
-            })
-        }
-    }
-
-    /**
-     * Interactive flow to add a new AWS credential profile with back navigation
-     * @returns Promise resolving to the newly created profile data
-     */
-    private static async addNewProfile(): Promise {
-        const logger = this.logger
-
-        try {
-            logger.debug('Starting add new profile flow')
-
-            const profileData = await this.collectProfileData()
-
-            if (profileData === 'BACK') {
-                // User navigated back, throw error to go back to credential management
-                throw new ToolkitError('User navigated back', { code: SmusErrorCodes.UserCancelled, cancelled: true })
-            }
-
-            // Add the profile to credentials file
-            await this.addProfileToCredentialsFile(
-                profileData.profileName,
-                profileData.accessKeyId,
-                profileData.secretAccessKey,
-                profileData.sessionToken,
-                profileData.region
-            )
-
-            // Show success message
-            void vscode.window.showInformationMessage(
-                `AWS profile '${profileData.profileName}' has been added successfully and will be used for authentication.`
-            )
-
-            logger.debug(`Successfully added new profile: ${profileData.profileName}`)
-
-            // Return the profile data to use it directly
-            return {
-                profileName: profileData.profileName,
-                region: profileData.region,
-            }
-        } catch (error) {
-            // Only log actual errors, not user cancellations
-            if (error instanceof ToolkitError && error.code === SmusErrorCodes.UserCancelled) {
-                logger.debug('User cancelled add new profile flow')
-                throw error // Re-throw for telemetry but don't log as error
-            }
-            logger.error('Failed to add new profile: %s', error)
-            throw new ToolkitError(`Failed to add new profile: ${(error as Error).message}`, {
-                code: 'AddProfileError',
-            })
-        }
-    }
-
-    /**
-     * Collects profile data through a multi-step flow with back navigation
-     */
-    private static async collectProfileData(): Promise<
-        | {
-              profileName: string
-              accessKeyId: string
-              secretAccessKey: string
-              sessionToken?: string
-              region: string
-          }
-        | 'BACK'
-    > {
-        let currentStep = 1
-        let profileName = ''
-        let accessKeyId = ''
-        let secretAccessKey = ''
-        let sessionToken = ''
-        let region = ''
-
-        while (currentStep <= 5) {
-            switch (currentStep) {
-                case 1: {
-                    // Step 1: Profile Name
-                    const result = await this.getProfileNameInput()
-                    if (result === 'BACK') {
-                        return 'BACK' // User wants to go back - exit to credential management menu
-                    }
-                    profileName = result
-                    currentStep = 2
-                    break
-                }
-                case 2: {
-                    // Step 2: Access Key ID
-                    const result = await this.getAccessKeyIdInput()
-                    if (result === 'BACK') {
-                        currentStep = 1 // Go back to step 1
-                    } else {
-                        accessKeyId = result
-                        currentStep = 3
-                    }
-                    break
-                }
-                case 3: {
-                    // Step 3: Secret Access Key
-                    const result = await this.getSecretAccessKeyInput()
-                    if (result === 'BACK') {
-                        currentStep = 2 // Go back to step 2
-                    } else {
-                        secretAccessKey = result
-                        currentStep = 4
-                    }
-                    break
-                }
-                case 4: {
-                    // Step 4: Session Token (optional)
-                    const result = await this.getSessionTokenInput()
-                    if (result === 'BACK') {
-                        currentStep = 3 // Go back to step 3
-                    } else {
-                        sessionToken = result
-                        currentStep = 5
-                    }
-                    break
-                }
-                case 5: {
-                    // Step 5: Region
-                    const result = await this.showRegionSelection({
-                        title: 'Add New AWS Profile - Step 5 of 5',
-                        placeholder: 'Select a default region',
-                        returnBackOnCancel: true,
-                    })
-                    if (result === 'BACK') {
-                        currentStep = 4 // Go back to step 4
-                    } else {
-                        region = result
-                        currentStep = 6 // Exit the loop
-                    }
-                    break
-                }
-            }
-        }
-
-        return {
-            profileName,
-            accessKeyId,
-            secretAccessKey,
-            sessionToken: sessionToken || undefined,
-            region, // Region is always set since step 5 is required
-        }
-    }
-
-    /**
-     * Gets profile name input with back navigation and existing profile validation
-     */
-    private static async getProfileNameInput(): Promise {
-        return new Promise((resolve) => {
-            const quickPick = this.createInputQuickPick(
-                'Add New AWS Profile - Step 1 of 5',
-                'Type a profile name (e.g., my-profile, dev, prod)'
-            )
-            quickPick.items = []
-
-            let isCompleted = false
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidChangeValue(async (value) => {
-                // Show placeholder when empty
-                if (!value) {
-                    quickPick.items = [
-                        {
-                            label: '$(edit) Enter profile name',
-                            description: 'e.g., my-profile, dev, prod',
-                            detail: 'Profile names can contain letters, numbers, hyphens, and underscores',
-                        },
-                    ]
-                    return
-                }
-
-                // Validate input as user types
-                if (value.includes(' ')) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: '$(error) Cannot contain spaces',
-                            detail: 'Valid characters: letters, numbers, hyphens, underscores',
-                        },
-                    ]
-                } else if (!this.profileNamePattern.test(value)) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: '$(error) Invalid characters',
-                            detail: 'Profile names can only contain letters, numbers, hyphens, and underscores',
-                        },
-                    ]
-                } else if (value.length < 2) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: `$(info) Too short (${value.length}/2 min)`,
-                            detail: 'Profile names should be at least 2 characters long',
-                        },
-                    ]
-                } else {
-                    // Check if profile already exists
-                    try {
-                        const profiles = await loadSharedCredentialsProfiles()
-                        const profileExists = profiles[value] !== undefined
-
-                        if (profileExists) {
-                            quickPick.items = [
-                                {
-                                    label: `${value}`,
-                                    description: '$(warning) Profile exists - will be overwritten',
-                                    detail: 'Press Enter to overwrite the existing profile',
-                                },
-                            ]
-                        } else {
-                            quickPick.items = [
-                                {
-                                    label: `${value}`,
-                                    description: `$(check) Valid (${value.length} characters)`,
-                                    detail: 'Press Enter to use this profile name',
-                                },
-                            ]
-                        }
-                    } catch (error) {
-                        // If we can't load profiles, just show as valid
-                        quickPick.items = [
-                            {
-                                label: `${value}`,
-                                description: `$(check) Valid (${value.length} characters)`,
-                                detail: 'Press Enter to use this profile name',
-                            },
-                        ]
-                    }
-                }
-            })
-
-            quickPick.onDidAccept(async () => {
-                const value = quickPick.value.trim()
-
-                // Validate final input
-                if (!value || value.length < 2) {
-                    return // Don't accept empty or too short input
-                }
-                if (value.includes(' ')) {
-                    return // Don't accept names with spaces
-                }
-                if (!this.profileNamePattern.test(value)) {
-                    return // Don't accept invalid characters
-                }
-
-                // Check if profile exists and ask for confirmation
-                try {
-                    const profiles = await loadSharedCredentialsProfiles()
-                    const profileExists = profiles[value] !== undefined
-
-                    if (profileExists) {
-                        isCompleted = true
-                        quickPick.dispose()
-
-                        // Ask for confirmation to overwrite
-                        const overwrite = await vscode.window.showWarningMessage(
-                            `Profile '${value}' already exists. Do you want to overwrite it?`,
-                            { modal: true },
-                            'Overwrite'
-                        )
-
-                        if (overwrite === 'Overwrite') {
-                            resolve(value)
-                        } else {
-                            // User cancelled, restart the input
-                            const result = await this.getProfileNameInput()
-                            resolve(result)
-                        }
-                        return
-                    }
-                } catch (error) {
-                    // If we can't load profiles, just continue
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-                resolve(value)
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Gets access key ID input with back navigation
-     */
-    private static async getAccessKeyIdInput(): Promise {
-        return new Promise((resolve) => {
-            const quickPick = this.createInputQuickPick(
-                'Add New AWS Profile - Step 2 of 5',
-                'Type your AWS Access Key ID (e.g., AKIAIOSFODNN7EXAMPLE)'
-            )
-            quickPick.items = []
-
-            let isCompleted = false
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidChangeValue((value) => {
-                // Show placeholder when empty
-                if (!value) {
-                    quickPick.items = [
-                        {
-                            label: '$(key) Enter AWS Access Key ID',
-                            description: 'e.g., AKIAIOSFODNN7EXAMPLE',
-                            detail: 'Access Key IDs are typically 16-32 characters long',
-                        },
-                    ]
-                    return
-                }
-
-                // Validate input as user types (AWS STS API: 16-128 chars, pattern [\w]*)
-                // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-                if (!this.accessKeyIdPattern.test(value)) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: '$(error) Invalid characters',
-                            detail: 'Access Key IDs can only contain letters, numbers, and underscores',
-                        },
-                    ]
-                } else if (value.length < 16) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: `$(info) Too short (${value.length}/16 min)`,
-                            detail: 'AWS Access Key IDs must be 16-128 characters long',
-                        },
-                    ]
-                } else if (value.length > 128) {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: `$(error) Too long (${value.length}/128 max)`,
-                            detail: 'AWS Access Key IDs must be 16-128 characters long',
-                        },
-                    ]
-                } else {
-                    quickPick.items = [
-                        {
-                            label: `${value}`,
-                            description: `$(check) Valid (${value.length} characters)`,
-                            detail: 'Press Enter to use this Access Key ID',
-                        },
-                    ]
-                }
-            })
-
-            quickPick.onDidAccept(() => {
-                const value = quickPick.value.trim()
-
-                // Validate final input (AWS STS API: 16-128 chars, pattern [\w]*)
-                // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-                if (!value) {
-                    return // Don't accept empty input
-                }
-                if (!this.accessKeyIdPattern.test(value)) {
-                    return // Don't accept invalid characters
-                }
-                if (value.length < 16 || value.length > 128) {
-                    return // Don't accept invalid length
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-                resolve(value)
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Gets secret access key input with back navigation
-     */
-    private static async getSecretAccessKeyInput(): Promise {
-        return new Promise((resolve) => {
-            const quickPick = this.createInputQuickPick(
-                'Add New AWS Profile - Step 3 of 5',
-                'Type your AWS Secret Access Key (will be hidden when typing)'
-            )
-            quickPick.items = []
-
-            let isCompleted = false
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidChangeValue((value) => {
-                // Show placeholder when empty
-                if (!value) {
-                    quickPick.items = [
-                        {
-                            label: '$(lock) Enter AWS Secret Access Key',
-                            description: 'Required field',
-                            detail: 'Enter your AWS Secret Access Key',
-                        },
-                    ]
-                    return
-                }
-
-                // AWS STS API: Required, no specific pattern/length constraints in docs
-                // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-                quickPick.items = [
-                    {
-                        label: '•'.repeat(Math.min(value.length, 40)),
-                        description: `$(check) ${value.length} characters entered`,
-                        detail: 'Press Enter to continue',
-                    },
-                ]
-            })
-
-            quickPick.onDidAccept(() => {
-                const value = quickPick.value.trim()
-
-                // Validate final input - AWS STS API only requires non-empty
-                // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-                if (!value) {
-                    return // Don't accept empty input
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-                resolve(value)
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Gets session token input with back navigation
-     */
-    private static async getSessionTokenInput(): Promise {
-        return new Promise((resolve) => {
-            const quickPick = this.createInputQuickPick(
-                'Add New AWS Profile - Step 4 of 5',
-                'Enter your AWS Session Token (optional for temporary credentials)'
-            )
-
-            // Start with skip option only
-            quickPick.items = [
-                {
-                    label: '$(arrow-right) Skip',
-                    description: 'Skip session token (for permanent credentials)',
-                    detail: 'Use this for regular IAM user access keys',
-                    action: SessionTokenAction.Skip,
-                } as vscode.QuickPickItem & { action: SessionTokenAction },
-            ]
-
-            let isCompleted = false
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === vscode.QuickInputButtons.Back) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidChangeValue((value) => {
-                if (!value) {
-                    // Show skip option when empty
-                    quickPick.items = [
-                        {
-                            label: '$(arrow-right) Skip',
-                            description: 'Skip session token (for permanent credentials)',
-                            detail: 'Use this for regular IAM user access keys',
-                            action: SessionTokenAction.Skip,
-                        } as vscode.QuickPickItem & { action: SessionTokenAction },
-                    ]
-                    return
-                }
-
-                // AWS STS API: Required for temporary credentials, no specific pattern/length constraints in docs
-                // Reference: https://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html
-                quickPick.items = [
-                    {
-                        label: '•'.repeat(Math.min(value.length, 40)),
-                        description: `$(check) ${value.length} characters entered`,
-                        detail: 'Press Enter to use this session token',
-                        action: SessionTokenAction.UseToken,
-                    } as vscode.QuickPickItem & { action: SessionTokenAction },
-                    {
-                        label: '$(arrow-right) Skip',
-                        description: 'Skip session token (for permanent credentials)',
-                        detail: 'Use this for regular IAM user access keys',
-                        action: SessionTokenAction.Skip,
-                    } as vscode.QuickPickItem & { action: SessionTokenAction },
-                ]
-            })
-
-            quickPick.onDidAccept(() => {
-                const selectedItem = quickPick.selectedItems[0]
-                const currentValue = quickPick.value
-
-                isCompleted = true
-                quickPick.dispose()
-
-                // If user typed something and pressed Enter without selecting an item, use the typed value (trimmed)
-                if (!selectedItem && currentValue) {
-                    resolve(currentValue.trim())
-                    return
-                }
-
-                // If no selection with empty value, skip
-                if (!selectedItem) {
-                    resolve('')
-                    return
-                }
-
-                const itemWithAction = selectedItem as vscode.QuickPickItem & { action: SessionTokenAction }
-
-                // Handle based on action
-                switch (itemWithAction.action) {
-                    case SessionTokenAction.Skip:
-                        resolve('')
-                        break
-                    case SessionTokenAction.UseToken:
-                        resolve(currentValue.trim())
-                        break
-                    case SessionTokenAction.Warning:
-                        // User can still proceed with warning, use the typed value
-                        resolve(currentValue.trim())
-                        break
-                    default:
-                        resolve('')
-                }
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-
-    /**
-     * Ensures the ~/.aws directory exists
-     */
-    private static async ensureAwsDirectoryExists(): Promise {
-        const awsDir = path.join(fs.getUserHomeDir(), '.aws')
-        if (!(await fs.existsDir(awsDir))) {
-            await fs.mkdir(awsDir)
-        }
-    }
-
-    /**
-     * Adds a new profile to the credentials file or overwrites existing one
-     */
-    private static async addProfileToCredentialsFile(
-        profileName: string,
-        accessKeyId: string,
-        secretAccessKey: string,
-        sessionToken?: string,
-        region?: string
-    ): Promise {
-        const credentialsPath = getCredentialsFilename()
-
-        // Ensure the .aws directory exists
-        await this.ensureAwsDirectoryExists()
-
-        // Read existing content or create new
-        let content = ''
-        if (await fs.existsFile(credentialsPath)) {
-            content = await fs.readFileText(credentialsPath)
-        }
-
-        // Create new profile lines (no spaces around =)
-        const newProfileLines = [
-            `[${profileName}]`,
-            `aws_access_key_id=${accessKeyId}`,
-            `aws_secret_access_key=${secretAccessKey}`,
-        ]
-
-        if (sessionToken) {
-            newProfileLines.push(`aws_session_token=${sessionToken}`)
-        }
-
-        if (region) {
-            newProfileLines.push(`region=${region}`)
-        }
-
-        // Parse the file line by line to handle profile replacement properly
-        const lines = content.split('\n')
-        const newLines: string[] = []
-        let inTargetProfile = false
-        let profileFound = false
-
-        for (let i = 0; i < lines.length; i++) {
-            const line = lines[i].trim()
-
-            // Check if this is a profile header
-            if (line.startsWith('[') && line.endsWith(']')) {
-                const currentProfileName = line.slice(1, -1)
-
-                if (currentProfileName === profileName) {
-                    // Found the target profile - replace it
-                    if (!profileFound) {
-                        newLines.push(...newProfileLines)
-                        profileFound = true
-                    }
-                    inTargetProfile = true
-                    continue
-                } else {
-                    // Different profile - end replacement mode
-                    inTargetProfile = false
-                    newLines.push(lines[i])
-                }
-            } else if (!inTargetProfile) {
-                // Not in target profile, keep the line
-                newLines.push(lines[i])
-            }
-            // If inTargetProfile is true, we skip the line (removing old profile content)
-        }
-
-        // If profile wasn't found, add it at the end
-        if (!profileFound) {
-            if (newLines.length > 0 && newLines[newLines.length - 1].trim() !== '') {
-                newLines.push('') // Add blank line before new profile
-            }
-            newLines.push(...newProfileLines)
-        }
-
-        // Update content with the new lines
-        content = newLines.join('\n')
-
-        // Write back to file
-        await fs.writeFile(credentialsPath, content)
-    }
-
-    /**
-     * Updates an existing profile with a new region
-     */
-    private static async updateProfileRegion(profileName: string, region: string): Promise {
-        const logger = this.logger
-
-        try {
-            logger.debug(`Updating profile ${profileName} with region ${region}`)
-
-            const credentialsPath = getCredentialsFilename()
-
-            if (!(await fs.existsFile(credentialsPath))) {
-                throw new ToolkitError('Credentials file not found', { code: 'CredentialsFileNotFound' })
-            }
-
-            // Read the current credentials file
-            const content = await fs.readFileText(credentialsPath)
-
-            // Find the profile section
-            const profileSectionRegex = new RegExp(`^\\[${profileName}\\]$`, 'm')
-            const profileMatch = content.match(profileSectionRegex)
-
-            if (!profileMatch) {
-                throw new ToolkitError(`Profile ${profileName} not found in credentials file`, {
-                    code: 'ProfileNotFound',
-                })
-            }
-
-            // Find the next profile section or end of file
-            const profileStartIndex = profileMatch.index!
-            const nextProfileMatch = content.slice(profileStartIndex + 1).match(/^\[.*\]$/m)
-            const profileEndIndex = nextProfileMatch ? profileStartIndex + 1 + nextProfileMatch.index! : content.length
-
-            // Extract the profile section
-            const profileSection = content.slice(profileStartIndex, profileEndIndex)
-
-            // Check if region already exists in the profile
-            let updatedProfileSection: string
-
-            if (this.regionLinePattern.test(profileSection)) {
-                // Replace existing region
-                updatedProfileSection = profileSection.replace(this.regionLinePattern, `region = ${region}`)
-            } else {
-                // Add region to the profile (before any empty lines at the end)
-                const lines = profileSection.split('\n')
-                // Find the last non-empty line index (compatible with older JS versions)
-                let lastNonEmptyIndex = -1
-                for (let i = lines.length - 1; i >= 0; i--) {
-                    if (lines[i].trim() !== '') {
-                        lastNonEmptyIndex = i
-                        break
-                    }
-                }
-                lines.splice(lastNonEmptyIndex + 1, 0, `region = ${region}`)
-                updatedProfileSection = lines.join('\n')
-            }
-
-            // Replace the profile section in the content
-            const updatedContent =
-                content.slice(0, profileStartIndex) + updatedProfileSection + content.slice(profileEndIndex)
-
-            // Write back to file
-            await fs.writeFile(credentialsPath, updatedContent)
-
-            logger.debug(`Successfully updated profile ${profileName} with region ${region}`)
-        } catch (error) {
-            logger.error('Failed to update profile region: %s', error)
-            throw new ToolkitError(`Failed to update profile region: ${(error as Error).message}`, {
-                code: 'UpdateProfileError',
-            })
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/auth/ui/ssoAuthentication.ts b/packages/core/src/sagemakerunifiedstudio/auth/ui/ssoAuthentication.ts
deleted file mode 100644
index 2d2efaa15f..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/auth/ui/ssoAuthentication.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { SmusUtils } from '../../shared/smusUtils'
-
-/**
- * SSO authentication UI components for SMUS
- */
-export class SmusSsoAuthenticationUI {
-    /**
-     * Shows domain URL input with back button support
-     */
-    public static async showDomainUrlInput(): Promise {
-        return new Promise((resolve) => {
-            const quickPick = vscode.window.createQuickPick()
-            quickPick.title = 'SageMaker Unified Studio Authentication'
-            quickPick.placeholder = 'Enter your SageMaker Unified Studio Domain URL'
-            quickPick.canSelectMany = false
-            quickPick.ignoreFocusOut = true
-
-            // Add back button
-            const backButton = vscode.QuickInputButtons.Back
-            quickPick.buttons = [backButton]
-
-            // Start with placeholder item
-            quickPick.items = [
-                {
-                    label: '$(globe) Enter Domain URL',
-                    description: 'e.g., https://dzd_xxxxxxxxx.sagemaker.region.on.aws',
-                    detail: 'Type your SageMaker Unified Studio domain URL above',
-                },
-            ]
-
-            let isCompleted = false
-
-            quickPick.onDidTriggerButton((button) => {
-                if (button === backButton) {
-                    isCompleted = true
-                    quickPick.dispose()
-                    resolve('BACK')
-                }
-            })
-
-            quickPick.onDidChangeValue((value) => {
-                if (!value) {
-                    quickPick.items = [
-                        {
-                            label: '$(globe) Enter Domain URL',
-                            description: 'e.g., https://dzd_xxxxxxxxx.sagemaker.region.on.aws',
-                            detail: 'Type your SageMaker Unified Studio domain URL above',
-                        },
-                    ]
-                    return
-                }
-
-                // Validate input as user types
-                const validation = SmusUtils.validateDomainUrl(value)
-                if (validation) {
-                    quickPick.items = [
-                        {
-                            label: '$(error) Invalid Domain URL',
-                            description: validation,
-                            detail: `Current input: "${value}"`,
-                        },
-                    ]
-                } else {
-                    quickPick.items = [
-                        {
-                            label: '$(check) Use this Domain URL',
-                            description: 'Press Enter to connect',
-                            detail: `Domain URL: ${value}`,
-                        },
-                    ]
-                }
-            })
-
-            quickPick.onDidAccept(() => {
-                const value = quickPick.value.trim()
-
-                // Validate final input
-                if (!value) {
-                    return // Don't accept empty input
-                }
-
-                const validation = SmusUtils.validateDomainUrl(value)
-                if (validation) {
-                    return // Don't accept invalid URLs
-                }
-
-                isCompleted = true
-                quickPick.dispose()
-                resolve(value)
-            })
-
-            quickPick.onDidHide(() => {
-                if (!isCompleted) {
-                    quickPick.dispose()
-                    resolve(undefined) // User cancelled
-                }
-            })
-
-            quickPick.show()
-        })
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/activation.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/activation.ts
deleted file mode 100644
index 97ffadacc6..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/activation.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { Constants } from './models/constants'
-import {
-    getStatusBarProviders,
-    showConnectionQuickPick,
-    showProjectQuickPick,
-    parseNotebookCells,
-} from './commands/commands'
-
-/**
- * Activates the SageMaker Unified Studio Connection Magics Selector feature.
- *
- * @param extensionContext The extension context
- */
-export async function activate(extensionContext: vscode.ExtensionContext): Promise {
-    extensionContext.subscriptions.push(
-        vscode.commands.registerCommand(Constants.CONNECTION_COMMAND, () => showConnectionQuickPick()),
-        vscode.commands.registerCommand(Constants.PROJECT_COMMAND, () => showProjectQuickPick())
-    )
-
-    if ('NotebookEdit' in vscode) {
-        const { connectionProvider, projectProvider, separatorProvider } = getStatusBarProviders()
-
-        extensionContext.subscriptions.push(
-            vscode.notebooks.registerNotebookCellStatusBarItemProvider('jupyter-notebook', connectionProvider),
-            vscode.notebooks.registerNotebookCellStatusBarItemProvider('jupyter-notebook', projectProvider),
-            vscode.notebooks.registerNotebookCellStatusBarItemProvider('jupyter-notebook', separatorProvider)
-        )
-
-        extensionContext.subscriptions.push(
-            vscode.window.onDidChangeActiveNotebookEditor(async () => {
-                await parseNotebookCells()
-            })
-        )
-
-        extensionContext.subscriptions.push(vscode.workspace.onDidChangeTextDocument(handleTextDocumentChange))
-
-        void parseNotebookCells()
-    }
-}
-
-/**
- * Handles text document changes to update status bar when cells are manually edited
- */
-function handleTextDocumentChange(event: vscode.TextDocumentChangeEvent): void {
-    if (event.document.uri.scheme !== 'vscode-notebook-cell') {
-        return
-    }
-
-    const editor = vscode.window.activeNotebookEditor
-    if (!editor) {
-        return
-    }
-
-    let changedCell: vscode.NotebookCell | undefined
-    for (let i = 0; i < editor.notebook.cellCount; i++) {
-        const cell = editor.notebook.cellAt(i)
-        if (cell.document.uri.toString() === event.document.uri.toString()) {
-            changedCell = cell
-            break
-        }
-    }
-
-    if (changedCell && changedCell.kind === vscode.NotebookCellKind.Code) {
-        const { notebookStateManager } = require('./services/notebookStateManager')
-
-        notebookStateManager.parseCellMagic(changedCell)
-
-        setTimeout(() => {
-            const { connectionProvider, projectProvider } = getStatusBarProviders()
-            connectionProvider.refreshCellStatusBar()
-            projectProvider.refreshCellStatusBar()
-        }, 100)
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/client/connectedSpaceDataZoneClient.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/client/connectedSpaceDataZoneClient.ts
deleted file mode 100644
index cc26dd3f43..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/client/connectedSpaceDataZoneClient.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { DataZone, ListConnectionsCommandOutput } from '@aws-sdk/client-datazone'
-import { getLogger } from '../../../shared/logger/logger'
-
-/**
- * Represents a DataZone connection
- */
-export interface DataZoneConnection {
-    connectionId: string
-    name: string
-    type: string
-    props?: Record
-}
-
-/**
- * DataZone client for use in a SageMaker Unified Studio connected space
- * Uses the user's current AWS credentials (project role credentials)
- */
-export class ConnectedSpaceDataZoneClient {
-    private datazoneClient: DataZone | undefined
-    private readonly logger = getLogger('smus')
-
-    constructor(
-        private readonly region: string,
-        private readonly customEndpoint?: string
-    ) {}
-
-    /**
-     * Gets the DataZone client, initializing it if necessary
-     * Uses default AWS credentials from the environment (project role)
-     * Supports custom endpoints for non-production environments
-     */
-    private getDataZoneClient(): DataZone {
-        if (!this.datazoneClient) {
-            try {
-                const clientConfig: any = {
-                    region: this.region,
-                }
-
-                // Use custom endpoint if provided (for non-prod environments)
-                if (this.customEndpoint) {
-                    clientConfig.endpoint = this.customEndpoint
-                    this.logger.debug(
-                        `ConnectedSpaceDataZoneClient: Using custom DataZone endpoint: ${this.customEndpoint}`
-                    )
-                } else {
-                    this.logger.debug(
-                        `ConnectedSpaceDataZoneClient: Using default AWS DataZone endpoint for region: ${this.region}`
-                    )
-                }
-
-                this.logger.debug('ConnectedSpaceDataZoneClient: Creating DataZone client with default credentials')
-                this.datazoneClient = new DataZone(clientConfig)
-                this.logger.debug('ConnectedSpaceDataZoneClient: Successfully created DataZone client')
-            } catch (err) {
-                this.logger.error('ConnectedSpaceDataZoneClient: Failed to create DataZone client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.datazoneClient
-    }
-
-    /**
-     * Lists the connections in a DataZone domain and project
-     * @param domainId The DataZone domain identifier
-     * @param projectId The DataZone project identifier
-     * @returns List of connections
-     */
-    public async listConnections(domainId: string, projectId: string): Promise {
-        try {
-            this.logger.info(
-                `ConnectedSpaceDataZoneClient: Listing connections for domain ${domainId}, project ${projectId}`
-            )
-
-            const datazoneClient = this.getDataZoneClient()
-
-            const response: ListConnectionsCommandOutput = await datazoneClient.listConnections({
-                domainIdentifier: domainId,
-                projectIdentifier: projectId,
-            })
-
-            if (!response.items || response.items.length === 0) {
-                this.logger.info(
-                    `ConnectedSpaceDataZoneClient: No connections found for domain ${domainId}, project ${projectId}`
-                )
-                return []
-            }
-
-            const connections: DataZoneConnection[] = response.items.map((connection) => ({
-                connectionId: connection.connectionId || '',
-                name: connection.name || '',
-                type: connection.type || '',
-                props: connection.props || {},
-            }))
-
-            this.logger.info(
-                `ConnectedSpaceDataZoneClient: Found ${connections.length} connections for domain ${domainId}, project ${projectId}`
-            )
-            return connections
-        } catch (err) {
-            this.logger.error('ConnectedSpaceDataZoneClient: Failed to list connections: %s', err as Error)
-            throw err
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/commands/commands.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/commands/commands.ts
deleted file mode 100644
index 01e269004c..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/commands/commands.ts
+++ /dev/null
@@ -1,195 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { connectionOptionsService } from '../services/connectionOptionsService'
-import { notebookStateManager } from '../services/notebookStateManager'
-import {
-    ConnectionStatusBarProvider,
-    ProjectStatusBarProvider,
-    SeparatorStatusBarProvider,
-} from '../providers/notebookStatusBarProviders'
-import { Constants } from '../models/constants'
-
-let connectionProvider: ConnectionStatusBarProvider | undefined
-let projectProvider: ProjectStatusBarProvider | undefined
-let separatorProvider: SeparatorStatusBarProvider | undefined
-
-/**
- * Gets the status bar providers for registration, auto-initializing if needed
- */
-export function getStatusBarProviders(): {
-    connectionProvider: ConnectionStatusBarProvider
-    projectProvider: ProjectStatusBarProvider
-    separatorProvider: SeparatorStatusBarProvider
-} {
-    if (!connectionProvider) {
-        connectionProvider = new ConnectionStatusBarProvider(3, Constants.CONNECTION_COMMAND)
-    }
-    if (!projectProvider) {
-        projectProvider = new ProjectStatusBarProvider(2, Constants.PROJECT_COMMAND)
-    }
-    if (!separatorProvider) {
-        separatorProvider = new SeparatorStatusBarProvider(1)
-    }
-
-    return {
-        connectionProvider,
-        projectProvider,
-        separatorProvider,
-    }
-}
-
-/**
- * Sets the selected connection for a cell and updates the magic command
- */
-export async function setSelectedConnection(cell: vscode.NotebookCell, connectionLabel: string): Promise {
-    notebookStateManager.setSelectedConnection(cell, connectionLabel, true)
-    await notebookStateManager.updateCellWithMagic(cell)
-}
-
-/**
- * Sets the selected project for a cell and updates the magic command
- */
-export async function setSelectedProject(cell: vscode.NotebookCell, projectLabel: string): Promise {
-    notebookStateManager.setSelectedProject(cell, projectLabel)
-    await notebookStateManager.updateCellWithMagic(cell)
-}
-
-/**
- * Shows a quick pick menu for selecting a connection type and sets the connection for the active cell
- */
-export async function showConnectionQuickPick(): Promise {
-    const editor = vscode.window.activeNotebookEditor
-    if (!editor) {
-        return
-    }
-
-    const cell = editor.selection.start !== undefined ? editor.notebook.cellAt(editor.selection.start) : undefined
-    if (!cell) {
-        return
-    }
-
-    await connectionOptionsService.updateConnectionAndProjectOptions()
-
-    const connectionOptions = connectionOptionsService.getConnectionOptionsSync()
-
-    // Sort connections based on preferred connection order
-    const sortedOptions = connectionOptions.sort((a, b) => {
-        // Comparison logic
-        const aIndex = Constants.CONNECTION_QUICK_PICK_ORDER.indexOf(a.label as any)
-        const bIndex = Constants.CONNECTION_QUICK_PICK_ORDER.indexOf(b.label as any)
-
-        // If both are in the priority list, sort by their position
-        if (aIndex !== -1 && bIndex !== -1) {
-            return aIndex - bIndex
-        }
-        // If only 'a' is in the priority list, it comes first
-        if (aIndex !== -1) {
-            return -1
-        }
-        // If only 'b' is in the priority list, it comes first
-        if (bIndex !== -1) {
-            return 1
-        }
-        // If neither is in the priority list, maintain original order
-        return 0
-    })
-
-    const quickPickItems: vscode.QuickPickItem[] = sortedOptions.map((option) => {
-        return {
-            label: option.label,
-            description: `(${option.magic})`,
-            iconPath: new vscode.ThemeIcon('plug'),
-        }
-    })
-
-    const selected = await vscode.window.showQuickPick(quickPickItems, {
-        placeHolder: Constants.CONNECTION_QUICK_PICK_LABEL_PLACEHOLDER,
-    })
-
-    if (selected) {
-        const connectionLabel = selected.detail || selected.label
-        await setSelectedConnection(cell, connectionLabel)
-    }
-}
-
-/**
- * Shows a quick pick menu for selecting a project type and sets the project for the active cell
- */
-export async function showProjectQuickPick(): Promise {
-    const editor = vscode.window.activeNotebookEditor
-    if (!editor) {
-        return
-    }
-
-    const cell = editor.selection.start !== undefined ? editor.notebook.cellAt(editor.selection.start) : undefined
-    if (!cell) {
-        return
-    }
-
-    const connection = notebookStateManager.getSelectedConnection(cell)
-    if (!connection) {
-        return
-    }
-
-    await connectionOptionsService.updateConnectionAndProjectOptions()
-
-    const options = notebookStateManager.getProjectOptionsForConnection(cell)
-    if (options.length === 0) {
-        return
-    }
-
-    const projectQuickPickItems: vscode.QuickPickItem[] = options.map((option) => {
-        return {
-            label: option.project,
-            description: `(${option.connection})`,
-            iconPath: new vscode.ThemeIcon('server'),
-        }
-    })
-
-    const selected = await vscode.window.showQuickPick(projectQuickPickItems, {
-        placeHolder: Constants.PROJECT_QUICK_PICK_LABEL_PLACEHOLDER,
-    })
-
-    if (selected) {
-        if (!selected.label) {
-            return
-        }
-
-        await setSelectedProject(cell, selected.label)
-    }
-}
-
-/**
- * Refreshes the status bar items
- */
-export function refreshStatusBarItems(): void {
-    connectionProvider?.refreshCellStatusBar()
-    projectProvider?.refreshCellStatusBar()
-    separatorProvider?.refreshCellStatusBar()
-}
-
-/**
- * Parses all notebook cells to current cell magics
- */
-export async function parseNotebookCells(): Promise {
-    await connectionOptionsService.updateConnectionAndProjectOptions()
-
-    const editor = vscode.window.activeNotebookEditor
-    if (!editor) {
-        return
-    }
-
-    for (let i = 0; i < editor.notebook.cellCount; i++) {
-        const cell = editor.notebook.cellAt(i)
-
-        if (cell.kind === vscode.NotebookCellKind.Code && cell.document.languageId !== 'markdown') {
-            notebookStateManager.parseCellMagic(cell)
-        }
-    }
-
-    refreshStatusBarItems()
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/index.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/index.ts
deleted file mode 100644
index 0f7f429b5e..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/index.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-export { activate } from './activation'
-
-export * from './models/constants'
-export * from './models/types'
-export * from './services/connectionOptionsService'
-export * from './services/notebookStateManager'
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/constants.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/constants.ts
deleted file mode 100644
index d94d4c9f3f..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/constants.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { ConnectionTypeProperties } from './types'
-
-export const Constants = {
-    // Connection types
-    CONNECTION_TYPE_EMR_EC2: 'SPARK_EMR_EC2',
-    CONNECTION_TYPE_EMR_SERVERLESS: 'SPARK_EMR_SERVERLESS',
-    CONNECTION_TYPE_GLUE: 'SPARK_GLUE',
-    CONNECTION_TYPE_SPARK: 'SPARK',
-    CONNECTION_TYPE_REDSHIFT: 'REDSHIFT',
-    CONNECTION_TYPE_ATHENA: 'ATHENA',
-    CONNECTION_TYPE_IAM: 'IAM',
-
-    // UI labels and placeholders
-    CONNECTION_QUICK_PICK_LABEL_PLACEHOLDER: 'Select Connection',
-    CONNECTION_STATUS_BAR_ITEM_LABEL: 'Select Connection',
-    CONNECTION_STATUS_BAR_ITEM_ICON: '$(plug)',
-    DEFAULT_CONNECTION_STATUS_BAR_ITEM_LABEL: 'Connection',
-    PROJECT_QUICK_PICK_LABEL_PLACEHOLDER: 'Select Compute',
-    PROJECT_STATUS_BAR_ITEM_LABEL: 'Select Compute',
-    PROJECT_STATUS_BAR_ITEM_ICON: '$(server)',
-    DEFAULT_PROJECT_STATUS_BAR_ITEM_LABEL: 'Compute',
-    CONNECTION_QUICK_PICK_ORDER: ['Local Python', 'PySpark', 'ScalaSpark', 'SQL'] as const,
-
-    // Command IDs
-    CONNECTION_COMMAND: 'aws.smus.connectionmagics.selectConnection',
-    PROJECT_COMMAND: 'aws.smus.connectionmagics.selectProject',
-
-    // Magic string literals
-    LOCAL_PYTHON: 'Local Python',
-    PYSPARK: 'PySpark',
-    SCALA_SPARK: 'ScalaSpark',
-    SQL: 'SQL',
-    MAGIC_PREFIX: '%%',
-    LOCAL_MAGIC: '%%local',
-    NAME_FLAG_LONG: '--name',
-    NAME_FLAG_SHORT: '-n',
-    SAGEMAKER_CONNECTION_METADATA_KEY: 'sagemakerConnection',
-    MARKDOWN_LANGUAGE: 'markdown',
-    PROJECT_PYTHON: 'project.python',
-    PROJECT_SPARK_COMPATIBILITY: 'project.spark.compatibility',
-} as const
-
-/**
- * Maps connection types to their display properties
- */
-export const connectionTypePropertiesMap: Record = {
-    [Constants.CONNECTION_TYPE_GLUE]: {
-        labels: ['PySpark', 'SQL'], // Glue supports both PySpark and SQL
-        magic: '%%pyspark',
-        language: 'python',
-        category: 'spark',
-    },
-    [Constants.CONNECTION_TYPE_EMR_EC2]: {
-        labels: ['PySpark', 'SQL'], // EMR supports both PySpark and SQL
-        magic: '%%pyspark',
-        language: 'python',
-        category: 'spark',
-    },
-    [Constants.CONNECTION_TYPE_EMR_SERVERLESS]: {
-        labels: ['PySpark', 'SQL'], // EMR supports both PySpark and SQL
-        magic: '%%pyspark',
-        language: 'python',
-        category: 'spark',
-    },
-    [Constants.CONNECTION_TYPE_REDSHIFT]: {
-        labels: ['SQL'], // Redshift only supports SQL
-        magic: '%%sql',
-        language: 'sql',
-        category: 'sql',
-    },
-    [Constants.CONNECTION_TYPE_ATHENA]: {
-        labels: ['SQL'], // Athena only supports SQL
-        magic: '%%sql',
-        language: 'sql',
-        category: 'sql',
-    },
-}
-
-/**
- * Maps connection labels to their display properties
- */
-export const connectionLabelPropertiesMap: Record<
-    string,
-    { description: string; magic: string; language: string; category: string }
-> = {
-    PySpark: {
-        description: 'Python with Spark',
-        magic: '%%pyspark',
-        language: 'python',
-        category: 'spark',
-    },
-    SQL: {
-        description: 'SQL Query',
-        magic: '%%sql',
-        language: 'sql',
-        category: 'sql',
-    },
-    ScalaSpark: {
-        description: 'Scala with Spark',
-        magic: '%%scalaspark',
-        language: 'python', // Scala is not a supported language mode, defaulting to Python
-        category: 'spark',
-    },
-    'Local Python': {
-        description: 'Python',
-        magic: '%%local',
-        language: 'python',
-        category: 'python',
-    },
-    IAM: {
-        description: 'IAM Connection',
-        magic: '%%iam',
-        language: 'python',
-        category: 'iam',
-    },
-}
-
-/**
- * Maps connection types to their platform display names for grouping
- */
-export const connectionTypeToComputeNameMap: Record = {
-    [Constants.CONNECTION_TYPE_GLUE]: 'Glue',
-    [Constants.CONNECTION_TYPE_REDSHIFT]: 'Redshift',
-    [Constants.CONNECTION_TYPE_ATHENA]: 'Athena',
-    [Constants.CONNECTION_TYPE_EMR_EC2]: 'EMR EC2',
-    [Constants.CONNECTION_TYPE_EMR_SERVERLESS]: 'EMR Serverless',
-}
-
-/**
- * Maps magic commands to their corresponding connection types
- */
-export const magicCommandToConnectionMap: Record = {
-    '%%spark': 'PySpark',
-    '%%pyspark': 'PySpark',
-    '%%scalaspark': 'ScalaSpark',
-    '%%local': 'Local Python',
-    '%%sql': 'SQL',
-} as const
-
-/**
- * Default project names for each connection type
- */
-export const defaultProjectsByConnection: Record = {
-    'Local Python': ['project.python'],
-    PySpark: ['project.spark.compatibility'],
-    ScalaSpark: ['project.spark.compatibility'],
-    SQL: ['project.spark.compatibility'],
-} as const
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/types.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/types.ts
deleted file mode 100644
index b14daab1ce..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/models/types.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-/**
- * SageMaker Connection Summary interface
- */
-export interface SageMakerConnectionSummary {
-    name: string
-    type: string
-}
-
-/**
- * Connection option type definition
- */
-export interface ConnectionOption {
-    label: string
-    description: string
-    magic: string
-    language: string
-    category: string
-}
-
-/**
- * Project option group type definition
- */
-export interface ProjectOptionGroup {
-    connection: string
-    projects: string[]
-}
-
-/**
- * Project option type definition
- */
-export interface ProjectOption {
-    connection: string
-    project: string
-}
-
-/**
- * Connection to project mapping type definition
- */
-export interface ConnectionProjectMapping {
-    connection: string
-    projectOptions: ProjectOptionGroup[]
-}
-
-/**
- * Represents the state of a notebook cell's connection settings
- */
-export interface CellState {
-    connection?: string
-    project?: string
-    isUserSelection?: boolean
-    originalMagicCommand?: string
-    lastParsedContent?: string
-}
-
-/**
- * Maps connection types to their display properties
- */
-export interface ConnectionTypeProperties {
-    labels: string[]
-    magic: string
-    language: string
-    category: string
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/providers/notebookStatusBarProviders.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/providers/notebookStatusBarProviders.ts
deleted file mode 100644
index 8551f61511..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/providers/notebookStatusBarProviders.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { notebookStateManager } from '../services/notebookStateManager'
-import { Constants } from '../models/constants'
-
-/**
- * Abstract base class for notebook status bar providers.
- */
-export abstract class BaseNotebookStatusBarProvider implements vscode.NotebookCellStatusBarItemProvider {
-    protected item: vscode.NotebookCellStatusBarItem
-    protected onDidChangeCellStatusBarItemsEmitter = new vscode.EventEmitter()
-    protected priority: number
-    protected icon?: string
-    protected command?: string
-    protected tooltip?: string
-
-    public constructor(priority: number, icon?: string, command?: string, tooltip?: string) {
-        this.priority = priority
-        this.icon = icon
-        this.command = command
-        this.tooltip = tooltip
-        this.item = new vscode.NotebookCellStatusBarItem('', vscode.NotebookCellStatusBarAlignment.Right)
-        this.item.priority = priority
-    }
-
-    /**
-     * Abstract method that each provider must implement to provide their specific status bar item.
-     */
-    public abstract provideCellStatusBarItems(
-        cell: vscode.NotebookCell,
-        token: vscode.CancellationToken
-    ): vscode.ProviderResult
-
-    /**
-     * Creates a status bar item with the provided text and applies common settings.
-     */
-    protected createStatusBarItem(text: string, isClickable: boolean = true): vscode.NotebookCellStatusBarItem {
-        const displayText = this.icon ? `${this.icon} ${text}` : text
-        const item = new vscode.NotebookCellStatusBarItem(displayText, vscode.NotebookCellStatusBarAlignment.Right)
-        item.priority = this.priority
-
-        if (isClickable && this.command) {
-            item.command = this.command
-            item.tooltip = this.tooltip
-        }
-
-        return item
-    }
-
-    /**
-     * Refreshes the cell status bar items.
-     */
-    public refreshCellStatusBar(): void {
-        this.onDidChangeCellStatusBarItemsEmitter.fire()
-    }
-
-    /**
-     * Event that fires when the cell status bar items have changed.
-     */
-    public get onDidChangeCellStatusBarItems(): vscode.Event {
-        return this.onDidChangeCellStatusBarItemsEmitter.event
-    }
-}
-
-/**
- * Status bar provider for connection selection in notebook cells.
- */
-export class ConnectionStatusBarProvider extends BaseNotebookStatusBarProvider {
-    public constructor(priority: number, command: string) {
-        super(priority, Constants.CONNECTION_STATUS_BAR_ITEM_ICON, command, Constants.CONNECTION_STATUS_BAR_ITEM_LABEL)
-    }
-
-    public provideCellStatusBarItems(
-        cell: vscode.NotebookCell,
-        token: vscode.CancellationToken
-    ): vscode.ProviderResult {
-        // Don't show on non-code or markdown code cells
-        if (cell.kind !== vscode.NotebookCellKind.Code || cell.document.languageId === 'markdown') {
-            return undefined
-        }
-
-        const connection = notebookStateManager.getSelectedConnection(cell)
-
-        const displayText = connection || Constants.DEFAULT_CONNECTION_STATUS_BAR_ITEM_LABEL
-        const item = this.createStatusBarItem(displayText)
-
-        return item
-    }
-}
-
-/**
- * Status bar provider for project selection in notebook cells.
- */
-export class ProjectStatusBarProvider extends BaseNotebookStatusBarProvider {
-    public constructor(priority: number, command: string) {
-        super(priority, Constants.PROJECT_STATUS_BAR_ITEM_ICON, command, Constants.PROJECT_STATUS_BAR_ITEM_LABEL)
-    }
-
-    public provideCellStatusBarItems(
-        cell: vscode.NotebookCell,
-        token: vscode.CancellationToken
-    ): vscode.ProviderResult {
-        // Don't show on non-code or markdown code cells
-        if (cell.kind !== vscode.NotebookCellKind.Code || cell.document.languageId === 'markdown') {
-            return undefined
-        }
-
-        const project = notebookStateManager.getSelectedProject(cell)
-
-        const displayText = project || Constants.DEFAULT_PROJECT_STATUS_BAR_ITEM_LABEL
-        const item = this.createStatusBarItem(displayText)
-
-        return item
-    }
-}
-
-/**
- * Status bar provider for displaying a separator between items in notebook cells.
- */
-export class SeparatorStatusBarProvider extends BaseNotebookStatusBarProvider {
-    public constructor(priority: number, separatorText: string = '|') {
-        super(priority)
-
-        this.item = new vscode.NotebookCellStatusBarItem(separatorText, vscode.NotebookCellStatusBarAlignment.Right)
-        this.item.priority = priority
-    }
-
-    public provideCellStatusBarItems(
-        cell: vscode.NotebookCell,
-        token: vscode.CancellationToken
-    ): vscode.ProviderResult {
-        // Don't show on non-code or markdown code cells
-        if (cell.kind !== vscode.NotebookCellKind.Code || cell.document.languageId === 'markdown') {
-            return undefined
-        }
-
-        return this.item
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/connectionOptionsService.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/connectionOptionsService.ts
deleted file mode 100644
index 9c258536f6..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/connectionOptionsService.ts
+++ /dev/null
@@ -1,293 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import {
-    Constants,
-    connectionTypePropertiesMap,
-    connectionLabelPropertiesMap,
-    connectionTypeToComputeNameMap,
-} from '../models/constants'
-import {
-    ConnectionOption,
-    ProjectOptionGroup,
-    ConnectionProjectMapping,
-    SageMakerConnectionSummary,
-} from '../models/types'
-import { ConnectedSpaceDataZoneClient } from '../client/connectedSpaceDataZoneClient'
-import { getResourceMetadata } from '../../shared/utils/resourceMetadataUtils'
-
-let datazoneClient: ConnectedSpaceDataZoneClient | undefined
-
-/**
- * Gets or creates the module-scoped DataZone client instance
- */
-function getDataZoneClient(): ConnectedSpaceDataZoneClient {
-    if (!datazoneClient) {
-        const resourceMetadata = getResourceMetadata()
-
-        if (!resourceMetadata?.AdditionalMetadata?.DataZoneDomainRegion) {
-            throw new Error('DataZone domain region not found in resource metadata')
-        }
-
-        const region = resourceMetadata.AdditionalMetadata.DataZoneDomainRegion
-        const customEndpoint = resourceMetadata.AdditionalMetadata?.DataZoneEndpoint
-
-        datazoneClient = new ConnectedSpaceDataZoneClient(region, customEndpoint)
-    }
-    return datazoneClient
-}
-
-/**
- * Service for managing connection options and project mappings
- */
-class ConnectionOptionsService {
-    private connectionOptions: ConnectionOption[] = []
-    private projectOptions: ConnectionProjectMapping[] = []
-    private cachedConnections: SageMakerConnectionSummary[] = []
-
-    constructor() {}
-
-    /**
-     * Gets the appropriate connection option for a given label
-     */
-    private getConnectionOptionForLabel(label: string): ConnectionOption | undefined {
-        const labelProps = connectionLabelPropertiesMap[label]
-        if (!labelProps) {
-            return undefined
-        }
-
-        return {
-            label,
-            description: labelProps.description,
-            magic: labelProps.magic,
-            language: labelProps.language,
-            category: labelProps.category,
-        }
-    }
-
-    /**
-     * Gets filtered connections from DataZone, excluding IAM connections and processing SPARK connections
-     */
-    private async getFilteredConnections(forceRefresh: boolean = false): Promise {
-        if (this.cachedConnections.length > 0 && !forceRefresh) {
-            return this.cachedConnections
-        }
-
-        try {
-            const resourceMetadata = getResourceMetadata()
-
-            if (!resourceMetadata?.AdditionalMetadata?.DataZoneDomainId) {
-                throw new Error('DataZone domain ID not found in resource metadata')
-            }
-
-            if (!resourceMetadata?.AdditionalMetadata?.DataZoneProjectId) {
-                throw new Error('DataZone project ID not found in resource metadata')
-            }
-
-            const connections = await getDataZoneClient().listConnections(
-                resourceMetadata.AdditionalMetadata.DataZoneDomainId,
-                resourceMetadata.AdditionalMetadata.DataZoneProjectId
-            )
-
-            const processedConnections: SageMakerConnectionSummary[] = []
-
-            for (const connection of connections) {
-                if (
-                    connection.type === Constants.CONNECTION_TYPE_REDSHIFT ||
-                    connection.type === Constants.CONNECTION_TYPE_ATHENA
-                ) {
-                    processedConnections.push({
-                        name: connection.name || '',
-                        type: connection.type || '',
-                    })
-                } else if (connection.type === Constants.CONNECTION_TYPE_SPARK) {
-                    if ('sparkGlueProperties' in (connection.props || {})) {
-                        processedConnections.push({
-                            name: connection.name || '',
-                            type: Constants.CONNECTION_TYPE_GLUE,
-                        })
-                    } else if (
-                        'sparkEmrProperties' in (connection.props || {}) &&
-                        'computeArn' in (connection.props?.sparkEmrProperties || {})
-                    ) {
-                        const computeArn = connection.props?.sparkEmrProperties?.computeArn || ''
-
-                        if (computeArn.includes('cluster')) {
-                            processedConnections.push({
-                                name: connection.name || '',
-                                type: Constants.CONNECTION_TYPE_EMR_EC2,
-                            })
-                        } else if (computeArn.includes('applications')) {
-                            processedConnections.push({
-                                name: connection.name || '',
-                                type: Constants.CONNECTION_TYPE_EMR_SERVERLESS,
-                            })
-                        }
-                    }
-                }
-            }
-
-            this.cachedConnections = processedConnections
-            return processedConnections
-        } catch (error) {
-            getLogger('smus').error('Failed to list DataZone connections: %s', error as Error)
-            return []
-        }
-    }
-
-    /**
-     * Adds custom Local Python option to the options list
-     */
-    private addLocalPythonOption(options: ConnectionOption[], addedLabels: Set): void {
-        const localPythonOption = this.getConnectionOptionForLabel('Local Python')
-        if (localPythonOption) {
-            options.push(localPythonOption)
-            addedLabels.add('Local Python')
-        }
-    }
-
-    /**
-     * Gets the available connection options, either from DataZone connections or defaults
-     * @returns Array of connection options
-     */
-    public async getConnectionOptions(): Promise {
-        try {
-            const connections = await this.getFilteredConnections()
-
-            if (connections.length === 0) {
-                return []
-            }
-
-            const options: ConnectionOption[] = []
-            const addedLabels = new Set()
-
-            this.addLocalPythonOption(options, addedLabels)
-
-            for (const connection of connections) {
-                const typeProps = connectionTypePropertiesMap[connection.type]
-                if (typeProps) {
-                    for (const label of typeProps.labels) {
-                        if (!addedLabels.has(label)) {
-                            const connectionOption = this.getConnectionOptionForLabel(label)
-                            if (connectionOption) {
-                                options.push(connectionOption)
-                                addedLabels.add(label)
-                            }
-                        }
-                    }
-                }
-            }
-
-            if (addedLabels.has(Constants.PYSPARK) && !addedLabels.has(Constants.SCALA_SPARK)) {
-                const scalaSparkOption = this.getConnectionOptionForLabel(Constants.SCALA_SPARK)
-                if (scalaSparkOption) {
-                    options.push(scalaSparkOption)
-                }
-            }
-
-            return options
-        } catch (error) {
-            getLogger('smus').error('Failed to get connection options: %s', error as Error)
-            return []
-        }
-    }
-
-    /**
-     * Gets the project options for a specific connection type
-     * @param connectionType The connection type
-     * @returns Project options for the connection type
-     */
-    public async getProjectOptionsForConnectionType(connectionType: string): Promise {
-        try {
-            const connections = await this.getFilteredConnections()
-
-            if (connections.length === 0) {
-                return []
-            }
-
-            const effectiveConnectionType = connectionType === 'ScalaSpark' ? 'PySpark' : connectionType
-            const filteredConnections: Record = {}
-
-            for (const connection of connections) {
-                const typeProps = connectionTypePropertiesMap[connection.type]
-
-                if (typeProps && typeProps.labels.includes(effectiveConnectionType)) {
-                    const compute = connectionTypeToComputeNameMap[connection.type] || 'Unknown'
-
-                    if (!filteredConnections[compute]) {
-                        filteredConnections[compute] = []
-                    }
-                    filteredConnections[compute].push(connection.name)
-                }
-            }
-
-            const projectOptions: ProjectOptionGroup[] = []
-            for (const [compute, projects] of Object.entries(filteredConnections)) {
-                projectOptions.push({ connection: compute, projects })
-            }
-
-            return projectOptions
-        } catch (error) {
-            getLogger('smus').error('Failed to get project options: %s', error as Error)
-            return []
-        }
-    }
-
-    /**
-     * Updates the connection and project options from DataZone
-     */
-    public async updateConnectionAndProjectOptions(): Promise {
-        try {
-            this.connectionOptions = await this.getConnectionOptions()
-
-            if (this.connectionOptions.length === 0) {
-                this.projectOptions = []
-                return
-            }
-
-            const newProjectOptions: ConnectionProjectMapping[] = []
-
-            newProjectOptions.push({
-                connection: 'Local Python',
-                projectOptions: [{ connection: 'Local', projects: ['project.python'] }],
-            })
-
-            for (const option of this.connectionOptions) {
-                if (option.label !== 'Local Python') {
-                    const projectOpts = await this.getProjectOptionsForConnectionType(option.label)
-                    if (projectOpts.length > 0) {
-                        newProjectOptions.push({
-                            connection: option.label,
-                            projectOptions: projectOpts,
-                        })
-                    }
-                }
-            }
-
-            this.projectOptions = newProjectOptions
-        } catch (error) {
-            getLogger('smus').error('Failed to update connection and project options: %s', error as Error)
-            this.connectionOptions = []
-            this.projectOptions = []
-        }
-    }
-
-    /**
-     * Gets the current cached connection options
-     */
-    public getConnectionOptionsSync(): ConnectionOption[] {
-        return this.connectionOptions
-    }
-
-    /**
-     * Gets the current cached project options
-     */
-    public getProjectOptionsSync(): ConnectionProjectMapping[] {
-        return this.projectOptions
-    }
-}
-
-export const connectionOptionsService = new ConnectionOptionsService()
diff --git a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/notebookStateManager.ts b/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/notebookStateManager.ts
deleted file mode 100644
index f2d9ec2392..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/connectionMagicsSelector/services/notebookStateManager.ts
+++ /dev/null
@@ -1,422 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { CellState, ProjectOption } from '../models/types'
-import { connectionOptionsService } from './connectionOptionsService'
-import { getLogger } from '../../../shared/logger/logger'
-import { magicCommandToConnectionMap, defaultProjectsByConnection, Constants } from '../models/constants'
-
-/**
- * State manager for tracking notebook cell states and selections
- */
-class NotebookStateManager {
-    private cellStates: Map = new Map()
-
-    constructor() {}
-
-    /**
-     * Gets the cell state for a specific cell
-     */
-    private getCellState(cell: vscode.NotebookCell): CellState {
-        const cellId = cell.document.uri.toString()
-        if (!this.cellStates.has(cellId)) {
-            this.cellStates.set(cellId, {})
-        }
-        return this.cellStates.get(cellId)!
-    }
-
-    /**
-     * Sets metadata on a cell
-     */
-    private async setCellMetadata(cell: vscode.NotebookCell, key: string, value: any): Promise {
-        try {
-            const edit = new vscode.WorkspaceEdit()
-            const notebookEdit = vscode.NotebookEdit.updateCellMetadata(cell.index, {
-                ...cell.metadata,
-                [key]: value,
-            })
-            edit.set(cell.notebook.uri, [notebookEdit])
-            await vscode.workspace.applyEdit(edit)
-        } catch (error) {
-            getLogger('smus').warn('setCellMetadata: Failed to set metadata, falling back to in-memory storage')
-        }
-    }
-
-    /**
-     * Gets the selected connection for a cell
-     */
-    public getSelectedConnection(cell: vscode.NotebookCell): string | undefined {
-        const connection = cell.metadata?.[Constants.SAGEMAKER_CONNECTION_METADATA_KEY] as string
-        if (connection) {
-            return connection
-        }
-
-        const state = this.getCellState(cell)
-        const currentCellContent = cell.document.getText()
-
-        if (!state.connection || (!state.isUserSelection && state.lastParsedContent !== currentCellContent)) {
-            this.parseCellMagic(cell)
-            const updatedState = this.getCellState(cell)
-            updatedState.lastParsedContent = currentCellContent
-
-            return updatedState.connection
-        }
-
-        return state.connection
-    }
-
-    /**
-     * Sets the selected connection for a cell
-     */
-    public setSelectedConnection(
-        cell: vscode.NotebookCell,
-        value: string | undefined,
-        isUserSelection: boolean = false
-    ): void {
-        const state = this.getCellState(cell)
-        const previousConnection = state.connection
-        state.connection = value
-
-        if (isUserSelection) {
-            state.isUserSelection = true
-
-            if (value) {
-                void this.setCellMetadata(cell, Constants.SAGEMAKER_CONNECTION_METADATA_KEY, value)
-            }
-        }
-
-        if (value === Constants.LOCAL_PYTHON || value === undefined) {
-            if (value === Constants.LOCAL_PYTHON && previousConnection !== value) {
-                state.project = undefined
-                this.setDefaultProjectForConnection(cell, Constants.LOCAL_PYTHON)
-            } else if (value === Constants.LOCAL_PYTHON && previousConnection === value) {
-                if (!state.project) {
-                    this.setDefaultProjectForConnection(cell, Constants.LOCAL_PYTHON)
-                }
-            } else {
-                state.project = undefined
-            }
-        } else if (previousConnection !== value) {
-            state.project = undefined
-            this.setDefaultProjectForConnection(cell, value)
-        }
-    }
-
-    /**
-     * Gets the selected project for a cell
-     */
-    public getSelectedProject(cell: vscode.NotebookCell): string | undefined {
-        return this.getCellState(cell).project
-    }
-
-    /**
-     * Sets the selected project for a cell
-     */
-    public setSelectedProject(cell: vscode.NotebookCell, value: string | undefined): void {
-        const state = this.getCellState(cell)
-        state.project = value
-    }
-
-    /**
-     * Gets the magic command for a cell using simplified format for UI operations
-     */
-    public getMagicCommand(cell: vscode.NotebookCell): string | undefined {
-        const connection = this.getSelectedConnection(cell)
-        if (!connection) {
-            return
-        }
-
-        if (connection === Constants.LOCAL_PYTHON) {
-            const state = this.getCellState(cell)
-            const hasLocalMagic = state.originalMagicCommand?.startsWith(Constants.LOCAL_MAGIC)
-
-            if (!hasLocalMagic) {
-                return undefined
-            }
-        }
-
-        const connectionOptions = connectionOptionsService.getConnectionOptionsSync()
-
-        const connectionOption = connectionOptions.find((option) => option.label === connection)
-        if (!connectionOption) {
-            return undefined
-        }
-
-        const project = this.getSelectedProject(cell)
-
-        if (!project) {
-            return connectionOption.magic
-        }
-
-        return `${connectionOption.magic} ${project}`
-    }
-
-    /**
-     * Parses a cell's content to detect magic commands and updates the state manager
-     * @param cell The notebook cell to parse
-     */
-    public parseCellMagic(cell: vscode.NotebookCell): void {
-        if (
-            !cell ||
-            cell.kind !== vscode.NotebookCellKind.Code ||
-            cell.document.languageId === Constants.MARKDOWN_LANGUAGE
-        ) {
-            return
-        }
-
-        const state = this.getCellState(cell)
-        if (state.isUserSelection) {
-            return
-        }
-
-        const cellText = cell.document.getText()
-        const lines = cellText.split('\n')
-
-        const firstLine = lines[0].trim()
-        if (!firstLine.startsWith(Constants.MAGIC_PREFIX)) {
-            this.setSelectedConnection(cell, Constants.LOCAL_PYTHON)
-            return
-        }
-
-        const parsed = this.parseMagicCommandLine(firstLine)
-        if (!parsed) {
-            return
-        }
-
-        const connectionType = magicCommandToConnectionMap[parsed.magic]
-        if (!connectionType) {
-            this.setSelectedConnection(cell, Constants.LOCAL_PYTHON)
-            this.setDefaultProjectForConnection(cell, Constants.LOCAL_PYTHON)
-            return
-        }
-
-        const cellState = this.getCellState(cell)
-        cellState.originalMagicCommand = firstLine
-
-        this.setSelectedConnection(cell, connectionType)
-
-        if (parsed.project) {
-            this.setSelectedProject(cell, parsed.project)
-        } else {
-            this.setDefaultProjectForConnection(cell, connectionType)
-        }
-    }
-
-    /**
-     * Parses a magic command line to extract magic and project parameters
-     * Supports formats: %%magic, %%magic project, %%magic --name project, %%magic -n project
-     */
-    private parseMagicCommandLine(line: string): { magic: string; project?: string } | undefined {
-        const tokens = line.split(/\s+/)
-        if (tokens.length === 0 || !tokens[0].startsWith(Constants.MAGIC_PREFIX)) {
-            return undefined
-        }
-
-        const magic = tokens[0]
-        let project: string | undefined
-
-        if (tokens.length === 2) {
-            // Format: %%magic project
-            project = tokens[1]
-        } else if (tokens.length >= 3) {
-            // Format: %%magic --name project or %%magic -n project
-            const flagIndex = tokens.findIndex(
-                (token) => token === Constants.NAME_FLAG_LONG || token === Constants.NAME_FLAG_SHORT
-            )
-            if (flagIndex !== -1 && flagIndex + 1 < tokens.length) {
-                project = tokens[flagIndex + 1]
-            }
-        }
-
-        return { magic, project }
-    }
-
-    /**
-     * Sets default project for a connection when no explicit project is specified
-     */
-    private setDefaultProjectForConnection(cell: vscode.NotebookCell, connectionType: string): void {
-        const projectOptions = connectionOptionsService.getProjectOptionsSync()
-
-        const mapping = projectOptions.find((option) => option.connection === connectionType)
-        if (!mapping || mapping.projectOptions.length === 0) {
-            return
-        }
-
-        const defaultProjects = defaultProjectsByConnection[connectionType] || []
-
-        for (const defaultProject of defaultProjects) {
-            for (const projectOption of mapping.projectOptions) {
-                if (projectOption.projects.includes(defaultProject)) {
-                    this.setSelectedProject(cell, defaultProject)
-                    return
-                }
-            }
-        }
-
-        const firstProjectOption = mapping.projectOptions[0]
-        if (firstProjectOption.projects.length > 0) {
-            this.setSelectedProject(cell, firstProjectOption.projects[0])
-        }
-    }
-
-    /**
-     * Updates the current cell with the magic command and sets the cell language
-     * @param cell The notebook cell to update
-     */
-    public async updateCellWithMagic(cell: vscode.NotebookCell): Promise {
-        const connection = this.getSelectedConnection(cell)
-        if (!connection) {
-            return
-        }
-
-        const connectionOptions = connectionOptionsService.getConnectionOptionsSync()
-        const connectionOption = connectionOptions.find((option) => option.label === connection)
-        if (!connectionOption) {
-            return
-        }
-
-        try {
-            await vscode.languages.setTextDocumentLanguage(cell.document, connectionOption.language)
-
-            const cellText = cell.document.getText()
-            const lines = cellText.split('\n')
-            const firstLine = lines[0] || ''
-            const isMagicCommand = firstLine.trim().startsWith(Constants.MAGIC_PREFIX)
-
-            let newCellContent = cellText
-
-            if (connection === Constants.LOCAL_PYTHON) {
-                const state = this.getCellState(cell)
-                const hasLocalMagic = state.originalMagicCommand?.startsWith(Constants.LOCAL_MAGIC)
-
-                if (hasLocalMagic) {
-                    const magicCommand = this.getMagicCommand(cell)
-                    if (magicCommand) {
-                        if (isMagicCommand) {
-                            newCellContent = magicCommand + '\n' + lines.slice(1).join('\n')
-                        } else {
-                            newCellContent = magicCommand + '\n' + cellText
-                        }
-                    }
-                } else {
-                    if (isMagicCommand) {
-                        newCellContent = lines.slice(1).join('\n')
-                    }
-                }
-            } else {
-                const magicCommand = this.getMagicCommand(cell)
-
-                if (magicCommand) {
-                    if (!magicCommand.startsWith(Constants.MAGIC_PREFIX)) {
-                        return
-                    }
-
-                    if (isMagicCommand) {
-                        newCellContent = magicCommand + '\n' + lines.slice(1).join('\n')
-                    } else {
-                        newCellContent = magicCommand + '\n' + cellText
-                    }
-                }
-            }
-
-            if (newCellContent !== cellText) {
-                await this.updateCellContent(cell, newCellContent)
-            }
-        } catch (error) {
-            getLogger('smus').error(`Error updating cell with magic command: ${error}`)
-        }
-    }
-
-    /**
-     * Updates the content of a notebook cell using the most appropriate API for the environment
-     * @param cell The notebook cell to update
-     * @param newContent The new content for the cell
-     */
-    private async updateCellContent(cell: vscode.NotebookCell, newContent: string): Promise {
-        try {
-            if (vscode.workspace.applyEdit && (vscode as any).NotebookEdit) {
-                const edit = new vscode.WorkspaceEdit()
-                const notebookUri = cell.notebook.uri
-                const cellIndex = cell.index
-
-                const newCellData = new vscode.NotebookCellData(cell.kind, newContent, cell.document.languageId)
-
-                const notebookEdit = (vscode as any).NotebookEdit.replaceCells(
-                    new vscode.NotebookRange(cellIndex, cellIndex + 1),
-                    [newCellData]
-                )
-                edit.set(notebookUri, [notebookEdit])
-
-                const success = await vscode.workspace.applyEdit(edit)
-                if (success) {
-                    return
-                }
-            }
-        } catch (error) {
-            getLogger('smus').error(
-                `NotebookEdit failed, attempting to update cell content with WorkspaceEdit: ${error}`
-            )
-        }
-
-        try {
-            const edit = new vscode.WorkspaceEdit()
-
-            const fullRange = new vscode.Range(
-                new vscode.Position(0, 0),
-                new vscode.Position(cell.document.lineCount, 0)
-            )
-
-            edit.replace(cell.document.uri, fullRange, newContent)
-
-            const success = await vscode.workspace.applyEdit(edit)
-            if (!success) {
-                getLogger('smus').error('WorkspaceEdit failed to apply')
-            }
-        } catch (error) {
-            getLogger('smus').error(`Failed to update cell content with WorkspaceEdit: ${error}`)
-
-            try {
-                const document = cell.document
-                if (document && 'getText' in document && 'uri' in document) {
-                    const edit = new vscode.WorkspaceEdit()
-                    const fullText = document.getText()
-                    const fullRange = new vscode.Range(document.positionAt(0), document.positionAt(fullText.length))
-                    edit.replace(document.uri, fullRange, newContent)
-                    await vscode.workspace.applyEdit(edit)
-                }
-            } catch (finalError) {
-                getLogger('smus').error(`All cell update methods failed: ${finalError}`)
-            }
-        }
-    }
-
-    /**
-     * Gets the project options for the selected connection in a cell
-     */
-    public getProjectOptionsForConnection(cell: vscode.NotebookCell): ProjectOption[] {
-        const connection = this.getSelectedConnection(cell)
-        if (!connection) {
-            return []
-        }
-
-        const projectOptions = connectionOptionsService.getProjectOptionsSync()
-        const mapping = projectOptions.find((option) => option.connection === connection)
-        if (!mapping) {
-            return []
-        }
-
-        const options: ProjectOption[] = []
-        for (const projectOption of mapping.projectOptions) {
-            for (const project of projectOption.projects) {
-                options.push({ connection: projectOption.connection, project: project })
-            }
-        }
-
-        return options
-    }
-}
-
-export const notebookStateManager = new NotebookStateManager()
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/activation.ts b/packages/core/src/sagemakerunifiedstudio/explorer/activation.ts
deleted file mode 100644
index 2b05e7be27..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/activation.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { ResourceTreeDataProvider } from '../../shared/treeview/resourceTreeDataProvider'
-import {
-    smusLoginCommand,
-    smusLearnMoreCommand,
-    smusSignOutCommand,
-    SageMakerUnifiedStudioRootNode,
-    selectSMUSProject,
-} from './nodes/sageMakerUnifiedStudioRootNode'
-import { openRemoteConnect, stopSpace } from '../../awsService/sagemaker/commands'
-import { SagemakerUnifiedStudioSpaceNode } from './nodes/sageMakerUnifiedStudioSpaceNode'
-import { SageMakerUnifiedStudioProjectNode } from './nodes/sageMakerUnifiedStudioProjectNode'
-import { getLogger } from '../../shared/logger/logger'
-import { setSmusConnectedContext, SmusAuthenticationProvider } from '../auth/providers/smusAuthenticationProvider'
-import { isSmusIamConnection } from '../auth/model'
-import { setupUserActivityMonitoring } from '../../awsService/sagemaker/sagemakerSpace'
-import { telemetry } from '../../shared/telemetry/telemetry'
-import { isSageMaker } from '../../shared/extensionUtilities'
-import { recordSpaceTelemetry } from '../shared/telemetry'
-import { DataZoneClient } from '../shared/client/datazoneClient'
-import { handleCredExpiredError } from '../shared/credentialExpiryHandler'
-
-export async function activate(extensionContext: vscode.ExtensionContext): Promise {
-    // Initialize the SMUS authentication provider
-    const logger = getLogger('smus')
-    logger.debug('Initializing authentication provider')
-    // Create the auth provider instance (this will trigger restore() in the constructor)
-    const smusAuthProvider = SmusAuthenticationProvider.fromContext()
-    await smusAuthProvider.restore()
-    // Set initial auth context after restore
-    void setSmusConnectedContext(smusAuthProvider.isConnected())
-    logger.debug('Authentication provider initialized')
-
-    // Create the SMUS projects tree view
-    const smusRootNode = new SageMakerUnifiedStudioRootNode(smusAuthProvider, extensionContext)
-    const treeDataProvider = new ResourceTreeDataProvider({ getChildren: () => smusRootNode.getChildren() })
-
-    // Register the tree view
-    const treeView = vscode.window.createTreeView('aws.smus.rootView', { treeDataProvider })
-    treeDataProvider.refresh()
-
-    // Register the commands
-    extensionContext.subscriptions.push(
-        smusLoginCommand.register(extensionContext),
-        smusLearnMoreCommand.register(),
-        smusSignOutCommand.register(extensionContext),
-        treeView,
-        vscode.commands.registerCommand('aws.smus.rootView.refresh', () => {
-            treeDataProvider.refresh()
-        }),
-
-        vscode.commands.registerCommand(
-            'aws.smus.projectView',
-            async (projectNode?: SageMakerUnifiedStudioProjectNode) => {
-                return await selectSMUSProject(projectNode)
-            }
-        ),
-
-        vscode.commands.registerCommand('aws.smus.refreshProject', async () => {
-            const projectNode = smusRootNode.getProjectSelectNode()
-            await projectNode.refreshNode()
-        }),
-
-        vscode.commands.registerCommand('aws.smus.refresh', async () => {
-            treeDataProvider.refresh()
-        }),
-
-        vscode.commands.registerCommand('aws.smus.switchProject', async () => {
-            // Get the project node from the root node to ensure we're using the same instance
-            const projectNode = smusRootNode.getProjectSelectNode()
-            return await selectSMUSProject(projectNode)
-        }),
-
-        vscode.commands.registerCommand('aws.smus.stopSpace', async (node: SagemakerUnifiedStudioSpaceNode) => {
-            if (!validateNode(node)) {
-                return
-            }
-            await telemetry.smus_stopSpace.run(async (span) => {
-                try {
-                    await recordSpaceTelemetry(span, node)
-                    await stopSpace(node.resource, extensionContext, node.resource.sageMakerClient)
-                } catch (err) {
-                    await handleCredExpiredError(err)
-                    throw err
-                }
-            })
-        }),
-
-        vscode.commands.registerCommand(
-            'aws.smus.openRemoteConnection',
-            async (node: SagemakerUnifiedStudioSpaceNode) => {
-                if (!validateNode(node)) {
-                    return
-                }
-                await telemetry.smus_openRemoteConnection.run(async (span) => {
-                    try {
-                        await recordSpaceTelemetry(span, node)
-                        await openRemoteConnect(node.resource, extensionContext, node.resource.sageMakerClient)
-                    } catch (err) {
-                        await handleCredExpiredError(err)
-                        throw err
-                    }
-                })
-            }
-        ),
-
-        vscode.commands.registerCommand('aws.smus.reauthenticate', async (connection?: any) => {
-            if (connection) {
-                try {
-                    await smusAuthProvider.reauthenticate(connection)
-                    const projectNode = smusRootNode.getProjectSelectNode()
-                    if (projectNode) {
-                        const project = projectNode.getProject()
-                        if (!project) {
-                            await vscode.commands.executeCommand('aws.smus.switchProject')
-                        }
-                    }
-                    treeDataProvider.refresh()
-
-                    // IAM connections handle their own success messages
-                    // Only show success message for SSO connections
-                    if (!isSmusIamConnection(connection)) {
-                        void vscode.window.showInformationMessage(
-                            'Successfully reauthenticated with SageMaker Unified Studio'
-                        )
-                    }
-                } catch (error) {
-                    // Extract the most detailed error message available
-                    let errorMessage = 'Unknown error'
-                    if (error instanceof Error) {
-                        // Check if this is a ToolkitError with a cause chain
-                        const cause = (error as any).cause
-                        if (cause instanceof Error) {
-                            // Use the cause's message as it contains the detailed validation error
-                            errorMessage = cause.message
-                        } else {
-                            // Fall back to the error's own message
-                            errorMessage = error.message
-                        }
-                    }
-
-                    // Show the detailed error message to the user
-                    void vscode.window.showErrorMessage(`${errorMessage}`)
-                    logger.error('Reauthentication failed: %O', error)
-                }
-            }
-        }),
-        // Dispose DataZoneClient when extension is deactivated
-        { dispose: () => DataZoneClient.dispose() },
-        // Dispose SMUS auth provider when extension is deactivated
-        { dispose: () => smusAuthProvider.dispose() }
-    )
-
-    // Track user activity for autoshutdown feature when in SageMaker Unified Studio environment
-    if (isSageMaker('SMUS-SPACE-REMOTE-ACCESS')) {
-        logger.info('SageMaker Unified Studio environment detected, setting up user activity monitoring')
-        try {
-            await setupUserActivityMonitoring(extensionContext)
-        } catch (error) {
-            logger.error(`Error in UserActivityMonitoring: ${error}`)
-            throw error
-        }
-    } else {
-        logger.info('Not in SageMaker Unified Studio remote environment, skipping user activity monitoring')
-    }
-}
-
-/**
- * Checks if a node  is undefined and shows a warning message if so.
- */
-function validateNode(node: SagemakerUnifiedStudioSpaceNode): boolean {
-    if (!node) {
-        void vscode.window.showWarningMessage('Space information is being refreshed. Please try again shortly.')
-        return false
-    }
-    return true
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.ts
deleted file mode 100644
index 467509cefa..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.ts
+++ /dev/null
@@ -1,333 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneConnection } from '../../shared/client/datazoneClient'
-import { GlueClient, ListEntitiesCommand, DescribeEntityCommand, Entity, Field } from '@aws-sdk/client-glue'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { getIcon } from '../../../shared/icons'
-import { createPlaceholderItem } from '../../../shared/treeview/utils'
-import { createErrorItem, createColumnTreeItem } from './utils'
-import { NO_DATA_FOUND_MESSAGE, NodeType } from './types'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-
-/**
- * Creates a federated connection node
- */
-export async function createFederatedConnectionNode(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string
-): Promise {
-    const logger = getLogger('smus')
-
-    // Check for error message in glue properties
-    // Create error node directly in this case
-    const connectionError = connection.props?.glueProperties?.errorMessage
-    if (connectionError) {
-        return createErrorItem(connectionError, 'glue-error', connection.connectionId)
-    }
-
-    return {
-        id: `federated-${connection.connectionId}`,
-        resource: connection,
-        getTreeItem: () => {
-            const item = new vscode.TreeItem(connection.name, vscode.TreeItemCollapsibleState.Collapsed)
-            item.contextValue = 'federatedConnection'
-            item.iconPath = getIcon('aws-sagemakerunifiedstudio-catalog')
-            item.tooltip = `Federated Connection: ${connection.name}`
-            return item
-        },
-        getChildren: async () => {
-            try {
-                return await getFederatedEntities(connection, connectionCredentialsProvider, region)
-            } catch (err) {
-                logger.error(`Failed to get federated entities: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [
-                    createErrorItem(`Failed to load entities - ${errorMessage}`, 'entities', connection.connectionId),
-                ]
-            }
-        },
-        getParent: () => undefined,
-    }
-}
-
-/**
- * Gets federated entities from Glue API
- */
-async function getFederatedEntities(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string
-): Promise {
-    const awsCredentialProvider = async () => {
-        const credentials = await connectionCredentialsProvider.getCredentials()
-        return {
-            accessKeyId: credentials.accessKeyId,
-            secretAccessKey: credentials.secretAccessKey,
-            sessionToken: credentials.sessionToken,
-            expiration: credentials.expiration,
-        }
-    }
-    const glueClient = new GlueClient({
-        region: region,
-        credentials: awsCredentialProvider,
-    })
-
-    const glueConnectionName = connection?.glueConnectionName
-    if (!glueConnectionName) {
-        return [createErrorItem('No Glue connection name found', 'glue-connection', connection.connectionId)]
-    }
-
-    const allEntities: Entity[] = []
-    let nextToken: string | undefined
-
-    do {
-        const response = await glueClient.send(
-            new ListEntitiesCommand({
-                ConnectionName: glueConnectionName,
-                NextToken: nextToken,
-            })
-        )
-
-        if (response.Entities) {
-            allEntities.push(...response.Entities)
-        }
-        nextToken = response.NextToken
-    } while (nextToken)
-
-    if (allEntities.length === 0) {
-        return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE)]
-    }
-
-    const entityNodes: TreeNode[] = []
-    const tableNodes: TreeNode[] = []
-
-    for (const entity of allEntities) {
-        const nodeType = getGlueNodeType(entity.Category)
-        const isTable = nodeType === NodeType.GLUE_TABLE
-
-        const entityNode = createGlueEntityNode(entity, connection, glueClient, glueConnectionName)
-
-        if (isTable) {
-            tableNodes.push(entityNode)
-        } else {
-            entityNodes.push(entityNode)
-        }
-    }
-
-    // Always group tables under a "Tables" container
-    if (tableNodes.length > 0) {
-        const tablesContainer = createTablesContainer(tableNodes, connection.connectionId)
-        return [...entityNodes, tablesContainer]
-    }
-
-    return entityNodes
-}
-
-/**
- * Creates a Glue entity node
- */
-function createGlueEntityNode(
-    entity: Entity,
-    connection: DataZoneConnection,
-    glueClient: GlueClient,
-    glueConnectionName: string
-): TreeNode {
-    const logger = getLogger('smus')
-    const nodeType = getGlueNodeType(entity.Category)
-    const isTable = nodeType === NodeType.GLUE_TABLE
-
-    return {
-        id: `${connection.connectionId}-${entity.EntityName}`,
-        resource: entity,
-        getTreeItem: () => {
-            const item = new vscode.TreeItem(
-                entity.Label || entity.EntityName || 'Unknown',
-                entity.IsParentEntity || (isTable && !entity.IsParentEntity)
-                    ? vscode.TreeItemCollapsibleState.Collapsed
-                    : vscode.TreeItemCollapsibleState.None
-            )
-            item.contextValue = nodeType
-            item.iconPath = getGlueEntityIcon(nodeType)
-            item.tooltip = `${entity.Category}: ${entity.Label || entity.EntityName}`
-            return item
-        },
-        getChildren: async () => {
-            try {
-                if (entity.IsParentEntity) {
-                    return await getChildEntities(entity, connection, glueClient, glueConnectionName)
-                } else if (isTable) {
-                    return await getTableColumns(entity, glueClient, glueConnectionName)
-                }
-                return []
-            } catch (err) {
-                logger.error(`Failed to get children for entity ${entity.EntityName}: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [
-                    createErrorItem(
-                        `Failed to load children - ${errorMessage}`,
-                        'entity-children',
-                        entity.EntityName || 'unknown'
-                    ),
-                ]
-            }
-        },
-        getParent: () => undefined,
-    }
-}
-
-/**
- * Gets child entities for parent entities
- */
-async function getChildEntities(
-    parentEntity: Entity,
-    connection: DataZoneConnection,
-    glueClient: GlueClient,
-    glueConnectionName: string
-): Promise {
-    const allEntities: Entity[] = []
-    let nextToken: string | undefined
-
-    do {
-        const response = await glueClient.send(
-            new ListEntitiesCommand({
-                ConnectionName: glueConnectionName,
-                ParentEntityName: parentEntity.EntityName,
-                NextToken: nextToken,
-            })
-        )
-
-        if (response.Entities) {
-            allEntities.push(...response.Entities)
-        }
-        nextToken = response.NextToken
-    } while (nextToken)
-
-    if (allEntities.length === 0) {
-        return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE)]
-    }
-
-    const entityNodes: TreeNode[] = []
-    const tableNodes: TreeNode[] = []
-
-    for (const entity of allEntities) {
-        const nodeType = getGlueNodeType(entity.Category)
-        const isTable = nodeType === NodeType.GLUE_TABLE
-        const entityNode = createGlueEntityNode(entity, connection, glueClient, glueConnectionName)
-
-        if (isTable) {
-            tableNodes.push(entityNode)
-        } else {
-            entityNodes.push(entityNode)
-        }
-    }
-
-    // Always group tables under a "Tables" container if there are any
-    if (tableNodes.length > 0) {
-        const tablesContainer = createTablesContainer(
-            tableNodes,
-            `${connection.connectionId}-${parentEntity.EntityName}`
-        )
-        return [...entityNodes, tablesContainer]
-    }
-
-    return entityNodes
-}
-
-/**
- * Gets table columns using DescribeEntity
- */
-async function getTableColumns(
-    entity: Entity,
-    glueClient: GlueClient,
-    glueConnectionName: string
-): Promise {
-    const response = await glueClient.send(
-        new DescribeEntityCommand({
-            ConnectionName: glueConnectionName,
-            EntityName: entity.EntityName,
-        })
-    )
-
-    if (!response.Fields || response.Fields.length === 0) {
-        return [createPlaceholderItem('No columns found')]
-    }
-
-    return response.Fields.map((field) => createColumnNode(field, entity.EntityName || 'unknown'))
-}
-
-/**
- * Creates a column node
- */
-function createColumnNode(field: Field, tableName: string): TreeNode {
-    return {
-        id: `${tableName}-${field.FieldName}`,
-        resource: field,
-        getTreeItem: () => {
-            return createColumnTreeItem(
-                field.Label || field.FieldName || 'Unknown',
-                field.FieldType || 'unknown',
-                NodeType.REDSHIFT_COLUMN
-            )
-        },
-        getChildren: async () => [],
-        getParent: () => undefined,
-    }
-}
-
-/**
- * Creates a tables container node
- */
-function createTablesContainer(tableNodes: TreeNode[], connectionId: string): TreeNode {
-    return {
-        id: `${connectionId}-tables`,
-        resource: {},
-        getTreeItem: () => {
-            const item = new vscode.TreeItem('Tables', vscode.TreeItemCollapsibleState.Collapsed)
-            item.contextValue = NodeType.GLUE_TABLE
-            item.iconPath = new vscode.ThemeIcon('table')
-            return item
-        },
-        getChildren: async () => tableNodes,
-        getParent: () => undefined,
-    }
-}
-
-/**
- * Maps Glue entity category to node type
- */
-function getGlueNodeType(category?: string): NodeType {
-    const lowerCategory = category?.toLowerCase()
-    if (lowerCategory?.includes('schema')) {
-        return NodeType.GLUE_DATABASE
-    } else if (lowerCategory?.includes('table')) {
-        return NodeType.GLUE_TABLE
-    } else if (lowerCategory?.includes('database')) {
-        return NodeType.GLUE_DATABASE
-    }
-    return NodeType.GLUE_CATALOG
-}
-
-/**
- * Gets icon for Glue entity node type
- */
-function getGlueEntityIcon(nodeType: NodeType): vscode.ThemeIcon | any {
-    switch (nodeType) {
-        case NodeType.GLUE_DATABASE:
-            return new vscode.ThemeIcon('database')
-        case NodeType.GLUE_TABLE:
-            return getIcon('aws-redshift-table')
-        case NodeType.GLUE_CATALOG:
-            return getIcon('aws-sagemakerunifiedstudio-catalog')
-        default:
-            return getIcon('vscode-circle-outline')
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.ts
deleted file mode 100644
index 123f92c1eb..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.ts
+++ /dev/null
@@ -1,602 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneConnection } from '../../shared/client/datazoneClient'
-import { Catalog } from '@amzn/glue-catalog-client'
-import { GlueClient } from '../../shared/client/glueClient'
-import { ConnectionClientStore } from '../../shared/client/connectionClientStore'
-import {
-    NODE_ID_DELIMITER,
-    NodeType,
-    NodeData,
-    DATA_DEFAULT_LAKEHOUSE_CONNECTION_NAME_REGEXP,
-    DATA_DEFAULT_ATHENA_CONNECTION_NAME_REGEXP,
-    DATA_DEFAULT_IAM_CONNECTION_NAME_REGEXP,
-    AWS_DATA_CATALOG,
-    DatabaseObjects,
-    NO_DATA_FOUND_MESSAGE,
-} from './types'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-import {
-    getLabel,
-    isLeafNode,
-    getIconForNodeType,
-    getTooltip,
-    createColumnTreeItem,
-    getColumnType,
-    createErrorItem,
-    isRedLakeCatalog,
-    isS3TablesCatalog,
-} from './utils'
-import { createPlaceholderItem } from '../../../shared/treeview/utils'
-import { Column, Database, Table } from '@aws-sdk/client-glue'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { recordDataConnectionTelemetry } from '../../shared/telemetry'
-import { GlueCatalogClient } from '../../shared/client/glueCatalogClient'
-
-/**
- * Lakehouse data node for SageMaker Unified Studio
- */
-export class LakehouseNode implements TreeNode {
-    private childrenNodes: TreeNode[] | undefined
-    private isLoading = false
-    private readonly logger = getLogger('smus')
-
-    constructor(
-        public readonly data: NodeData,
-        private readonly childrenProvider?: (node: LakehouseNode) => Promise
-    ) {}
-
-    public get id(): string {
-        return this.data.id
-    }
-
-    public get resource(): any {
-        return this.data.value || {}
-    }
-
-    public async getChildren(): Promise {
-        // Return cached children if available
-        if (this.childrenNodes && !this.isLoading) {
-            return this.childrenNodes
-        }
-
-        // Return empty array for leaf nodes
-        if (isLeafNode(this.data)) {
-            return []
-        }
-
-        // If we have a children provider, use it
-        if (this.childrenProvider) {
-            try {
-                this.isLoading = true
-                const childrenNodes = await this.childrenProvider(this)
-                this.childrenNodes = childrenNodes
-                this.isLoading = false
-                return this.childrenNodes
-            } catch (err) {
-                this.isLoading = false
-                this.logger.error(`Failed to get children for node ${this.data.id}: ${(err as Error).message}`)
-
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'getChildren', this.id) as LakehouseNode]
-            }
-        }
-
-        return []
-    }
-
-    public async getTreeItem(): Promise {
-        const label = getLabel(this.data)
-        const isLeaf = isLeafNode(this.data)
-
-        // For column nodes, show type as secondary text
-        if (this.data.nodeType === NodeType.REDSHIFT_COLUMN && this.data.value?.type) {
-            return createColumnTreeItem(label, this.data.value.type, this.data.nodeType)
-        }
-
-        const collapsibleState = isLeaf
-            ? vscode.TreeItemCollapsibleState.None
-            : vscode.TreeItemCollapsibleState.Collapsed
-
-        const item = new vscode.TreeItem(label, collapsibleState)
-
-        // Set icon based on node type
-        item.iconPath = getIconForNodeType(this.data.nodeType, this.data.isContainer)
-
-        // Set context value for command enablement
-        item.contextValue = this.data.nodeType
-
-        // Set tooltip
-        item.tooltip = getTooltip(this.data)
-
-        return item
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.data.parent
-    }
-}
-
-/**
- * Creates a Lakehouse connection node
- */
-export function createLakehouseConnectionNode(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string
-): LakehouseNode {
-    const logger = getLogger('smus')
-
-    // Create Glue clients
-    const clientStore = ConnectionClientStore.getInstance()
-    const glueCatalogClient = clientStore.getGlueCatalogClient(
-        connection.connectionId,
-        region,
-        connectionCredentialsProvider
-    )
-    const glueClient = clientStore.getGlueClient(connection.connectionId, region, connectionCredentialsProvider)
-
-    // Create the connection node
-    return new LakehouseNode(
-        {
-            id: connection.connectionId,
-            nodeType: NodeType.CONNECTION,
-            value: { connection },
-            path: {
-                connection: connection.name,
-            },
-        },
-        async (node) => {
-            return telemetry.smus_renderLakehouseNode.run(async (span) => {
-                await recordDataConnectionTelemetry(span, connection, connectionCredentialsProvider)
-                try {
-                    logger.info(`Loading Lakehouse catalogs for connection ${connection.name}`)
-
-                    // Check if this is a default connection
-                    const isDefaultConnection =
-                        DATA_DEFAULT_IAM_CONNECTION_NAME_REGEXP.test(connection.name) ||
-                        DATA_DEFAULT_LAKEHOUSE_CONNECTION_NAME_REGEXP.test(connection.name) ||
-                        DATA_DEFAULT_ATHENA_CONNECTION_NAME_REGEXP.test(connection.name)
-
-                    // Follow the reference pattern with Promise.allSettled
-                    const [awsDataCatalogResult, catalogsResult] = await Promise.allSettled([
-                        // AWS Data Catalog node (only for default connections)
-                        isDefaultConnection
-                            ? Promise.resolve([createAwsDataCatalogNode(node, glueClient)])
-                            : Promise.resolve([]),
-                        // Get catalogs by calling Glue API
-                        getCatalogs(glueCatalogClient, glueClient, node),
-                    ])
-
-                    const awsDataCatalog = awsDataCatalogResult.status === 'fulfilled' ? awsDataCatalogResult.value : []
-                    const apiCatalogs = catalogsResult.status === 'fulfilled' ? catalogsResult.value : []
-                    const errors: LakehouseNode[] = []
-
-                    if (awsDataCatalogResult.status === 'rejected') {
-                        const error = awsDataCatalogResult.reason as Error
-                        const errorMessage = error.message
-                        errors.push(createErrorItem(errorMessage, 'aws-data-catalog', node.id) as LakehouseNode)
-                        await handleCredExpiredError(error, true)
-                    }
-
-                    if (catalogsResult.status === 'rejected') {
-                        const error = catalogsResult.reason as Error
-                        const errorMessage = error.message
-                        errors.push(createErrorItem(errorMessage, 'catalogs', node.id) as LakehouseNode)
-                        await handleCredExpiredError(error, true)
-                    }
-
-                    const allNodes = [...awsDataCatalog, ...apiCatalogs, ...errors]
-                    return allNodes.length > 0
-                        ? allNodes
-                        : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-                } catch (err) {
-                    logger.error(`Failed to get Lakehouse catalogs: ${(err as Error).message}`)
-                    const errorMessage = (err as Error).message
-                    await handleCredExpiredError(err, true)
-                    return [createErrorItem(errorMessage, 'lakehouse-catalogs', node.id) as LakehouseNode]
-                }
-            })
-        }
-    )
-}
-
-/**
- * Creates AWS Data Catalog node for default connections
- */
-function createAwsDataCatalogNode(parent: LakehouseNode, glueClient: GlueClient): LakehouseNode {
-    return new LakehouseNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${AWS_DATA_CATALOG}`,
-            nodeType: NodeType.GLUE_CATALOG,
-            value: {
-                catalog: { name: AWS_DATA_CATALOG, type: 'AWS' },
-                catalogName: AWS_DATA_CATALOG,
-            },
-            path: {
-                ...parent.data.path,
-                catalog: AWS_DATA_CATALOG,
-            },
-            parent,
-        },
-        async (node) => {
-            const allDatabases = []
-            let nextToken: string | undefined
-
-            do {
-                const { databases, nextToken: token } = await glueClient.getDatabases(
-                    undefined,
-                    'ALL',
-                    ['NAME'],
-                    nextToken
-                )
-                allDatabases.push(...databases)
-                nextToken = token
-            } while (nextToken)
-
-            return allDatabases.length > 0
-                ? allDatabases.map((database) => createDatabaseNode(database.Name || '', database, glueClient, node))
-                : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-        }
-    )
-}
-
-export interface CatalogTree {
-    parent: Catalog
-    children?: Catalog[]
-}
-
-/**
- * Builds catalog tree from flat catalog list
- *
- * AWS Glue catalogs can have parent-child relationships, but the API returns them as a flat list.
- * This function reconstructs the hierarchical tree structure needed for proper UI display.
- *
- * Two-pass algorithm is required because:
- * 1. First pass: Create a lookup map of all catalogs by name for O(1) access during relationship building
- * 2. Second pass: Build parent-child relationships by linking catalogs that reference ParentCatalogNames
- *
- * Without the first pass, we'd need O(n²) time to find parent catalogs for each child catalog.
- */
-function buildCatalogTree(catalogs: Catalog[]): CatalogTree[] {
-    const catalogMap: Record = {}
-    const rootCatalogs: CatalogTree[] = []
-
-    // First pass: create a map of all catalogs with their metadata
-    // This allows us to quickly look up any catalog by name when building parent-child relationships in the second pass
-    for (const catalog of catalogs) {
-        if (catalog.Name) {
-            catalogMap[catalog.Name] = { parent: catalog, children: [] }
-        }
-    }
-
-    // Second pass: build the hierarchical tree structure by linking children to their parents
-    // Catalogs with ParentCatalogNames become children, others become root-level catalogs
-    for (const catalog of catalogs) {
-        if (catalog.Name) {
-            if (catalog.ParentCatalogNames && catalog.ParentCatalogNames.length > 0) {
-                const parentName = catalog.ParentCatalogNames[0]
-                const parent = catalogMap[parentName]
-                if (parent) {
-                    if (!parent.children) {
-                        parent.children = []
-                    }
-                    parent.children.push(catalog)
-                }
-            } else {
-                rootCatalogs.push(catalogMap[catalog.Name])
-            }
-        }
-    }
-    rootCatalogs.sort((a, b) => {
-        const timeA = new Date(a.parent.CreateTime ?? 0).getTime()
-        const timeB = new Date(b.parent.CreateTime ?? 0).getTime()
-        return timeA - timeB // For oldest first
-    })
-
-    return rootCatalogs
-}
-
-/**
- * Gets catalogs from the GlueCatalogClient
- */
-async function getCatalogs(
-    glueCatalogClient: GlueCatalogClient,
-    glueClient: GlueClient,
-    parent: LakehouseNode
-): Promise {
-    const allCatalogs = []
-    let nextToken: string | undefined
-
-    do {
-        const { catalogs, nextToken: token } = await glueCatalogClient.getCatalogs(nextToken)
-        allCatalogs.push(...catalogs)
-        nextToken = token
-    } while (nextToken)
-
-    const catalogs = allCatalogs
-    const tree = buildCatalogTree(catalogs)
-
-    return tree.map((catalog) => {
-        const parentCatalog = catalog.parent
-
-        // If parent catalog has children, create node that shows child catalogs
-        if (catalog.children && catalog.children.length > 0) {
-            return new LakehouseNode(
-                {
-                    id: parentCatalog.Name || parentCatalog.CatalogId || '',
-                    nodeType: NodeType.GLUE_CATALOG,
-                    value: {
-                        catalog: parentCatalog,
-                        catalogName: parentCatalog.Name || '',
-                    },
-                    path: {
-                        ...parent.data.path,
-                        catalog: parentCatalog.CatalogId || '',
-                    },
-                    parent,
-                },
-                async (node: LakehouseNode) => {
-                    // Parent catalogs only show child catalogs
-                    const childCatalogs =
-                        catalog.children?.map((childCatalog) =>
-                            createCatalogNode(childCatalog.CatalogId || '', childCatalog, glueClient, node, false)
-                        ) || []
-                    return childCatalogs
-                }
-            )
-        }
-
-        // For catalogs without children, create regular catalog node
-        // For RedLake and S3TableCatalog, they are supposed to have children
-        // Pass isParent as true
-        return createCatalogNode(
-            parentCatalog.CatalogId || '',
-            parentCatalog,
-            glueClient,
-            parent,
-            isRedLakeCatalog(parentCatalog) || isS3TablesCatalog(parentCatalog)
-        )
-    })
-}
-
-/**
- * Creates a catalog node
- */
-function createCatalogNode(
-    catalogId: string,
-    catalog: Catalog,
-    glueClient: GlueClient,
-    parent: LakehouseNode,
-    isParent: boolean = false
-): LakehouseNode {
-    const logger = getLogger('smus')
-
-    return new LakehouseNode(
-        {
-            id: catalog.Name || catalogId,
-            nodeType: NodeType.GLUE_CATALOG,
-            value: {
-                catalog,
-                catalogName: catalog.Name || catalogId,
-            },
-            path: {
-                ...parent.data.path,
-                catalog: catalogId,
-            },
-            parent,
-        },
-        // Child catalogs load databases, parent catalogs will have their children provider overridden
-        isParent
-            ? async () => [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-            : async (node) => {
-                  try {
-                      logger.info(`Loading databases for catalog ${catalogId}`)
-
-                      const allDatabases = []
-                      let nextToken: string | undefined
-
-                      do {
-                          const { databases, nextToken: token } = await glueClient.getDatabases(
-                              catalogId,
-                              undefined,
-                              ['NAME'],
-                              nextToken
-                          )
-                          allDatabases.push(...databases)
-                          nextToken = token
-                      } while (nextToken)
-
-                      return allDatabases.length > 0
-                          ? allDatabases.map((database) =>
-                                createDatabaseNode(database.Name || '', database, glueClient, node)
-                            )
-                          : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-                  } catch (err) {
-                      logger.error(`Failed to get databases for catalog ${catalogId}: ${(err as Error).message}`)
-                      const errorMessage = (err as Error).message
-                      await handleCredExpiredError(err, true)
-                      return [createErrorItem(errorMessage, 'catalog-databases', node.id) as LakehouseNode]
-                  }
-              }
-    )
-}
-
-/**
- * Creates a database node
- */
-function createDatabaseNode(
-    databaseName: string,
-    database: Database,
-    glueClient: GlueClient,
-    parent: LakehouseNode
-): LakehouseNode {
-    const logger = getLogger('smus')
-
-    return new LakehouseNode(
-        {
-            id: databaseName,
-            nodeType: NodeType.GLUE_DATABASE,
-            value: {
-                database,
-                databaseName,
-            },
-            path: {
-                ...parent.data.path,
-                database: databaseName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                logger.info(`Loading tables for database ${databaseName}`)
-
-                const allTables = []
-                let nextToken: string | undefined
-                const catalogId = parent.data.path?.catalog === AWS_DATA_CATALOG ? undefined : parent.data.path?.catalog
-
-                do {
-                    const { tables, nextToken: token } = await glueClient.getTables(
-                        databaseName,
-                        catalogId,
-                        ['NAME', 'TABLE_TYPE'],
-                        nextToken
-                    )
-                    allTables.push(...tables)
-                    nextToken = token
-                } while (nextToken)
-
-                // Group tables and views separately
-                const tables = allTables.filter((table) => table.TableType !== DatabaseObjects.VIRTUAL_VIEW)
-                const views = allTables.filter((table) => table.TableType === DatabaseObjects.VIRTUAL_VIEW)
-
-                const containerNodes: LakehouseNode[] = []
-
-                // Create tables container if there are tables
-                if (tables.length > 0) {
-                    containerNodes.push(createContainerNode(NodeType.GLUE_TABLE, tables, glueClient, node))
-                }
-
-                // Create views container if there are views
-                if (views.length > 0) {
-                    containerNodes.push(createContainerNode(NodeType.GLUE_VIEW, views, glueClient, node))
-                }
-
-                return containerNodes.length > 0
-                    ? containerNodes
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-            } catch (err) {
-                logger.error(`Failed to get tables for database ${databaseName}: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'database-tables', node.id) as LakehouseNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a table node
- */
-function createTableNode(
-    tableName: string,
-    table: Table,
-    glueClient: GlueClient,
-    parent: LakehouseNode
-): LakehouseNode {
-    const logger = getLogger('smus')
-
-    return new LakehouseNode(
-        {
-            id: tableName,
-            nodeType: NodeType.GLUE_TABLE,
-            value: {
-                table,
-                tableName,
-            },
-            path: {
-                ...parent.data.path,
-                table: tableName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                logger.info(`Loading columns for table ${tableName}`)
-
-                const databaseName = node.data.path?.database || ''
-                const catalogId = node.data.path?.catalog === AWS_DATA_CATALOG ? undefined : node.data.path?.catalog
-                const tableDetails = await glueClient.getTable(databaseName, tableName, catalogId)
-                const columns = tableDetails?.StorageDescriptor?.Columns || []
-                const partitions = tableDetails?.PartitionKeys || []
-
-                const allColumns = [...columns, ...partitions]
-                return allColumns.length > 0
-                    ? allColumns.map((column) => createColumnNode(column.Name || '', column, node))
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-            } catch (err) {
-                logger.error(`Failed to get columns for table ${tableName}: ${(err as Error).message}`)
-                await handleCredExpiredError(err)
-                return []
-            }
-        }
-    )
-}
-
-/**
- * Creates a column node
- */
-function createColumnNode(columnName: string, column: Column, parent: LakehouseNode): LakehouseNode {
-    const columnType = getColumnType(column?.Type)
-
-    return new LakehouseNode({
-        id: `${parent.id}${NODE_ID_DELIMITER}${columnName}`,
-        nodeType: NodeType.REDSHIFT_COLUMN,
-        value: {
-            name: columnName,
-            type: columnType,
-        },
-        path: {
-            ...parent.data.path,
-            column: columnName,
-        },
-        parent,
-    })
-}
-
-/**
- * Creates a container node for grouping objects by type
- */
-function createContainerNode(
-    nodeType: NodeType,
-    items: Table[],
-    glueClient: GlueClient,
-    parent: LakehouseNode
-): LakehouseNode {
-    return new LakehouseNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${nodeType}-container`,
-            nodeType: nodeType,
-            value: {
-                items,
-            },
-            path: parent.data.path,
-            parent,
-            isContainer: true,
-        },
-        async (node) => {
-            // Map items to nodes
-            return items.length > 0
-                ? items.map((item) => createTableNode(item.Name || '', item, glueClient, node))
-                : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as LakehouseNode]
-        }
-    )
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.ts
deleted file mode 100644
index 45d82c44ec..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.ts
+++ /dev/null
@@ -1,1041 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneConnection } from '../../shared/client/datazoneClient'
-import { ConnectionConfig, createRedshiftConnectionConfig } from '../../shared/client/sqlWorkbenchClient'
-import { ConnectionClientStore } from '../../shared/client/connectionClientStore'
-import { NODE_ID_DELIMITER, NodeType, ResourceType, NodeData, NO_DATA_FOUND_MESSAGE } from './types'
-import {
-    getLabel,
-    isLeafNode,
-    getIconForNodeType,
-    createColumnTreeItem,
-    isRedLakeDatabase,
-    getTooltip,
-    getColumnType,
-    createErrorItem,
-} from './utils'
-import { createPlaceholderItem } from '../../../shared/treeview/utils'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { Catalog } from '@amzn/glue-catalog-client'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { recordDataConnectionTelemetry } from '../../shared/telemetry'
-
-/**
- * Redshift data node for SageMaker Unified Studio
- */
-export class RedshiftNode implements TreeNode {
-    private childrenNodes: TreeNode[] | undefined
-    private isLoading = false
-    private readonly logger = getLogger('smus')
-
-    constructor(
-        public readonly data: NodeData,
-        private readonly childrenProvider?: (node: RedshiftNode) => Promise
-    ) {}
-
-    public get id(): string {
-        return this.data.id
-    }
-
-    public get resource(): any {
-        return this.data.value || {}
-    }
-
-    public async getChildren(): Promise {
-        // Return cached children if available
-        if (this.childrenNodes && !this.isLoading) {
-            return this.childrenNodes
-        }
-
-        // Return empty array for leaf nodes
-        if (isLeafNode(this.data)) {
-            return []
-        }
-
-        // If we have a children provider, use it
-        if (this.childrenProvider) {
-            try {
-                this.isLoading = true
-                const childrenNodes = await this.childrenProvider(this)
-                this.childrenNodes = childrenNodes
-                this.isLoading = false
-                return this.childrenNodes
-            } catch (err) {
-                this.isLoading = false
-                this.logger.error(`Failed to get children for node ${this.data.id}: ${(err as Error).message}`)
-
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'getChildren', this.id) as RedshiftNode]
-            }
-        }
-
-        return []
-    }
-
-    public async getTreeItem(): Promise {
-        const label = getLabel(this.data)
-        const isLeaf = isLeafNode(this.data)
-
-        // For column nodes, create a TreeItem with label and description (column type)
-        if (this.data.nodeType === NodeType.REDSHIFT_COLUMN && this.data.value?.type) {
-            return createColumnTreeItem(label, this.data.value.type, this.data.nodeType)
-        }
-
-        // For other nodes, use standard TreeItem
-        const collapsibleState = isLeaf
-            ? vscode.TreeItemCollapsibleState.None
-            : vscode.TreeItemCollapsibleState.Collapsed
-
-        const item = new vscode.TreeItem(label, collapsibleState)
-
-        // Set icon based on node type
-        item.iconPath = getIconForNodeType(this.data.nodeType, this.data.isContainer)
-
-        // Set context value for command enablement
-        item.contextValue = this.data.nodeType
-
-        // Set tooltip
-        item.tooltip = getTooltip(this.data)
-
-        return item
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.data.parent
-    }
-}
-
-/**
- * Creates a Redshift connection node
- */
-export function createRedshiftConnectionNode(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider
-): RedshiftNode {
-    const logger = getLogger('smus')
-    return new RedshiftNode(
-        {
-            id: connection.connectionId,
-            nodeType: NodeType.CONNECTION,
-            value: { connection, connectionCredentialsProvider },
-            path: {
-                connection: connection.name,
-            },
-        },
-        async (node) => {
-            return telemetry.smus_renderRedshiftNode.run(async (span) => {
-                logger.info(`Loading Redshift resources for connection ${connection.name}`)
-                await recordDataConnectionTelemetry(span, connection, connectionCredentialsProvider)
-
-                const connectionParams = extractConnectionParams(connection)
-                if (!connectionParams) {
-                    return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-                }
-
-                const isGlueCatalogDatabase = isRedLakeDatabase(connectionParams.database)
-
-                // Create connection config with all available information
-                const connectionConfig = await createRedshiftConnectionConfig(
-                    connectionParams.host,
-                    connectionParams.database,
-                    connectionParams.accountId,
-                    connectionParams.region,
-                    connectionParams.secretArn,
-                    isGlueCatalogDatabase
-                )
-
-                // Wake up the database with a simple query
-                await wakeUpDatabase(
-                    connectionConfig,
-                    connectionParams.region,
-                    connectionCredentialsProvider,
-                    connection
-                )
-
-                const clientStore = ConnectionClientStore.getInstance()
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    connection.connectionId,
-                    connectionParams.region,
-                    connectionCredentialsProvider
-                )
-
-                // Fetch Glue catalogs for filtering purposes only
-                // This will help determine which catalogs are accessible within the project
-                let glueCatalogs: Catalog[] = []
-                try {
-                    glueCatalogs = await listGlueCatalogs(
-                        connection.connectionId,
-                        connectionParams.region,
-                        connectionCredentialsProvider
-                    )
-                } catch (err) {
-                    logger.warn(`Failed to fetch Glue catalogs for filtering: ${(err as Error).message}`)
-                }
-
-                // Fetch databases and catalogs using getResources
-                const [databasesResult, catalogsResult] = await Promise.allSettled([
-                    fetchResources(sqlClient, connectionConfig, ResourceType.DATABASE),
-                    fetchResources(sqlClient, connectionConfig, ResourceType.CATALOG),
-                ])
-
-                const databases = databasesResult.status === 'fulfilled' ? databasesResult.value : []
-                const catalogs = catalogsResult.status === 'fulfilled' ? catalogsResult.value : []
-                const allNodes: RedshiftNode[] = []
-
-                // Filter databases
-                const filteredDatabases = databases.filter(
-                    (r: any) =>
-                        r.type === ResourceType.DATABASE ||
-                        r.type === ResourceType.EXTERNAL_DATABASE ||
-                        r.type === ResourceType.SHARED_DATABASE
-                )
-
-                // Filter catalogs using listGlueCatalogs results
-                const filteredCatalogs = catalogs.filter((catalog: any) => {
-                    if (catalog.displayName?.toLowerCase() === 'awsdatacatalog') {
-                        return true // Always include AWS Data Catalog
-                    }
-                    // Filter using Glue catalogs list
-                    return glueCatalogs.some((glueCatalog) => catalog.displayName?.endsWith(glueCatalog.Name ?? ''))
-                })
-
-                // Add database nodes
-                if (filteredDatabases.length === 0) {
-                    if (databasesResult.status === 'rejected') {
-                        const error = databasesResult.reason as Error
-                        const errorMessage = `Failed to fetch databases - ${databasesResult.reason?.message || databasesResult.reason}.`
-                        allNodes.push(createErrorItem(errorMessage, 'databases', node.id) as RedshiftNode)
-                        await handleCredExpiredError(error, true)
-                    } else {
-                        allNodes.push(createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode)
-                    }
-                } else {
-                    allNodes.push(
-                        ...filteredDatabases.map((db: any) =>
-                            createDatabaseNode(db.displayName, connectionConfig, node)
-                        )
-                    )
-                }
-
-                // Add catalog nodes
-                if (filteredCatalogs.length === 0) {
-                    if (catalogsResult.status === 'rejected') {
-                        const error = catalogsResult.reason as Error
-                        const errorMessage = `Failed to fetch catalogs - ${catalogsResult.reason?.message || catalogsResult.reason}`
-                        allNodes.push(createErrorItem(errorMessage, 'catalogs', node.id) as RedshiftNode)
-                        await handleCredExpiredError(error, true)
-                    } else {
-                        allNodes.push(createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode)
-                    }
-                } else {
-                    allNodes.push(
-                        ...filteredCatalogs.map((catalog: any) =>
-                            createCatalogNode(
-                                catalog.displayName || catalog.identifier || '',
-                                catalog,
-                                connectionConfig,
-                                node
-                            )
-                        )
-                    )
-                }
-
-                return allNodes
-            })
-        }
-    )
-}
-
-/**
- * Extracts connection parameters from DataZone connection
- */
-function extractConnectionParams(connection: DataZoneConnection) {
-    const redshiftProps = connection.props?.redshiftProperties || {}
-    const jdbcConnection = connection.props?.jdbcConnection || {}
-
-    let host = jdbcConnection.host
-    if (!host && jdbcConnection.jdbcUrl) {
-        // Example: jdbc:redshift://test-cluster.123456789012.us-east-1.redshift.amazonaws.com:5439/dev
-        // match[0] = entire URL, match[1] = host, match[2] = port, match[3] = database
-        const match = jdbcConnection.jdbcUrl.match(/jdbc:redshift:\/\/([^:]+):(\d+)\/(.+)/)
-        if (match) {
-            host = match[1]
-        }
-    }
-
-    const database = jdbcConnection.dbname || redshiftProps.databaseName
-    const secretArn = jdbcConnection.secretId || redshiftProps.credentials?.secretArn
-    const accountId = connection.location?.awsAccountId
-    const region = connection.location?.awsRegion
-
-    if (!host || !database || !accountId || !region) {
-        return undefined
-    }
-
-    return { host, database, secretArn, accountId, region }
-}
-
-/**
- * Wake up the database with a simple query
- */
-async function wakeUpDatabase(
-    connectionConfig: ConnectionConfig,
-    region: string,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    connection: DataZoneConnection
-) {
-    const logger = getLogger('smus')
-    const clientStore = ConnectionClientStore.getInstance()
-    const sqlClient = clientStore.getSQLWorkbenchClient(connection.connectionId, region, connectionCredentialsProvider)
-    try {
-        await sqlClient.executeQuery(connectionConfig, 'select 1 from sys_query_history limit 1;')
-    } catch (e) {
-        logger.debug(`Wake-up query failed: ${(e as Error).message}`)
-    }
-}
-
-/**
- * Creates a database node
- */
-function createDatabaseNode(
-    databaseName: string,
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    const logger = getLogger('smus')
-
-    return new RedshiftNode(
-        {
-            id: databaseName,
-            nodeType: NodeType.REDSHIFT_DATABASE,
-            value: {
-                database: databaseName,
-                connectionConfig,
-                identifier: databaseName,
-                type: ResourceType.DATABASE,
-                childObjectTypes: [ResourceType.SCHEMA, ResourceType.EXTERNAL_SCHEMA, ResourceType.SHARED_SCHEMA],
-            },
-            path: {
-                ...parent.data.path,
-                database: databaseName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                // Get the original credentials from the root connection node
-                const rootCredentials = getRootCredentials(parent)
-
-                // Create SQL client with the original credentials
-                const clientStore = ConnectionClientStore.getInstance()
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    connectionConfig.id,
-                    connectionConfig.id.split(':')[3], // region
-                    rootCredentials
-                )
-
-                // Update connection config with the database
-                const dbConnectionConfig = {
-                    ...connectionConfig,
-                    database: databaseName,
-                }
-
-                // Get schemas
-                const allResources = []
-                let nextToken: string | undefined
-
-                do {
-                    const response = await sqlClient.getResources({
-                        connection: dbConnectionConfig,
-                        resourceType: ResourceType.SCHEMA,
-                        includeChildren: true,
-                        maxItems: 100,
-                        parents: [
-                            {
-                                parentId: databaseName,
-                                parentType: ResourceType.DATABASE,
-                            },
-                        ],
-                        forceRefresh: true,
-                        pageToken: nextToken,
-                    })
-                    allResources.push(...(response.resources || []))
-                    nextToken = response.nextToken
-                } while (nextToken)
-
-                const schemas = allResources.filter(
-                    (r: any) =>
-                        r.type === ResourceType.SCHEMA ||
-                        r.type === ResourceType.EXTERNAL_SCHEMA ||
-                        r.type === ResourceType.SHARED_SCHEMA
-                )
-
-                if (schemas.length === 0) {
-                    return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-                }
-
-                // Map schemas to nodes
-                return schemas.map((schema: any) => createSchemaNode(schema.displayName, dbConnectionConfig, node))
-            } catch (err) {
-                logger.error(`Failed to get schemas: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'schemas', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a schema node
- */
-function createSchemaNode(schemaName: string, connectionConfig: ConnectionConfig, parent: RedshiftNode): RedshiftNode {
-    const logger = getLogger('smus')
-
-    return new RedshiftNode(
-        {
-            id: schemaName,
-            nodeType: NodeType.REDSHIFT_SCHEMA,
-            value: {
-                schema: schemaName,
-                connectionConfig,
-                identifier: schemaName,
-                type: ResourceType.SCHEMA,
-                childObjectTypes: [
-                    ResourceType.TABLE,
-                    ResourceType.VIEW,
-                    ResourceType.FUNCTION,
-                    ResourceType.STORED_PROCEDURE,
-                    ResourceType.EXTERNAL_TABLE,
-                    ResourceType.CATALOG_TABLE,
-                    ResourceType.DATA_CATALOG_TABLE,
-                ],
-            },
-            path: {
-                ...parent.data.path,
-                schema: schemaName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                // Get the original credentials from the root connection node
-                const rootCredentials = getRootCredentials(parent)
-
-                // Create SQL client with the original credentials
-                const clientStore = ConnectionClientStore.getInstance()
-                const rootConnection = getRootConnection(parent)
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    rootConnection.connectionId,
-                    connectionConfig.id.split(':')[3], // region
-                    rootCredentials
-                )
-
-                // Get schema objects
-                // Make sure we're using the correct database in the connection config
-                const schemaConnectionConfig = {
-                    ...connectionConfig,
-                    database: parent.data.path?.database || connectionConfig.database,
-                }
-
-                // Create request params object for logging
-                const requestParams = {
-                    connection: schemaConnectionConfig,
-                    resourceType: ResourceType.TABLE,
-                    includeChildren: true,
-                    maxItems: 100,
-                    parents: [
-                        {
-                            parentId: schemaName,
-                            parentType: ResourceType.SCHEMA,
-                        },
-                        {
-                            parentId: schemaConnectionConfig.database,
-                            parentType: ResourceType.DATABASE,
-                        },
-                    ],
-                    forceRefresh: true,
-                }
-
-                const allResources = []
-                let nextToken: string | undefined
-
-                do {
-                    const response = await sqlClient.getResources({
-                        ...requestParams,
-                        pageToken: nextToken,
-                    })
-                    allResources.push(...(response.resources || []))
-                    nextToken = response.nextToken
-                } while (nextToken)
-
-                // Group resources by type
-                const tables = allResources.filter(
-                    (r: any) =>
-                        r.type === ResourceType.TABLE ||
-                        r.type === ResourceType.EXTERNAL_TABLE ||
-                        r.type === ResourceType.CATALOG_TABLE ||
-                        r.type === ResourceType.DATA_CATALOG_TABLE
-                )
-                const views = allResources.filter((r: any) => r.type === ResourceType.VIEW)
-                const functions = allResources.filter((r: any) => r.type === ResourceType.FUNCTION)
-                const procedures = allResources.filter((r: any) => r.type === ResourceType.STORED_PROCEDURE)
-
-                // Create container nodes for each type
-                const containerNodes: RedshiftNode[] = []
-
-                // Tables container
-                if (tables.length > 0) {
-                    containerNodes.push(createContainerNode(NodeType.REDSHIFT_TABLE, tables, connectionConfig, node))
-                }
-
-                // Views container
-                if (views.length > 0) {
-                    containerNodes.push(createContainerNode(NodeType.REDSHIFT_VIEW, views, connectionConfig, node))
-                }
-
-                // Functions container
-                if (functions.length > 0) {
-                    containerNodes.push(
-                        createContainerNode(NodeType.REDSHIFT_FUNCTION, functions, connectionConfig, node)
-                    )
-                }
-
-                // Stored procedures container
-                if (procedures.length > 0) {
-                    containerNodes.push(
-                        createContainerNode(NodeType.REDSHIFT_STORED_PROCEDURE, procedures, connectionConfig, node)
-                    )
-                }
-
-                if (containerNodes.length === 0) {
-                    return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-                }
-
-                return containerNodes
-            } catch (err) {
-                logger.error(`Failed to get schema contents: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'schema-contents', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a container node for grouping objects by type
- */
-function createContainerNode(
-    nodeType: NodeType,
-    resources: any[],
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    return new RedshiftNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${nodeType}-container`,
-            nodeType: nodeType,
-            value: {
-                connectionConfig,
-                resources,
-            },
-            path: parent.data.path,
-            parent,
-            isContainer: true,
-        },
-        async (node) => {
-            // Map resources to nodes
-            if (nodeType === NodeType.REDSHIFT_TABLE && parent.data.value?.type === ResourceType.CATALOG_DATABASE) {
-                // For catalog tables, use catalog table node
-                return resources.length > 0
-                    ? resources.map((resource: any) =>
-                          createCatalogTableNode(resource.displayName, resource, connectionConfig, node)
-                      )
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-            }
-            return resources.length > 0
-                ? resources.map((resource: any) =>
-                      createObjectNode(resource.displayName, nodeType, resource, connectionConfig, node)
-                  )
-                : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-        }
-    )
-}
-
-/**
- * Creates an object node (table, view, function, etc.)
- */
-function createObjectNode(
-    name: string,
-    nodeType: NodeType,
-    resource: any,
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    const logger = getLogger('smus')
-
-    return new RedshiftNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${name}`,
-            nodeType: nodeType,
-            value: {
-                ...resource,
-                connectionConfig,
-            },
-            path: {
-                ...parent.data.path,
-                [nodeType]: name,
-            },
-            parent,
-        },
-        async (node) => {
-            // Only tables have columns
-            if (nodeType !== NodeType.REDSHIFT_TABLE) {
-                return []
-            }
-
-            try {
-                // Get the original credentials from the root connection node
-                const rootCredentials = getRootCredentials(parent)
-
-                // Create SQL client with the original credentials
-                const clientStore = ConnectionClientStore.getInstance()
-                const rootConnection = getRootConnection(parent)
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    rootConnection.connectionId,
-                    connectionConfig.id.split(':')[3], // region
-                    rootCredentials
-                )
-
-                // Get schema and database from path
-                const schemaName = node.data.path?.schema
-                const databaseName = node.data.path?.database
-                const tableName = node.data.path?.table
-
-                if (!schemaName || !databaseName || !tableName) {
-                    logger.error('Missing schema, database, or table name in path')
-                    return []
-                }
-
-                // Create request params for getResources to get columns
-                const requestParams = {
-                    connection: connectionConfig,
-                    resourceType: ResourceType.COLUMNS,
-                    includeChildren: true,
-                    maxItems: 100,
-                    parents: [
-                        {
-                            parentId: tableName,
-                            parentType: ResourceType.TABLE,
-                        },
-                        {
-                            parentId: schemaName,
-                            parentType: ResourceType.SCHEMA,
-                        },
-                        {
-                            parentId: databaseName,
-                            parentType: ResourceType.DATABASE,
-                        },
-                    ],
-                    forceRefresh: true,
-                }
-
-                // Call getResources to get columns
-                const allColumns = []
-                let nextToken: string | undefined
-
-                do {
-                    const response = await sqlClient.getResources({
-                        ...requestParams,
-                        pageToken: nextToken,
-                    })
-                    allColumns.push(...(response.resources || []))
-                    nextToken = response.nextToken
-                } while (nextToken)
-
-                // Create column nodes from API response
-                return allColumns.length > 0
-                    ? allColumns.map((column: any) => {
-                          // Extract column type from resourceMetadata
-                          let columnType = 'UNKNOWN'
-                          if (column.resourceMetadata && Array.isArray(column.resourceMetadata)) {
-                              const typeMetadata = column.resourceMetadata.find(
-                                  (meta: any) => meta.key === 'COLUMN_TYPE'
-                              )
-                              if (typeMetadata) {
-                                  columnType = typeMetadata.value
-                              }
-                          }
-
-                          columnType = getColumnType(columnType)
-
-                          return createColumnNode(
-                              column.displayName,
-                              {
-                                  name: column.displayName,
-                                  type: columnType,
-                              },
-                              connectionConfig,
-                              node
-                          )
-                      })
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-            } catch (err) {
-                logger.error(`Failed to get columns: ${(err as Error).message}`)
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'columns', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a column node
- */
-function createColumnNode(
-    name: string,
-    columnInfo: { name: string; type: string },
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    return new RedshiftNode({
-        id: `${parent.id}${NODE_ID_DELIMITER}${name}`,
-        nodeType: NodeType.REDSHIFT_COLUMN,
-        value: {
-            name,
-            type: columnInfo.type,
-            connectionConfig,
-        },
-        path: {
-            ...parent.data.path,
-            column: name,
-        },
-        parent,
-    })
-}
-
-/**
- * Gets the root connection from a node
- */
-function getRootConnection(node: RedshiftNode): DataZoneConnection {
-    // Start with the current node
-    let currentNode = node
-
-    // Traverse up to the root connection node
-    while (currentNode.data.parent) {
-        currentNode = currentNode.data.parent
-    }
-
-    // Get connection from the root node
-    return currentNode.data.value?.connection
-}
-
-/**
- * Gets the original credentials from the root connection node
- */
-function getRootCredentials(node: RedshiftNode): ConnectionCredentialsProvider {
-    // Start with the current node
-    let currentNode = node
-
-    // Traverse up to the root connection node
-    while (currentNode.data.parent) {
-        currentNode = currentNode.data.parent
-    }
-
-    // Get credentials from the root node
-    const credentials = currentNode.data.value?.connectionCredentialsProvider
-
-    // Return credentials or fallback to dummy credentials
-    return (
-        credentials || {
-            accessKeyId: 'dummy',
-            secretAccessKey: 'dummy',
-        }
-    )
-}
-
-/**
- * Fetch glue catalogs, this will help determine which catalogs are accessible within the project
- */
-async function listGlueCatalogs(
-    connectionId: string,
-    region: string,
-    connectionCredentialsProvider: ConnectionCredentialsProvider
-): Promise {
-    const clientStore = ConnectionClientStore.getInstance()
-    const glueCatalogClient = clientStore.getGlueCatalogClient(connectionId, region, connectionCredentialsProvider)
-
-    const allCatalogs = []
-    let nextToken: string | undefined
-
-    do {
-        const { catalogs, nextToken: token } = await glueCatalogClient.getCatalogs(nextToken)
-        allCatalogs.push(...catalogs)
-        nextToken = token
-    } while (nextToken)
-
-    return allCatalogs
-}
-
-/**
- * Main logic to fetch catalog and database resources using getResources
- */
-async function fetchResources(
-    sqlClient: any,
-    connectionConfig: ConnectionConfig,
-    resourceType: ResourceType,
-    parents: any[] = []
-): Promise {
-    const allResources = []
-    let nextToken: string | undefined
-
-    do {
-        const requestParams = {
-            connection: connectionConfig,
-            resourceType,
-            includeChildren: true,
-            maxItems: 100,
-            parents,
-            forceRefresh: true,
-            pageToken: nextToken,
-        }
-        const response = await sqlClient.getResources(requestParams)
-        allResources.push(...(response.resources || []))
-        nextToken = response.nextToken
-    } while (nextToken)
-
-    return allResources
-}
-
-/**
- * Creates a catalog database node
- */
-function createCatalogDatabaseNode(
-    databaseName: string,
-    database: any,
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    return new RedshiftNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${databaseName}`,
-            nodeType: NodeType.REDSHIFT_CATALOG_DATABASE,
-            value: {
-                ...database,
-                connectionConfig,
-                identifier: databaseName,
-                type: ResourceType.CATALOG_DATABASE,
-            },
-            path: {
-                ...parent.data.path,
-                database: databaseName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                const rootCredentials = getRootCredentials(parent)
-                const clientStore = ConnectionClientStore.getInstance()
-                const rootConnection = getRootConnection(parent)
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    rootConnection.connectionId,
-                    connectionConfig.id.split(':')[3],
-                    rootCredentials
-                )
-
-                // Use getResources to fetch tables within this catalog database
-                const tables = await fetchResources(sqlClient, connectionConfig, ResourceType.CATALOG_TABLE, [
-                    {
-                        parentId: database.identifier,
-                        parentType: ResourceType.CATALOG_DATABASE,
-                    },
-                    {
-                        parentId: parent.data.value?.catalog?.identifier || parent.data.path?.catalog,
-                        parentType: ResourceType.CATALOG,
-                    },
-                ])
-
-                if (tables.length === 0) {
-                    return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-                }
-
-                // Create container node for tables
-                return [createContainerNode(NodeType.REDSHIFT_TABLE, tables, connectionConfig, node)]
-            } catch (err) {
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'catalog-tables', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a catalog table node
- */
-function createCatalogTableNode(
-    tableName: string,
-    table: any,
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    return new RedshiftNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${tableName}`,
-            nodeType: NodeType.REDSHIFT_TABLE,
-            value: {
-                ...table,
-                connectionConfig,
-            },
-            path: {
-                ...parent.data.path,
-                table: tableName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                const rootCredentials = getRootCredentials(parent)
-                const clientStore = ConnectionClientStore.getInstance()
-                const rootConnection = getRootConnection(parent)
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    rootConnection.connectionId,
-                    connectionConfig.id.split(':')[3],
-                    rootCredentials
-                )
-
-                // Use getResources to fetch columns within this catalog table
-                // Need to traverse up to find the actual database and catalog nodes
-                let databaseNode = parent
-                while (databaseNode && databaseNode.data.nodeType !== NodeType.REDSHIFT_CATALOG_DATABASE) {
-                    databaseNode = databaseNode.data.parent
-                }
-
-                let catalogNode = databaseNode?.data.parent
-                while (catalogNode && catalogNode.data.nodeType !== NodeType.REDSHIFT_CATALOG) {
-                    catalogNode = catalogNode.data.parent
-                }
-
-                const parents = [
-                    {
-                        parentId: table.identifier,
-                        parentType: ResourceType.CATALOG_TABLE,
-                    },
-                    {
-                        parentId: databaseNode?.data.value?.identifier,
-                        parentType: ResourceType.CATALOG_DATABASE,
-                    },
-                    {
-                        parentId: catalogNode?.data.value?.catalog?.identifier || catalogNode?.data.value?.identifier,
-                        parentType: ResourceType.CATALOG,
-                    },
-                ]
-
-                const columns = await fetchResources(sqlClient, connectionConfig, ResourceType.CATALOG_COLUMN, parents)
-
-                return columns.length > 0
-                    ? columns.map((column: any) => {
-                          let columnType = 'UNKNOWN'
-                          if (column.resourceMetadata && Array.isArray(column.resourceMetadata)) {
-                              const typeMetadata = column.resourceMetadata.find(
-                                  (meta: any) => meta.key === 'COLUMN_TYPE'
-                              )
-                              if (typeMetadata) {
-                                  columnType = typeMetadata.value
-                              }
-                          }
-
-                          columnType = getColumnType(columnType)
-
-                          return createColumnNode(
-                              column.displayName,
-                              {
-                                  name: column.displayName,
-                                  type: columnType,
-                              },
-                              connectionConfig,
-                              node
-                          )
-                      })
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-            } catch (err) {
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'catalog-columns', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
-
-/**
- * Creates a catalog node
- */
-function createCatalogNode(
-    catalogName: string,
-    catalog: any,
-    connectionConfig: ConnectionConfig,
-    parent: RedshiftNode
-): RedshiftNode {
-    return new RedshiftNode(
-        {
-            id: `${parent.id}${NODE_ID_DELIMITER}${catalogName}`,
-            nodeType: NodeType.REDSHIFT_CATALOG,
-            value: {
-                catalog,
-                catalogName,
-                connectionConfig,
-                identifier: catalogName,
-                type: ResourceType.CATALOG,
-            },
-            path: {
-                ...parent.data.path,
-                catalog: catalogName,
-            },
-            parent,
-        },
-        async (node) => {
-            try {
-                const rootCredentials = getRootCredentials(parent)
-                const clientStore = ConnectionClientStore.getInstance()
-                const rootConnection = getRootConnection(parent)
-                const sqlClient = clientStore.getSQLWorkbenchClient(
-                    rootConnection.connectionId,
-                    connectionConfig.id.split(':')[3],
-                    rootCredentials
-                )
-
-                // Use getResources to fetch databases within this catalog
-                const databases = await fetchResources(sqlClient, connectionConfig, ResourceType.CATALOG_DATABASE, [
-                    {
-                        parentId: catalog.identifier,
-                        parentType: ResourceType.CATALOG,
-                    },
-                ])
-
-                if (databases.length === 0) {
-                    return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-                }
-
-                return databases.length > 0
-                    ? databases.map((database: any) =>
-                          createCatalogDatabaseNode(database.displayName, database, connectionConfig, node)
-                      )
-                    : [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as RedshiftNode]
-            } catch (err) {
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'catalog-databases', node.id) as RedshiftNode]
-            }
-        }
-    )
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/s3Strategy.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/s3Strategy.ts
deleted file mode 100644
index 53d481bd2d..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/s3Strategy.ts
+++ /dev/null
@@ -1,698 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneConnection } from '../../shared/client/datazoneClient'
-import { S3Client } from '../../shared/client/s3Client'
-import { ConnectionClientStore } from '../../shared/client/connectionClientStore'
-import { NODE_ID_DELIMITER, NodeType, ConnectionType, NodeData, NO_DATA_FOUND_MESSAGE } from './types'
-import { getLabel, isLeafNode, getIconForNodeType, getTooltip, createErrorItem } from './utils'
-import { createPlaceholderItem } from '../../../shared/treeview/utils'
-import {
-    ListCallerAccessGrantsCommand,
-    GetDataAccessCommand,
-    ListCallerAccessGrantsEntry,
-} from '@aws-sdk/client-s3-control'
-import { S3, ListObjectsV2Command } from '@aws-sdk/client-s3'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { recordDataConnectionTelemetry } from '../../shared/telemetry'
-
-// Regex to match default S3 connection names
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP = /^(project\.s3_default_folder)|(default\.s3)$/
-
-/**
- * S3 data node for SageMaker Unified Studio
- */
-export class S3Node implements TreeNode {
-    private readonly logger = getLogger('smus')
-    private childrenNodes: TreeNode[] | undefined
-    private isLoading = false
-
-    constructor(
-        public readonly data: NodeData,
-        private readonly childrenProvider?: (node: S3Node) => Promise
-    ) {}
-
-    public get id(): string {
-        return this.data.id
-    }
-
-    public get resource(): any {
-        return this.data.value || {}
-    }
-
-    public async getChildren(): Promise {
-        // Return cached children if available
-        if (this.childrenNodes && !this.isLoading) {
-            return this.childrenNodes
-        }
-
-        // Return empty array for leaf nodes
-        if (isLeafNode(this.data)) {
-            return []
-        }
-
-        // If we have a children provider, use it
-        if (this.childrenProvider) {
-            try {
-                this.isLoading = true
-                const childrenNodes = await this.childrenProvider(this)
-                this.childrenNodes = childrenNodes
-                this.isLoading = false
-                return this.childrenNodes
-            } catch (err) {
-                this.isLoading = false
-                this.logger.error(`Failed to get children for node ${this.data.id}: ${(err as Error).message}`)
-
-                const errorMessage = (err as Error).message
-                await handleCredExpiredError(err, true)
-                return [createErrorItem(errorMessage, 'getChildren', this.id) as S3Node]
-            }
-        }
-
-        return []
-    }
-
-    public async getTreeItem(): Promise {
-        const collapsibleState = isLeafNode(this.data)
-            ? vscode.TreeItemCollapsibleState.None
-            : vscode.TreeItemCollapsibleState.Collapsed
-
-        const label = getLabel(this.data)
-        const item = new vscode.TreeItem(label, collapsibleState)
-
-        // Set icon based on node type
-        item.iconPath = getIconForNodeType(this.data.nodeType, this.data.isContainer)
-
-        // Set context value for command enablement
-        item.contextValue = this.data.nodeType
-
-        // Set tooltip
-        item.tooltip = getTooltip(this.data)
-
-        return item
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.data.parent
-    }
-}
-
-/**
- * Creates an S3 connection node
- */
-export function createS3ConnectionNode(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string
-): S3Node {
-    const logger = getLogger('smus')
-
-    // Parse S3 URI from connection
-    const s3Info = parseS3Uri(connection)
-    if (!s3Info) {
-        logger.warn(`No S3 URI found in connection properties for connection ${connection.name}`)
-        const errorMessage = 'No S3 URI configured'
-        void vscode.window.showErrorMessage(errorMessage)
-        return createErrorItem(errorMessage, 'connection', connection.connectionId) as S3Node
-    }
-
-    // Handle case where s3Uri is "s3://" (all buckets access)
-    const isAllBucketsAccess = !s3Info.bucket
-
-    // Get S3 client from store
-    const clientStore = ConnectionClientStore.getInstance()
-    const s3Client = clientStore.getS3Client(connection.connectionId, region, connectionCredentialsProvider)
-
-    // Check if this is a default S3 connection
-    const isDefaultConnection = DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP.test(connection.name)
-
-    // Create the connection node
-    return new S3Node(
-        {
-            id: connection.connectionId,
-            nodeType: NodeType.CONNECTION,
-            connectionType: ConnectionType.S3,
-            value: { connection },
-            path: {
-                connection: connection.name,
-                bucket: s3Info.bucket,
-            },
-        },
-        async (node) => {
-            return telemetry.smus_renderS3Node.run(async (span) => {
-                await recordDataConnectionTelemetry(span, connection, connectionCredentialsProvider)
-                try {
-                    if (isAllBucketsAccess) {
-                        // For all buckets access (s3://), list all accessible buckets
-                        try {
-                            const buckets = await s3Client.listBuckets()
-                            if (buckets.length === 0) {
-                                return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as S3Node]
-                            }
-
-                            return buckets.map((bucket) => {
-                                return new S3Node(
-                                    {
-                                        id: bucket.Name || 'unknown-bucket',
-                                        nodeType: NodeType.S3_BUCKET,
-                                        connectionType: ConnectionType.S3,
-                                        value: { bucket: bucket.Name },
-                                        path: {
-                                            connection: connection.name,
-                                            bucket: bucket.Name,
-                                        },
-                                        parent: node,
-                                    },
-                                    async (bucketNode) => {
-                                        try {
-                                            const allPaths = []
-                                            let nextToken: string | undefined
-
-                                            do {
-                                                const result = await s3Client.listPaths(
-                                                    bucket.Name || '',
-                                                    undefined,
-                                                    nextToken
-                                                )
-                                                allPaths.push(...result.paths)
-                                                nextToken = result.nextToken
-                                            } while (nextToken)
-
-                                            if (allPaths.length === 0) {
-                                                return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as S3Node]
-                                            }
-
-                                            return allPaths.map((path) => {
-                                                const nodeId = `${path.bucket}-${path.prefix || 'root'}`
-
-                                                return new S3Node(
-                                                    {
-                                                        id: nodeId,
-                                                        nodeType: path.isFolder ? NodeType.S3_FOLDER : NodeType.S3_FILE,
-                                                        connectionType: ConnectionType.S3,
-                                                        value: path,
-                                                        path: {
-                                                            connection: connection.name,
-                                                            bucket: path.bucket,
-                                                            key: path.prefix,
-                                                            label: path.displayName,
-                                                        },
-                                                        parent: bucketNode,
-                                                    },
-                                                    path.isFolder
-                                                        ? createFolderChildrenProvider(s3Client, path)
-                                                        : undefined
-                                                )
-                                            })
-                                        } catch (err) {
-                                            logger.error(`Failed to list bucket contents: ${(err as Error).message}`)
-                                            const errorMessage = (err as Error).message
-                                            await handleCredExpiredError(err, true)
-                                            return [
-                                                createErrorItem(
-                                                    errorMessage,
-                                                    'bucket-contents-all-access',
-                                                    bucketNode.id
-                                                ) as S3Node,
-                                            ]
-                                        }
-                                    }
-                                )
-                            })
-                        } catch (err) {
-                            logger.error(`Failed to list buckets: ${(err as Error).message}`)
-                            const errorMessage = (err as Error).message
-                            await handleCredExpiredError(err, true)
-                            return [createErrorItem(errorMessage, 'list-buckets', node.id) as S3Node]
-                        }
-                    } else if (isDefaultConnection && s3Info.prefix) {
-                        // For default connections, show the full path as the first node
-                        const fullPath = `${s3Info.bucket}/${s3Info.prefix}`
-                        return [
-                            new S3Node(
-                                {
-                                    id: fullPath,
-                                    nodeType: NodeType.S3_BUCKET,
-                                    connectionType: ConnectionType.S3,
-                                    value: { bucket: s3Info.bucket, prefix: s3Info.prefix },
-                                    path: {
-                                        connection: connection.name,
-                                        bucket: s3Info.bucket,
-                                        key: s3Info.prefix,
-                                        label: fullPath,
-                                    },
-                                    parent: node,
-                                },
-                                async (bucketNode) => {
-                                    try {
-                                        // List objects starting from the prefix
-                                        const allPaths = []
-                                        let nextToken: string | undefined
-
-                                        do {
-                                            const result = await s3Client.listPaths(
-                                                s3Info.bucket,
-                                                s3Info.prefix,
-                                                nextToken
-                                            )
-                                            allPaths.push(...result.paths)
-                                            nextToken = result.nextToken
-                                        } while (nextToken)
-
-                                        if (allPaths.length === 0) {
-                                            return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as S3Node]
-                                        }
-
-                                        // Convert paths to nodes
-                                        return allPaths.map((path) => {
-                                            const nodeId = `${path.bucket}-${path.prefix || 'root'}`
-
-                                            return new S3Node(
-                                                {
-                                                    id: nodeId,
-                                                    nodeType: path.isFolder ? NodeType.S3_FOLDER : NodeType.S3_FILE,
-                                                    connectionType: ConnectionType.S3,
-                                                    value: path,
-                                                    path: {
-                                                        connection: connection.name,
-                                                        bucket: path.bucket,
-                                                        key: path.prefix,
-                                                        label: path.displayName,
-                                                    },
-                                                    parent: bucketNode,
-                                                },
-                                                path.isFolder ? createFolderChildrenProvider(s3Client, path) : undefined
-                                            )
-                                        })
-                                    } catch (err) {
-                                        logger.error(`Failed to list bucket contents: ${(err as Error).message}`)
-                                        const errorMessage = (err as Error).message
-                                        await handleCredExpiredError(err, true)
-                                        return [
-                                            createErrorItem(
-                                                errorMessage,
-                                                'bucket-contents-default',
-                                                bucketNode.id
-                                            ) as S3Node,
-                                        ]
-                                    }
-                                }
-                            ),
-                        ]
-                    } else {
-                        // For non-default connections, show bucket as the first node
-                        return [
-                            new S3Node(
-                                {
-                                    id: s3Info.bucket,
-                                    nodeType: NodeType.S3_BUCKET,
-                                    connectionType: ConnectionType.S3,
-                                    value: { bucket: s3Info.bucket },
-                                    path: {
-                                        connection: connection.name,
-                                        bucket: s3Info.bucket,
-                                    },
-                                    parent: node,
-                                },
-                                async (bucketNode) => {
-                                    try {
-                                        // List objects in the bucket
-                                        const allPaths = []
-                                        let nextToken: string | undefined
-
-                                        do {
-                                            const result = await s3Client.listPaths(
-                                                s3Info.bucket,
-                                                s3Info.prefix,
-                                                nextToken
-                                            )
-                                            allPaths.push(...result.paths)
-                                            nextToken = result.nextToken
-                                        } while (nextToken)
-
-                                        if (allPaths.length === 0) {
-                                            return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as S3Node]
-                                        }
-
-                                        // Convert paths to nodes
-                                        return allPaths.map((path) => {
-                                            const nodeId = `${path.bucket}-${path.prefix || 'root'}`
-
-                                            return new S3Node(
-                                                {
-                                                    id: nodeId,
-                                                    nodeType: path.isFolder ? NodeType.S3_FOLDER : NodeType.S3_FILE,
-                                                    connectionType: ConnectionType.S3,
-                                                    value: path,
-                                                    path: {
-                                                        connection: connection.name,
-                                                        bucket: path.bucket,
-                                                        key: path.prefix,
-                                                        label: path.displayName,
-                                                    },
-                                                    parent: bucketNode,
-                                                },
-                                                path.isFolder ? createFolderChildrenProvider(s3Client, path) : undefined
-                                            )
-                                        })
-                                    } catch (err) {
-                                        logger.error(`Failed to list bucket contents: ${(err as Error).message}`)
-                                        const errorMessage = (err as Error).message
-                                        await handleCredExpiredError(err, true)
-                                        return [
-                                            createErrorItem(
-                                                errorMessage,
-                                                'bucket-contents-regular',
-                                                bucketNode.id
-                                            ) as S3Node,
-                                        ]
-                                    }
-                                }
-                            ),
-                        ]
-                    }
-                } catch (err) {
-                    logger.error(`Failed to create bucket node: ${(err as Error).message}`)
-                    const errorMessage = (err as Error).message
-                    await handleCredExpiredError(err, true)
-                    return [createErrorItem(errorMessage, 'bucket-node', node.id) as S3Node]
-                }
-            })
-        }
-    )
-}
-
-/**
- * Creates S3 access grant nodes for project.s3_default_folder connections
- */
-export async function createS3AccessGrantNodes(
-    connection: DataZoneConnection,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string,
-    accountId: string | undefined
-): Promise {
-    if (connection.name !== 'project.s3_default_folder' || !accountId) {
-        return []
-    }
-
-    return await listCallerAccessGrants(connectionCredentialsProvider, region, accountId, connection.connectionId)
-}
-
-/**
- * Creates a children provider function for a folder node
- */
-function createFolderChildrenProvider(s3Client: S3Client, folderPath: any): (node: S3Node) => Promise {
-    const logger = getLogger('smus')
-
-    return async (node: S3Node) => {
-        try {
-            // List objects in the folder
-            const allPaths = []
-            let nextToken: string | undefined
-
-            do {
-                const result = await s3Client.listPaths(folderPath.bucket, folderPath.prefix, nextToken)
-                allPaths.push(...result.paths)
-                nextToken = result.nextToken
-            } while (nextToken)
-
-            if (allPaths.length === 0) {
-                return [createPlaceholderItem(NO_DATA_FOUND_MESSAGE) as S3Node]
-            }
-
-            // Convert paths to nodes
-            return allPaths.map((path) => {
-                const nodeId = `${path.bucket}-${path.prefix || 'root'}`
-
-                return new S3Node(
-                    {
-                        id: nodeId,
-                        nodeType: path.isFolder ? NodeType.S3_FOLDER : NodeType.S3_FILE,
-                        connectionType: ConnectionType.S3,
-                        value: path,
-                        path: {
-                            connection: node.data.path?.connection,
-                            bucket: path.bucket,
-                            key: path.prefix,
-                            label: path.displayName,
-                        },
-                        parent: node,
-                    },
-                    path.isFolder ? createFolderChildrenProvider(s3Client, path) : undefined
-                )
-            })
-        } catch (err) {
-            logger.error(`Failed to list folder contents: ${(err as Error).message}`)
-            const errorMessage = (err as Error).message
-            await handleCredExpiredError(err, true)
-            return [createErrorItem(errorMessage, 'folder-contents', node.id) as S3Node]
-        }
-    }
-}
-
-/**
- * Parse S3 URI from connection
- */
-function parseS3Uri(connection: DataZoneConnection): { bucket: string; prefix?: string } | undefined {
-    const s3Properties = connection.props?.s3Properties
-    const s3Uri = s3Properties?.s3Uri
-
-    if (!s3Uri) {
-        return undefined
-    }
-
-    // Handle case where s3Uri is just "s3://" (all buckets access)
-    if (s3Uri === 's3://') {
-        return { bucket: '', prefix: undefined }
-    }
-
-    // Parse S3 URI: s3://bucket-name/prefix/path/
-    const uriWithoutPrefix = s3Uri.replace('s3://', '')
-
-    // Handle empty URI after removing prefix
-    if (!uriWithoutPrefix) {
-        return { bucket: '', prefix: undefined }
-    }
-
-    // Since the URI ends with a slash, the last item will be an empty string, so ignore it in the parts.
-    const parts = uriWithoutPrefix.split('/').slice(0, -1)
-    const bucket = parts[0] || ''
-
-    // If parts only contains 1 item, then only a bucket was provided, and the key is empty.
-    const prefix = parts.length > 1 ? parts.slice(1).join('/') + '/' : undefined
-
-    return { bucket, prefix }
-}
-
-async function listCallerAccessGrants(
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    region: string,
-    accountId: string,
-    connectionId: string
-): Promise {
-    const logger = getLogger('smus')
-    try {
-        const clientStore = ConnectionClientStore.getInstance()
-        const s3ControlClient = clientStore.getS3ControlClient(connectionId, region, connectionCredentialsProvider)
-
-        const allGrants: ListCallerAccessGrantsEntry[] = []
-        let nextToken: string | undefined
-
-        do {
-            const command = new ListCallerAccessGrantsCommand({
-                AccountId: accountId,
-                NextToken: nextToken,
-            })
-
-            const response = await s3ControlClient.send(command)
-            const grants = response.CallerAccessGrantsList?.filter((entry) => !!entry) ?? []
-            allGrants.push(...grants)
-            nextToken = response.NextToken
-        } while (nextToken)
-
-        logger.info(`Listed ${allGrants.length} caller access grants`)
-
-        const accessGrantNodes = allGrants.map((grant) =>
-            getRootNodeFromS3AccessGrant(grant, accountId, region, connectionCredentialsProvider, connectionId)
-        )
-        return accessGrantNodes
-    } catch (error) {
-        logger.error(`Failed to list caller access grants: ${(error as Error).message}`)
-        await handleCredExpiredError(error)
-        return []
-    }
-}
-
-function parseS3UriForAccessGrant(s3Uri: string): { bucket: string; key: string } {
-    const uriWithoutPrefix = s3Uri.replace('s3://', '')
-    const parts = uriWithoutPrefix.split('/').slice(0, -1)
-    const bucket = parts[0]
-    const key = parts.length > 1 ? parts.slice(1).join('/') + '/' : ''
-    return { bucket, key }
-}
-
-function getRootNodeFromS3AccessGrant(
-    s3AccessGrant: ListCallerAccessGrantsEntry,
-    accountId: string,
-    region: string,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    connectionId: string
-): S3Node {
-    const s3Uri = s3AccessGrant.GrantScope
-    let bucket: string | undefined
-    let key: string | undefined
-    let nodeId = ''
-    let label: string
-
-    if (s3Uri) {
-        const { bucket: parsedBucket, key: parsedKey } = parseS3UriForAccessGrant(s3Uri)
-        bucket = parsedBucket
-        key = parsedKey
-        label = s3Uri.replace('s3://', '').replace('*', '')
-        nodeId = label
-    } else {
-        label = s3AccessGrant.GrantScope ?? ''
-    }
-
-    return new S3Node(
-        {
-            id: nodeId,
-            nodeType: NodeType.S3_ACCESS_GRANT,
-            connectionType: ConnectionType.S3,
-            value: s3AccessGrant,
-            path: { accountId, bucket, key, label },
-        },
-        async (node) => {
-            return await fetchAccessGrantChildren(node, accountId, region, connectionCredentialsProvider, connectionId)
-        }
-    )
-}
-
-async function fetchAccessGrantChildren(
-    node: S3Node,
-    accountId: string,
-    region: string,
-    connectionCredentialsProvider: ConnectionCredentialsProvider,
-    connectionId: string
-): Promise {
-    const logger = getLogger('smus')
-    const path = node.data.path
-
-    try {
-        const clientStore = ConnectionClientStore.getInstance()
-        const s3ControlClient = clientStore.getS3ControlClient(connectionId, region, connectionCredentialsProvider)
-
-        const target = `s3://${path?.bucket ?? ''}/${path?.key ?? ''}*`
-
-        const getDataAccessCommand = new GetDataAccessCommand({
-            AccountId: accountId,
-            Target: target,
-            Permission: 'READ',
-        })
-
-        const grantCredentialsProvider = async () => {
-            const response = await s3ControlClient.send(getDataAccessCommand)
-            if (
-                !response.Credentials?.AccessKeyId ||
-                !response.Credentials?.SecretAccessKey ||
-                !response.Credentials?.SessionToken
-            ) {
-                throw new Error('Missing required credentials from access grant response')
-            }
-            return {
-                accessKeyId: response.Credentials.AccessKeyId,
-                secretAccessKey: response.Credentials.SecretAccessKey,
-                sessionToken: response.Credentials.SessionToken,
-                expiration: response.Credentials.Expiration,
-            }
-        }
-
-        const s3ClientWithGrant = new S3({
-            credentials: grantCredentialsProvider,
-            region,
-        })
-
-        const response = await s3ClientWithGrant.send(
-            new ListObjectsV2Command({
-                Bucket: path?.bucket ?? '',
-                Prefix: path?.key ?? '',
-                Delimiter: '/',
-                MaxKeys: 100,
-            })
-        )
-
-        const children: S3Node[] = []
-
-        // Add folders
-        if (response.CommonPrefixes) {
-            for (const prefix of response.CommonPrefixes) {
-                const folderName =
-                    prefix.Prefix?.split('/')
-                        .filter((name) => !!name)
-                        .at(-1) + '/'
-                children.push(
-                    new S3Node(
-                        {
-                            id: `${node.id}${NODE_ID_DELIMITER}${folderName}`,
-                            nodeType: NodeType.S3_FOLDER,
-                            connectionType: ConnectionType.S3,
-                            value: prefix,
-                            path: {
-                                accountId,
-                                bucket: path?.bucket,
-                                key: prefix.Prefix,
-                                label: folderName,
-                            },
-                            parent: node,
-                        },
-                        async (folderNode) => {
-                            return await fetchAccessGrantChildren(
-                                folderNode,
-                                accountId,
-                                region,
-                                connectionCredentialsProvider,
-                                connectionId
-                            )
-                        }
-                    )
-                )
-            }
-        }
-
-        // Add files
-        if (response.Contents) {
-            for (const content of response.Contents.filter((content) => content.Key !== response.Prefix)) {
-                const fileName = content.Key?.split('/').at(-1) ?? ''
-                children.push(
-                    new S3Node({
-                        id: `${node.id}${NODE_ID_DELIMITER}${fileName}`,
-                        nodeType: NodeType.S3_FILE,
-                        connectionType: ConnectionType.S3,
-                        value: content,
-                        path: {
-                            bucket: path?.bucket,
-                            key: content.Key,
-                            label: fileName,
-                        },
-                        parent: node,
-                    })
-                )
-            }
-        }
-
-        return children
-    } catch (error) {
-        logger.error(`Failed to fetch access grant children: ${(error as Error).message}`)
-        const errorMessage = (error as Error).message
-        await handleCredExpiredError(error, true)
-        return [createErrorItem(errorMessage, 'access-grant-children', node.id) as S3Node]
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.ts
deleted file mode 100644
index cca19378c2..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { SageMakerUnifiedStudioRootNode } from './sageMakerUnifiedStudioRootNode'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { SmusIamConnection } from '../../auth/model'
-import { getContext } from '../../../shared/vscode/setContext'
-import { loadSharedConfigFiles } from '@smithy/shared-ini-file-loader'
-
-/**
- * Node representing the SageMaker Unified Studio authentication information
- */
-export class SageMakerUnifiedStudioAuthInfoNode implements TreeNode {
-    public readonly id = 'smusAuthInfoNode'
-    public readonly resource = this
-    private readonly authProvider: SmusAuthenticationProvider
-
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
-
-    constructor(private readonly parent?: SageMakerUnifiedStudioRootNode) {
-        this.authProvider = SmusAuthenticationProvider.fromContext()
-
-        // Subscribe to auth provider connection changes to refresh the node
-        this.authProvider.onDidChange(() => {
-            this.onDidChangeEmitter.fire()
-        })
-        this.authProvider.onDidChangeActiveConnection(() => {
-            this.onDidChangeEmitter.fire()
-        })
-    }
-
-    public async getTreeItem(): Promise {
-        // Use the cached authentication provider to check connection status
-        const isConnected = this.authProvider.isConnected()
-        const isValid = this.authProvider.isConnectionValid()
-
-        // Get the domain ID and region from auth provider
-        let domainId = 'Unknown'
-        let region = 'Unknown'
-
-        if (isConnected && this.authProvider.activeConnection) {
-            domainId = this.authProvider.getDomainId() || 'Unknown'
-            region = this.authProvider.getDomainRegion() || 'Unknown'
-        }
-
-        // Create display based on connection status
-        let label: string
-        let iconPath: vscode.ThemeIcon
-        let tooltip: string
-        let description: string | undefined
-
-        // Get profile name for IAM mode
-        const isIamMode = getContext('aws.smus.isIamMode')
-        let profileName: string | undefined
-        if (isIamMode) {
-            const activeConnection = this.authProvider.activeConnection!
-            const { configFile } = await loadSharedConfigFiles()
-            profileName =
-                (activeConnection as SmusIamConnection).profileName || (configFile['default'] ? 'default' : undefined)
-        }
-
-        if (isConnected && isValid) {
-            // Get session name and role ARN dynamically for IAM connections in IAM mode
-            let sessionName: string | undefined
-            let roleArn: string | undefined
-            if (isIamMode) {
-                sessionName = await this.authProvider.getSessionName()
-                roleArn = await this.authProvider.getIamPrincipalArn()
-            }
-
-            // Format label with session name if available
-            const sessionSuffix = sessionName ? ` (session: ${sessionName})` : ''
-            label = isIamMode ? `Connected with profile: ${profileName}${sessionSuffix}` : `Domain: ${domainId}`
-            iconPath = new vscode.ThemeIcon('key', new vscode.ThemeColor('charts.green'))
-
-            // Add role ARN and session name to tooltip if available (role ARN before session)
-            const roleArnTooltip = roleArn ? `\nRole ARN: ${roleArn}` : ''
-            const sessionTooltip = sessionName ? `\nSession: ${sessionName}` : ''
-            tooltip = `Connected to SageMaker Unified Studio\n${isIamMode ? `Profile: ${profileName}` : `Domain ID: ${domainId}`}\nRegion: ${region}${roleArnTooltip}${sessionTooltip}\nStatus: Connected`
-            description = region
-        } else if (isConnected && !isValid) {
-            label = isIamMode
-                ? `Profile: ${profileName} (Expired) - Click to reauthenticate`
-                : `Domain: ${domainId} (Expired) - Click to reauthenticate`
-            iconPath = new vscode.ThemeIcon('warning', new vscode.ThemeColor('charts.yellow'))
-            tooltip = `Connection to SageMaker Unified Studio has expired\n${isIamMode ? `Profile: ${profileName}` : `Domain ID: ${domainId}`}\nRegion: ${region}\nStatus: Expired - Click to reauthenticate`
-            description = region
-        } else {
-            label = 'Not Connected'
-            iconPath = new vscode.ThemeIcon('circle-slash', new vscode.ThemeColor('charts.red'))
-            tooltip = 'Not connected to SageMaker Unified Studio\nPlease sign in to access your projects'
-            description = undefined
-        }
-
-        const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None)
-
-        // Add command for reauthentication when connection is expired
-        if (isConnected && !isValid) {
-            item.command = {
-                command: 'aws.smus.reauthenticate',
-                title: 'Reauthenticate',
-                arguments: [this.authProvider.activeConnection],
-            }
-        }
-
-        item.tooltip = tooltip
-        item.contextValue = 'smusAuthInfo'
-        item.iconPath = iconPath
-        item.description = description
-        return item
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.ts
deleted file mode 100644
index eab4a58fbf..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.ts
+++ /dev/null
@@ -1,69 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getIcon } from '../../../shared/icons'
-import { SageMakerUnifiedStudioSpacesParentNode } from './sageMakerUnifiedStudioSpacesParentNode'
-import { SageMakerUnifiedStudioProjectNode } from './sageMakerUnifiedStudioProjectNode'
-import { SagemakerClient } from '../../../shared/clients/sagemaker'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { SageMakerUnifiedStudioConnectionParentNode } from './sageMakerUnifiedStudioConnectionParentNode'
-import { ConnectionType } from '@aws-sdk/client-datazone'
-import { getContext } from '../../../shared/vscode/setContext'
-
-export class SageMakerUnifiedStudioComputeNode implements TreeNode {
-    public readonly id = 'smusComputeNode'
-    public readonly resource = this
-    private spacesNode: SageMakerUnifiedStudioSpacesParentNode | undefined
-
-    constructor(
-        public readonly parent: SageMakerUnifiedStudioProjectNode,
-        private readonly extensionContext: vscode.ExtensionContext,
-        public readonly authProvider: SmusAuthenticationProvider,
-        private readonly sagemakerClient: SagemakerClient
-    ) {}
-
-    public async getTreeItem(): Promise {
-        const item = new vscode.TreeItem('Compute', vscode.TreeItemCollapsibleState.Expanded)
-        item.iconPath = getIcon('vscode-chip')
-        item.contextValue = this.getContext()
-        return item
-    }
-
-    public async getChildren(): Promise {
-        const childrenNodes: TreeNode[] = []
-        const projectId = this.parent.getProject()?.id
-
-        if (projectId) {
-            if (!getContext('aws.smus.isIamMode')) {
-                childrenNodes.push(
-                    new SageMakerUnifiedStudioConnectionParentNode(this, ConnectionType.REDSHIFT, 'Data warehouse')
-                )
-                childrenNodes.push(
-                    new SageMakerUnifiedStudioConnectionParentNode(this, ConnectionType.SPARK, 'Data processing')
-                )
-            }
-            this.spacesNode = new SageMakerUnifiedStudioSpacesParentNode(
-                this,
-                projectId,
-                this.extensionContext,
-                this.authProvider,
-                this.sagemakerClient
-            )
-            childrenNodes.push(this.spacesNode)
-        }
-
-        return childrenNodes
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-
-    private getContext(): string {
-        return 'smusComputeNode'
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.ts
deleted file mode 100644
index 588d4f4206..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getLogger } from '../../../shared/logger/logger'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { SageMakerUnifiedStudioConnectionParentNode } from './sageMakerUnifiedStudioConnectionParentNode'
-import { ConnectionSummary, ConnectionType } from '@aws-sdk/client-datazone'
-
-export class SageMakerUnifiedStudioConnectionNode implements TreeNode {
-    public resource: SageMakerUnifiedStudioConnectionNode
-    contextValue: string
-    private readonly logger = getLogger('smus')
-    id: string
-    public constructor(
-        private readonly parent: SageMakerUnifiedStudioConnectionParentNode,
-        private readonly connection: ConnectionSummary
-    ) {
-        this.id = connection.name ?? ''
-        this.resource = this
-        this.contextValue = this.getContext()
-        this.logger.debug(`SageMaker Space Node created: ${this.id}`)
-    }
-
-    public async getTreeItem(): Promise {
-        const item = new vscode.TreeItem(this.id, vscode.TreeItemCollapsibleState.None)
-        item.contextValue = this.getContext()
-        item.tooltip = new vscode.MarkdownString(this.buildTooltip())
-        return item
-    }
-    private buildTooltip(): string {
-        if (this.connection.type === ConnectionType.REDSHIFT) {
-            const tooltip = ''.concat(
-                '### Compute Details\n\n',
-                `**Type**  \n${this.connection.type}\n\n`,
-                `**Environment ID**  \n${this.connection.environmentId}\n\n`,
-                `**JDBC URL**  \n${this.connection.props?.redshiftProperties?.jdbcUrl}`
-            )
-            return tooltip
-        } else if (this.connection.type === ConnectionType.SPARK) {
-            const tooltip = ''.concat(
-                '### Compute Details\n\n',
-                `**Type**  \n${this.connection.type}\n\n`,
-                `**Glue version**  \n${this.connection.props?.sparkGlueProperties?.glueVersion}\n\n`,
-                `**Worker type**  \n${this.connection.props?.sparkGlueProperties?.workerType}\n\n`,
-                `**Number of workers**  \n${this.connection.props?.sparkGlueProperties?.numberOfWorkers}\n\n`,
-                `**Idle timeout (minutes)**  \n${this.connection.props?.sparkGlueProperties?.idleTimeout}\n\n`
-            )
-            return tooltip
-        } else {
-            return ''
-        }
-    }
-    private getContext(): string {
-        return 'SageMakerUnifiedStudioConnectionNode'
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.ts
deleted file mode 100644
index 3bb7fa8022..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { SageMakerUnifiedStudioComputeNode } from './sageMakerUnifiedStudioComputeNode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { ListConnectionsCommandOutput, ConnectionType } from '@aws-sdk/client-datazone'
-import { SageMakerUnifiedStudioConnectionNode } from './sageMakerUnifiedStudioConnectionNode'
-import { createDZClientBaseOnDomainMode } from './utils'
-
-// eslint-disable-next-line id-length
-export class SageMakerUnifiedStudioConnectionParentNode implements TreeNode {
-    public resource: SageMakerUnifiedStudioConnectionParentNode
-    contextValue: string
-    public connections: ListConnectionsCommandOutput | undefined
-    public constructor(
-        private readonly parent: SageMakerUnifiedStudioComputeNode,
-        private readonly connectionType: ConnectionType,
-        public id: string
-    ) {
-        this.resource = this
-        this.contextValue = this.getContext()
-    }
-
-    public async getTreeItem(): Promise {
-        const item = new vscode.TreeItem(this.id, vscode.TreeItemCollapsibleState.Collapsed)
-        item.contextValue = this.getContext()
-        return item
-    }
-
-    public async getChildren(): Promise {
-        const client = await createDZClientBaseOnDomainMode(this.parent.authProvider)
-        this.connections = await client.fetchConnections(
-            this.parent.parent.project?.domainId,
-            this.parent.parent.project?.id,
-            this.connectionType
-        )
-        const childrenNodes = []
-        if (!this.connections?.items || this.connections.items.length === 0) {
-            return [
-                {
-                    id: 'smusNoConnections',
-                    resource: {},
-                    getTreeItem: () =>
-                        new vscode.TreeItem('[No connections found]', vscode.TreeItemCollapsibleState.None),
-                    getParent: () => this,
-                },
-            ]
-        }
-        for (const connection of this.connections.items) {
-            childrenNodes.push(new SageMakerUnifiedStudioConnectionNode(this, connection))
-        }
-        return childrenNodes
-    }
-
-    private getContext(): string {
-        return 'SageMakerUnifiedStudioConnectionParentNode'
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.ts
deleted file mode 100644
index 20d181bd1c..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.ts
+++ /dev/null
@@ -1,315 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getIcon } from '../../../shared/icons'
-
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneConnection, DataZoneProject } from '../../shared/client/datazoneClient'
-import { createS3ConnectionNode, createS3AccessGrantNodes } from './s3Strategy'
-import { createRedshiftConnectionNode } from './redshiftStrategy'
-import { createLakehouseConnectionNode } from './lakehouseStrategy'
-import { SageMakerUnifiedStudioProjectNode } from './sageMakerUnifiedStudioProjectNode'
-import { isFederatedConnection, createErrorItem } from './utils'
-import { createPlaceholderItem } from '../../../shared/treeview/utils'
-import {
-    ConnectionType,
-    DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP,
-    NO_DATA_FOUND_MESSAGE,
-    S3_PROJECT_NON_GIT_PROJECT_REPOSITORY_LOCATION_NAME_REGEXP,
-} from './types'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { createFederatedConnectionNode } from './federatedConnectionStrategy'
-import { createDZClientForProject } from './utils'
-import { getContext } from '../../../shared/vscode/setContext'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-
-/**
- * Tree node representing a Data folder that contains S3 and Redshift connections
- */
-export class SageMakerUnifiedStudioDataNode implements TreeNode {
-    public readonly id = 'smusDataExplorer'
-    public readonly resource = {}
-    private readonly logger = getLogger('smus')
-    private childrenNodes: TreeNode[] | undefined
-    private readonly authProvider: SmusAuthenticationProvider
-
-    constructor(
-        private readonly parent: SageMakerUnifiedStudioProjectNode,
-        initialChildren: TreeNode[] = []
-    ) {
-        this.childrenNodes = initialChildren.length > 0 ? initialChildren : undefined
-        this.authProvider = SmusAuthenticationProvider.fromContext()
-    }
-
-    public getTreeItem(): vscode.TreeItem {
-        const item = new vscode.TreeItem('Data', vscode.TreeItemCollapsibleState.Collapsed)
-        item.iconPath = getIcon('vscode-library')
-        item.contextValue = 'dataFolder'
-        return item
-    }
-
-    public async getChildren(): Promise {
-        if (this.childrenNodes !== undefined) {
-            return this.childrenNodes
-        }
-
-        try {
-            const project = this.parent.getProject()
-            if (!project) {
-                const errorMessage = 'No project information available'
-                this.logger.error(errorMessage)
-                void vscode.window.showErrorMessage(errorMessage)
-                return [createErrorItem(errorMessage, 'project', this.id)]
-            }
-
-            const datazoneClient = await createDZClientForProject(this.authProvider, project.id)
-
-            const connections = await datazoneClient.listConnections(project.domainId, undefined, project.id)
-            this.logger.info(`Found ${connections.length} connections for project ${project.id}`)
-
-            if (connections.length === 0) {
-                this.childrenNodes = [createPlaceholderItem(NO_DATA_FOUND_MESSAGE)]
-                return this.childrenNodes
-            }
-
-            const dataNodes = await this.createConnectionNodes(project, connections)
-            this.childrenNodes = dataNodes
-            return dataNodes
-        } catch (err) {
-            const project = this.parent.getProject()
-            const projectInfo = project ? `project: ${project.id}, domain: ${project.domainId}` : 'unknown project'
-            const errorMessage = 'Failed to get connections'
-            this.logger.error(`Failed to get connections for ${projectInfo}: ${(err as Error).message}`)
-            await handleCredExpiredError(err, true)
-            return [createErrorItem(errorMessage, 'connections', this.id)]
-        }
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-
-    private async createConnectionNodes(
-        project: DataZoneProject,
-        connections: DataZoneConnection[]
-    ): Promise {
-        const region = this.authProvider.getDomainRegion()
-        const dataNodes: TreeNode[] = []
-
-        const s3Connections = connections.filter((conn) => (conn.type as ConnectionType) === ConnectionType.S3)
-        const redshiftConnections = connections.filter(
-            (conn) => (conn.type as ConnectionType) === ConnectionType.REDSHIFT
-        )
-        const lakehouseConnections = connections.filter(
-            (conn) => (conn.type as ConnectionType) === ConnectionType.LAKEHOUSE
-        )
-
-        // Add Lakehouse nodes first
-        for (const connection of lakehouseConnections) {
-            const node = await this.createLakehouseNode(project, connection, region)
-            dataNodes.push(node)
-        }
-
-        // Add Redshift nodes second
-        if (!getContext('aws.smus.isIamMode')) {
-            for (const connection of redshiftConnections) {
-                if (connection.name.startsWith('project.lakehouse')) {
-                    continue
-                }
-                if (isFederatedConnection(connection)) {
-                    continue
-                }
-                const node = await this.createRedshiftNode(project, connection, region)
-                dataNodes.push(node)
-            }
-        } else {
-            const federatedConnections = connections.filter((conn) => isFederatedConnection(conn))
-            if (federatedConnections.length > 0) {
-                const connectionsNode = this.createConnectionsParentNode(project, federatedConnections, region)
-                dataNodes.push(connectionsNode)
-            }
-        }
-
-        // Add S3 Bucket parent node last
-        if (s3Connections.length > 0) {
-            const bucketNode = this.createBucketParentNode(project, s3Connections, region)
-            dataNodes.push(bucketNode)
-        }
-
-        this.logger.info(`Created ${dataNodes.length} total connection nodes`)
-        return dataNodes
-    }
-
-    private async createS3Node(
-        project: DataZoneProject,
-        connection: DataZoneConnection,
-        region: string
-    ): Promise {
-        try {
-            const connectionCredentialsProvider = await this.authProvider.getConnectionCredentialsProvider(
-                connection.connectionId,
-                project.id,
-                connection.location?.awsRegion || region
-            )
-
-            const s3ConnectionNode = createS3ConnectionNode(
-                connection,
-                connectionCredentialsProvider,
-                connection.location?.awsRegion || region
-            )
-
-            const accessGrantNodes = await createS3AccessGrantNodes(
-                connection,
-                connectionCredentialsProvider,
-                connection.location?.awsRegion || region,
-                connection.location?.awsAccountId
-            )
-
-            return [s3ConnectionNode, ...accessGrantNodes]
-        } catch (connErr) {
-            const errorMessage = `Failed to get S3 connection - ${(connErr as Error).message}`
-            this.logger.error(`Failed to get S3 connection details: ${(connErr as Error).message}`)
-            await handleCredExpiredError(connErr, true)
-            return [createErrorItem(errorMessage, `s3-${connection.connectionId}`, this.id)]
-        }
-    }
-
-    private async createRedshiftNode(
-        project: DataZoneProject,
-        connection: DataZoneConnection,
-        region: string
-    ): Promise {
-        try {
-            const datazoneClient = await createDZClientForProject(this.authProvider, project.id)
-            const getConnectionResponse = await datazoneClient.getConnection({
-                domainIdentifier: project.domainId,
-                identifier: connection.connectionId,
-                withSecret: true,
-            })
-
-            const connectionCredentialsProvider = await this.authProvider.getConnectionCredentialsProvider(
-                connection.connectionId,
-                project.id,
-                getConnectionResponse.location?.awsRegion || region
-            )
-
-            return createRedshiftConnectionNode(connection, connectionCredentialsProvider)
-        } catch (connErr) {
-            const errorMessage = `Failed to get Redshift connection - ${(connErr as Error).message}`
-            this.logger.error(`Failed to get Redshift connection details: ${(connErr as Error).message}`)
-            await handleCredExpiredError(connErr, true)
-            return createErrorItem(errorMessage, `redshift-${connection.connectionId}`, this.id)
-        }
-    }
-
-    private async createLakehouseNode(
-        project: DataZoneProject,
-        connection: DataZoneConnection,
-        region: string
-    ): Promise {
-        try {
-            const connectionCredentialsProvider = await this.authProvider.getConnectionCredentialsProvider(
-                connection.connectionId,
-                project.id,
-                connection.location?.awsRegion || region
-            )
-
-            return createLakehouseConnectionNode(connection, connectionCredentialsProvider, region)
-        } catch (connErr) {
-            const errorMessage = `Failed to get Lakehouse connection - ${(connErr as Error).message}`
-            this.logger.error(`Failed to get Lakehouse connection details: ${(connErr as Error).message}`)
-            await handleCredExpiredError(connErr, true)
-            return createErrorItem(errorMessage, `lakehouse-${connection.connectionId}`, this.id)
-        }
-    }
-
-    private createBucketParentNode(
-        project: DataZoneProject,
-        s3Connections: DataZoneConnection[],
-        region: string
-    ): TreeNode {
-        return {
-            id: 'bucket-parent',
-            resource: {},
-            getTreeItem: () => {
-                const item = new vscode.TreeItem('Buckets', vscode.TreeItemCollapsibleState.Collapsed)
-                item.contextValue = 'bucketFolder'
-                return item
-            },
-            getChildren: async () => {
-                // Filter connections inside the bucket parent node
-                const defaultS3Connection = s3Connections.find((conn) =>
-                    DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP.test(conn.name)
-                )
-                const otherS3Connections = s3Connections.filter(
-                    (conn) =>
-                        !DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP.test(conn.name) &&
-                        !S3_PROJECT_NON_GIT_PROJECT_REPOSITORY_LOCATION_NAME_REGEXP.test(conn.name)
-                )
-
-                const s3Nodes: TreeNode[] = []
-
-                // Add default connections first
-                if (defaultS3Connection) {
-                    const defaultS3Node = await this.createS3Node(project, defaultS3Connection, region)
-                    s3Nodes.push(...defaultS3Node)
-                }
-
-                // Add other connections
-                for (const connection of otherS3Connections) {
-                    const nodes = await this.createS3Node(project, connection, region)
-                    s3Nodes.push(...nodes)
-                }
-                return s3Nodes
-            },
-            getParent: () => this,
-        }
-    }
-
-    private createConnectionsParentNode(
-        project: DataZoneProject,
-        federatedConnections: DataZoneConnection[],
-        region: string
-    ): TreeNode {
-        return {
-            id: 'connections-parent',
-            resource: {},
-            getTreeItem: () => {
-                const item = new vscode.TreeItem('Connections', vscode.TreeItemCollapsibleState.Collapsed)
-                item.contextValue = 'connectionsFolder'
-                return item
-            },
-            getChildren: async () => {
-                const nodes: TreeNode[] = []
-                for (const connection of federatedConnections) {
-                    try {
-                        const connectionCredentialsProvider = await this.authProvider.getConnectionCredentialsProvider(
-                            connection.connectionId,
-                            project.id,
-                            connection.location?.awsRegion || region
-                        )
-                        const node = await createFederatedConnectionNode(
-                            connection,
-                            connectionCredentialsProvider,
-                            region
-                        )
-                        nodes.push(node)
-                    } catch (err) {
-                        const errorMessage = `Failed to create federated connection - ${(err as Error).message}`
-                        this.logger.error(
-                            `Failed to create federated connection ${connection.name}: ${(err as Error).message}`
-                        )
-                        nodes.push(createErrorItem(errorMessage, `federated-${connection.connectionId}`, this.id))
-                        await handleCredExpiredError(err)
-                    }
-                }
-                return nodes
-            },
-            getParent: () => this,
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.ts
deleted file mode 100644
index 6b41189b78..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.ts
+++ /dev/null
@@ -1,286 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { AwsCredentialIdentity } from '@aws-sdk/types'
-import { SageMakerUnifiedStudioDataNode } from './sageMakerUnifiedStudioDataNode'
-import { DataZoneClient, DataZoneProject } from '../../shared/client/datazoneClient'
-import { SageMakerUnifiedStudioRootNode } from './sageMakerUnifiedStudioRootNode'
-import { SagemakerClient } from '../../../shared/clients/sagemaker'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { SageMakerUnifiedStudioComputeNode } from './sageMakerUnifiedStudioComputeNode'
-import { getIcon } from '../../../shared/icons'
-import { getResourceMetadata } from '../../shared/utils/resourceMetadataUtils'
-import { getContext } from '../../../shared/vscode/setContext'
-import { ToolkitError } from '../../../shared/errors'
-import { SmusErrorCodes } from '../../shared/smusUtils'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-import { SmusIamConnection } from '../../auth/model'
-import { createDZClientBaseOnDomainMode, createErrorItem } from './utils'
-
-/**
- * Tree node representing a SageMaker Unified Studio project
- */
-export class SageMakerUnifiedStudioProjectNode implements TreeNode {
-    public readonly id = 'smusProjectNode'
-    public readonly resource = this
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
-    public readonly onDidChangeChildren = this.onDidChangeEmitter.event
-    public project?: DataZoneProject
-    private logger = getLogger('smus')
-    private sagemakerClient?: SagemakerClient
-    private hasShownFirstTimeMessage = false
-    private isFirstTimeSelection = false
-
-    constructor(
-        private readonly parent: SageMakerUnifiedStudioRootNode,
-        private readonly authProvider: SmusAuthenticationProvider,
-        private readonly extensionContext: vscode.ExtensionContext
-    ) {
-        // If we're in SMUS space environment, set project from resource metadata
-        if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-            const resourceMetadata = getResourceMetadata()!
-            if (resourceMetadata.AdditionalMetadata!.DataZoneProjectId) {
-                this.project = {
-                    id: resourceMetadata!.AdditionalMetadata!.DataZoneProjectId!,
-                    name: 'Current Project',
-                    domainId: resourceMetadata!.AdditionalMetadata!.DataZoneDomainId!,
-                }
-                // Fetch the actual project name asynchronously
-                void this.fetchProjectName()
-            }
-        }
-    }
-
-    public async getTreeItem(): Promise {
-        if (this.project) {
-            const item = new vscode.TreeItem('Project: ' + this.project.name, vscode.TreeItemCollapsibleState.Expanded)
-            item.contextValue = 'smusSelectedProject'
-            item.tooltip = `Project: ${this.project.name}\nID: ${this.project.id}`
-            item.iconPath = getIcon('vscode-folder-opened')
-            return item
-        }
-
-        const item = new vscode.TreeItem('Select a project', vscode.TreeItemCollapsibleState.Expanded)
-        item.contextValue = 'smusProjectSelectPicker'
-        item.command = {
-            command: 'aws.smus.projectView',
-            title: 'Select Project',
-            arguments: [this],
-        }
-        item.iconPath = getIcon('vscode-folder-opened')
-
-        return item
-    }
-
-    public async getChildren(): Promise {
-        if (!this.project) {
-            return []
-        }
-
-        return telemetry.smus_renderProjectChildrenNode.run(async (span) => {
-            try {
-                const isInSmusSpace = getContext('aws.smus.inSmusSpaceEnvironment')
-
-                // Get auth mode directly from connection type
-                const authMode = this.authProvider.activeConnection?.type
-
-                const accountId = await this.authProvider.getDomainAccountId()
-                span.record({
-                    smusToolkitEnv: isInSmusSpace ? 'smus_space' : 'local',
-                    smusDomainId: this.project?.domainId,
-                    smusDomainAccountId: accountId,
-                    smusProjectId: this.project?.id,
-                    smusDomainRegion: this.authProvider.getDomainRegion(),
-                    ...(authMode && { smusAuthMode: authMode }),
-                })
-
-                // Skip access check if we're in SMUS space environment (already in project space)
-                if (!getContext('aws.smus.inSmusSpaceEnvironment')) {
-                    try {
-                        const hasAccess = await this.checkProjectCredsAccess(this.project!.id)
-                        if (!hasAccess) {
-                            return [
-                                {
-                                    id: 'smusProjectAccessDenied',
-                                    resource: {},
-                                    getTreeItem: () => {
-                                        const item = new vscode.TreeItem(
-                                            'You do not have access to this project. Contact your administrator.',
-                                            vscode.TreeItemCollapsibleState.None
-                                        )
-                                        return item
-                                    },
-                                    getParent: () => this,
-                                },
-                            ]
-                        }
-                    } catch (err) {
-                        const errorMessage = (err as Error).message
-                        this.logger.error('Failed to check project credentials: %s', errorMessage)
-                        await handleCredExpiredError(err, true)
-                        return [createErrorItem(`Failed to load the project`, this.project?.id || '', this.id)]
-                    }
-                }
-
-                const dataNode = new SageMakerUnifiedStudioDataNode(this)
-
-                // If we're in SMUS space environment, only show data node
-                if (getContext('aws.smus.inSmusSpaceEnvironment')) {
-                    return [dataNode]
-                }
-
-                const dzClient = await createDZClientBaseOnDomainMode(this.authProvider)
-                if (!this.project?.id) {
-                    throw new Error('Project ID is required')
-                }
-                const toolingEnv = await dzClient.getToolingEnvironment(this.project.id)
-                const spaceAwsAccountRegion = toolingEnv.awsAccountRegion
-
-                if (!spaceAwsAccountRegion) {
-                    throw new Error('No AWS account region found in tooling environment')
-                }
-                if (this.isFirstTimeSelection && !this.hasShownFirstTimeMessage) {
-                    this.hasShownFirstTimeMessage = true
-                    void vscode.window.showInformationMessage(
-                        'Find your space in the Explorer panel under SageMaker Unified Studio. Hover over a space and click the connection icon to connect remotely.'
-                    )
-                }
-                this.sagemakerClient = await this.initializeSagemakerClient(spaceAwsAccountRegion)
-                const computeNode = new SageMakerUnifiedStudioComputeNode(
-                    this,
-                    this.extensionContext,
-                    this.authProvider,
-                    this.sagemakerClient
-                )
-                return [dataNode, computeNode]
-            } catch (err) {
-                this.logger.error('Failed to select project: %s', (err as Error).message)
-                await handleCredExpiredError(err)
-                throw err
-            }
-        })
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-
-    public async refreshNode(): Promise {
-        this.onDidChangeEmitter.fire()
-    }
-
-    public async setProject(project: any): Promise {
-        await this.cleanupProjectResources()
-        this.isFirstTimeSelection = !this.project
-        this.project = project
-    }
-
-    public getProject(): DataZoneProject | undefined {
-        return this.project
-    }
-
-    public async clearProject(): Promise {
-        await this.cleanupProjectResources()
-        // Don't clear project if we're in SMUS space environment
-        if (!getContext('aws.smus.inSmusSpaceEnvironment')) {
-            this.project = undefined
-        }
-        await this.refreshNode()
-    }
-
-    private async cleanupProjectResources(): Promise {
-        await this.authProvider.invalidateAllProjectCredentialsInCache()
-        if (this.sagemakerClient) {
-            this.sagemakerClient.dispose()
-            this.sagemakerClient = undefined
-        }
-    }
-
-    private async checkProjectCredsAccess(projectId: string): Promise {
-        // TODO: Ideally we should be checking user project access by calling fetchAllProjectMemberships
-        // and checking if user is part of that, or get user groups and check if any of the groupIds
-        // exists in the project memberships for more comprehensive access validation.
-        try {
-            const projectProvider = await this.authProvider.getProjectCredentialProvider(projectId)
-            this.logger.info(`Successfully obtained project credentials provider for project ${projectId}`)
-            await projectProvider.getCredentials()
-            return true
-        } catch (err) {
-            // If err.name is 'AccessDeniedException', it means user doesn't have access to the project
-            // We can safely return false in that case without logging the error
-            if ((err as any).name === 'AccessDeniedException') {
-                this.logger.debug(
-                    'Access denied when obtaining project credentials, user likely lacks project access or role permissions'
-                )
-                return false
-            }
-            throw err
-        }
-    }
-
-    private async fetchProjectName(): Promise {
-        if (!this.project || !getContext('aws.smus.inSmusSpaceEnvironment')) {
-            return
-        }
-
-        try {
-            const dzClient = await createDZClientBaseOnDomainMode(this.authProvider)
-            const projectDetails = await dzClient.getProject(this.project.id)
-
-            if (projectDetails && projectDetails.name) {
-                this.project.name = projectDetails.name
-                // Refresh the tree item to show the updated name
-                this.onDidChangeEmitter.fire()
-            }
-        } catch (err) {
-            // No need to show error, this is just to dynamically show project name
-            // If we fail to fetch project name, we will just show the default name
-            this.logger.debug(`Failed to fetch project name: ${(err as Error).message}`)
-        }
-    }
-
-    private async initializeSagemakerClient(regionCode: string): Promise {
-        if (!this.project) {
-            throw new Error('No project selected for initializing SageMaker client')
-        }
-        let awsCredentialProvider
-        if (getContext('aws.smus.isIamMode')) {
-            const datazoneClient = DataZoneClient.createWithCredentials(
-                this.authProvider.getDomainRegion(),
-                this.authProvider.getDomainId(),
-                await this.authProvider.getCredentialsProviderForIamProfile(
-                    (this.authProvider.activeConnection as SmusIamConnection).profileName
-                )
-            )
-            const projectId = this.project.id
-            awsCredentialProvider = async (): Promise => {
-                const creds = await datazoneClient.getProjectDefaultEnvironmentCreds(projectId)
-                if (!creds.accessKeyId || !creds.secretAccessKey) {
-                    throw new ToolkitError('Missing default environment credentials', {
-                        code: SmusErrorCodes.CredentialRetrievalFailed,
-                    })
-                }
-                return {
-                    accessKeyId: creds.accessKeyId!,
-                    secretAccessKey: creds.secretAccessKey!,
-                    sessionToken: creds.sessionToken,
-                }
-            }
-        } else {
-            const projectProvider = await this.authProvider.getProjectCredentialProvider(this.project.id)
-            this.logger.info(`Successfully obtained project credentials provider for project ${this.project.id}`)
-            awsCredentialProvider = async (): Promise => {
-                return await projectProvider.getCredentials()
-            }
-        }
-        const sagemakerClient = new SagemakerClient(regionCode, awsCredentialProvider)
-        return sagemakerClient
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.ts
deleted file mode 100644
index d03ab98145..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.ts
+++ /dev/null
@@ -1,645 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { getIcon } from '../../../shared/icons'
-import { getLogger } from '../../../shared/logger/logger'
-import { DataZoneProject, DataZoneClient } from '../../shared/client/datazoneClient'
-import { Commands } from '../../../shared/vscode/commands2'
-import { telemetry } from '../../../shared/telemetry/telemetry'
-import { createQuickPick } from '../../../shared/ui/pickerPrompter'
-import { SageMakerUnifiedStudioProjectNode } from './sageMakerUnifiedStudioProjectNode'
-import { SageMakerUnifiedStudioAuthInfoNode } from './sageMakerUnifiedStudioAuthInfoNode'
-import { SmusErrorCodes, SmusUtils } from '../../shared/smusUtils'
-import { handleCredExpiredError } from '../../shared/credentialExpiryHandler'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { ToolkitError } from '../../../../src/shared/errors'
-import { SmusAuthenticationMethod } from '../../auth/ui/authenticationMethodSelection'
-import { SmusAuthenticationOrchestrator } from '../../auth/authenticationOrchestrator'
-import { isSmusSsoConnection, isSmusIamConnection } from '../../auth/model'
-import { getContext } from '../../../shared/vscode/setContext'
-import { createDZClientBaseOnDomainMode } from './utils'
-import { DataZoneCustomClientHelper } from '../../shared/client/datazoneCustomClientHelper'
-import { recordAuthTelemetry } from '../../shared/telemetry'
-
-const contextValueSmusRoot = 'sageMakerUnifiedStudioRoot'
-const contextValueSmusLogin = 'sageMakerUnifiedStudioLogin'
-const contextValueSmusLearnMore = 'sageMakerUnifiedStudioLearnMore'
-const projectPickerTitle = 'Select a SageMaker Unified Studio project you want to open'
-const projectPickerPlaceholder = 'Select project'
-
-export class SageMakerUnifiedStudioRootNode implements TreeNode {
-    public readonly id = 'smusRootNode'
-    public readonly resource = this
-    private readonly logger = getLogger('smus')
-    private readonly projectNode: SageMakerUnifiedStudioProjectNode
-    private readonly authInfoNode: SageMakerUnifiedStudioAuthInfoNode
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
-    public readonly onDidChangeChildren = this.onDidChangeEmitter.event
-
-    public constructor(
-        private readonly authProvider: SmusAuthenticationProvider,
-        private readonly extensionContext: vscode.ExtensionContext
-    ) {
-        this.authInfoNode = new SageMakerUnifiedStudioAuthInfoNode(this)
-        this.projectNode = new SageMakerUnifiedStudioProjectNode(this, this.authProvider, this.extensionContext)
-
-        // Subscribe to auth provider connection changes to refresh the node
-        this.authProvider.onDidChange(async () => {
-            // Clear the project when connection changes
-            await this.projectNode.clearProject()
-            this.onDidChangeEmitter.fire()
-            // Immediately refresh the tree view to show authenticated state
-            try {
-                await vscode.commands.executeCommand('aws.smus.rootView.refresh')
-            } catch (refreshErr) {
-                this.logger.debug(
-                    `Failed to refresh views after connection state change: ${(refreshErr as Error).message}`
-                )
-            }
-        })
-    }
-
-    public getTreeItem(): vscode.TreeItem {
-        const item = new vscode.TreeItem('SageMaker Unified Studio', vscode.TreeItemCollapsibleState.Expanded)
-        item.contextValue = contextValueSmusRoot
-        item.iconPath = getIcon('vscode-database')
-
-        // Set description based on authentication state
-        if (!this.isAuthenticated()) {
-            item.description = 'Not authenticated'
-        } else {
-            item.description = 'Connected'
-        }
-
-        return item
-    }
-
-    public async getChildren(): Promise {
-        const isAuthenticated = this.isAuthenticated()
-        const hasExpiredConnection = this.hasExpiredConnection()
-
-        this.logger.debug(
-            `SMUS Root Node getChildren: isAuthenticated=${isAuthenticated}, hasExpiredConnection=${hasExpiredConnection}`
-        )
-
-        // Check for expired connection first
-        if (hasExpiredConnection) {
-            // Show auth info node with expired indication
-            return [this.authInfoNode] // This will show expired connection info
-        }
-
-        // Check authentication state
-        if (!isAuthenticated) {
-            // Show login option and learn more link when not authenticated
-            return [
-                {
-                    id: 'smusLogin',
-                    resource: {},
-                    getTreeItem: () => {
-                        const item = new vscode.TreeItem('Sign in to get started', vscode.TreeItemCollapsibleState.None)
-                        item.contextValue = contextValueSmusLogin
-                        item.iconPath = getIcon('vscode-account')
-
-                        // Set up the login command
-                        item.command = {
-                            command: 'aws.smus.login',
-                            title: 'Sign in to SageMaker Unified Studio',
-                        }
-
-                        return item
-                    },
-                    getParent: () => this,
-                },
-                {
-                    id: 'smusLearnMore',
-                    resource: {},
-                    getTreeItem: () => {
-                        const item = new vscode.TreeItem(
-                            'Learn more about SageMaker Unified Studio',
-                            vscode.TreeItemCollapsibleState.None
-                        )
-                        item.contextValue = contextValueSmusLearnMore
-                        item.iconPath = getIcon('vscode-question')
-
-                        // Set up the learn more command
-                        item.command = {
-                            command: 'aws.smus.learnMore',
-                            title: 'Learn more about SageMaker Unified Studio',
-                        }
-
-                        return item
-                    },
-                    getParent: () => this,
-                },
-            ]
-        }
-
-        // When authenticated, show auth info and projects (same for both IAM and non-IAM mode)
-        return [this.authInfoNode, this.projectNode]
-    }
-
-    public getProjectSelectNode(): SageMakerUnifiedStudioProjectNode {
-        return this.projectNode
-    }
-
-    public getAuthInfoNode(): SageMakerUnifiedStudioAuthInfoNode {
-        return this.authInfoNode
-    }
-
-    public refresh(): void {
-        this.onDidChangeEmitter.fire()
-    }
-
-    /**
-     * Checks if the user has authenticated to SageMaker Unified Studio
-     * This is validated by checking existing Connections for SMUS or resource metadata.
-     */
-    private isAuthenticated(): boolean {
-        try {
-            // Check if the connection is valid using the authentication provider
-            const result = this.authProvider.isConnectionValid()
-            this.logger.debug(`Authentication check result: ${result}`)
-            return result
-        } catch (err) {
-            this.logger.debug('Authentication check failed: %s', (err as Error).message)
-            return false
-        }
-    }
-
-    private hasExpiredConnection(): boolean {
-        try {
-            const activeConnection = this.authProvider.activeConnection
-            const isConnectionValid = this.authProvider.isConnectionValid()
-
-            this.logger.debug(
-                `SMUS Root Node: activeConnection=${!!activeConnection}, isConnectionValid=${isConnectionValid}`
-            )
-
-            // Check if there's an active connection but it's expired/invalid
-            const hasExpiredConnection = activeConnection && !isConnectionValid
-
-            if (hasExpiredConnection) {
-                this.logger.debug('Connection is expired')
-                // Only show reauthentication prompt for SSO connections, not IAM connections
-                if (isSmusSsoConnection(activeConnection)) {
-                    this.logger.debug('Showing reauthentication prompt for SSO connection')
-                    void this.authProvider.showReauthenticationPrompt(activeConnection)
-                } else {
-                    this.logger.debug('Skipping reauthentication prompt for non-SSO connection')
-                }
-                return true
-            }
-            return false
-        } catch (err) {
-            this.logger.debug('Failed to check expired connection: %s', (err as Error).message)
-            return false
-        }
-    }
-}
-
-/**
- * Command to open the SageMaker Unified Studio documentation
- */
-export const smusLearnMoreCommand = Commands.declare('aws.smus.learnMore', () => async () => {
-    const logger = getLogger('smus')
-    try {
-        // Open the SageMaker Unified Studio documentation
-        await vscode.env.openExternal(vscode.Uri.parse('https://aws.amazon.com/sagemaker/unified-studio/'))
-
-        // Log telemetry
-        telemetry.record({
-            name: 'smus_learnMoreClicked',
-            result: 'Succeeded',
-            passive: false,
-        })
-    } catch (err) {
-        logger.error('Failed to open SageMaker Unified Studio documentation: %s', (err as Error).message)
-
-        // Log failure telemetry
-        telemetry.record({
-            name: 'smus_learnMoreClicked',
-            result: 'Failed',
-            passive: false,
-        })
-    }
-})
-
-/**
- * Command to login to SageMaker Unified Studio
- */
-export const smusLoginCommand = Commands.declare('aws.smus.login', (context: vscode.ExtensionContext) => async () => {
-    const logger = getLogger('smus')
-    return telemetry.smus_login.run(async (span) => {
-        try {
-            // Get the authentication provider instance
-            const authProvider = SmusAuthenticationProvider.fromContext()
-
-            // Import authentication method selection components
-            const { SmusAuthenticationMethodSelector } = await import('../../auth/ui/authenticationMethodSelection.js')
-            const { SmusAuthenticationPreferencesManager } = await import(
-                '../../auth/preferences/authenticationPreferences.js'
-            )
-
-            // Check for preferred authentication method
-            const preferredMethod = SmusAuthenticationPreferencesManager.getPreferredMethod(context)
-            logger.debug(`Retrieved preferred method: ${preferredMethod}`)
-
-            let selectedMethod: SmusAuthenticationMethod | undefined = preferredMethod
-            let authCompleted = false
-
-            // Main authentication loop - handles back navigation
-            while (!authCompleted) {
-                // Check if we should skip method selection (user has a remembered preference)
-                if (selectedMethod) {
-                    logger.debug(`Using authentication method: ${selectedMethod}`)
-                } else {
-                    // Show authentication method selection dialog
-                    logger.debug('Showing authentication method selection dialog')
-                    const methodSelection = await SmusAuthenticationMethodSelector.showAuthenticationMethodSelection()
-                    selectedMethod = methodSelection.method
-                }
-
-                // Handle the selected authentication method
-                logger.debug(`Processing authentication method: ${selectedMethod}`)
-                if (selectedMethod === 'sso') {
-                    // SSO Authentication - use SSO flow
-                    const ssoResult = await SmusAuthenticationOrchestrator.handleSsoAuthentication(
-                        authProvider,
-                        span,
-                        context
-                    )
-
-                    if (ssoResult.status === 'BACK') {
-                        // User wants to go back to authentication method selection
-                        selectedMethod = undefined // Reset to show method selection again
-                        continue // Restart the loop
-                    }
-
-                    authCompleted = true
-                } else {
-                    // IAM Authentication - use new IAM profile selection flow
-                    const iamResult = await SmusAuthenticationOrchestrator.handleIamAuthentication(
-                        authProvider,
-                        span,
-                        context
-                    )
-
-                    if (iamResult.status === 'BACK') {
-                        // User wants to go back to authentication method selection
-                        selectedMethod = undefined // Reset to show method selection again
-                        continue // Restart the loop
-                    }
-
-                    if (iamResult.status === 'EDITING') {
-                        // User is editing credentials, show helpful message with option to return to profile selection
-                        const action = await vscode.window.showInformationMessage(
-                            'Complete your AWS credential setup and try again, or return to profile selection.',
-                            'Select Profile',
-                            'Done'
-                        )
-
-                        if (action === 'Select Profile') {
-                            // User wants to return to profile selection, continue the loop
-                            continue
-                        } else {
-                            // User chose "Done" or dismissed, exit the authentication flow
-                            throw new ToolkitError('User cancelled credential setup', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        }
-                    }
-
-                    if (iamResult.status === 'INVALID_PROFILE') {
-                        // Profile validation failed, show error with option to select another profile
-                        const action = await vscode.window.showErrorMessage(
-                            `${iamResult.error}`,
-                            'Select Another Profile',
-                            'Cancel'
-                        )
-
-                        if (action === 'Select Another Profile') {
-                            // User wants to select a different profile, continue the loop
-                            continue
-                        } else {
-                            // User chose "Cancel" or dismissed, exit the authentication flow
-                            throw new ToolkitError('User cancelled profile selection', {
-                                code: SmusErrorCodes.UserCancelled,
-                                cancelled: true,
-                            })
-                        }
-                    }
-
-                    authCompleted = true
-                }
-            }
-
-            // Record telemetry with connection details after successful login
-            const domainId = authProvider.getDomainId?.()
-            const region = authProvider.getDomainRegion?.()
-            await recordAuthTelemetry(span, authProvider, domainId, region)
-        } catch (err) {
-            const isUserCancelled = err instanceof ToolkitError && err.code === SmusErrorCodes.UserCancelled
-            if (!isUserCancelled) {
-                void vscode.window.showErrorMessage(`Failed to initiate login: ${(err as Error).message}`)
-                logger.error('Failed to initiate login: %s', (err as Error).message)
-            }
-            throw err
-        }
-    })
-})
-
-/**
- * Command to sign out from SageMaker Unified Studio
- */
-export const smusSignOutCommand = Commands.declare(
-    'aws.smus.signOut',
-    (context: vscode.ExtensionContext) => async () => {
-        const logger = getLogger('smus')
-        return telemetry.smus_signOut.run(async (span) => {
-            try {
-                // Get the authentication provider instance
-                const authProvider = SmusAuthenticationProvider.fromContext()
-
-                // Check if there's an active connection to sign out from
-                if (!authProvider.isConnected()) {
-                    void vscode.window.showInformationMessage(
-                        'No active SageMaker Unified Studio connection to sign out from.'
-                    )
-                    return
-                }
-
-                // Capture connection details BEFORE signing out (for telemetry)
-                const activeConnection = authProvider.activeConnection
-                const domainId = authProvider.getDomainId?.()
-                const region = authProvider.getDomainRegion?.()
-
-                // Record telemetry with captured values BEFORE signing out
-                await recordAuthTelemetry(span, authProvider, domainId, region)
-
-                // Sign out from SMUS (behavior depends on connection type)
-                if (activeConnection) {
-                    await authProvider.signOut()
-                    logger.info(`Signed out from SageMaker Unified Studio: ${domainId}`)
-
-                    // Clear connection-specific preferences on sign out (but keep auth method preference)
-                    const { SmusAuthenticationPreferencesManager } = await import(
-                        '../../auth/preferences/authenticationPreferences.js'
-                    )
-                    await SmusAuthenticationPreferencesManager.clearConnectionPreferences(context)
-                }
-
-                // Dispose smusAuthProvider
-                authProvider.dispose()
-
-                // Show success message
-                void vscode.window.showInformationMessage('Successfully signed out from SageMaker Unified Studio.')
-
-                // Refresh the tree view to show the sign-in state
-                try {
-                    await vscode.commands.executeCommand('aws.smus.rootView.refresh')
-                } catch (refreshErr) {
-                    logger.debug(`Failed to refresh views after sign out: ${(refreshErr as Error).message}`)
-                    throw new ToolkitError('Failed to refresh views after sign out.', {
-                        cause: refreshErr as Error,
-                        code: (refreshErr as Error).name,
-                    })
-                }
-            } catch (err) {
-                void vscode.window.showErrorMessage(
-                    `SageMaker Unified Studio: Failed to sign out: ${(err as Error).message}`
-                )
-                logger.error('Failed to sign out: %s', (err as Error).message)
-
-                // Log failure telemetry
-                throw new ToolkitError('Failed to sign out.', {
-                    cause: err as Error,
-                    code: (err as Error).name,
-                })
-            }
-        })
-    }
-)
-
-function isAccessDenied(error: Error): boolean {
-    return error.name.includes('AccessDenied')
-}
-
-function createProjectQuickPickItems(projects: DataZoneProject[]) {
-    return projects
-        .sort(
-            (a, b) =>
-                (b.updatedAt ? new Date(b.updatedAt).getTime() : 0) -
-                (a.updatedAt ? new Date(a.updatedAt).getTime() : 0)
-        )
-        .filter((project) => project.name !== 'GenerativeAIModelGovernanceProject')
-        .map((project) => ({
-            label: project.name,
-            detail: 'ID: ' + project.id,
-            description: project.description,
-            data: project,
-        }))
-}
-
-async function showQuickPick(items: any[]) {
-    const quickPick = createQuickPick(items, {
-        title: projectPickerTitle,
-        placeholder: projectPickerPlaceholder,
-    })
-    return await quickPick.prompt()
-}
-
-/**
- * Fetches projects filtered by IAM principal
- * For IAM users: filters by user profile using userIdentifier
- * For IAM role sessions: filters by group profile using groupIdentifier
- * @param authProvider The SMUS authentication provider
- * @param datazoneClient The DataZone client instance
- * @returns Promise resolving to filtered projects array
- * @throws Error if profile retrieval fails
- */
-async function fetchProjectsByIamProfile(
-    authProvider: SmusAuthenticationProvider,
-    datazoneClient: DataZoneClient
-): Promise {
-    const logger = getLogger('smus')
-
-    // Get credentials provider for IAM profile
-    const activeConnection = authProvider.activeConnection
-    if (!isSmusIamConnection(activeConnection)) {
-        throw new Error('Active connection is not a valid IAM connection')
-    }
-
-    // Use cached caller identity ARN from auth provider
-    const callerIdentityArn = await authProvider.getIamPrincipalArn()
-    if (!callerIdentityArn) {
-        throw new Error('Unable to retrieve caller identity ARN from cache')
-    }
-
-    // Determine if this is an IAM user or IAM role session using utility method
-    const isIamUser = SmusUtils.isIamUserArn(callerIdentityArn)
-    logger.debug(
-        `Using cached caller identity ARN: ${callerIdentityArn}. Identity type: ${isIamUser ? 'IAM User' : 'IAM Role Session'}`
-    )
-
-    let projects: DataZoneProject[]
-
-    if (isIamUser) {
-        // IAM User flow - use GetUserProfile and filter by userIdentifier
-        logger.debug('Using IAM user flow with GetUserProfile API')
-
-        // Get user profile ID for the IAM user using DataZone client
-        const userProfileId = await datazoneClient.getUserProfileIdForIamPrincipal(
-            callerIdentityArn,
-            authProvider.getDomainId()
-        )
-        logger.info(`Retrieved user profile ID: ${userProfileId} for IAM principal ${callerIdentityArn}`)
-
-        // Fetch projects filtered by user profile
-        projects = await datazoneClient.fetchAllProjects({ userIdentifier: userProfileId })
-        logger.debug(`Fetched ${projects.length} projects for user profile ${userProfileId}`)
-    } else {
-        const credentialsProvider = await authProvider.getCredentialsProviderForIamProfile(activeConnection.profileName)
-        const datazoneCustomClientHelper = DataZoneCustomClientHelper.getInstance(
-            credentialsProvider,
-            authProvider.getDomainRegion()
-        )
-
-        // IAM Role Session flow - use SearchGroupProfile and filter by groupIdentifier
-        // The cached ARN needs conversion for role sessions
-        const roleArn = SmusUtils.convertAssumedRoleArnToIamRoleArn(callerIdentityArn)
-        logger.debug(`Using IAM role ARN: ${roleArn}`)
-
-        // Get group profile ID for the current role
-        const groupProfileId = await datazoneCustomClientHelper.getGroupProfileId(authProvider.getDomainId(), roleArn)
-        logger.info(`Retrieved group profile ID: ${groupProfileId}`)
-
-        // Fetch projects filtered by group profile
-        projects = await datazoneClient.fetchAllProjects({ groupIdentifier: groupProfileId })
-        logger.debug(`Fetched ${projects.length} projects for group profile ${groupProfileId}`)
-    }
-
-    return projects
-}
-
-export async function selectSMUSProject(projectNode?: SageMakerUnifiedStudioProjectNode) {
-    const logger = getLogger('smus')
-
-    return telemetry.smus_accessProject.run(async (span) => {
-        try {
-            const authProvider = SmusAuthenticationProvider.fromContext()
-            if (!authProvider.activeConnection) {
-                logger.error('No active connection to display project view')
-                return
-            }
-
-            const datazoneClient = await createDZClientBaseOnDomainMode(authProvider)
-            logger.debug('DataZone client instance obtained successfully')
-
-            let allProjects: DataZoneProject[]
-
-            if (getContext('aws.smus.isIamMode')) {
-                // Filter projects by IAM profile (user or role session)
-                try {
-                    allProjects = await fetchProjectsByIamProfile(authProvider, datazoneClient)
-                } catch (err) {
-                    const error = err as Error
-
-                    // Handle no profile found (user or group)
-                    if (
-                        error instanceof ToolkitError &&
-                        (error.code === SmusErrorCodes.NoGroupProfileFound ||
-                            error.code === SmusErrorCodes.NoUserProfileFound)
-                    ) {
-                        logger.error('No profile found for IAM principal: %s', error.message)
-
-                        const principalArn = await authProvider.getIamPrincipalArn()
-                        const arnSuffix = principalArn ? `: ${principalArn}` : ''
-                        void vscode.window.showErrorMessage(
-                            `No resources found for IAM principal${arnSuffix}. Ensure SageMaker Unified Studio resources exist for this IAM principal.`
-                        )
-                        return error
-                    }
-
-                    // Handle access denied
-                    if (isAccessDenied(error)) {
-                        logger.error('Access denied when retrieving profile: %s', error.message)
-                        void vscode.window.showErrorMessage(
-                            "You don't have permissions to access this resource. Please contact your administrator"
-                        )
-                        return error
-                    }
-
-                    // Handle other errors
-                    logger.error('Failed to retrieve profile information: %s', error.message)
-                    void vscode.window.showErrorMessage('Failed to fetch IAM principal information. Try again.')
-                    return error
-                }
-            } else {
-                // In non-IAM mode, fetch all projects without filtering
-                allProjects = await datazoneClient.fetchAllProjects()
-            }
-
-            const items = createProjectQuickPickItems(allProjects)
-
-            // Handle no projects scenario
-            if (items.length === 0) {
-                if (getContext('aws.smus.isIamMode')) {
-                    logger.debug('No accessible projects found for IAM principal')
-                    void vscode.window.showInformationMessage('No accessible projects found for your IAM principal')
-                } else {
-                    logger.debug('No projects found in the domain')
-                    void vscode.window.showInformationMessage('No projects found in the domain')
-                }
-                return
-            }
-
-            // Show project picker
-            const selectedProject = await showQuickPick(items)
-
-            const accountId = await authProvider.getDomainAccountId()
-            span.record({
-                smusAuthMode: authProvider.activeConnection?.type,
-                smusDomainId: authProvider.getDomainId(),
-                smusProjectId: (selectedProject as DataZoneProject).id as string | undefined,
-                smusDomainRegion: authProvider.getDomainRegion(),
-                smusDomainAccountId: accountId,
-            })
-            if (
-                selectedProject &&
-                typeof selectedProject === 'object' &&
-                selectedProject !== null &&
-                !('type' in selectedProject) &&
-                projectNode
-            ) {
-                await projectNode.setProject(selectedProject)
-                await vscode.commands.executeCommand('aws.smus.rootView.refresh')
-            }
-
-            return selectedProject
-        } catch (err) {
-            const error = err as Error
-
-            // Handle access denied scenarios
-            if (isAccessDenied(error)) {
-                logger.error('Access denied when fetching projects: %s', error.message)
-                await showQuickPick([
-                    {
-                        label: '$(error)',
-                        description: "You don't have permissions to view projects. Please contact your administrator",
-                    },
-                ])
-                return
-            }
-
-            // Handle network/API failures
-            logger.error('Failed to select project: %s', error.message)
-            await handleCredExpiredError(err, true)
-        }
-    })
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.ts
deleted file mode 100644
index 53ae501d96..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { SagemakerClient, SagemakerSpaceApp } from '../../../shared/clients/sagemaker'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { SageMakerUnifiedStudioSpacesParentNode } from './sageMakerUnifiedStudioSpacesParentNode'
-import { SagemakerSpace } from '../../../awsService/sagemaker/sagemakerSpace'
-
-export class SagemakerUnifiedStudioSpaceNode implements TreeNode {
-    private smSpace: SagemakerSpace
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
-    public readonly onDidChangeChildren = this.onDidChangeEmitter.event
-
-    public constructor(
-        private readonly parent: SageMakerUnifiedStudioSpacesParentNode,
-        public readonly sageMakerClient: SagemakerClient,
-        public readonly regionCode: string,
-        public readonly spaceApp: SagemakerSpaceApp,
-        isSMUSSpace: boolean
-    ) {
-        this.smSpace = new SagemakerSpace(this.sageMakerClient, this.regionCode, this.spaceApp, isSMUSSpace)
-    }
-
-    public getTreeItem(): vscode.TreeItem {
-        return {
-            label: this.smSpace.label,
-            description: this.smSpace.description,
-            tooltip: this.smSpace.tooltip,
-            iconPath: this.smSpace.iconPath,
-            contextValue: this.smSpace.contextValue,
-            collapsibleState: vscode.TreeItemCollapsibleState.None,
-        }
-    }
-
-    public getChildren(): TreeNode[] {
-        return []
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-
-    public async refreshNode(): Promise {
-        this.onDidChangeEmitter.fire()
-    }
-
-    public get id(): string {
-        return 'smusSpaceNode' + this.name
-    }
-
-    public get resource() {
-        return this
-    }
-
-    // Delegate all core functionality to SageMakerSpace instance
-    public updateSpace(spaceApp: SagemakerSpaceApp) {
-        this.smSpace.updateSpace(spaceApp)
-        if (this.isPending()) {
-            this.parent.trackPendingNode(this.DomainSpaceKey)
-        }
-    }
-
-    public setSpaceStatus(spaceStatus: string, appStatus: string) {
-        this.smSpace.setSpaceStatus(spaceStatus, appStatus)
-    }
-    public isPending(): boolean {
-        return this.smSpace.isPending()
-    }
-    public getStatus(): string {
-        return this.smSpace.getStatus()
-    }
-    public async getAppStatus() {
-        return this.smSpace.getAppStatus()
-    }
-    public get name(): string {
-        return this.smSpace.name
-    }
-    public get arn(): string {
-        return this.smSpace.arn
-    }
-    public async getAppArn() {
-        return this.smSpace.getAppArn()
-    }
-    public async getSpaceArn() {
-        return this.smSpace.getSpaceArn()
-    }
-    public async updateSpaceAppStatus() {
-        await this.smSpace.updateSpaceAppStatus()
-
-        if (this.isPending()) {
-            this.parent.trackPendingNode(this.DomainSpaceKey)
-        }
-        return
-    }
-    public buildTooltip() {
-        return this.smSpace.buildTooltip()
-    }
-    public getAppIcon() {
-        return this.smSpace.getAppIcon()
-    }
-    public get DomainSpaceKey(): string {
-        return this.smSpace.DomainSpaceKey
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.ts
deleted file mode 100644
index d2d7e4a817..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.ts
+++ /dev/null
@@ -1,372 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { SageMakerUnifiedStudioComputeNode } from './sageMakerUnifiedStudioComputeNode'
-import { updateInPlace } from '../../../shared/utilities/collectionUtils'
-import { DescribeDomainResponse } from '@amzn/sagemaker-client'
-import { getDomainUserProfileKey } from '../../../awsService/sagemaker/utils'
-import { getLogger } from '../../../shared/logger/logger'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import { SagemakerClient, SagemakerSpaceApp } from '../../../shared/clients/sagemaker'
-import { UserProfileMetadata } from '../../../awsService/sagemaker/explorer/sagemakerStudioNode'
-import { SagemakerUnifiedStudioSpaceNode } from './sageMakerUnifiedStudioSpaceNode'
-import { PollingSet } from '../../../shared/utilities/pollingSet'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { SmusUtils, SmusErrorCodes } from '../../shared/smusUtils'
-import { getIcon } from '../../../shared/icons'
-import { PENDING_NODE_POLLING_INTERVAL_MS } from './utils'
-import { getContext } from '../../../shared/vscode/setContext'
-import { createDZClientBaseOnDomainMode } from './utils'
-import { SmusIamConnection } from '../../auth/model'
-import { DataZoneCustomClientHelper } from '../../shared/client/datazoneCustomClientHelper'
-import { ToolkitError } from '../../../shared/errors'
-
-export class SageMakerUnifiedStudioSpacesParentNode implements TreeNode {
-    public readonly id = 'smusSpacesParentNode'
-    public readonly resource = this
-    private readonly sagemakerSpaceNodes: Map = new Map()
-    private spaceApps: Map = new Map()
-    private domainUserProfiles: Map = new Map()
-    private readonly logger = getLogger('smus')
-    private readonly onDidChangeEmitter = new vscode.EventEmitter()
-    public readonly onDidChangeTreeItem = this.onDidChangeEmitter.event
-    public readonly onDidChangeChildren = this.onDidChangeEmitter.event
-    public readonly pollingSet: PollingSet = new PollingSet(
-        PENDING_NODE_POLLING_INTERVAL_MS,
-        this.updatePendingNodes.bind(this)
-    )
-    private spaceAwsAccountRegion: string | undefined
-
-    public constructor(
-        private readonly parent: SageMakerUnifiedStudioComputeNode,
-        private readonly projectId: string,
-        private readonly extensionContext: vscode.ExtensionContext,
-        private readonly authProvider: SmusAuthenticationProvider,
-        private readonly sagemakerClient: SagemakerClient
-    ) {}
-
-    public async getTreeItem(): Promise {
-        const item = new vscode.TreeItem('Spaces', vscode.TreeItemCollapsibleState.Expanded)
-        item.iconPath = {
-            light: vscode.Uri.joinPath(
-                this.extensionContext.extensionUri,
-                'resources/icons/aws/sagemakerunifiedstudio/spaces-dark.svg'
-            ),
-            dark: vscode.Uri.joinPath(
-                this.extensionContext.extensionUri,
-                'resources/icons/aws/sagemakerunifiedstudio/spaces.svg'
-            ),
-        }
-        item.contextValue = 'smusSpacesNode'
-        item.description = 'Hover over any space and click the connection icon to connect remotely'
-        item.tooltip = item.description
-        return item
-    }
-
-    public async getChildren(): Promise {
-        try {
-            await this.updateChildren()
-        } catch (err) {
-            const error = err as Error
-            if (error.name === 'AccessDeniedException') {
-                return this.getAccessDeniedChildren()
-            }
-            // Handle no profile found (user or group) using error codes
-            if (
-                error instanceof ToolkitError &&
-                (error.code === SmusErrorCodes.NoGroupProfileFound || error.code === SmusErrorCodes.NoUserProfileFound)
-            ) {
-                return await this.getNoUserProfileChildren()
-            }
-            if (error.message.includes('Failed to retrieve user profile information')) {
-                return this.getUserProfileErrorChildren(error.message)
-            }
-            return this.getNoSpacesFoundChildren()
-        }
-        const nodes = [...this.sagemakerSpaceNodes.values()]
-        if (nodes.length === 0) {
-            return this.getNoSpacesFoundChildren()
-        }
-        return nodes
-    }
-
-    private getNoSpacesFoundChildren(): TreeNode[] {
-        return [
-            {
-                id: 'smusNoSpaces',
-                resource: {},
-                getTreeItem: () => new vscode.TreeItem('[No Spaces found]', vscode.TreeItemCollapsibleState.None),
-                getParent: () => this,
-            },
-        ]
-    }
-
-    private getAccessDeniedChildren(): TreeNode[] {
-        return [
-            {
-                id: 'smusAccessDenied',
-                resource: {},
-                getTreeItem: () => {
-                    const item = new vscode.TreeItem(
-                        "You don't have permission to view spaces. Please contact your administrator.",
-                        vscode.TreeItemCollapsibleState.None
-                    )
-                    item.iconPath = getIcon('vscode-error')
-                    return item
-                },
-                getParent: () => this,
-            },
-        ]
-    }
-
-    private async getNoUserProfileChildren(): Promise {
-        // Log the IAM principal ARN for debugging
-        const principalArn = await this.authProvider.getIamPrincipalArn()
-        if (principalArn) {
-            this.logger.error(`No spaces found for IAM principal: ${principalArn}`)
-        }
-
-        return [
-            {
-                id: 'smusNoUserProfile',
-                resource: {},
-                getTreeItem: () => {
-                    const item = new vscode.TreeItem(
-                        'No spaces found for IAM principal',
-                        vscode.TreeItemCollapsibleState.None
-                    )
-                    item.iconPath = getIcon('vscode-error')
-                    return item
-                },
-                getParent: () => this,
-            },
-        ]
-    }
-
-    private getUserProfileErrorChildren(message: string): TreeNode[] {
-        return [
-            {
-                id: 'smusUserProfileError',
-                resource: {},
-                getTreeItem: () => {
-                    const item = new vscode.TreeItem(
-                        'Failed to retrieve spaces. Please try again.',
-                        vscode.TreeItemCollapsibleState.None
-                    )
-                    item.iconPath = getIcon('vscode-error')
-                    return item
-                },
-                getParent: () => this,
-            },
-        ]
-    }
-
-    public getParent(): TreeNode | undefined {
-        return this.parent
-    }
-
-    public getProjectId(): string {
-        return this.projectId
-    }
-
-    public getAuthProvider(): SmusAuthenticationProvider {
-        return this.authProvider
-    }
-
-    public async refreshNode(): Promise {
-        this.onDidChangeEmitter.fire()
-    }
-
-    public trackPendingNode(domainSpaceKey: string) {
-        this.pollingSet.add(domainSpaceKey)
-    }
-
-    public getSpaceNodes(spaceKey: string): SagemakerUnifiedStudioSpaceNode {
-        const childNode = this.sagemakerSpaceNodes.get(spaceKey)
-        if (childNode) {
-            return childNode
-        } else {
-            throw new Error(`Node with id ${spaceKey} from polling set not found`)
-        }
-    }
-
-    public async getSageMakerDomainId(): Promise {
-        const activeConnection = this.authProvider.activeConnection
-        if (!activeConnection) {
-            this.logger.error('There is no active connection to get SageMaker domain ID')
-            throw new Error('No active connection found to get SageMaker domain ID')
-        }
-
-        this.logger.debug('Getting DataZone client instance')
-        const datazoneClient = await createDZClientBaseOnDomainMode(this.authProvider)
-        if (!datazoneClient) {
-            throw new Error('DataZone client is not initialized')
-        }
-
-        const toolingEnv = await datazoneClient.getToolingEnvironment(this.projectId)
-        this.spaceAwsAccountRegion = toolingEnv.awsAccountRegion
-        if (toolingEnv.provisionedResources) {
-            for (const resource of toolingEnv.provisionedResources) {
-                if (resource.name === 'sageMakerDomainId') {
-                    if (!resource.value) {
-                        throw new Error('SageMaker domain ID not found in tooling environment')
-                    }
-                    getLogger('smus').debug(`Found SageMaker domain ID: ${resource.value}`)
-                    return resource.value
-                }
-            }
-        }
-        throw new Error('No SageMaker domain found in the tooling environment')
-    }
-
-    private async updatePendingNodes() {
-        for (const spaceKey of this.pollingSet.values()) {
-            const childNode = this.getSpaceNodes(spaceKey)
-            await this.updatePendingSpaceNode(childNode)
-        }
-    }
-
-    private async updatePendingSpaceNode(node: SagemakerUnifiedStudioSpaceNode) {
-        await node.updateSpaceAppStatus()
-        if (!node.isPending()) {
-            this.pollingSet.delete(node.DomainSpaceKey)
-            await node.refreshNode()
-        }
-    }
-
-    /**
-     * Retrieves the user profile ID for IAM mode (IAM authentication)
-     * @returns Promise resolving to the user profile ID
-     * @throws Error if user profile retrieval fails
-     */
-    private async getUserProfileIdForIamAuthMode(): Promise {
-        try {
-            // Get cached caller IAM identity ARN from auth provider
-            const callerArn = await this.authProvider.getIamPrincipalArn()
-
-            if (!callerArn) {
-                throw new Error('Unable to retrieve caller identity ARN')
-            }
-            // Determine if this is an IAM user or role session based on ARN format
-            if (SmusUtils.isIamUserArn(callerArn)) {
-                // For IAM users, use GetUserProfile API directly via DataZoneClient
-                this.logger.debug(`Detected IAM user, using GetUserProfile API with ARN: ${callerArn}`)
-
-                const datazoneClient = await createDZClientBaseOnDomainMode(this.authProvider)
-                const userProfileId = await datazoneClient.getUserProfileIdForIamPrincipal(
-                    callerArn,
-                    this.authProvider.getDomainId()
-                )
-
-                if (!userProfileId) {
-                    throw new ToolkitError('No user profile found for IAM user')
-                }
-
-                this.logger.debug(`Retrieved user profile ID for IAM user: ${userProfileId}`)
-                return userProfileId
-            } else {
-                // For IAM role sessions, use SearchUserProfile API via DataZoneCustomClientHelper
-                // Need to get the full assumed role ARN (with session) for filtering
-                const assumedRoleArn = await this.authProvider.getCachedIamCallerIdentityArn()
-
-                if (!assumedRoleArn) {
-                    throw new Error('Unable to retrieve assumed role ARN with session')
-                }
-
-                this.logger.debug(
-                    `SMUS: Detected IAM role session, using SearchUserProfile API with ARN: ${assumedRoleArn}`
-                )
-
-                // Get credentials provider for the IAM profile
-                const credentialsProvider = await this.authProvider.getCredentialsProviderForIamProfile(
-                    (this.authProvider.activeConnection as SmusIamConnection).profileName
-                )
-
-                const datazoneCustomClientHelper = DataZoneCustomClientHelper.getInstance(
-                    credentialsProvider,
-                    this.authProvider.getDomainRegion()
-                )
-
-                const userProfileId = await datazoneCustomClientHelper.getUserProfileIdForSession(
-                    this.authProvider.getDomainId(),
-                    assumedRoleArn
-                )
-
-                this.logger.debug(`Retrieved user profile ID for role session: ${userProfileId}`)
-                return userProfileId
-            }
-        } catch (err) {
-            const error = err as Error
-            this.logger.error(`Failed to retrieve user profile information: ${error.message}`)
-
-            if (error.name === 'AccessDeniedException') {
-                throw new Error("You don't have permissions to access this resource. Please contact your administrator")
-            }
-            throw err
-        }
-    }
-
-    private async updateChildren(): Promise {
-        const datazoneClient = await createDZClientBaseOnDomainMode(this.authProvider)
-
-        let userProfileId
-        if (getContext('aws.smus.isIamMode')) {
-            userProfileId = await this.getUserProfileIdForIamAuthMode()
-        } else {
-            // Will be of format: 'ABCA4NU3S7PEOLDQPLXYZ:user-12345678-d061-70a4-0bf2-eeee67a6ab12'
-            const userId = await datazoneClient.getUserId()
-            userProfileId = SmusUtils.extractSSOIdFromUserId(userId || '')
-        }
-
-        const sagemakerDomainId = await this.getSageMakerDomainId()
-        const [spaceApps, domains] = await this.sagemakerClient.fetchSpaceAppsAndDomains(
-            sagemakerDomainId,
-            false /* filterSmusDomains */
-        )
-
-        // Filter spaceApps to only show spaces owned by current user
-        this.logger.debug(`Filtering spaces for user profile ID: ${userProfileId}`)
-        const filteredSpaceApps = new Map()
-        for (const [key, app] of spaceApps.entries()) {
-            const userProfile = app.OwnershipSettingsSummary?.OwnerUserProfileName
-            if (userProfileId === userProfile) {
-                filteredSpaceApps.set(key, app)
-            }
-        }
-        this.spaceApps = filteredSpaceApps
-        this.domainUserProfiles.clear()
-
-        for (const app of this.spaceApps.values()) {
-            const domainId = app.DomainId
-            const userProfile = app.OwnershipSettingsSummary?.OwnerUserProfileName
-            if (!domainId || !userProfile) {
-                continue
-            }
-
-            const domainUserProfileKey = getDomainUserProfileKey(domainId, userProfile)
-            this.domainUserProfiles.set(domainUserProfileKey, {
-                domain: domains.get(domainId) as DescribeDomainResponse,
-            })
-        }
-
-        updateInPlace(
-            this.sagemakerSpaceNodes,
-            this.spaceApps.keys(),
-            (key) => this.sagemakerSpaceNodes.get(key)!.updateSpace(this.spaceApps.get(key)!),
-            (key) =>
-                new SagemakerUnifiedStudioSpaceNode(
-                    this as any,
-                    this.sagemakerClient,
-                    this.spaceAwsAccountRegion ||
-                        (() => {
-                            throw new Error('No AWS account region found in tooling environment')
-                        })(),
-                    this.spaceApps.get(key)!,
-                    true /* isSMUSSpace */
-                )
-        )
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/types.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/types.ts
deleted file mode 100644
index 73da925a8f..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/types.ts
+++ /dev/null
@@ -1,229 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-// Node delimiter for creating unique IDs
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const NODE_ID_DELIMITER = '/'
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const AWS_DATA_CATALOG = 'AwsDataCatalog'
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const DATA_DEFAULT_IAM_CONNECTION_NAME_REGEXP = /^(project\.iam)|(default\.iam)$/
-// eslint-disable-next-line @typescript-eslint/naming-convention, id-length
-export const DATA_DEFAULT_LAKEHOUSE_CONNECTION_NAME_REGEXP = /^(project\.default_lakehouse)|(default\.catalog)$/
-// eslint-disable-next-line @typescript-eslint/naming-convention, id-length
-export const DATA_DEFAULT_ATHENA_CONNECTION_NAME_REGEXP = /^(project\.athena)|(default\.sql)$/
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const DATA_DEFAULT_S3_CONNECTION_NAME_REGEXP = /^(project\.s3_default_folder)|(default\.s3)$/
-// eslint-disable-next-line @typescript-eslint/naming-convention, id-length
-export const S3_PROJECT_NON_GIT_PROJECT_REPOSITORY_LOCATION_NAME_REGEXP =
-    /^(project\.non_git_project_repository_location)|(default\.s3_shared)$/
-
-// Database object types
-export enum DatabaseObjects {
-    EXTERNAL_TABLE = 'EXTERNAL_TABLE',
-    VIRTUAL_VIEW = 'VIRTUAL_VIEW',
-}
-
-// Ref: https://docs.aws.amazon.com/athena/latest/ug/data-types.html
-export const lakeHouseColumnTypes = {
-    NUMERIC: ['TINYINT', 'SMALLINT', 'INT', 'INTEGER', 'BIGINT', 'FLOAT', 'REAL', 'DOUBLE', 'DECIMAL'],
-    STRING: ['CHAR', 'STRING', 'VARCHAR', 'UUID'],
-    TIME: ['DATE', 'TIMESTAMP', 'INTERVAL'],
-    BOOLEAN: ['BOOLEAN'],
-    BINARY: ['BINARY', 'VARBINARY'],
-    COMPLEX: ['ARRAY', 'MAP', 'STRUCT', 'ROW', 'JSON'],
-}
-
-// Ref: https://docs.aws.amazon.com/redshift/latest/dg/c_Supported_data_types.html
-export const redshiftColumnTypes = {
-    NUMERIC: ['SMALLINT', 'INT2', 'INTEGER', 'INT', 'BIGINT', 'DECIMAL', 'NUMERIC', 'REAL', 'FLOAT', 'DOUBLE'],
-    STRING: ['CHAR', 'CHARACTER', 'NCHAR', 'BPCHAR', 'VARCHAR', 'VARCHAR', 'VARYING', 'NVARCHAR', 'TEXT'],
-    TIME: ['TIME', 'TIMETZ', 'TIMESTAMP', 'TIMESTAMPTZ', 'INTERVAL'],
-    BOOLEAN: ['BOOLEAN', 'BOOL'],
-    BINARY: ['VARBYTE', 'VARBINARY', 'BINARY', 'VARYING'],
-    COMPLEX: ['HLLSKETCH', 'SUPER', 'GEOMETRY', 'GEOGRAPHY'],
-}
-
-/**
- * Node types for different resources
- */
-export enum NodeType {
-    // Common types
-    CONNECTION = 'connection',
-    ERROR = 'error',
-    LOADING = 'loading',
-    EMPTY = 'empty',
-
-    // S3 types
-    S3_BUCKET = 's3-bucket',
-    S3_FOLDER = 'folder',
-    S3_FILE = 'file',
-    S3_ACCESS_GRANT = 's3-access-grant',
-
-    // Redshift types
-    REDSHIFT_CLUSTER = 'redshift-cluster',
-    REDSHIFT_DATABASE = 'database',
-    REDSHIFT_SCHEMA = 'schema',
-    REDSHIFT_TABLE = 'table',
-    REDSHIFT_VIEW = 'view',
-    REDSHIFT_FUNCTION = 'function',
-    REDSHIFT_STORED_PROCEDURE = 'storedProcedure',
-    REDSHIFT_COLUMN = 'column',
-    REDSHIFT_CONTAINER = 'container',
-
-    // Glue types
-    GLUE_CATALOG = 'catalog',
-    // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
-    GLUE_DATABASE = 'database',
-    // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
-    GLUE_TABLE = 'table',
-    // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
-    GLUE_VIEW = 'view',
-
-    // Redshift-specific catalog types
-    REDSHIFT_CATALOG = 'redshift-catalog',
-    REDSHIFT_CATALOG_DATABASE = 'redshift-catalog-database',
-}
-
-/**
- * Connection types
- */
-export enum ConnectionType {
-    S3 = 'S3',
-    REDSHIFT = 'REDSHIFT',
-    ATHENA = 'ATHENA',
-    GLUE = 'GLUE',
-    LAKEHOUSE = 'LAKEHOUSE',
-}
-
-/**
- * Resource types for Redshift
- */
-export enum ResourceType {
-    DATABASE = 'DATABASE',
-    CATALOG_DATABASE = 'CATALOG_DATABASE',
-    SCHEMA = 'SCHEMA',
-    TABLE = 'TABLE',
-    VIEW = 'VIEW',
-    FUNCTION = 'FUNCTION',
-    STORED_PROCEDURE = 'STORED_PROCEDURE',
-    COLUMNS = 'COLUMNS',
-    CATALOG = 'CATALOG',
-    EXTERNAL_DATABASE = 'EXTERNAL_DATABASE',
-    SHARED_DATABASE = 'SHARED_DATABASE',
-    EXTERNAL_SCHEMA = 'EXTERNAL_SCHEMA',
-    SHARED_SCHEMA = 'SHARED_SCHEMA',
-    EXTERNAL_TABLE = 'EXTERNAL_TABLE',
-    CATALOG_TABLE = 'CATALOG_TABLE',
-    DATA_CATALOG_TABLE = 'DATA_CATALOG_TABLE',
-    CATALOG_COLUMN = 'CATALOG_COLUMN',
-}
-
-/**
- * Node path information
- */
-export interface NodePath {
-    connection?: string
-    bucket?: string
-    key?: string
-    catalog?: string
-    database?: string
-    schema?: string
-    table?: string
-    column?: string
-    cluster?: string
-    label?: string
-    [key: string]: any
-}
-
-/**
- * Node data interface for tree nodes
- */
-export interface NodeData {
-    id: string
-    nodeType: NodeType
-    connectionType?: ConnectionType
-    value?: any
-    path?: NodePath
-    parent?: any
-    isContainer?: boolean
-    children?: any[]
-}
-
-/**
- * Redshift deployment types
- */
-export enum RedshiftType {
-    Serverless = 'SERVERLESS',
-    ServerlessDev = 'SERVERLESS_DEV',
-    ServerlessQA = 'SERVERLESS_QA',
-    Cluster = 'CLUSTER',
-    ClusterDev = 'CLUSTER_DEV',
-    ClusterQA = 'CLUSTER_QA',
-}
-
-/**
- * Authentication types for database integration connections
- */
-export enum DatabaseIntegrationConnectionAuthenticationTypes {
-    FEDERATED = '4',
-    TEMPORARY_CREDENTIALS_WITH_IAM = '5',
-    SECRET = '6',
-    IDC_ENHANCED_IAM_CREDENTIALS = '8',
-}
-
-/**
- * Redshift service model URLs
- */
-export const RedshiftServiceModelUrl = {
-    REDSHIFT_SERVERLESS_URL: 'redshift-serverless.amazonaws.com',
-    REDSHIFT_CLUSTER_URL: 'redshift.amazonaws.com',
-}
-
-/**
- * Client types for ClientStore
- */
-export enum ClientType {
-    S3Client = 'S3Client',
-    S3ControlClient = 'S3ControlClient',
-    SQLWorkbenchClient = 'SQLWorkbenchClient',
-    GlueClient = 'GlueClient',
-    GlueCatalogClient = 'GlueCatalogClient',
-}
-
-/**
- * Node types that are always leaf nodes
- */
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const LEAF_NODE_TYPES = [
-    NodeType.S3_FILE,
-    NodeType.REDSHIFT_COLUMN,
-    NodeType.ERROR,
-    NodeType.LOADING,
-    NodeType.EMPTY,
-]
-
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const NO_DATA_FOUND_MESSAGE = '[No data found]'
-
-/**
- * Glue connection types
- */
-export const glueConnectionTypes = [
-    'BIGQUERY',
-    'DOCUMENTDB',
-    'DYNAMODB',
-    'MYSQL',
-    'OPENSEARCH',
-    'ORACLE',
-    'POSTGRESQL',
-    'REDSHIFT',
-    'SAPHANA',
-    'SNOWFLAKE',
-    'SQLSERVER',
-    'TERADATA',
-    'VERTICA',
-]
diff --git a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/utils.ts b/packages/core/src/sagemakerunifiedstudio/explorer/nodes/utils.ts
deleted file mode 100644
index 64c2ffa64b..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/explorer/nodes/utils.ts
+++ /dev/null
@@ -1,538 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as vscode from 'vscode'
-import { getIcon, IconPath, addColor } from '../../../shared/icons'
-import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider'
-import {
-    NODE_ID_DELIMITER,
-    NodeType,
-    RedshiftServiceModelUrl,
-    RedshiftType,
-    ConnectionType,
-    NodeData,
-    LEAF_NODE_TYPES,
-    DATA_DEFAULT_LAKEHOUSE_CONNECTION_NAME_REGEXP,
-    redshiftColumnTypes,
-    lakeHouseColumnTypes,
-    glueConnectionTypes,
-} from './types'
-import { DataZoneClient, DataZoneConnection } from '../../shared/client/datazoneClient'
-import { getContext } from '../../../shared/vscode/setContext'
-import { SmusAuthenticationProvider } from '../../auth/providers/smusAuthenticationProvider'
-import { SmusIamConnection } from '../../auth/model'
-import { ConnectionStatus } from '@aws-sdk/client-datazone'
-import { Catalog } from '@amzn/glue-catalog-client'
-
-/**
- * Polling interval in milliseconds for checking space status updates
- */
-// eslint-disable-next-line @typescript-eslint/naming-convention
-export const PENDING_NODE_POLLING_INTERVAL_MS = 5000
-
-/**
- * Check if a catalog is a RedLake catalog
- */
-export const isRedLakeCatalog = (catalog?: Catalog) => {
-    return (
-        catalog?.FederatedCatalog?.ConnectionName === 'aws:redshift' ||
-        catalog?.CatalogProperties?.DataLakeAccessProperties?.CatalogType === 'aws:redshift'
-    )
-}
-
-/**
- * Check if a catalog is a S3 table catalog
- */
-export const isS3TablesCatalog = (catalog?: Catalog) => {
-    return catalog?.FederatedCatalog?.ConnectionName === 'aws:s3tables'
-}
-
-/**
- * Gets the label for a node based on its data
- */
-export function getLabel(data: {
-    id: string
-    nodeType: NodeType
-    isContainer?: boolean
-    path?: { key?: string; label?: string }
-    value?: any
-}): string {
-    // For S3 access grant nodes, use S3 (label) format
-    if (data.nodeType === NodeType.S3_ACCESS_GRANT && data.path?.label) {
-        return `S3 (${data.path.label})`
-    }
-
-    // For connection nodes, use the connection name
-    if (data.nodeType === NodeType.CONNECTION && data.value?.connection?.name) {
-        if (
-            data.value?.connection?.type === ConnectionType.LAKEHOUSE &&
-            DATA_DEFAULT_LAKEHOUSE_CONNECTION_NAME_REGEXP.test(data.value?.connection?.name)
-        ) {
-            if (getContext('aws.smus.isIamMode')) {
-                return 'Catalogs'
-            }
-            return 'Lakehouse'
-        }
-        const formattedType = data.value?.connection?.type?.replace(/([A-Z]+(?:_[A-Z]+)*)/g, (match: string) => {
-            const words = match.split('_')
-            return words.map((word: string) => word.charAt(0) + word.slice(1).toLowerCase()).join(' ')
-        })
-        return `${formattedType} (${data.value.connection.name})`
-    }
-
-    // For container nodes, use the node type
-    if (data.isContainer) {
-        switch (data.nodeType) {
-            case NodeType.REDSHIFT_TABLE:
-                return 'Tables'
-            case NodeType.REDSHIFT_VIEW:
-                return 'Views'
-            case NodeType.REDSHIFT_FUNCTION:
-                return 'Functions'
-            case NodeType.REDSHIFT_STORED_PROCEDURE:
-                return 'Stored Procedures'
-            default:
-                return data.nodeType
-        }
-    }
-
-    // For path-based nodes, use the last part of the path
-    if (data.path?.label) {
-        return data.path.label
-    }
-
-    // For S3 folders, add a trailing slash
-    if (data.nodeType === NodeType.S3_FOLDER) {
-        const key = data.path?.key || ''
-        const parts = key.split('/')
-        return parts[parts.length - 2] + '/'
-    }
-
-    // For S3 files, use the filename
-    if (data.nodeType === NodeType.S3_FILE) {
-        const key = data.path?.key || ''
-        const parts = key.split('/')
-        return parts[parts.length - 1]
-    }
-
-    // For other nodes, use the last part of the ID
-    const parts = data.id.split(NODE_ID_DELIMITER)
-    return parts[parts.length - 1]
-}
-
-/**
- * Determines if a node is a leaf node
- */
-export function isLeafNode(data: { nodeType: NodeType; isContainer?: boolean }): boolean {
-    // Container nodes are never leaf nodes
-    if (data.isContainer) {
-        return false
-    }
-
-    return LEAF_NODE_TYPES.includes(data.nodeType)
-}
-
-/**
- * Gets the icon for a node type
- */
-export function getIconForNodeType(nodeType: NodeType, isContainer?: boolean): vscode.ThemeIcon | IconPath | undefined {
-    switch (nodeType) {
-        case NodeType.CONNECTION:
-        case NodeType.S3_ACCESS_GRANT:
-            return undefined
-        case NodeType.S3_BUCKET:
-            return getIcon('aws-s3-bucket')
-        case NodeType.S3_FOLDER:
-            return getIcon('vscode-folder')
-        case NodeType.S3_FILE:
-            return getIcon('vscode-file')
-        case NodeType.REDSHIFT_CLUSTER:
-            return getIcon('aws-redshift-cluster')
-        case NodeType.REDSHIFT_DATABASE:
-        case NodeType.GLUE_DATABASE:
-            return new vscode.ThemeIcon('database')
-        case NodeType.REDSHIFT_SCHEMA:
-            return getIcon('aws-redshift-schema')
-        case NodeType.REDSHIFT_TABLE:
-        case NodeType.GLUE_TABLE:
-            return isContainer ? new vscode.ThemeIcon('table') : getIcon('aws-redshift-table')
-        case NodeType.REDSHIFT_VIEW:
-            return isContainer ? new vscode.ThemeIcon('list-tree') : new vscode.ThemeIcon('eye')
-        case NodeType.REDSHIFT_FUNCTION:
-        case NodeType.REDSHIFT_STORED_PROCEDURE:
-            return isContainer ? new vscode.ThemeIcon('list-tree') : new vscode.ThemeIcon('symbol-method')
-        case NodeType.GLUE_CATALOG:
-            return getIcon('aws-sagemakerunifiedstudio-catalog')
-        case NodeType.REDSHIFT_CATALOG:
-            return new vscode.ThemeIcon('database')
-        case NodeType.REDSHIFT_CATALOG_DATABASE:
-            return getIcon('aws-redshift-schema')
-        case NodeType.ERROR:
-            return new vscode.ThemeIcon('error')
-        case NodeType.LOADING:
-            return new vscode.ThemeIcon('loading~spin')
-        case NodeType.EMPTY:
-            return new vscode.ThemeIcon('info')
-        default:
-            return getIcon('vscode-circle-outline')
-    }
-}
-
-/**
- * Creates a standard tree item for a node
- */
-export function createTreeItem(
-    label: string,
-    nodeType: NodeType,
-    isLeaf: boolean,
-    isContainer?: boolean,
-    tooltip?: string
-): vscode.TreeItem {
-    const collapsibleState = isLeaf ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed
-
-    const item = new vscode.TreeItem(label, collapsibleState)
-
-    // Set icon based on node type
-    item.iconPath = getIconForNodeType(nodeType, isContainer)
-
-    // Set context value for command enablement
-    item.contextValue = nodeType
-
-    // Set tooltip if provided
-    if (tooltip) {
-        item.tooltip = tooltip
-    }
-
-    return item
-}
-
-/**
- * Gets the column type category from a raw column type string
- */
-export function getColumnType(columnTypeString?: string): string {
-    if (!columnTypeString) {
-        return 'UNKNOWN'
-    }
-
-    const lowerType = columnTypeString.toLowerCase()
-
-    // Search in both redshift and lakehouse column types
-    const allTypes = [...Object.values(redshiftColumnTypes).flat(), ...Object.values(lakeHouseColumnTypes).flat()].map(
-        (type) => type.toLowerCase()
-    )
-
-    return allTypes.find((key) => lowerType.startsWith(key)) || 'UNKNOWN'
-}
-
-/**
- * Gets the icon for a column based on its type
- */
-function getColumnIcon(columnType: string): vscode.ThemeIcon | IconPath {
-    const upperType = columnType.toUpperCase()
-
-    // Check if it's a numeric type
-    if (
-        lakeHouseColumnTypes.NUMERIC.some((type) => upperType.includes(type)) ||
-        redshiftColumnTypes.NUMERIC.some((type) => upperType.includes(type))
-    ) {
-        return getIcon('aws-sagemakerunifiedstudio-symbol-int')
-    }
-
-    // Check if it's a string type
-    if (
-        lakeHouseColumnTypes.STRING.some((type) => upperType.includes(type)) ||
-        redshiftColumnTypes.STRING.some((type) => upperType.includes(type))
-    ) {
-        return getIcon('vscode-symbol-key')
-    }
-
-    // Check if it's a time type
-    if (
-        lakeHouseColumnTypes.TIME.some((type) => upperType.includes(type)) ||
-        redshiftColumnTypes.TIME.some((type) => upperType.includes(type))
-    ) {
-        return getIcon('vscode-calendar')
-    }
-
-    // Check if it's a boolean type
-    if (
-        lakeHouseColumnTypes.BOOLEAN.some((type) => upperType.includes(type)) ||
-        redshiftColumnTypes.BOOLEAN.some((type) => upperType.includes(type))
-    ) {
-        return getIcon('vscode-symbol-boolean')
-    }
-
-    // Default icon for unknown types
-    return new vscode.ThemeIcon('symbol-field')
-}
-
-/**
- * Creates a tree item for a column node with type information
- */
-export function createColumnTreeItem(label: string, columnType: string, nodeType: NodeType): vscode.TreeItem {
-    const item = new vscode.TreeItem(label, vscode.TreeItemCollapsibleState.None)
-
-    // Add column type as description (secondary text)
-    item.description = columnType
-
-    // Set icon based on column type
-    item.iconPath = getColumnIcon(columnType)
-
-    // Set context value for command enablement
-    item.contextValue = nodeType
-
-    // Set tooltip
-    item.tooltip = `${label}: ${columnType}`
-
-    return item
-}
-
-/**
- * Creates an error node
- */
-export function createErrorTreeItem(message: string): vscode.TreeItem {
-    const item = new vscode.TreeItem(message, vscode.TreeItemCollapsibleState.None)
-    item.iconPath = new vscode.ThemeIcon('error')
-    return item
-}
-
-/**
- * Creates an error item with unique ID and proper styling
- */
-export function createErrorItem(message: string, context: string, parentId: string): TreeNode {
-    return {
-        id: `${parentId}-error-${context}-${Date.now()}`,
-        resource: message,
-        getTreeItem: () => {
-            const item = new vscode.TreeItem(message, vscode.TreeItemCollapsibleState.None)
-            item.iconPath = addColor(getIcon('vscode-error'), 'testing.iconErrored')
-            return item
-        },
-    }
-}
-
-export const isRedLakeDatabase = (databaseName?: string) => {
-    if (!databaseName) {
-        return false
-    }
-    const regex = /[\w\d\-_]+@[\w\d\-_]+/gs
-    return regex.test(databaseName)
-}
-
-/**
- * Gets the tooltip for a node
- * @param data The node data
- * @returns The tooltip text
- */
-export function getTooltip(data: NodeData): string {
-    const label = getLabel(data)
-
-    switch (data.nodeType) {
-        // Common node types
-        case NodeType.CONNECTION:
-            return data.connectionType === ConnectionType.REDSHIFT
-                ? `Redshift Connection: ${label}`
-                : `Connection: ${label}\nType: ${data.connectionType}`
-
-        // S3 node types
-        case NodeType.S3_BUCKET:
-            return `S3 Bucket: ${data.path?.bucket}`
-        case NodeType.S3_FOLDER:
-            return `Folder: ${label}\nBucket: ${data.path?.bucket}`
-        case NodeType.S3_FILE:
-            return `File: ${label}\nBucket: ${data.path?.bucket}`
-
-        // Redshift node types
-        case NodeType.REDSHIFT_CLUSTER:
-            return `Redshift Cluster: ${label}`
-        case NodeType.REDSHIFT_DATABASE:
-            return `Database: ${label}`
-        case NodeType.REDSHIFT_SCHEMA:
-            return `Schema: ${label}`
-        case NodeType.REDSHIFT_TABLE:
-            return data.isContainer ? `Tables in ${data.path?.schema}` : `Table: ${data.path?.schema}.${label}`
-        case NodeType.REDSHIFT_VIEW:
-            return data.isContainer ? `Views in ${data.path?.schema}` : `View: ${data.path?.schema}.${label}`
-        case NodeType.REDSHIFT_FUNCTION:
-            return data.isContainer ? `Functions in ${data.path?.schema}` : `Function: ${data.path?.schema}.${label}`
-        case NodeType.REDSHIFT_STORED_PROCEDURE:
-            return data.isContainer
-                ? `Stored Procedures in ${data.path?.schema}`
-                : `Stored Procedure: ${data.path?.schema}.${label}`
-
-        // Glue node types
-        case NodeType.GLUE_CATALOG:
-            return `Glue Catalog: ${label}`
-        case NodeType.GLUE_DATABASE:
-            return `Glue Database: ${label}`
-        case NodeType.GLUE_TABLE:
-            return `Glue Table: ${label}`
-
-        // Default
-        default:
-            return label
-    }
-}
-
-/**
- * Gets the Redshift type from a host
- * @param host Redshift host
- * @returns Redshift type or null if not recognized
- */
-export function getRedshiftTypeFromHost(host?: string): RedshiftType | undefined {
-    /*
-      'default-workgroup.{accountID}.us-west-2.redshift-serverless.amazonaws.com' - SERVERLESS
-      'default-rs-cluster.{id}.us-west-2.redshift.amazonaws.com' - CLUSTER
-      'default-rs-cluster.{id}.us-west-2.redshift.amazonaws.com:5439/dev' - CLUSTER
-     */
-    if (!host) {
-        return undefined
-    }
-
-    const cleanHost = host.split(':')[0]
-    const parts = cleanHost.split('.')
-    if (parts.length < 3) {
-        return undefined
-    }
-
-    const domain = parts.slice(parts.length - 3).join('.')
-
-    if (domain === RedshiftServiceModelUrl.REDSHIFT_SERVERLESS_URL) {
-        return RedshiftType.Serverless
-    } else if (domain === RedshiftServiceModelUrl.REDSHIFT_CLUSTER_URL) {
-        return RedshiftType.Cluster
-    } else {
-        return undefined
-    }
-}
-
-/**
- * This function searches for property keys that end with "Properties" (like "snowflakeProperties",
- * "redshiftProperties", "athenaProperties") and returns the actual property object, not just the key name.
- * It only works for connections that have a glueConnectionName, indicating they are federated connections.
- *
- * @param connection - The DataZone connection object to search
- * @returns The property object (not the key name) if found, undefined otherwise
- *
- * @example
- * ```typescript
- * // Redshift connection
- * const redshiftConnection = {
- *   glueConnectionName: 'my-redshift-glue-conn',
- *   props: {
- *     redshiftProperties: {
- *       status: 'FAILED',
- *       errorMessage: 'Connection timeout'
- *     }
- *   }
- * }
- * const result = getGluePropertiesKey(redshiftConnection)
- * // Returns: { status: 'FAILED', errorMessage: 'Connection timeout' }
- */
-export function getGluePropertiesKey(connection: DataZoneConnection) {
-    if (!connection?.props) {
-        return undefined
-    }
-    if (!connection.glueConnectionName) {
-        return undefined
-    }
-    // Check for other properties that might contain glue connection info
-    const propertiesKey = Object.keys(connection.props).find(
-        (key) =>
-            key.endsWith('Properties') &&
-            typeof connection.props![key] === 'object' &&
-            !Array.isArray(connection.props![key])
-    )
-
-    return propertiesKey ? connection.props[propertiesKey] : undefined
-}
-
-/**
- * This function handles the refactor where connections moved from a single `glueProperties` object to
- * connector-specific property bags (like `snowflakeProperties`, `redshiftProperties`, `athenaProperties`).
- * It first checks for the legacy `glueProperties` field, then falls back to connector-specific properties.
- *
- * @param connection - The DataZone connection object to extract properties from
- * @returns Object with optional status and errorMessage fields, or undefined if no properties found
- */
-export function getGlueProperties(connection?: DataZoneConnection) {
-    if (!connection?.props) {
-        return undefined
-    }
-    // Check for direct glueProperties
-    if ('glueProperties' in connection.props) {
-        return connection.props.glueProperties
-    }
-
-    return connection?.props?.[getGluePropertiesKey(connection)!] as
-        | { status?: ConnectionStatus; errorMessage?: string }
-        | undefined
-}
-
-/**
- * Determines if a connection is a federated connection by checking its type.
- * A connection is considered federated if it's either:
- * 1. A Redshift connection with Glue properties, or
- * 2. A connection type that exists in GlueConnectionType
- *
- * @param connection
- * @returns - boolean
- */
-export function isFederatedConnection(connection?: DataZoneConnection): boolean {
-    if (connection?.type === ConnectionType.REDSHIFT) {
-        return !!getGlueProperties(connection)
-    }
-
-    // Check if connection type exists in GlueConnectionType enum values
-    return glueConnectionTypes.includes(connection?.type || '')
-}
-
-/**
- * Creates a DataZoneClient with appropriate credentials provider based on domain mode
- * If domain mode is IAM mode, use the credential profile credential provider
- * If domain mode is not IAM mode, use the DER credential provider
- * @param smusAuthProvider The SMUS authentication provider
- * @returns Promise resolving to DataZoneClient instance
- */
-export async function createDZClientBaseOnDomainMode(
-    smusAuthProvider: SmusAuthenticationProvider
-): Promise {
-    let credentialsProvider
-    if (getContext('aws.smus.isIamMode') && !getContext('aws.smus.inSmusSpaceEnvironment')) {
-        credentialsProvider = await smusAuthProvider.getCredentialsProviderForIamProfile(
-            (smusAuthProvider.activeConnection as SmusIamConnection).profileName
-        )
-    } else {
-        credentialsProvider = await smusAuthProvider.getDerCredentialsProvider()
-    }
-    return DataZoneClient.createWithCredentials(
-        smusAuthProvider.getDomainRegion(),
-        smusAuthProvider.getDomainId(),
-        credentialsProvider
-    )
-}
-
-/**
- * Creates a DataZoneClient with appropriate credentials provider for a specific project
- * If domain mode is IAM mode, use the project credential provider
- * If domain mode is not IAM mode, use the DER credential provider
- * @param smusAuthProvider The SMUS authentication provider
- * @param projectId The project ID for project-specific credentials
- * @returns Promise resolving to DataZoneClient instance
- */
-export async function createDZClientForProject(
-    smusAuthProvider: SmusAuthenticationProvider,
-    projectId: string
-): Promise {
-    const credentialsProvider = getContext('aws.smus.isIamMode')
-        ? await smusAuthProvider.getProjectCredentialProvider(projectId)
-        : await smusAuthProvider.getDerCredentialsProvider()
-
-    return DataZoneClient.createWithCredentials(
-        smusAuthProvider.getDomainRegion(),
-        smusAuthProvider.getDomainId(),
-        credentialsProvider
-    )
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/README.md b/packages/core/src/sagemakerunifiedstudio/shared/client/README.md
deleted file mode 100644
index 17cc4767be..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/README.md
+++ /dev/null
@@ -1 +0,0 @@
-# Common business logic and APIs for SageMaker Unified Studio features
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/connectionClientStore.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/connectionClientStore.ts
deleted file mode 100644
index e48f96f6bd..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/connectionClientStore.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { S3Client } from './s3Client'
-import { SQLWorkbenchClient } from './sqlWorkbenchClient'
-import { GlueClient } from './glueClient'
-import { GlueCatalogClient } from './glueCatalogClient'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { ClientType } from '../../explorer/nodes/types'
-import { S3ControlClient } from '@aws-sdk/client-s3-control'
-import { getLogger } from '../../../shared/logger/logger'
-
-/**
- * Client store for managing service clients per connection
- */
-export class ConnectionClientStore {
-    private static instance: ConnectionClientStore
-    private clientCache: Record> = {}
-
-    private constructor() {}
-
-    public static getInstance(): ConnectionClientStore {
-        if (!ConnectionClientStore.instance) {
-            ConnectionClientStore.instance = new ConnectionClientStore()
-        }
-        return ConnectionClientStore.instance
-    }
-
-    /**
-     * Gets or creates a client for a specific connection
-     */
-    public getClient(connectionId: string, clientType: string, factory: () => T): T {
-        if (!this.clientCache[connectionId]) {
-            this.clientCache[connectionId] = {}
-        }
-
-        if (!this.clientCache[connectionId][clientType]) {
-            this.clientCache[connectionId][clientType] = factory()
-        }
-
-        return this.clientCache[connectionId][clientType]
-    }
-
-    /**
-     * Gets or creates an S3Client for a connection
-     */
-    public getS3Client(
-        connectionId: string,
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): S3Client {
-        return this.getClient(
-            connectionId,
-            ClientType.S3Client,
-            () => new S3Client(region, connectionCredentialsProvider)
-        )
-    }
-
-    /**
-     * Gets or creates a SQLWorkbenchClient for a connection
-     */
-    public getSQLWorkbenchClient(
-        connectionId: string,
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): SQLWorkbenchClient {
-        return this.getClient(connectionId, ClientType.SQLWorkbenchClient, () =>
-            SQLWorkbenchClient.createWithCredentials(region, connectionCredentialsProvider)
-        )
-    }
-
-    /**
-     * Gets or creates a GlueClient for a connection
-     */
-    public getGlueClient(
-        connectionId: string,
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): GlueClient {
-        return this.getClient(
-            connectionId,
-            ClientType.GlueClient,
-            () => new GlueClient(region, connectionCredentialsProvider)
-        )
-    }
-
-    /**
-     * Gets or creates a GlueCatalogClient for a connection
-     */
-    public getGlueCatalogClient(
-        connectionId: string,
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): GlueCatalogClient {
-        return this.getClient(connectionId, ClientType.GlueCatalogClient, () =>
-            GlueCatalogClient.createWithCredentials(region, connectionCredentialsProvider)
-        )
-    }
-
-    /**
-     * Gets or creates an S3ControlClient for a connection
-     */
-    public getS3ControlClient(
-        connectionId: string,
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): S3ControlClient {
-        return this.getClient(connectionId, ClientType.S3ControlClient, () => {
-            const credentialsProvider = async () => {
-                const credentials = await connectionCredentialsProvider.getCredentials()
-                return {
-                    accessKeyId: credentials.accessKeyId,
-                    secretAccessKey: credentials.secretAccessKey,
-                    sessionToken: credentials.sessionToken,
-                    expiration: credentials.expiration,
-                }
-            }
-            return new S3ControlClient({ region, credentials: credentialsProvider })
-        })
-    }
-
-    /**
-     * Clears all cached clients for a connection
-     */
-    public clearConnection(connectionId: string): void {
-        delete this.clientCache[connectionId]
-    }
-
-    /**
-     * Clears all cached clients
-     */
-    public clearAll(): void {
-        getLogger('smus').info('SMUS Connection: Clearing all cached clients')
-        this.clientCache = {}
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/credentialsAdapter.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/credentialsAdapter.ts
deleted file mode 100644
index ee8fef5a5d..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/credentialsAdapter.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import * as AWS from 'aws-sdk'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { getLogger } from '../../../shared/logger/logger'
-import { CredentialsProvider } from '../../../auth/providers/credentials'
-
-/**
- * Adapts a ConnectionCredentialsProvider (SDK v3) to work with SDK v2's CredentialProviderChain
- */
-export function adaptConnectionCredentialsProvider(
-    connectionCredentialsProvider: ConnectionCredentialsProvider | CredentialsProvider
-): AWS.CredentialProviderChain {
-    const provider = () => {
-        // Create SDK v2 Credentials that will resolve the provider when needed
-        const credentials = new AWS.Credentials({
-            accessKeyId: '',
-            secretAccessKey: '',
-            sessionToken: '',
-        })
-
-        // Override the get method to use the connection credentials provider
-        credentials.get = (callback) => {
-            getLogger('smus').debug('Attempting to get credentials from ConnectionCredentialsProvider')
-
-            connectionCredentialsProvider
-                .getCredentials()
-                .then((creds) => {
-                    getLogger('smus').debug('Successfully got credentials')
-
-                    credentials.accessKeyId = creds.accessKeyId as string
-                    credentials.secretAccessKey = creds.secretAccessKey as string
-                    credentials.sessionToken = creds.sessionToken as string
-                    credentials.expireTime = creds.expiration as Date
-                    callback()
-                })
-                .catch((err) => {
-                    getLogger('smus').debug(`Failed to get credentials: ${err}`)
-
-                    callback(err)
-                })
-        }
-
-        // Override needsRefresh to delegate to the connection credentials provider
-        credentials.needsRefresh = () => {
-            return true // Always call refresh, this is okay because there is caching existing in credential provider
-        }
-
-        // Override refresh to use the connection credentials provider
-        credentials.refresh = (callback) => {
-            credentials.get(callback)
-        }
-
-        return credentials
-    }
-
-    return new AWS.CredentialProviderChain([provider])
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneClient.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneClient.ts
deleted file mode 100644
index be612ce459..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneClient.ts
+++ /dev/null
@@ -1,873 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import {
-    ConnectionCredentials,
-    ConnectionSummary,
-    DataZone,
-    GetConnectionCommandOutput,
-    GetEnvironmentCredentialsCommandOutput,
-    ListConnectionsCommandOutput,
-    PhysicalEndpoint,
-    RedshiftPropertiesOutput,
-    S3PropertiesOutput,
-    ConnectionType,
-    GluePropertiesOutput,
-    GetEnvironmentCommandOutput,
-} from '@aws-sdk/client-datazone'
-import { getLogger } from '../../../shared/logger/logger'
-import { DefaultStsClient } from '../../../shared/clients/stsClient'
-import { getContext } from '../../../shared/vscode/setContext'
-import { CredentialsProvider } from '../../../auth/providers/credentials'
-import { DevSettings } from '../../../shared/settings'
-import { ToolkitError } from '../../../shared/errors'
-import { SmusErrorCodes } from '../smusUtils'
-
-/**
- * Represents a DataZone domain
- */
-export interface DataZoneDomain {
-    id: string
-    name: string
-    description?: string
-    status?: string
-    createdAt?: Date
-    updatedAt?: Date
-}
-
-/**
- * Represents a DataZone project
- */
-export interface DataZoneProject {
-    id: string
-    name: string
-    description?: string
-    domainId: string
-    createdBy?: string
-    createdAt?: Date
-    updatedAt?: Date
-}
-
-/**
- * Represents JDBC connection properties
- */
-export interface JdbcConnection {
-    jdbcIamUrl?: string
-    jdbcUrl?: string
-    username?: string
-    password?: string
-    secretId?: string
-    isProvisionedSecret?: boolean
-    redshiftTempDir?: string
-    host?: string
-    engine?: string
-    port?: number
-    dbname?: string
-    [key: string]: any
-}
-
-/**
- * Represents a DataZone connection
- */
-export interface DataZoneConnection {
-    connectionId: string
-    name: string
-    description?: string
-    type: string
-    domainId: string
-    environmentId?: string
-    projectId: string
-    props?: {
-        s3Properties?: S3PropertiesOutput
-        redshiftProperties?: RedshiftPropertiesOutput
-        glueProperties?: GluePropertiesOutput
-        jdbcConnection?: JdbcConnection
-        [key: string]: any
-    }
-    /**
-     * Connection credentials when retrieved with withSecret=true
-     */
-    connectionCredentials?: ConnectionCredentials
-    /**
-     * Location information parsed from physical endpoints
-     */
-    location?: {
-        accessRole?: string
-        awsRegion?: string
-        awsAccountId?: string
-        iamConnectionId?: string
-    }
-    /**
-     * Glue connection name
-     */
-    glueConnectionName?: string
-}
-
-// Constants for DataZone environment configuration
-const toolingBlueprintName = 'Tooling'
-const sageMakerProviderName = 'Amazon SageMaker'
-
-/**
- * Client for interacting with AWS DataZone API
- *
- * This client can be used with different credential providers
- */
-export class DataZoneClient {
-    private datazoneClient: DataZone | undefined
-    private static instances = new Map()
-    private readonly logger = getLogger('smus')
-
-    private constructor(
-        private readonly region: string,
-        private readonly domainId: string,
-        private readonly credentialsProvider?: CredentialsProvider
-    ) {}
-
-    /**
-     * Creates a new DataZoneClient instance with specific credentials
-     * @param region AWS region
-     * @param domainId DataZone domain ID
-     * @param credentialsProvider Credentials provider
-     * @returns DataZoneClient instance with credentials
-     */
-    public static createWithCredentials(
-        region: string,
-        domainId: string,
-        credentialsProvider: CredentialsProvider
-    ): DataZoneClient {
-        const instanceKey = credentialsProvider.getHashCode()
-
-        if (DataZoneClient.instances.has(instanceKey)) {
-            const existingInstance = DataZoneClient.instances.get(instanceKey)!
-            getLogger('smus').debug(`DataZoneClient: Using existing instance, instance key is ${instanceKey}`)
-            return existingInstance
-        }
-
-        // Create new instance
-        getLogger('smus').debug(`DataZoneClient: Creating new instance with instance key ${instanceKey}`)
-        const instance = new DataZoneClient(region, domainId, credentialsProvider)
-        DataZoneClient.instances.set(instanceKey, instance)
-
-        return instance
-    }
-
-    /**
-     * Disposes all cached DataZoneClient instances
-     */
-    public static dispose(): void {
-        const logger = getLogger('smus')
-        getLogger('smus').debug('DataZoneClient: Disposing all cached instances')
-
-        for (const [key, instance] of DataZoneClient.instances.entries()) {
-            instance.datazoneClient = undefined
-            logger.debug(`DataZoneClient: Disposed instance for: ${key}`)
-        }
-
-        DataZoneClient.instances.clear()
-    }
-
-    /**
-     * Parse a Redshift connection info object from JDBC URL
-     * @param jdbcURL Example JDBC URL: jdbc:redshift://redshift-serverless-workgroup-3zzw0fjmccdixz.123456789012.us-east-1.redshift-serverless.amazonaws.com:5439/dev
-     * @returns A object contains info of host, engine, port, dbName
-     */
-    private getRedshiftConnectionInfoFromJdbcURL(jdbcURL: string) {
-        if (!jdbcURL) {
-            return
-        }
-
-        const [, engine, hostWithLeadingSlashes, portAndDBName] = jdbcURL.split(':')
-        const [port, dbName] = portAndDBName.split('/')
-        return {
-            host: hostWithLeadingSlashes.split('/')[2],
-            engine,
-            port,
-            dbName,
-        }
-    }
-
-    /**
-     * Builds a JDBC connection object from Redshift properties
-     * @param redshiftProps The Redshift properties
-     * @returns A JDBC connection object
-     */
-    private buildJdbcConnectionFromRedshiftProps(redshiftProps: RedshiftPropertiesOutput): JdbcConnection {
-        const redshiftConnectionInfo = this.getRedshiftConnectionInfoFromJdbcURL(redshiftProps.jdbcUrl ?? '')
-
-        return {
-            jdbcIamUrl: redshiftProps.jdbcIamUrl,
-            jdbcUrl: redshiftProps.jdbcUrl,
-            username: redshiftProps.credentials?.usernamePassword?.username,
-            password: redshiftProps.credentials?.usernamePassword?.password,
-            secretId: redshiftProps.credentials?.secretArn,
-            isProvisionedSecret: redshiftProps.isProvisionedSecret,
-            redshiftTempDir: redshiftProps.redshiftTempDir,
-            host: redshiftConnectionInfo?.host,
-            engine: redshiftConnectionInfo?.engine,
-            port: Number(redshiftConnectionInfo?.port),
-            dbname: redshiftConnectionInfo?.dbName,
-        }
-    }
-
-    /**
-     * Gets the DataZone domain ID
-     * @returns DataZone domain ID
-     */
-    public getDomainId(): string {
-        return this.domainId
-    }
-
-    /**
-     * Gets the AWS region
-     * @returns AWS region
-     */
-    public getRegion(): string {
-        return this.region
-    }
-
-    /**
-     * Gets the default tooling environment credentials for a DataZone project
-     * @param projectId The DataZone project identifier
-     * @returns Promise resolving to environment credentials
-     * @throws Error if tooling blueprint or environment is not found
-     */
-    public async getProjectDefaultEnvironmentCreds(projectId: string): Promise {
-        try {
-            this.logger.debug(
-                `Getting project default environment credentials for domain ${this.domainId}, project ${projectId}`
-            )
-            const datazoneClient = await this.getDataZoneClient()
-
-            this.logger.debug('Listing environment blueprints')
-            const domainBlueprints = await datazoneClient.listEnvironmentBlueprints({
-                domainIdentifier: this.domainId,
-                managed: true,
-                name: this.getToolingBlueprintName(),
-            })
-            const toolingBlueprint = domainBlueprints.items?.[0]
-            if (!toolingBlueprint) {
-                this.logger.error('Failed to get tooling blueprint')
-                throw new Error('Failed to get tooling blueprint')
-            }
-            this.logger.debug(`Found tooling blueprint with ID: ${toolingBlueprint.id}, listing environments`)
-
-            const listEnvs = await datazoneClient.listEnvironments({
-                domainIdentifier: this.domainId,
-                projectIdentifier: projectId,
-                environmentBlueprintIdentifier: toolingBlueprint.id,
-                provider: sageMakerProviderName,
-            })
-
-            const defaultEnv = listEnvs.items?.[0]
-            if (!defaultEnv) {
-                this.logger.error('Failed to find default Tooling environment')
-                throw new Error('Failed to find default Tooling environment')
-            }
-            this.logger.debug(`Found default environment with ID: ${defaultEnv.id}, getting environment credentials`)
-
-            const defaultEnvCreds = await datazoneClient.getEnvironmentCredentials({
-                domainIdentifier: this.domainId,
-                environmentIdentifier: defaultEnv.id,
-            })
-
-            return defaultEnvCreds
-        } catch (err) {
-            this.logger.error('Failed to get project default environment credentials: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the DataZone client, initializing it if necessary
-     */
-    private async getDataZoneClient(): Promise {
-        if (!this.datazoneClient) {
-            try {
-                if (this.credentialsProvider) {
-                    const awsCredentialProvider = async () => {
-                        const credentials = await this.credentialsProvider!.getCredentials()
-                        return {
-                            accessKeyId: credentials.accessKeyId,
-                            secretAccessKey: credentials.secretAccessKey,
-                            sessionToken: credentials.sessionToken,
-                            expiration: credentials.expiration,
-                        }
-                    }
-
-                    const clientConfig: any = {
-                        region: this.region,
-                        credentials: awsCredentialProvider,
-                    }
-
-                    // Use user setting for endpoint if provided
-                    const devSettings = DevSettings.instance
-                    const customEndpoint = devSettings.get('endpoints', {})['datazone']
-                    if (customEndpoint) {
-                        clientConfig.endpoint = customEndpoint
-                        this.logger.debug(
-                            `DataZoneClient: Using custom DataZone endpoint from settings: ${customEndpoint}`
-                        )
-                    }
-
-                    this.datazoneClient = new DataZone(clientConfig)
-                } else {
-                    throw new Error('No credentials provider provided')
-                }
-
-                this.logger.info('DataZoneClient: Successfully created authenticated DataZone client')
-            } catch (err) {
-                this.logger.error('DataZoneClient: Failed to create DataZone client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.datazoneClient
-    }
-
-    /**
-     * Lists project memberships in a DataZone project with pagination support
-     * @param options Options for listing project memberships
-     * @returns Paginated list of DataZone project permissions with nextToken
-     */
-    public async listProjectMemberships(options: {
-        projectIdentifier: string
-        maxResults?: number
-        nextToken?: string
-    }): Promise<{ memberships: any[]; nextToken?: string }> {
-        try {
-            this.logger.info(
-                `DataZoneClient: Listing project memberships for project ${options.projectIdentifier} in domain ${this.domainId}`
-            )
-
-            const datazoneClient = await this.getDataZoneClient()
-
-            const response = await datazoneClient.listProjectMemberships({
-                domainIdentifier: this.domainId,
-                projectIdentifier: options.projectIdentifier,
-                maxResults: options.maxResults,
-                nextToken: options.nextToken,
-            })
-
-            if (!response.members || response.members.length === 0) {
-                this.logger.info(
-                    `DataZoneClient: No project memberships found for project ${options.projectIdentifier}`
-                )
-                return { memberships: [] }
-            }
-
-            this.logger.debug(
-                `DataZoneClient: Found ${response.members.length} project memberships for project ${options.projectIdentifier}`
-            )
-            return { memberships: response.members, nextToken: response.nextToken }
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to list project memberships: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Fetches all project memberships in a DataZone project by handling pagination automatically
-     * @param projectIdentifier The DataZone project identifier
-     * @returns Promise resolving to an array of all project memberships
-     */
-    public async fetchAllProjectMemberships(projectIdentifier: string): Promise {
-        try {
-            let allMemberships: any[] = []
-            let nextToken: string | undefined
-            do {
-                const maxResultsPerPage = 50
-                const response = await this.listProjectMemberships({
-                    projectIdentifier,
-                    nextToken,
-                    maxResults: maxResultsPerPage,
-                })
-                allMemberships = [...allMemberships, ...response.memberships]
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            this.logger.debug(`DataZoneClient: Fetched a total of ${allMemberships.length} project memberships`)
-            return allMemberships
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to fetch all project memberships: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Lists projects in a DataZone domain with pagination support
-     * @param options Options for listing projects
-     * @returns Paginated list of DataZone projects with nextToken
-     */
-    public async listProjects(options?: {
-        maxResults?: number
-        userIdentifier?: string
-        groupIdentifier?: string
-        name?: string
-        nextToken?: string
-    }): Promise<{ projects: DataZoneProject[]; nextToken?: string }> {
-        try {
-            this.logger.info(`DataZoneClient: Listing projects for domain ${this.domainId} in region ${this.region}`)
-
-            const datazoneClient = await this.getDataZoneClient()
-
-            // Call the DataZone API to list projects with pagination
-            const response = await datazoneClient.listProjects({
-                domainIdentifier: this.domainId,
-                maxResults: options?.maxResults,
-                userIdentifier: options?.userIdentifier,
-                groupIdentifier: options?.groupIdentifier,
-                name: options?.name,
-                nextToken: options?.nextToken,
-            })
-
-            if (!response.items || response.items.length === 0) {
-                this.logger.info(`DataZoneClient: No projects found for domain ${this.domainId}`)
-                return { projects: [] }
-            }
-
-            // Map the response to our DataZoneProject interface
-            const projects: DataZoneProject[] = response.items.map((project) => ({
-                id: project.id || '',
-                name: project.name || '',
-                description: project.description,
-                domainId: this.domainId,
-                createdBy: project.createdBy,
-                createdAt: project.createdAt ? new Date(project.createdAt) : undefined,
-                updatedAt: project.updatedAt ? new Date(project.updatedAt) : undefined,
-            }))
-
-            this.logger.debug(`DataZoneClient: Found ${projects.length} projects for domain ${this.domainId}`)
-            return { projects, nextToken: response.nextToken }
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to list projects: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Fetches all projects in a DataZone domain by handling pagination automatically
-     * @param options Options for listing projects (excluding nextToken which is handled internally)
-     * @returns Promise resolving to an array of all DataZone projects
-     */
-    public async fetchAllProjects(options?: {
-        userIdentifier?: string
-        groupIdentifier?: string
-        name?: string
-    }): Promise {
-        try {
-            let allProjects: DataZoneProject[] = []
-            let nextToken: string | undefined
-            do {
-                const maxResultsPerPage = 50
-                const response = await this.listProjects({
-                    ...options,
-                    nextToken,
-                    maxResults: maxResultsPerPage,
-                })
-                allProjects = [...allProjects, ...response.projects]
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            this.logger.debug(`DataZoneClient: Fetched a total of ${allProjects.length} projects`)
-            return allProjects
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to fetch all projects: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Gets a specific project by ID
-     * @param projectId The project identifier
-     * @returns Promise resolving to the project details
-     */
-    public async getProject(projectId: string): Promise {
-        try {
-            this.logger.info(`DataZoneClient: Getting project ${projectId} in domain ${this.domainId}`)
-
-            const datazoneClient = await this.getDataZoneClient()
-
-            const response = await datazoneClient.getProject({
-                domainIdentifier: this.domainId,
-                identifier: projectId,
-            })
-
-            const project: DataZoneProject = {
-                id: response.id || '',
-                name: response.name || '',
-                description: response.description,
-                domainId: this.domainId,
-                createdAt: response.createdAt ? new Date(response.createdAt) : undefined,
-                updatedAt: response.lastUpdatedAt ? new Date(response.lastUpdatedAt) : undefined,
-            }
-
-            this.logger.debug(`DataZoneClient: Retrieved project ${projectId} with name: ${project.name}`)
-            return project
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to get project: %s', err as Error)
-            throw err
-        }
-    }
-
-    /*
-     * Processes a connection response to add jdbcConnection if it's a Redshift connection
-     * @param connection The connection object to process
-     * @param connectionType The connection type
-     */
-    private processRedshiftConnection(connection: ConnectionSummary): void {
-        if (
-            connection &&
-            connection.props &&
-            'redshiftProperties' in connection.props &&
-            connection.props.redshiftProperties &&
-            connection.type?.toLowerCase().includes('redshift')
-        ) {
-            const redshiftProps = connection.props.redshiftProperties as RedshiftPropertiesOutput
-            const props = connection.props as Record
-
-            if (!props.jdbcConnection) {
-                props.jdbcConnection = this.buildJdbcConnectionFromRedshiftProps(redshiftProps)
-            }
-        }
-    }
-
-    /**
-     * Parses location from physical endpoints
-     * @param physicalEndpoints Array of physical endpoints
-     * @returns Location object or undefined
-     */
-    private parseLocationFromPhysicalEndpoints(physicalEndpoints?: PhysicalEndpoint[]): DataZoneConnection['location'] {
-        if (physicalEndpoints && physicalEndpoints.length > 0) {
-            const physicalEndpoint = physicalEndpoints[0]
-            return {
-                accessRole: physicalEndpoint.awsLocation?.accessRole,
-                awsRegion: physicalEndpoint.awsLocation?.awsRegion,
-                awsAccountId: physicalEndpoint.awsLocation?.awsAccountId,
-                iamConnectionId: physicalEndpoint.awsLocation?.iamConnectionId,
-            }
-        }
-        return undefined
-    }
-
-    /**
-     * Parses glueConnectionName from physical endpoints
-     * @param physicalEndpoints Array of physical endpoints
-     * @returns glueConnectionName or undefined
-     */
-    // eslint-disable-next-line id-length
-    private parseGlueConnectionNameFromPhysicalEndpoints(
-        physicalEndpoints?: PhysicalEndpoint[]
-    ): DataZoneConnection['glueConnectionName'] {
-        if (physicalEndpoints && physicalEndpoints.length > 0) {
-            const physicalEndpoint = physicalEndpoints[0]
-            return physicalEndpoint.glueConnectionName
-        }
-        return undefined
-    }
-
-    /**
-     * Gets a specific connection by ID
-     * @param params Parameters for getting a connection
-     * @returns The connection details
-     */
-    public async getConnection(params: {
-        domainIdentifier: string
-        identifier: string
-        withSecret?: boolean
-    }): Promise {
-        try {
-            this.logger.info(
-                `DataZoneClient: Getting connection ${params.identifier} in domain ${params.domainIdentifier}`
-            )
-
-            const datazoneClient = await this.getDataZoneClient()
-
-            // Call the DataZone API to get connection
-            const response: GetConnectionCommandOutput = await datazoneClient.getConnection({
-                domainIdentifier: params.domainIdentifier,
-                identifier: params.identifier,
-                withSecret: params.withSecret !== undefined ? params.withSecret : true,
-            })
-
-            // Process the connection to add jdbcConnection if it's a Redshift connection
-            this.processRedshiftConnection(response)
-
-            // Parse location from physical endpoints
-            const location = this.parseLocationFromPhysicalEndpoints(response.physicalEndpoints)
-
-            const glueConnectionName = this.parseGlueConnectionNameFromPhysicalEndpoints(response.physicalEndpoints)
-
-            // Return as DataZoneConnection, currently only required fields are added
-            // Can always include new fields in DataZoneConnection when needed
-            const connection: DataZoneConnection = {
-                connectionId: response.connectionId || '',
-                name: response.name || '',
-                description: response.description,
-                type: response.type || '',
-                domainId: params.domainIdentifier,
-                projectId: response.projectId || '',
-                props: response.props || {},
-                connectionCredentials: response.connectionCredentials,
-                location,
-                glueConnectionName,
-            }
-
-            return connection
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to get connection: %s', err as Error)
-            throw err
-        }
-    }
-
-    public async fetchConnections(
-        domain: string | undefined,
-        project: string | undefined,
-        ConnectionType: ConnectionType
-    ): Promise {
-        const datazoneClient = await this.getDataZoneClient()
-        return datazoneClient.listConnections({
-            domainIdentifier: domain,
-            projectIdentifier: project,
-            type: ConnectionType,
-        })
-    }
-    /**
-     * Lists connections in a DataZone environment
-     * @param domainId The DataZone domain identifier
-     * @param environmentId The DataZone environment identifier
-     * @param projectId The DataZone project identifier
-     * @returns List of DataZone connections
-     */
-    public async listConnections(
-        domainId: string,
-        environmentId: string | undefined,
-        projectId: string
-    ): Promise {
-        try {
-            this.logger.info(
-                `DataZoneClient: Listing connections for environment ${environmentId} in domain ${domainId}`
-            )
-
-            const datazoneClient = await this.getDataZoneClient()
-            let allConnections: DataZoneConnection[] = []
-            let nextToken: string | undefined
-
-            do {
-                // Call the DataZone API to list connections with pagination
-                const response: ListConnectionsCommandOutput = await datazoneClient.listConnections({
-                    domainIdentifier: domainId,
-                    projectIdentifier: projectId,
-                    environmentIdentifier: environmentId,
-                    nextToken,
-                    maxResults: 50,
-                })
-
-                if (response.items && response.items.length > 0) {
-                    // Map the response to our DataZoneConnection interface
-                    const connections: DataZoneConnection[] = response.items.map((connection) => {
-                        // Process the connection to add jdbcConnection if it's a Redshift connection
-                        this.processRedshiftConnection(connection)
-
-                        // Parse location from physical endpoints
-                        const location = this.parseLocationFromPhysicalEndpoints(connection.physicalEndpoints)
-
-                        const glueConnectionName = this.parseGlueConnectionNameFromPhysicalEndpoints(
-                            connection.physicalEndpoints
-                        )
-
-                        return {
-                            connectionId: connection.connectionId || '',
-                            name: connection.name || '',
-                            description: '',
-                            type: connection.type || '',
-                            domainId,
-                            environmentId,
-                            projectId,
-                            props: connection.props || {},
-                            location,
-                            glueConnectionName,
-                        }
-                    })
-                    allConnections = [...allConnections, ...connections]
-                }
-
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            this.logger.info(`DataZoneClient: Fetched a total of ${allConnections.length} connections`)
-            return allConnections
-        } catch (err) {
-            this.logger.error('DataZoneClient: Failed to list connections: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the tooling environment ID for a project
-     * @param domainId The DataZone domain identifier
-     * @param projectId The DataZone project identifier
-     * @returns Promise resolving to the tooling environment ID
-     */
-    public async getToolingEnvironmentId(domainId: string, projectId: string): Promise {
-        this.logger.debug(`Getting tooling environment ID for domain ${domainId}, project ${projectId}`)
-        const datazoneClient = await this.getDataZoneClient()
-
-        let domainBlueprints
-        try {
-            // Get the tooling blueprint
-            domainBlueprints = await datazoneClient.listEnvironmentBlueprints({
-                domainIdentifier: domainId,
-                managed: true,
-                name: this.getToolingBlueprintName(),
-            })
-        } catch (err) {
-            this.logger.error(
-                'Failed to list environment blueprints for domain %s, %s',
-                domainId,
-                (err as Error).message
-            )
-            throw err
-        }
-
-        const toolingBlueprint = domainBlueprints.items?.[0]
-        if (!toolingBlueprint) {
-            this.logger.error('No tooling blueprint found for domain %s', domainId)
-            throw new Error('No tooling blueprint found')
-        }
-
-        // List environments for the project
-        let listEnvs
-        try {
-            this.logger.debug(`Listing environments for project ${projectId} with blueprint ${toolingBlueprint.id}`)
-            listEnvs = await datazoneClient.listEnvironments({
-                domainIdentifier: domainId,
-                projectIdentifier: projectId,
-                environmentBlueprintIdentifier: toolingBlueprint.id,
-                provider: sageMakerProviderName,
-            })
-        } catch (err) {
-            this.logger.error(
-                'Failed to list environments for domainId: %s, projectId: %s, %s',
-                domainId,
-                projectId,
-                (err as Error).message
-            )
-            throw err
-        }
-
-        const defaultEnv = listEnvs.items?.[0]
-        if (!defaultEnv || !defaultEnv.id) {
-            this.logger.error(
-                'No default Tooling environment found for domainId: %s, projectId: %s',
-                domainId,
-                projectId
-            )
-            throw new Error('No default Tooling environment found for project')
-        }
-        this.logger.debug(`Found tooling environment with ID: ${defaultEnv.id}`)
-        return defaultEnv.id
-    }
-
-    /**
-     * Gets environment details
-     * @param environmentId The environment identifier
-     * @returns Promise resolving to environment details
-     */
-    public async getEnvironmentDetails(
-        environmentId: string
-    ): Promise {
-        try {
-            this.logger.debug(
-                `Getting environment details for domain ${this.getDomainId()}, environment ${environmentId}`
-            )
-            const datazoneClient = await this.getDataZoneClient()
-
-            const environment = await datazoneClient.getEnvironment({
-                domainIdentifier: this.getDomainId(),
-                identifier: environmentId,
-            })
-
-            this.logger.debug(`Retrieved environment details for ${environmentId}`)
-            return environment
-        } catch (err) {
-            this.logger.error('Failed to get environment details: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the tooling environment details for a project
-     * @param projectId The project ID
-     * @returns The tooling environment details
-     */
-    public async getToolingEnvironment(projectId: string): Promise {
-        const toolingEnvId = await this.getToolingEnvironmentId(this.getDomainId(), projectId)
-        if (!toolingEnvId) {
-            throw new Error('No default environment found for project')
-        }
-        return await this.getEnvironmentDetails(toolingEnvId)
-    }
-
-    public async getUserId(): Promise {
-        if (!this.credentialsProvider) {
-            throw new Error('Credentials provider is required for getUserId')
-        }
-        const callerCredentials = await this.credentialsProvider.getCredentials()
-        const stsClient = new DefaultStsClient(this.getRegion(), callerCredentials)
-        const callerIdentity = await stsClient.getCallerIdentity()
-        this.logger.debug(`Retrieved caller identity, UserId: ${callerIdentity.UserId}`)
-        return callerIdentity.UserId
-    }
-
-    /**
-     * Gets the user profile ID for a given IAM principal
-     * @param userIdentifier IAM user or role ARN
-     * @param domainIdentifier Optional domain identifier. If not provided, uses the client's domain ID
-     * @returns Promise resolving to the user profile ID
-     * @throws ToolkitError with appropriate error code
-     */
-    public async getUserProfileIdForIamPrincipal(
-        userIdentifier: string,
-        domainIdentifier?: string
-    ): Promise {
-        try {
-            this.logger.debug(`DataZoneClient: Getting user profile for IAM ARN: ${userIdentifier}`)
-
-            const datazoneClient = await this.getDataZoneClient()
-
-            const params = {
-                domainIdentifier: domainIdentifier || this.getDomainId(),
-                userIdentifier: userIdentifier,
-            }
-
-            const userProfile = await datazoneClient.getUserProfile(params)
-
-            if (!userProfile.id) {
-                this.logger.error(`DataZoneClient: No user profile ID returned for ARN: ${userIdentifier}`)
-                throw new ToolkitError(`No user profile found for IAM principal: ${userIdentifier}`, {
-                    code: SmusErrorCodes.NoUserProfileFound,
-                })
-            }
-
-            this.logger.debug(`DataZoneClient: Retrieved user profile ID: ${userProfile.id}`)
-            return userProfile.id
-        } catch (err) {
-            // Re-throw if it's already a ToolkitError
-            if (err instanceof ToolkitError) {
-                throw err
-            }
-
-            // Log and wrap other errors
-            this.logger.error('DataZoneClient: Failed to get user profile ID: %s', (err as Error).message)
-            throw ToolkitError.chain(err, 'Failed to get user profile ID')
-        }
-    }
-
-    /**
-     * Gets the correct tooling blueprint name
-     */
-    private getToolingBlueprintName(): string {
-        return getContext('aws.smus.isIamMode') ? 'ToolingLite' : toolingBlueprintName
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.ts
deleted file mode 100644
index 684bddf2e0..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.ts
+++ /dev/null
@@ -1,525 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import apiConfig = require('./datazonecustomclient.json')
-import globals from '../../../shared/extensionGlobals'
-import { Service } from 'aws-sdk'
-import { ServiceConfigurationOptions } from 'aws-sdk/lib/service'
-import * as DataZoneCustomClient from './datazonecustomclient'
-import { adaptConnectionCredentialsProvider } from './credentialsAdapter'
-import { CredentialsProvider } from '../../../auth/providers/credentials'
-import { ToolkitError } from '../../../shared/errors'
-import { SmusUtils, isIamDomain } from '../smusUtils'
-import { DevSettings } from '../../../shared/settings'
-
-import { SmusErrorCodes } from '../smusUtils'
-
-/**
- * Error codes for DataZone operations
- * @deprecated Use SmusErrorCodes instead
- */
-export const DataZoneErrorCode = {
-    NoGroupProfileFound: SmusErrorCodes.NoGroupProfileFound,
-    NoUserProfileFound: SmusErrorCodes.NoUserProfileFound,
-} as const
-
-/**
- * Helper client for interacting with AWS DataZone Custom API
- */
-export class DataZoneCustomClientHelper {
-    private datazoneCustomClient: DataZoneCustomClient | undefined
-    private static instances = new Map()
-    private readonly logger = getLogger('smus')
-
-    private constructor(
-        private readonly credentialProvider: CredentialsProvider,
-        private readonly region: string
-    ) {}
-
-    /**
-     * Gets a singleton instance of the DataZoneCustomClientHelper
-     * @returns DataZoneCustomClientHelper instance
-     */
-    public static getInstance(credentialProvider: CredentialsProvider, region: string): DataZoneCustomClientHelper {
-        const logger = getLogger('smus')
-
-        const instanceKey = `${region}`
-
-        // Check if we already have an instance for this instanceKey
-        if (DataZoneCustomClientHelper.instances.has(instanceKey)) {
-            const existingInstance = DataZoneCustomClientHelper.instances.get(instanceKey)!
-            logger.debug(`DataZoneCustomClientHelper: Using existing instance for instanceKey ${instanceKey}`)
-            return existingInstance
-        }
-
-        // Create new instance
-        logger.debug('DataZoneCustomClientHelper: Creating new instance')
-        const instance = new DataZoneCustomClientHelper(credentialProvider, region)
-        DataZoneCustomClientHelper.instances.set(instanceKey, instance)
-
-        logger.debug(`DataZoneCustomClientHelper: Created instance with instanceKey ${instanceKey}`)
-
-        return instance
-    }
-
-    /**
-     * Disposes all instances and cleans up resources
-     */
-    public static dispose(): void {
-        const logger = getLogger('smus')
-        logger.debug('DataZoneCustomClientHelper: Disposing all instances')
-
-        for (const [key, instance] of DataZoneCustomClientHelper.instances.entries()) {
-            instance.datazoneCustomClient = undefined
-            logger.debug(`DataZoneCustomClientHelper: Disposed instance for: ${key}`)
-        }
-
-        DataZoneCustomClientHelper.instances.clear()
-    }
-
-    /**
-     * Gets the AWS region
-     * @returns AWS region
-     */
-    public getRegion(): string {
-        return this.region
-    }
-
-    /**
-     * Gets the DataZone client, initializing it if necessary
-     */
-    private async getDataZoneCustomClient(): Promise {
-        if (!this.datazoneCustomClient) {
-            try {
-                this.logger.info('DataZoneCustomClientHelper: Creating authenticated DataZone client')
-
-                // Use user setting for endpoint if provided, otherwise use default
-                const devSettings = DevSettings.instance
-                const customEndpoint = devSettings.get('endpoints', {})['datazone']
-                const endpoint = customEndpoint || `https://datazone.${this.region}.api.aws`
-
-                if (customEndpoint) {
-                    this.logger.debug(
-                        `DataZoneCustomClientHelper: Using custom DataZone endpoint from settings: ${endpoint}`
-                    )
-                }
-
-                this.datazoneCustomClient = (await globals.sdkClientBuilder.createAwsService(
-                    Service,
-                    {
-                        apiConfig: apiConfig,
-                        endpoint: endpoint,
-                        region: this.region,
-                        credentialProvider: adaptConnectionCredentialsProvider(this.credentialProvider),
-                    } as ServiceConfigurationOptions,
-                    undefined,
-                    false
-                )) as DataZoneCustomClient
-
-                this.logger.info('DataZoneCustomClientHelper: Successfully created authenticated DataZone client')
-            } catch (err) {
-                this.logger.error('DataZoneCustomClientHelper: Failed to create DataZone client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.datazoneCustomClient
-    }
-
-    /**
-     * Lists domains in DataZone with pagination support
-     * @param options Options for listing domains
-     * @returns Paginated list of DataZone domains with nextToken
-     */
-    public async listDomains(options?: {
-        maxResults?: number
-        status?: string
-        nextToken?: string
-    }): Promise<{ domains: DataZoneCustomClient.Types.DomainSummary[]; nextToken?: string }> {
-        try {
-            this.logger.info(`DataZoneCustomClientHelper: Listing domains in region ${this.region}`)
-
-            const datazoneCustomClient = await this.getDataZoneCustomClient()
-
-            // Call DataZone API to list domains with pagination
-            const response = await datazoneCustomClient
-                .listDomains({
-                    maxResults: options?.maxResults,
-                    status: options?.status,
-                    nextToken: options?.nextToken,
-                })
-                .promise()
-
-            const domains = response.items || []
-
-            if (domains.length === 0) {
-                this.logger.info(`DataZoneCustomClientHelper: No domains found`)
-            } else {
-                this.logger.debug(`DataZoneCustomClientHelper: Found ${domains.length} domains`)
-            }
-
-            return { domains, nextToken: response.nextToken }
-        } catch (err) {
-            this.logger.error('DataZoneCustomClientHelper: Failed to list domains: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Fetches all domains by handling pagination automatically
-     * @param options Options for listing domains (excluding nextToken which is handled internally)
-     * @returns Promise resolving to an array of all DataZone domains
-     */
-    public async fetchAllDomains(options?: { status?: string }): Promise {
-        try {
-            let allDomains: DataZoneCustomClient.Types.DomainSummary[] = []
-            let nextToken: string | undefined
-            do {
-                const maxResultsPerPage = 25
-                const response = await this.listDomains({
-                    ...options,
-                    nextToken,
-                    maxResults: maxResultsPerPage,
-                })
-                allDomains = [...allDomains, ...response.domains]
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            this.logger.debug(`DataZoneCustomClientHelper: Fetched a total of ${allDomains.length} domains`)
-            return allDomains
-        } catch (err) {
-            this.logger.error('DataZoneCustomClientHelper: Failed to fetch all domains: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the domain with IAM authentication mode using pagination with early termination
-     * @returns Promise resolving to the DataZone domain or undefined if not found
-     */
-    public async getIamDomain(): Promise {
-        const logger = getLogger('smus')
-
-        try {
-            logger.info('DataZoneCustomClientHelper: Getting the domain info')
-
-            let nextToken: string | undefined
-            let totalDomainsChecked = 0
-            const maxResultsPerPage = 25
-
-            // Paginate through domains and check each page for IAM domain
-            do {
-                const response = await this.listDomains({
-                    status: 'AVAILABLE',
-                    nextToken,
-                    maxResults: maxResultsPerPage,
-                })
-
-                const { domains } = response
-                totalDomainsChecked += domains.length
-
-                logger.debug(
-                    `DataZoneCustomClientHelper: Checking ${domains.length} domains in current page (total checked: ${totalDomainsChecked})`
-                )
-
-                // Check each domain in the current page for IAM domain
-                for (const domain of domains) {
-                    // Log the complete domain object for debugging
-                    logger.debug(`DataZoneCustomClientHelper: Domain ${domain.id} full response: %O`, domain)
-
-                    const isIam = isIamDomain({
-                        domainVersion: domain.domainVersion,
-                        iamSignIns: domain.iamSignIns,
-                        domainId: domain.id,
-                    })
-
-                    if (isIam) {
-                        logger.info(`DataZoneCustomClientHelper: Found IAM domain, id: ${domain.id} (${domain.name})`)
-                        return domain
-                    }
-                }
-
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            logger.info(
-                `DataZoneCustomClientHelper: No IAM domain found after checking all ${totalDomainsChecked} domains`
-            )
-            return undefined
-        } catch (err) {
-            logger.error('DataZoneCustomClientHelper: Failed to get domain info: %s', err as Error)
-            throw new Error(`Failed to get domain info: ${(err as Error).message}`)
-        }
-    }
-
-    /**
-     * Gets a specific domain by its ID
-     * @param domainId The ID of the domain to retrieve
-     * @returns Promise resolving to the GetDomainOutput
-     */
-    public async getDomain(domainId: string): Promise {
-        try {
-            this.logger.debug(`DataZoneCustomClientHelper: Getting domain with ID: ${domainId}`)
-
-            const datazoneCustomClient = await this.getDataZoneCustomClient()
-
-            const response = await datazoneCustomClient
-                .getDomain({
-                    identifier: domainId,
-                })
-                .promise()
-
-            this.logger.debug(`DataZoneCustomClientHelper: Successfully retrieved domain: ${domainId}`)
-            return response
-        } catch (err) {
-            this.logger.error('DataZoneCustomClientHelper: Failed to get domain: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Searches for group profiles in the DataZone domain
-     * @param domainIdentifier The domain identifier to search in
-     * @param options Options for searching group profiles
-     * @returns Promise resolving to group profile search results with pagination
-     */
-    public async searchGroupProfiles(
-        domainIdentifier: string,
-        options?: {
-            groupType?: string
-            searchText?: string
-            maxResults?: number
-            nextToken?: string
-        }
-    ): Promise<{ items: DataZoneCustomClient.Types.GroupProfileSummary[]; nextToken?: string }> {
-        try {
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Searching group profiles in domain ${domainIdentifier} with groupType: ${options?.groupType}, searchText: ${options?.searchText}`
-            )
-
-            const datazoneCustomClient = await this.getDataZoneCustomClient()
-
-            // Build the request parameters
-            const params: DataZoneCustomClient.Types.SearchGroupProfilesInput = {
-                domainIdentifier,
-                groupType: options?.groupType as DataZoneCustomClient.Types.GroupSearchType,
-                searchText: options?.searchText,
-                maxResults: options?.maxResults,
-                nextToken: options?.nextToken,
-            }
-
-            // Call DataZone API to search group profiles
-            const response = await datazoneCustomClient.searchGroupProfiles(params).promise()
-
-            const items = response.items || []
-
-            if (items.length === 0) {
-                this.logger.debug(`DataZoneCustomClientHelper: No group profiles found`)
-            } else {
-                this.logger.debug(`DataZoneCustomClientHelper: Found ${items.length} group profiles`)
-            }
-
-            return { items, nextToken: response.nextToken }
-        } catch (err) {
-            this.logger.error('DataZoneCustomClientHelper: Failed to search group profiles: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Searches for user profiles in the DataZone domain
-     * @param domainIdentifier The domain identifier to search in
-     * @param options Options for searching user profiles
-     * @returns Promise resolving to user profile search results with pagination
-     */
-    public async searchUserProfiles(
-        domainIdentifier: string,
-        options: {
-            userType: string
-            searchText?: string
-            maxResults?: number
-            nextToken?: string
-        }
-    ): Promise<{ items: DataZoneCustomClient.Types.UserProfileSummary[]; nextToken?: string }> {
-        try {
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Searching user profiles in domain ${domainIdentifier} with userType: ${options.userType}, searchText: ${options.searchText}`
-            )
-
-            const datazoneCustomClient = await this.getDataZoneCustomClient()
-
-            // Build the request parameters
-            const params: DataZoneCustomClient.Types.SearchUserProfilesInput = {
-                domainIdentifier,
-                userType: options.userType as DataZoneCustomClient.Types.UserSearchType,
-                searchText: options.searchText,
-                maxResults: options.maxResults,
-                nextToken: options.nextToken,
-            }
-
-            // Call DataZone API to search user profiles
-            const response = await datazoneCustomClient.searchUserProfiles(params).promise()
-
-            const items = response.items || []
-
-            if (items.length === 0) {
-                this.logger.debug(`DataZoneCustomClientHelper: No user profiles found`)
-            } else {
-                this.logger.debug(`DataZoneCustomClientHelper: Found ${items.length} user profiles`)
-            }
-
-            return { items, nextToken: response.nextToken }
-        } catch (err) {
-            this.logger.error('DataZoneCustomClientHelper: Failed to search user profiles: %s', (err as Error).message)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the group profile ID for a given IAM role ARN
-     * @param domainIdentifier The domain identifier to search in
-     * @param roleArn The base IAM role ARN (format: arn:aws:iam::ACCOUNT:role/ROLE_NAME)
-     * @returns Promise resolving to the group profile ID
-     * @throws ToolkitError with appropriate error code
-     */
-    public async getGroupProfileId(domainIdentifier: string, roleArn: string): Promise {
-        try {
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Getting group profile ID for role ARN: ${roleArn} in domain ${domainIdentifier}`
-            )
-
-            // Use searchText to filter server-side for better performance
-            const response = await this.searchGroupProfiles(domainIdentifier, {
-                groupType: 'IAM_ROLE_SESSION_GROUP',
-                searchText: roleArn,
-                maxResults: 50,
-            })
-
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Received ${response.items.length} group profiles from search`
-            )
-
-            // Find exact match in filtered results
-            for (const profile of response.items) {
-                this.logger.debug(
-                    `DataZoneCustomClientHelper: Checking group profile - ID: ${profile.id}, rolePrincipalArn: ${profile.rolePrincipalArn}, status: ${profile.status}`
-                )
-
-                if (profile.rolePrincipalArn === roleArn) {
-                    this.logger.info(`DataZoneCustomClientHelper: Found matching group profile with ID: ${profile.id}`)
-                    return profile.id!
-                }
-            }
-
-            // No matching profile found
-            this.logger.error(`DataZoneCustomClientHelper: No group profile found for IAM role: ${roleArn}`)
-            throw new ToolkitError(`No group profile found for IAM role: ${roleArn}`, {
-                code: SmusErrorCodes.NoGroupProfileFound,
-            })
-        } catch (err) {
-            // Re-throw if it's already a ToolkitError
-            if (err instanceof ToolkitError) {
-                throw err
-            }
-
-            // Log and wrap other errors
-            this.logger.error('DataZoneCustomClientHelper: Failed to get group profile ID: %s', (err as Error).message)
-            throw ToolkitError.chain(err, 'Failed to get group profile ID')
-        }
-    }
-
-    /**
-     * Gets the user profile ID for a given IAM role session
-     * @param domainIdentifier The domain identifier to search in
-     * @param roleArnWithSession The assumed role ARN with session name (format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME)
-     * @returns Promise resolving to the user profile ID
-     * @throws ToolkitError with appropriate error code
-     */
-    public async getUserProfileIdForSession(domainIdentifier: string, roleArnWithSession: string): Promise {
-        try {
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Getting user profile ID for role ARN with session: ${roleArnWithSession} in domain ${domainIdentifier}`
-            )
-
-            // Extract session name from the assumed role ARN
-            // Format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME
-            const sessionName = SmusUtils.extractSessionNameFromArn(roleArnWithSession)
-            if (!sessionName) {
-                throw new ToolkitError(`Unable to extract session name from ARN: ${roleArnWithSession}`, {
-                    code: SmusErrorCodes.NoUserProfileFound,
-                })
-            }
-
-            // Convert assumed role ARN to IAM role ARN for matching
-            // Format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME -> arn:aws:iam::ACCOUNT:role/ROLE_NAME
-            const iamRoleArn = SmusUtils.convertAssumedRoleArnToIamRoleArn(roleArnWithSession)
-            if (!iamRoleArn) {
-                throw new ToolkitError(`Unable to convert assumed role ARN to IAM role ARN: ${roleArnWithSession}`, {
-                    code: SmusErrorCodes.NoUserProfileFound,
-                })
-            }
-
-            this.logger.debug(
-                `DataZoneCustomClientHelper: Extracted session name: ${sessionName}, IAM role ARN: ${iamRoleArn}`
-            )
-
-            // Use searchText to filter by role ARN on server side, then filter by session name on client side
-            let nextToken: string | undefined
-            let totalProfilesChecked = 0
-
-            do {
-                this.logger.debug(
-                    `DataZoneCustomClientHelper: Calling searchUserProfiles with searchText: ${iamRoleArn}`
-                )
-
-                const response = await this.searchUserProfiles(domainIdentifier, {
-                    userType: 'DATAZONE_IAM_USER',
-                    searchText: iamRoleArn, // Server-side filter by role ARN
-                    maxResults: 50,
-                    nextToken,
-                })
-
-                totalProfilesChecked += response.items.length
-                this.logger.debug(
-                    `DataZoneCustomClientHelper: Received ${response.items.length} user profiles matching role ARN in current page (total checked: ${totalProfilesChecked})`
-                )
-
-                // Find exact match in current page using client-side filtering for session name
-                // Server-side filtering by role ARN should have already reduced the result set significantly
-                for (const profile of response.items) {
-                    // Match based on session name (role ARN already filtered by searchText)
-                    // principalId format: PRINCIPAL_ID:SESSION_NAME
-                    const matchesSession = profile.details?.iam?.principalId?.includes(sessionName)
-
-                    if (matchesSession) {
-                        this.logger.info(
-                            `DataZoneCustomClientHelper: Found matching user profile with ID: ${profile.id} (role: ${iamRoleArn}, session: ${sessionName}) after checking ${totalProfilesChecked} profiles`
-                        )
-                        return profile.id!
-                    }
-                }
-
-                nextToken = response.nextToken
-            } while (nextToken)
-
-            // No matching profile found after checking all pages
-            this.logger.error(
-                `DataZoneCustomClientHelper: No user profile found for role: ${iamRoleArn} with session: ${sessionName} after checking ${totalProfilesChecked} profiles`
-            )
-            throw new ToolkitError(`No user profile found for role: ${iamRoleArn} with session: ${sessionName}`, {
-                code: SmusErrorCodes.NoUserProfileFound,
-            })
-        } catch (err) {
-            // Re-throw if it's already a ToolkitError
-            if (err instanceof ToolkitError) {
-                throw err
-            }
-
-            // Log and wrap other errors
-            this.logger.error('DataZoneCustomClientHelper: Failed to get user profile ID: %s', (err as Error).message)
-            throw ToolkitError.chain(err, 'Failed to get user profile ID')
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/datazonecustomclient.json b/packages/core/src/sagemakerunifiedstudio/shared/client/datazonecustomclient.json
deleted file mode 100644
index 8414d3340b..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/datazonecustomclient.json
+++ /dev/null
@@ -1,919 +0,0 @@
-{
-    "version": "2.0",
-    "metadata": {
-        "apiVersion": "2018-05-10",
-        "auth": ["aws.auth#sigv4"],
-        "endpointPrefix": "datazone",
-        "protocol": "rest-json",
-        "protocols": ["rest-json"],
-        "serviceFullName": "Amazon DataZone",
-        "serviceId": "DataZone",
-        "signatureVersion": "v4",
-        "signingName": "datazone",
-        "uid": "datazone-2018-05-10"
-    },
-    "operations": {
-        "GetDomain": {
-            "name": "GetDomain",
-            "http": {
-                "method": "GET",
-                "requestUri": "/v2/domains/{identifier}",
-                "responseCode": 200
-            },
-            "input": {
-                "shape": "GetDomainInput"
-            },
-            "output": {
-                "shape": "GetDomainOutput"
-            },
-            "errors": [
-                {
-                    "shape": "InternalServerException"
-                },
-                {
-                    "shape": "ResourceNotFoundException"
-                },
-                {
-                    "shape": "AccessDeniedException"
-                },
-                {
-                    "shape": "ThrottlingException"
-                },
-                {
-                    "shape": "ServiceQuotaExceededException"
-                },
-                {
-                    "shape": "ValidationException"
-                },
-                {
-                    "shape": "UnauthorizedException"
-                }
-            ],
-            "readonly": true
-        },
-        "ListDomains": {
-            "name": "ListDomains",
-            "http": {
-                "method": "GET",
-                "requestUri": "/v2/domains",
-                "responseCode": 200
-            },
-            "input": {
-                "shape": "ListDomainsInput"
-            },
-            "output": {
-                "shape": "ListDomainsOutput"
-            },
-            "errors": [
-                {
-                    "shape": "InternalServerException"
-                },
-                {
-                    "shape": "ResourceNotFoundException"
-                },
-                {
-                    "shape": "AccessDeniedException"
-                },
-                {
-                    "shape": "ThrottlingException"
-                },
-                {
-                    "shape": "ServiceQuotaExceededException"
-                },
-                {
-                    "shape": "ConflictException"
-                },
-                {
-                    "shape": "ValidationException"
-                },
-                {
-                    "shape": "UnauthorizedException"
-                }
-            ],
-            "readonly": true
-        },
-        "SearchGroupProfiles": {
-            "name": "SearchGroupProfiles",
-            "http": {
-                "method": "POST",
-                "requestUri": "/v2/domains/{domainIdentifier}/search-group-profiles",
-                "responseCode": 200
-            },
-            "input": {
-                "shape": "SearchGroupProfilesInput"
-            },
-            "output": {
-                "shape": "SearchGroupProfilesOutput"
-            },
-            "errors": [
-                {
-                    "shape": "InternalServerException"
-                },
-                {
-                    "shape": "ResourceNotFoundException"
-                },
-                {
-                    "shape": "AccessDeniedException"
-                },
-                {
-                    "shape": "ThrottlingException"
-                },
-                {
-                    "shape": "ConflictException"
-                },
-                {
-                    "shape": "ValidationException"
-                },
-                {
-                    "shape": "UnauthorizedException"
-                }
-            ]
-        },
-        "SearchUserProfiles": {
-            "name": "SearchUserProfiles",
-            "http": {
-                "method": "POST",
-                "requestUri": "/v2/domains/{domainIdentifier}/search-user-profiles",
-                "responseCode": 200
-            },
-            "input": {
-                "shape": "SearchUserProfilesInput"
-            },
-            "output": {
-                "shape": "SearchUserProfilesOutput"
-            },
-            "errors": [
-                {
-                    "shape": "InternalServerException"
-                },
-                {
-                    "shape": "ResourceNotFoundException"
-                },
-                {
-                    "shape": "AccessDeniedException"
-                },
-                {
-                    "shape": "ThrottlingException"
-                },
-                {
-                    "shape": "ConflictException"
-                },
-                {
-                    "shape": "ValidationException"
-                },
-                {
-                    "shape": "UnauthorizedException"
-                }
-            ]
-        }
-    },
-    "shapes": {
-        "GetDomainInput": {
-            "type": "structure",
-            "required": ["identifier"],
-            "members": {
-                "identifier": {
-                    "shape": "DomainId",
-                    "location": "uri",
-                    "locationName": "identifier"
-                }
-            }
-        },
-        "DomainId": {
-            "type": "string",
-            "pattern": "dzd[-_][a-zA-Z0-9_-]{1,36}"
-        },
-        "GetDomainOutput": {
-            "type": "structure",
-            "required": ["id", "status"],
-            "members": {
-                "id": {
-                    "shape": "DomainId"
-                },
-                "rootDomainUnitId": {
-                    "shape": "DomainUnitId"
-                },
-                "name": {
-                    "shape": "String"
-                },
-                "description": {
-                    "shape": "String"
-                },
-                "singleSignOn": {
-                    "shape": "SingleSignOn"
-                },
-                "domainExecutionRole": {
-                    "shape": "RoleArn"
-                },
-                "arn": {
-                    "shape": "String"
-                },
-                "kmsKeyIdentifier": {
-                    "shape": "KmsKeyArn"
-                },
-                "status": {
-                    "shape": "DomainStatus"
-                },
-                "failureReasons": {
-                    "shape": "FailureReasonsList"
-                },
-                "portalUrl": {
-                    "shape": "String"
-                },
-                "createdAt": {
-                    "shape": "CreatedAt"
-                },
-                "lastUpdatedAt": {
-                    "shape": "UpdatedAt"
-                },
-                "tags": {
-                    "shape": "Tags"
-                },
-                "provisionStatus": {
-                    "shape": "ProvisionStatus",
-                    "internalonly": true
-                },
-                "domainVersion": {
-                    "shape": "DomainVersion"
-                },
-                "domainServiceRole": {
-                    "shape": "RoleArn",
-                    "deprecated": true,
-                    "internalonly": true
-                },
-                "serviceRole": {
-                    "shape": "RoleArn"
-                },
-                "supportedDomainVersions": {
-                    "shape": "SupportedDomainVersions",
-                    "internalonly": true
-                },
-                "iamSignIns": {
-                    "shape": "IamSignIns",
-                    "internalonly": true
-                },
-                "preferences": {
-                    "shape": "Preferences",
-                    "internalonly": true
-                }
-            }
-        },
-        "DomainUnitId": {
-            "type": "string",
-            "max": 256,
-            "min": 1,
-            "pattern": "[a-z0-9_\\-]+"
-        },
-        "String": {
-            "type": "string"
-        },
-        "SingleSignOn": {
-            "type": "structure",
-            "members": {
-                "type": {
-                    "shape": "AuthType"
-                },
-                "userAssignment": {
-                    "shape": "UserAssignment"
-                },
-                "idcInstanceArn": {
-                    "shape": "SingleSignOnIdcInstanceArnString"
-                },
-                "ssoUrl": {
-                    "shape": "String",
-                    "internalonly": true
-                },
-                "idcApplicationArn": {
-                    "shape": "String",
-                    "internalonly": true
-                }
-            }
-        },
-        "AuthType": {
-            "type": "string",
-            "enum": ["IAM_IDC", "DISABLED", "SAML"]
-        },
-        "UserAssignment": {
-            "type": "string",
-            "enum": ["AUTOMATIC", "MANUAL"]
-        },
-        "SingleSignOnIdcInstanceArnString": {
-            "type": "string",
-            "pattern": ".*arn:(aws|aws-us-gov|aws-cn|aws-iso|aws-iso-b):sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}.*"
-        },
-        "RoleArn": {
-            "type": "string",
-            "pattern": "arn:aws[^:]*:iam::\\d{12}:role(/[a-zA-Z0-9+=,.@_-]+)*/[a-zA-Z0-9+=,.@_-]+"
-        },
-        "KmsKeyArn": {
-            "type": "string",
-            "max": 1024,
-            "min": 1,
-            "pattern": "arn:aws(|-cn|-us-gov):kms:[a-zA-Z0-9-]*:[0-9]{12}:key/[a-zA-Z0-9-]{36}"
-        },
-        "DomainStatus": {
-            "type": "string",
-            "enum": ["CREATING", "AVAILABLE", "CREATION_FAILED", "DELETING", "DELETED", "DELETION_FAILED"]
-        },
-        "FailureReasonsList": {
-            "type": "list",
-            "member": {
-                "shape": "FailureReason"
-            }
-        },
-        "FailureReason": {
-            "type": "structure",
-            "members": {
-                "code": {
-                    "shape": "String"
-                },
-                "message": {
-                    "shape": "String"
-                }
-            }
-        },
-        "CreatedAt": {
-            "type": "timestamp"
-        },
-        "UpdatedAt": {
-            "type": "timestamp"
-        },
-        "Tags": {
-            "type": "map",
-            "key": {
-                "shape": "TagKey"
-            },
-            "value": {
-                "shape": "TagValue"
-            }
-        },
-        "TagKey": {
-            "type": "string",
-            "max": 128,
-            "min": 1,
-            "pattern": "[\\w \\.:/=+@-]+"
-        },
-        "TagValue": {
-            "type": "string",
-            "max": 256,
-            "min": 0,
-            "pattern": "[\\w \\.:/=+@-]*"
-        },
-        "ProvisionStatus": {
-            "type": "string",
-            "enum": [
-                "PROVISIONING",
-                "PROVISIONING_PROJECT_PROFILES",
-                "PROVISIONING_MODEL_ASSETS",
-                "PROVISION_FAILED",
-                "PROVISION_COMPLETE"
-            ]
-        },
-        "DomainVersion": {
-            "type": "string",
-            "enum": ["V1", "V2"]
-        },
-        "SupportedDomainVersions": {
-            "type": "list",
-            "member": {
-                "shape": "DomainVersion"
-            }
-        },
-        "IamSignIns": {
-            "type": "list",
-            "member": {
-                "shape": "IamSignIn"
-            },
-            "internalonly": true
-        },
-        "IamSignIn": {
-            "type": "string",
-            "enum": ["IAM_ROLE", "IAM_USER"],
-            "internalonly": true
-        },
-        "Preferences": {
-            "type": "map",
-            "key": {
-                "shape": "PreferenceKey"
-            },
-            "value": {
-                "shape": "PreferenceValue"
-            },
-            "max": 10,
-            "min": 0
-        },
-        "PreferenceKey": {
-            "type": "string",
-            "max": 128,
-            "min": 1,
-            "pattern": "[\\w \\.:/=+@-]+"
-        },
-        "PreferenceValue": {
-            "type": "string",
-            "max": 256,
-            "min": 0,
-            "pattern": "[\\w \\.:/=+@-]*"
-        },
-        "InternalServerException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 500
-            },
-            "exception": true,
-            "fault": true,
-            "retryable": {
-                "throttling": false
-            }
-        },
-        "ErrorMessage": {
-            "type": "string"
-        },
-        "ResourceNotFoundException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 404,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "AccessDeniedException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 403,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "ThrottlingException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 429,
-                "senderFault": true
-            },
-            "exception": true,
-            "retryable": {
-                "throttling": false
-            }
-        },
-        "ServiceQuotaExceededException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 402,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "ValidationException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 400,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "UnauthorizedException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                }
-            },
-            "error": {
-                "httpStatusCode": 401,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "ListDomainsInput": {
-            "type": "structure",
-            "members": {
-                "status": {
-                    "shape": "DomainStatus",
-                    "location": "querystring",
-                    "locationName": "status"
-                },
-                "maxResults": {
-                    "shape": "MaxResultsForListDomains",
-                    "location": "querystring",
-                    "locationName": "maxResults"
-                },
-                "nextToken": {
-                    "shape": "PaginationToken",
-                    "location": "querystring",
-                    "locationName": "nextToken"
-                }
-            }
-        },
-        "MaxResultsForListDomains": {
-            "type": "integer",
-            "box": true,
-            "max": 25,
-            "min": 1
-        },
-        "PaginationToken": {
-            "type": "string",
-            "max": 8192,
-            "min": 1
-        },
-        "ListDomainsOutput": {
-            "type": "structure",
-            "required": ["items"],
-            "members": {
-                "items": {
-                    "shape": "DomainSummaries"
-                },
-                "domains": {
-                    "shape": "DomainSummaries",
-                    "internalonly": true
-                },
-                "nextToken": {
-                    "shape": "PaginationToken"
-                }
-            }
-        },
-        "DomainSummaries": {
-            "type": "list",
-            "member": {
-                "shape": "DomainSummary"
-            }
-        },
-        "DomainSummary": {
-            "type": "structure",
-            "required": ["id", "name", "arn", "managedAccountId", "status", "createdAt"],
-            "members": {
-                "id": {
-                    "shape": "DomainId"
-                },
-                "name": {
-                    "shape": "DomainName"
-                },
-                "description": {
-                    "shape": "DomainDescription"
-                },
-                "arn": {
-                    "shape": "String"
-                },
-                "managedAccountId": {
-                    "shape": "String"
-                },
-                "status": {
-                    "shape": "DomainStatus"
-                },
-                "portalUrl": {
-                    "shape": "String"
-                },
-                "createdAt": {
-                    "shape": "CreatedAt"
-                },
-                "lastUpdatedAt": {
-                    "shape": "UpdatedAt"
-                },
-                "domainVersion": {
-                    "shape": "DomainVersion"
-                },
-                "iamSignIns": {
-                    "shape": "IamSignIns",
-                    "internalonly": true
-                },
-                "preferences": {
-                    "shape": "Preferences",
-                    "internalonly": true
-                }
-            }
-        },
-        "DomainName": {
-            "type": "string",
-            "sensitive": true
-        },
-        "DomainDescription": {
-            "type": "string",
-            "sensitive": true
-        },
-        "ConflictException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": {
-                    "shape": "ErrorMessage"
-                },
-                "reason": {
-                    "shape": "ConflictReason"
-                },
-                "details": {
-                    "shape": "ConflictDetails"
-                }
-            },
-            "error": {
-                "httpStatusCode": 409,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "ConflictReason": {
-            "type": "string",
-            "enum": ["RESOURCE_LOCKED"]
-        },
-        "ConflictDetails": {
-            "type": "structure",
-            "members": {
-                "lock": {
-                    "shape": "LockDetails"
-                }
-            },
-            "union": true
-        },
-        "LockDetails": {
-            "type": "structure",
-            "required": ["lockedBy", "lockedAt", "lockExpiresAt"],
-            "members": {
-                "lockedBy": {
-                    "shape": "String"
-                },
-                "lockedAt": {
-                    "shape": "Timestamp"
-                },
-                "lockExpiresAt": {
-                    "shape": "Timestamp"
-                }
-            }
-        },
-        "Timestamp": {
-            "type": "timestamp"
-        },
-        "SearchGroupProfilesInput": {
-            "type": "structure",
-            "required": ["domainIdentifier", "groupType"],
-            "members": {
-                "domainIdentifier": {
-                    "shape": "DomainId",
-                    "location": "uri",
-                    "locationName": "domainIdentifier"
-                },
-                "groupType": {
-                    "shape": "GroupSearchType"
-                },
-                "searchText": {
-                    "shape": "GroupSearchText"
-                },
-                "maxResults": {
-                    "shape": "MaxResults"
-                },
-                "nextToken": {
-                    "shape": "PaginationToken"
-                }
-            }
-        },
-        "GroupSearchType": {
-            "type": "string",
-            "enum": ["SSO_GROUP", "DATAZONE_SSO_GROUP", "IAM_ROLE_SESSION_GROUP"]
-        },
-        "GroupSearchText": {
-            "type": "string",
-            "max": 1024,
-            "min": 0,
-            "sensitive": true
-        },
-        "MaxResults": {
-            "type": "integer",
-            "box": true,
-            "max": 50,
-            "min": 1
-        },
-        "SearchGroupProfilesOutput": {
-            "type": "structure",
-            "members": {
-                "items": {
-                    "shape": "GroupProfileSummaries"
-                },
-                "nextToken": {
-                    "shape": "PaginationToken"
-                }
-            }
-        },
-        "GroupProfileSummaries": {
-            "type": "list",
-            "member": {
-                "shape": "GroupProfileSummary"
-            }
-        },
-        "GroupProfileSummary": {
-            "type": "structure",
-            "members": {
-                "domainId": {
-                    "shape": "DomainId"
-                },
-                "id": {
-                    "shape": "GroupProfileId"
-                },
-                "status": {
-                    "shape": "GroupProfileStatus"
-                },
-                "groupName": {
-                    "shape": "GroupProfileName"
-                },
-                "rolePrincipalArn": {
-                    "shape": "String",
-                    "internalonly": true
-                },
-                "rolePrincipalId": {
-                    "shape": "String",
-                    "internalonly": true
-                }
-            }
-        },
-        "GroupProfileId": {
-            "type": "string",
-            "pattern": "([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"
-        },
-        "GroupProfileStatus": {
-            "type": "string",
-            "enum": ["ASSIGNED", "NOT_ASSIGNED"]
-        },
-        "GroupProfileName": {
-            "type": "string",
-            "max": 1024,
-            "min": 1,
-            "pattern": "[a-zA-Z_0-9+=,.@-]+",
-            "sensitive": true
-        },
-        "SearchUserProfilesInput": {
-            "type": "structure",
-            "required": ["domainIdentifier", "userType"],
-            "members": {
-                "domainIdentifier": {
-                    "shape": "DomainId",
-                    "location": "uri",
-                    "locationName": "domainIdentifier"
-                },
-                "userType": {
-                    "shape": "UserSearchType"
-                },
-                "searchText": {
-                    "shape": "UserSearchText"
-                },
-                "maxResults": {
-                    "shape": "MaxResults"
-                },
-                "nextToken": {
-                    "shape": "PaginationToken"
-                }
-            }
-        },
-        "UserSearchType": {
-            "type": "string",
-            "enum": ["SSO_USER", "DATAZONE_USER", "DATAZONE_SSO_USER", "DATAZONE_IAM_USER"]
-        },
-        "UserSearchText": {
-            "type": "string",
-            "max": 1024,
-            "min": 0,
-            "sensitive": true
-        },
-        "SearchUserProfilesOutput": {
-            "type": "structure",
-            "members": {
-                "items": {
-                    "shape": "UserProfileSummaries"
-                },
-                "nextToken": {
-                    "shape": "PaginationToken"
-                }
-            }
-        },
-        "UserProfileSummaries": {
-            "type": "list",
-            "member": {
-                "shape": "UserProfileSummary"
-            }
-        },
-        "UserProfileSummary": {
-            "type": "structure",
-            "members": {
-                "domainId": {
-                    "shape": "DomainId"
-                },
-                "id": {
-                    "shape": "UserProfileId"
-                },
-                "type": {
-                    "shape": "UserProfileType"
-                },
-                "status": {
-                    "shape": "UserProfileStatus"
-                },
-                "details": {
-                    "shape": "UserProfileDetails"
-                }
-            }
-        },
-        "UserProfileId": {
-            "type": "string",
-            "pattern": "([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"
-        },
-        "UserProfileType": {
-            "type": "string",
-            "enum": ["IAM", "SSO", "SAML"]
-        },
-        "UserProfileStatus": {
-            "type": "string",
-            "enum": ["ASSIGNED", "NOT_ASSIGNED", "ACTIVATED", "DEACTIVATED", "ARCHIVED"]
-        },
-        "UserProfileDetails": {
-            "type": "structure",
-            "members": {
-                "iam": {
-                    "shape": "IamUserProfileDetails"
-                },
-                "sso": {
-                    "shape": "SsoUserProfileDetails"
-                }
-            },
-            "union": true
-        },
-        "IamUserProfileDetails": {
-            "type": "structure",
-            "members": {
-                "arn": {
-                    "shape": "String"
-                },
-                "principalId": {
-                    "shape": "String"
-                }
-            }
-        },
-        "SsoUserProfileDetails": {
-            "type": "structure",
-            "members": {
-                "username": {
-                    "shape": "UserProfileName"
-                },
-                "firstName": {
-                    "shape": "FirstName"
-                },
-                "lastName": {
-                    "shape": "LastName"
-                },
-                "email": {
-                    "shape": "String",
-                    "internalonly": true
-                },
-                "userId": {
-                    "shape": "String",
-                    "internalonly": true
-                }
-            }
-        },
-        "UserProfileName": {
-            "type": "string",
-            "max": 1024,
-            "min": 1,
-            "pattern": "[a-zA-Z_0-9+=,.@-]+",
-            "sensitive": true
-        },
-        "FirstName": {
-            "type": "string",
-            "sensitive": true
-        },
-        "LastName": {
-            "type": "string",
-            "sensitive": true
-        }
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/glueCatalogClient.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/glueCatalogClient.ts
deleted file mode 100644
index 1cc3c29e29..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/glueCatalogClient.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { getLogger } from '../../../shared/logger/logger'
-import { GlueCatalog, Catalog } from '@amzn/glue-catalog-client'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-
-/**
- * Client for interacting with Glue Catalog API
- */
-export class GlueCatalogClient {
-    private glueClient: GlueCatalog | undefined
-    private static instance: GlueCatalogClient | undefined
-    private readonly logger = getLogger('smus')
-
-    private constructor(
-        private readonly region: string,
-        private readonly connectionCredentialsProvider?: ConnectionCredentialsProvider
-    ) {}
-
-    /**
-     * Gets a singleton instance of the GlueCatalogClient
-     * @returns GlueCatalogClient instance
-     */
-    public static getInstance(region: string): GlueCatalogClient {
-        if (!GlueCatalogClient.instance) {
-            GlueCatalogClient.instance = new GlueCatalogClient(region)
-        }
-        return GlueCatalogClient.instance
-    }
-
-    /**
-     * Creates a new GlueCatalogClient instance with specific credentials
-     * @param region AWS region
-     * @param credentials AWS credentials
-     * @returns GlueCatalogClient instance with credentials
-     */
-    public static createWithCredentials(
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): GlueCatalogClient {
-        return new GlueCatalogClient(region, connectionCredentialsProvider)
-    }
-
-    /**
-     * Gets the AWS region
-     * @returns AWS region
-     */
-    public getRegion(): string {
-        return this.region
-    }
-
-    /**
-     * Lists Glue catalogs with pagination support
-     * @param nextToken Optional pagination token
-     * @returns Object containing catalogs and nextToken
-     */
-    public async getCatalogs(nextToken?: string): Promise<{ catalogs: Catalog[]; nextToken?: string }> {
-        try {
-            this.logger.info(`GlueCatalogClient: Getting catalogs in region ${this.region}`)
-
-            const glueClient = await this.getGlueCatalogClient()
-
-            // Call the GetCatalogs API with pagination
-            const response = await glueClient.getCatalogs({
-                Recursive: true,
-                NextToken: nextToken,
-            })
-
-            const catalogs: Catalog[] = response.CatalogList || []
-
-            this.logger.info(`GlueCatalogClient: Found ${catalogs.length} catalogs in this page`)
-            return {
-                catalogs,
-                nextToken: response.NextToken,
-            }
-        } catch (err) {
-            this.logger.error('GlueCatalogClient: Failed to get catalogs: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the Glue client, initializing it if necessary
-     */
-    private async getGlueCatalogClient(): Promise {
-        if (!this.glueClient) {
-            try {
-                if (this.connectionCredentialsProvider) {
-                    // Create client with credential provider function for auto-refresh
-                    const awsCredentialProvider = async () => {
-                        const credentials = await this.connectionCredentialsProvider!.getCredentials()
-                        return {
-                            accessKeyId: credentials.accessKeyId,
-                            secretAccessKey: credentials.secretAccessKey,
-                            sessionToken: credentials.sessionToken,
-                            expiration: credentials.expiration,
-                        }
-                    }
-
-                    this.glueClient = new GlueCatalog({
-                        region: this.region,
-                        credentials: awsCredentialProvider,
-                    })
-                } else {
-                    // Use default credentials
-                    this.glueClient = new GlueCatalog({
-                        region: this.region,
-                    })
-                }
-
-                this.logger.debug('GlueCatalogClient: Successfully created Glue client')
-            } catch (err) {
-                this.logger.error('GlueCatalogClient: Failed to create Glue client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.glueClient
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/glueClient.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/glueClient.ts
deleted file mode 100644
index cad84d2333..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/glueClient.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import {
-    Glue,
-    GetDatabasesCommand,
-    GetTablesCommand,
-    GetTableCommand,
-    Table,
-    ResourceShareType,
-    DatabaseAttributes,
-    TableAttributes,
-    Database,
-} from '@aws-sdk/client-glue'
-import { getLogger } from '../../../shared/logger/logger'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-
-/**
- * Client for interacting with AWS Glue API using public SDK
- */
-export class GlueClient {
-    private glueClient: Glue | undefined
-    private readonly logger = getLogger('smus')
-
-    constructor(
-        private readonly region: string,
-        private readonly connectionCredentialsProvider: ConnectionCredentialsProvider
-    ) {}
-
-    /**
-     * Gets databases from a catalog
-     * @param catalogId Optional catalog ID (uses default if not provided)
-     * @param nextToken Optional pagination token
-     * @returns List of databases
-     */
-    public async getDatabases(
-        catalogId?: string,
-        resourceShareType?: ResourceShareType,
-        attributesToGet?: DatabaseAttributes[],
-        nextToken?: string
-    ): Promise<{ databases: Database[]; nextToken?: string }> {
-        try {
-            this.logger.info(`GlueClient: Getting databases for catalog ${catalogId || 'default'}`)
-
-            const glueClient = await this.getGlueClient()
-            const response = await glueClient.send(
-                new GetDatabasesCommand({
-                    CatalogId: catalogId,
-                    ResourceShareType: resourceShareType,
-                    AttributesToGet: attributesToGet,
-                    NextToken: nextToken,
-                    MaxResults: 100,
-                })
-            )
-
-            const databases = response.DatabaseList || []
-            this.logger.info(`GlueClient: Found ${databases.length} databases`)
-
-            return {
-                databases,
-                nextToken: response.NextToken,
-            }
-        } catch (err) {
-            this.logger.error('GlueClient: Failed to get databases: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets tables from a database
-     * @param databaseName Database name
-     * @param catalogId Optional catalog ID
-     * @param nextToken Optional pagination token
-     * @returns List of tables
-     */
-    public async getTables(
-        databaseName: string,
-        catalogId?: string,
-        attributesToGet?: TableAttributes[],
-        nextToken?: string
-    ): Promise<{ tables: Table[]; nextToken?: string }> {
-        try {
-            this.logger.info(`GlueClient: Getting tables for database ${databaseName}`)
-
-            const glueClient = await this.getGlueClient()
-            const response = await glueClient.send(
-                new GetTablesCommand({
-                    DatabaseName: databaseName,
-                    CatalogId: catalogId,
-                    AttributesToGet: attributesToGet,
-                    NextToken: nextToken,
-                    MaxResults: 100,
-                })
-            )
-
-            const tables = response.TableList || []
-            this.logger.info(`GlueClient: Found ${tables.length} tables`)
-
-            return {
-                tables,
-                nextToken: response.NextToken,
-            }
-        } catch (err) {
-            this.logger.error('GlueClient: Failed to get tables: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets table details including columns
-     * @param databaseName Database name
-     * @param tableName Table name
-     * @param catalogId Optional catalog ID
-     * @returns Table details with columns
-     */
-    public async getTable(databaseName: string, tableName: string, catalogId?: string): Promise {
-        try {
-            this.logger.info(`GlueClient: Getting table ${tableName} from database ${databaseName}`)
-
-            const glueClient = await this.getGlueClient()
-            const response = await glueClient.send(
-                new GetTableCommand({
-                    DatabaseName: databaseName,
-                    Name: tableName,
-                    CatalogId: catalogId,
-                })
-            )
-
-            return response.Table
-        } catch (err) {
-            this.logger.error('GlueClient: Failed to get table: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the Glue client, initializing it if necessary
-     */
-    private async getGlueClient(): Promise {
-        if (!this.glueClient) {
-            try {
-                const credentialsProvider = async () => {
-                    const credentials = await this.connectionCredentialsProvider.getCredentials()
-                    return {
-                        accessKeyId: credentials.accessKeyId,
-                        secretAccessKey: credentials.secretAccessKey,
-                        sessionToken: credentials.sessionToken,
-                        expiration: credentials.expiration,
-                    }
-                }
-
-                this.glueClient = new Glue({
-                    region: this.region,
-                    credentials: credentialsProvider,
-                })
-                this.logger.debug('GlueClient: Successfully created Glue client')
-            } catch (err) {
-                this.logger.error('GlueClient: Failed to create Glue client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.glueClient
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/s3Client.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/s3Client.ts
deleted file mode 100644
index 5375c3d5e9..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/s3Client.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { S3, ListBucketsCommand } from '@aws-sdk/client-s3'
-import { getLogger } from '../../../shared/logger/logger'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-
-/**
- * Represents an S3 path (bucket or prefix)
- */
-export interface S3Path {
-    bucket: string
-    prefix?: string
-    displayName: string
-    isFolder: boolean
-    size?: number
-    lastModified?: Date
-}
-
-/**
- * Client for interacting with AWS S3 API using project credentials
- */
-export class S3Client {
-    private s3Client: S3 | undefined
-    private readonly logger = getLogger('smus')
-
-    constructor(
-        private readonly region: string,
-        private readonly connectionCredentialsProvider: ConnectionCredentialsProvider
-    ) {}
-
-    /**
-     * Lists S3 paths (folders and objects) using prefix-based navigation
-     * Uses S3's hierarchical folder-like structure by leveraging prefixes and delimiters
-     * @param bucket S3 bucket name to list objects from
-     * @param prefix Optional prefix to filter objects (acts like a folder path)
-     * @param continuationToken Optional continuation token for pagination
-     * @returns Object containing paths and nextToken for pagination
-     */
-    public async listPaths(
-        bucket: string,
-        prefix?: string,
-        continuationToken?: string
-    ): Promise<{ paths: S3Path[]; nextToken?: string }> {
-        try {
-            this.logger.info(`S3Client: Listing paths in bucket ${bucket} with prefix ${prefix}`)
-
-            const s3Client = await this.getS3Client()
-
-            // Call S3 ListObjectsV2 API with delimiter to simulate folder structure
-            // Delimiter '/' treats forward slashes as folder separators
-            // This returns both CommonPrefixes (folders) and Contents (files)
-            const response = await s3Client.listObjectsV2({
-                Bucket: bucket,
-                Prefix: prefix, // Filter objects that start with this prefix
-                Delimiter: '/', // Treat '/' as folder separator for hierarchical listing
-                ContinuationToken: continuationToken, // For pagination
-            })
-
-            const paths: S3Path[] = []
-
-            // Process CommonPrefixes - these represent "folders" in S3
-            // CommonPrefixes are object keys that share a common prefix up to the delimiter
-            if (response.CommonPrefixes) {
-                for (const commonPrefix of response.CommonPrefixes) {
-                    if (commonPrefix.Prefix) {
-                        // Extract folder name by removing the parent prefix and trailing slash
-                        // Example: if prefix="folder1/" and commonPrefix="folder1/subfolder/"
-                        // folderName becomes "subfolder"
-                        const folderName = commonPrefix.Prefix.replace(prefix || '', '').replace('/', '')
-                        paths.push({
-                            bucket,
-                            prefix: commonPrefix.Prefix, // Full S3 prefix for this folder
-                            displayName: folderName, // Human-readable folder name
-                            isFolder: true, // Mark as folder for UI rendering
-                        })
-                    }
-                }
-            }
-
-            // Process Contents - these represent actual S3 objects (files)
-            if (response.Contents) {
-                for (const object of response.Contents) {
-                    // Skip if no key or if key matches the prefix exactly (folder itself)
-                    if (object.Key && object.Key !== prefix) {
-                        // Extract file name by removing the parent prefix
-                        // Example: if prefix="folder1/" and object.Key="folder1/file.txt"
-                        // fileName becomes "file.txt"
-                        const fileName = object.Key.replace(prefix || '', '')
-
-                        // Only include actual files (not folder markers ending with '/')
-                        if (fileName && !fileName.endsWith('/')) {
-                            paths.push({
-                                bucket,
-                                prefix: object.Key, // Full S3 object key
-                                displayName: fileName, // Human-readable file name
-                                isFolder: false, // Mark as file for UI rendering
-                                size: object.Size, // File size in bytes
-                                lastModified: object.LastModified, // Last modification timestamp
-                            })
-                        }
-                    }
-                }
-            }
-
-            this.logger.info(`S3Client: Found ${paths.length} paths in bucket ${bucket}`)
-            return {
-                paths,
-                nextToken: response.NextContinuationToken,
-            }
-        } catch (err) {
-            this.logger.error('S3Client: Failed to list paths: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Lists all S3 buckets accessible to the current credentials
-     * @returns Array of bucket objects
-     */
-    public async listBuckets(): Promise> {
-        try {
-            this.logger.debug('S3Client: Listing all accessible buckets')
-
-            const s3Client = await this.getS3Client()
-            const allBuckets: Array<{ Name?: string; CreationDate?: Date }> = []
-            let continuationToken: string | undefined
-
-            do {
-                const response = await s3Client.send(
-                    new ListBucketsCommand({
-                        ContinuationToken: continuationToken,
-                        BucketRegion: this.region,
-                    })
-                )
-
-                if (response.Buckets) {
-                    allBuckets.push(...response.Buckets)
-                }
-                continuationToken = response.ContinuationToken
-            } while (continuationToken)
-
-            this.logger.debug(`S3Client: Found ${allBuckets.length} accessible buckets`)
-            return allBuckets
-        } catch (err) {
-            this.logger.error('S3Client: Failed to list buckets: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the S3 client, initializing it if necessary
-     */
-    private async getS3Client(): Promise {
-        if (!this.s3Client) {
-            try {
-                const credentialsProvider = async () => {
-                    const credentials = await this.connectionCredentialsProvider.getCredentials()
-                    return {
-                        accessKeyId: credentials.accessKeyId,
-                        secretAccessKey: credentials.secretAccessKey,
-                        sessionToken: credentials.sessionToken,
-                        expiration: credentials.expiration,
-                    }
-                }
-
-                this.s3Client = new S3({
-                    region: this.region,
-                    credentials: credentialsProvider,
-                })
-                this.logger.debug('S3Client: Successfully created S3 client')
-            } catch (err) {
-                this.logger.error('S3Client: Failed to create S3 client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.s3Client
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.ts b/packages/core/src/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.ts
deleted file mode 100644
index 76527d1d62..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.ts
+++ /dev/null
@@ -1,318 +0,0 @@
-/*!
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-import { Service } from 'aws-sdk'
-import { ServiceConfigurationOptions } from 'aws-sdk/lib/service'
-import globals from '../../../shared/extensionGlobals'
-import { getLogger } from '../../../shared/logger/logger'
-import * as SQLWorkbench from './sqlworkbench'
-import apiConfig = require('./sqlworkbench.json')
-import { v4 as uuidv4 } from 'uuid'
-import { getRedshiftTypeFromHost } from '../../explorer/nodes/utils'
-import { DatabaseIntegrationConnectionAuthenticationTypes, RedshiftType } from '../../explorer/nodes/types'
-import { ConnectionCredentialsProvider } from '../../auth/providers/connectionCredentialsProvider'
-import { adaptConnectionCredentialsProvider } from './credentialsAdapter'
-
-/**
- * Connection configuration for SQL Workbench
- */
-export interface ConnectionConfig {
-    id: string
-    type: string
-    databaseType: string
-    connectableResourceIdentifier: string
-    connectableResourceType: string
-    database: string
-    auth?: {
-        secretArn?: string
-    }
-}
-
-/**
- * Resource parent information
- */
-export interface ParentResource {
-    parentId: string
-    parentType: string
-}
-
-/**
- * Gets a SQL Workbench ARN
- * @param region AWS region
- * @param accountId Optional AWS account ID (will be determined if not provided)
- * @returns SQL Workbench ARN
- */
-export async function generateSqlWorkbenchArn(region: string, accountId: string): Promise {
-    return `arn:aws:sqlworkbench:${region}:${accountId}:connection/${uuidv4()}`
-}
-
-/**
- * Creates a connection configuration for Redshift
- */
-export async function createRedshiftConnectionConfig(
-    host: string,
-    database: string,
-    accountId: string,
-    region: string,
-    secretArn?: string,
-    isGlueCatalogDatabase?: boolean
-): Promise {
-    // Get Redshift deployment type from host
-    const redshiftDeploymentType = getRedshiftTypeFromHost(host)
-
-    // Extract resource identifier from host
-    const resourceIdentifier = host.split('.')[0]
-
-    if (!resourceIdentifier) {
-        throw new Error('Resource identifier could not be determined from host')
-    }
-
-    // Create connection ID using the proper ARN format
-    const connectionId = await generateSqlWorkbenchArn(region, accountId)
-
-    // Determine if serverless or cluster based on deployment type
-    const isServerless =
-        redshiftDeploymentType === RedshiftType.Serverless ||
-        redshiftDeploymentType === RedshiftType.ServerlessDev ||
-        redshiftDeploymentType === RedshiftType.ServerlessQA
-
-    const isCluster =
-        redshiftDeploymentType === RedshiftType.Cluster ||
-        redshiftDeploymentType === RedshiftType.ClusterDev ||
-        redshiftDeploymentType === RedshiftType.ClusterQA
-
-    // Validate the Redshift type
-    if (!isServerless && !isCluster) {
-        throw new Error(`Unsupported Redshift type for host: ${host}`)
-    }
-
-    // Determine auth type based on the provided parameters
-    let authType: string
-
-    if (secretArn) {
-        authType = DatabaseIntegrationConnectionAuthenticationTypes.SECRET
-    } else if (isCluster) {
-        authType = DatabaseIntegrationConnectionAuthenticationTypes.TEMPORARY_CREDENTIALS_WITH_IAM
-    } else {
-        // For serverless
-        authType = DatabaseIntegrationConnectionAuthenticationTypes.FEDERATED
-    }
-
-    // Enforce specific authentication type for S3Table/RedLake databases
-    if (isGlueCatalogDatabase) {
-        authType = isServerless
-            ? DatabaseIntegrationConnectionAuthenticationTypes.FEDERATED
-            : DatabaseIntegrationConnectionAuthenticationTypes.TEMPORARY_CREDENTIALS_WITH_IAM
-    }
-
-    // Create the connection configuration
-    const connectionConfig: ConnectionConfig = {
-        id: connectionId,
-        type: authType,
-        databaseType: 'REDSHIFT',
-        connectableResourceIdentifier: resourceIdentifier,
-        connectableResourceType: isServerless ? 'WORKGROUP' : 'CLUSTER',
-        database: database,
-    }
-
-    // Add auth object for SECRET authentication type
-    if (
-        (authType as DatabaseIntegrationConnectionAuthenticationTypes) ===
-            DatabaseIntegrationConnectionAuthenticationTypes.SECRET &&
-        secretArn
-    ) {
-        connectionConfig.auth = { secretArn }
-    }
-
-    return connectionConfig
-}
-
-/**
- * Client for interacting with SQL Workbench API
- */
-export class SQLWorkbenchClient {
-    private sqlClient: SQLWorkbench | undefined
-    private static instance: SQLWorkbenchClient | undefined
-    private readonly logger = getLogger('smus')
-
-    private constructor(
-        private readonly region: string,
-        private readonly connectionCredentialsProvider?: ConnectionCredentialsProvider
-    ) {}
-
-    /**
-     * Gets a singleton instance of the SQLWorkbenchClient
-     * @returns SQLWorkbenchClient instance
-     */
-    public static getInstance(region: string): SQLWorkbenchClient {
-        if (!SQLWorkbenchClient.instance) {
-            SQLWorkbenchClient.instance = new SQLWorkbenchClient(region)
-        }
-        return SQLWorkbenchClient.instance
-    }
-
-    /**
-     * Creates a new SQLWorkbenchClient instance with specific credentials
-     * @param region AWS region
-     * @param connectionCredentialsProvider ConnectionCredentialsProvider
-     * @returns SQLWorkbenchClient instance with credentials provider
-     */
-    public static createWithCredentials(
-        region: string,
-        connectionCredentialsProvider: ConnectionCredentialsProvider
-    ): SQLWorkbenchClient {
-        return new SQLWorkbenchClient(region, connectionCredentialsProvider)
-    }
-
-    /**
-     * Gets the AWS region
-     * @returns AWS region
-     */
-    public getRegion(): string {
-        return this.region
-    }
-
-    /**
-     * Gets resources from SQL Workbench
-     * @param params Request parameters
-     * @returns Raw response from getResources API
-     */
-    public async getResources(params: {
-        connection: ConnectionConfig
-        resourceType: string
-        includeChildren?: boolean
-        maxItems?: number
-        parents?: ParentResource[]
-        pageToken?: string
-        forceRefresh?: boolean
-    }): Promise {
-        try {
-            this.logger.info(`SQLWorkbenchClient: Getting resources in region ${this.region}`)
-
-            const sqlClient = await this.getSQLClient()
-
-            const requestParams = {
-                connection: params.connection,
-                type: params.resourceType,
-                maxItems: params.maxItems || 100,
-                parents: params.parents || [],
-                pageToken: params.pageToken,
-                forceRefresh: params.forceRefresh || true,
-                accountSettings: {},
-            }
-
-            // Call the GetResources API
-            const response = await sqlClient.getResources(requestParams).promise()
-
-            return {
-                resources: response.resources || [],
-                nextToken: response.nextToken,
-            }
-        } catch (err) {
-            this.logger.error('SQLWorkbenchClient: Failed to get resources: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Execute a SQL query
-     * @param connectionConfig Connection configuration
-     * @param query SQL query to execute
-     * @returns Query execution ID
-     */
-    public async executeQuery(connectionConfig: ConnectionConfig, query: string): Promise {
-        try {
-            this.logger.info(`SQLWorkbenchClient: Executing query in region ${this.region}`)
-
-            const sqlClient = await this.getSQLClient()
-
-            // Call the ExecuteQuery API
-            const response = await sqlClient
-                .executeQuery({
-                    connection: connectionConfig as any,
-                    databaseType: 'REDSHIFT',
-                    accountSettings: {},
-                    executionContext: [
-                        {
-                            parentType: 'DATABASE',
-                            parentId: connectionConfig.database || '',
-                        },
-                    ],
-                    query,
-                    queryExecutionType: 'NO_SESSION',
-                    queryResponseDeliveryType: 'ASYNC',
-                    maxItems: 100,
-                    ignoreHistory: true,
-                    tabId: 'data_explorer',
-                })
-                .promise()
-
-            // Log the response
-            this.logger.info(
-                `SQLWorkbenchClient: Query execution started with ID: ${response.queryExecutions?.[0]?.queryExecutionId}`
-            )
-
-            return response.queryExecutions?.[0]?.queryExecutionId
-        } catch (err) {
-            this.logger.error('SQLWorkbenchClient: Failed to execute query: %s', err as Error)
-            throw err
-        }
-    }
-
-    /**
-     * Gets the SQL client, initializing it if necessary
-     */
-    /**
-     * Gets the SQL Workbench endpoint URL for the given region
-     * @param region AWS region
-     * @returns SQL Workbench endpoint URL
-     */
-    private getSQLWorkbenchEndpoint(region: string): string {
-        return `https://api-v2.sqlworkbench.${region}.amazonaws.com`
-    }
-
-    private async getSQLClient(): Promise {
-        if (!this.sqlClient) {
-            try {
-                // Get the endpoint URL for the region
-                const endpoint = this.getSQLWorkbenchEndpoint(this.region)
-                this.logger.info(`Using SQL Workbench endpoint: ${endpoint}`)
-
-                if (this.connectionCredentialsProvider) {
-                    // Create client with provided credentials
-                    this.sqlClient = (await globals.sdkClientBuilder.createAwsService(
-                        Service,
-                        {
-                            apiConfig: apiConfig,
-                            region: this.region,
-                            endpoint: endpoint,
-                            credentialProvider: adaptConnectionCredentialsProvider(this.connectionCredentialsProvider),
-                        } as ServiceConfigurationOptions,
-                        undefined,
-                        false
-                    )) as SQLWorkbench
-                } else {
-                    // Use the SDK client builder for default credentials
-                    this.sqlClient = (await globals.sdkClientBuilder.createAwsService(
-                        Service,
-                        {
-                            apiConfig: apiConfig,
-                            region: this.region,
-                            endpoint: endpoint,
-                        } as ServiceConfigurationOptions,
-                        undefined,
-                        false
-                    )) as SQLWorkbench
-                }
-
-                this.logger.debug('SQLWorkbenchClient: Successfully created SQL client')
-            } catch (err) {
-                this.logger.error('SQLWorkbenchClient: Failed to create SQL client: %s', err as Error)
-                throw err
-            }
-        }
-        return this.sqlClient
-    }
-}
diff --git a/packages/core/src/sagemakerunifiedstudio/shared/client/sqlworkbench.json b/packages/core/src/sagemakerunifiedstudio/shared/client/sqlworkbench.json
deleted file mode 100644
index e403ec34a8..0000000000
--- a/packages/core/src/sagemakerunifiedstudio/shared/client/sqlworkbench.json
+++ /dev/null
@@ -1,2102 +0,0 @@
-{
-    "version": "2.0",
-    "metadata": {
-        "apiVersion": "2024-02-12",
-        "auth": ["aws.auth#sigv4"],
-        "endpointPrefix": "sqlworkbench",
-        "protocol": "rest-json",
-        "protocols": ["rest-json"],
-        "serviceFullName": "AmazonSQLWorkbench",
-        "serviceId": "SQLWorkbench",
-        "signatureVersion": "v4",
-        "signingName": "sqlworkbench",
-        "uid": "sqlworkbench-2024-02-12"
-    },
-    "operations": {
-        "CancelQueries": {
-            "name": "CancelQueries",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/cancelQueries",
-                "responseCode": 200
-            },
-            "input": { "shape": "CancelQueriesRequest" },
-            "output": { "shape": "CancelQueriesResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "CreateConnection": {
-            "name": "CreateConnection",
-            "http": {
-                "method": "PUT",
-                "requestUri": "/connections",
-                "responseCode": 200
-            },
-            "input": { "shape": "CreateConnectionRequest" },
-            "output": { "shape": "CreateConnectionResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "DeleteConnection": {
-            "name": "DeleteConnection",
-            "http": {
-                "method": "DELETE",
-                "requestUri": "/connections/{connectionId}",
-                "responseCode": 200
-            },
-            "input": { "shape": "DeleteConnectionRequest" },
-            "output": { "shape": "DeleteConnectionResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "ExecuteQuery": {
-            "name": "ExecuteQuery",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/executeQuery",
-                "responseCode": 200
-            },
-            "input": { "shape": "ExecuteQueryRequest" },
-            "output": { "shape": "ExecuteQueryResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "ExportQueryResults": {
-            "name": "ExportQueryResults",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/exportResults",
-                "responseCode": 200
-            },
-            "input": { "shape": "ExportQueryResultsRequest" },
-            "output": { "shape": "ExportQueryResultsResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetConnectableResources": {
-            "name": "GetConnectableResources",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/getConnectableResources",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetConnectableResourcesRequest" },
-            "output": { "shape": "GetConnectableResourcesResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetConnection": {
-            "name": "GetConnection",
-            "http": {
-                "method": "GET",
-                "requestUri": "/connections/{connectionId}",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetConnectionRequest" },
-            "output": { "shape": "GetConnectionResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetDatabaseConfigurations": {
-            "name": "GetDatabaseConfigurations",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/configurations",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetDatabaseConfigurationsRequest" },
-            "output": { "shape": "GetDatabaseConfigurationsResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetQueryExecutionHistory": {
-            "name": "GetQueryExecutionHistory",
-            "http": {
-                "method": "POST",
-                "requestUri": "/queryExecutionHistory/details",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetQueryExecutionHistoryRequest" },
-            "output": { "shape": "GetQueryExecutionHistoryResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetQueryResult": {
-            "name": "GetQueryResult",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/getQueryResults",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetQueryResultRequest" },
-            "output": { "shape": "GetQueryResultResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetResources": {
-            "name": "GetResources",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/getResources",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetResourcesRequest" },
-            "output": { "shape": "GetResourcesResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "GetTabStates": {
-            "name": "GetTabStates",
-            "http": {
-                "method": "POST",
-                "requestUri": "/tab/state",
-                "responseCode": 200
-            },
-            "input": { "shape": "GetTabStatesRequest" },
-            "output": { "shape": "GetTabStatesResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "ListQueryExecutionHistory": {
-            "name": "ListQueryExecutionHistory",
-            "http": {
-                "method": "POST",
-                "requestUri": "/queryExecutionHistory/list",
-                "responseCode": 200
-            },
-            "input": { "shape": "ListQueryExecutionHistoryRequest" },
-            "output": { "shape": "ListQueryExecutionHistoryResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "ListTagsForResource": {
-            "name": "ListTagsForResource",
-            "http": {
-                "method": "GET",
-                "requestUri": "/tags/{resourceArn}",
-                "responseCode": 200
-            },
-            "input": { "shape": "ListTagsForResourceRequest" },
-            "output": { "shape": "ListTagsForResourceResponse" },
-            "errors": [
-                { "shape": "BadRequestError" },
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "PollQueryExecutionEvents": {
-            "name": "PollQueryExecutionEvents",
-            "http": {
-                "method": "POST",
-                "requestUri": "/database/pollQueryExecutionEvents",
-                "responseCode": 200
-            },
-            "input": { "shape": "PollQueryExecutionEventsRequest" },
-            "output": { "shape": "PollQueryExecutionEventsResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "BadRequestError" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "TagResource": {
-            "name": "TagResource",
-            "http": {
-                "method": "POST",
-                "requestUri": "/tags/{resourceArn}",
-                "responseCode": 204
-            },
-            "input": { "shape": "TagResourceRequest" },
-            "output": { "shape": "TagResourceResponse" },
-            "errors": [
-                { "shape": "BadRequestError" },
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "UntagResource": {
-            "name": "UntagResource",
-            "http": {
-                "method": "DELETE",
-                "requestUri": "/tags/{resourceArn}",
-                "responseCode": 204
-            },
-            "input": { "shape": "UntagResourceRequest" },
-            "output": { "shape": "UntagResourceResponse" },
-            "errors": [
-                { "shape": "BadRequestError" },
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ],
-            "idempotent": true
-        },
-        "UpdateConnection": {
-            "name": "UpdateConnection",
-            "http": {
-                "method": "POST",
-                "requestUri": "/connections",
-                "responseCode": 200
-            },
-            "input": { "shape": "UpdateConnectionRequest" },
-            "output": { "shape": "UpdateConnectionResponse" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "ConflictException" },
-                { "shape": "InternalServerError" },
-                { "shape": "ValidationException" }
-            ]
-        },
-        "VerifyResourcesExistForTagris": {
-            "name": "VerifyResourcesExistForTagris",
-            "http": {
-                "method": "POST",
-                "requestUri": "/verifyResourcesExistForTagris",
-                "responseCode": 200
-            },
-            "input": { "shape": "TagrisVerifyResourcesExistInput" },
-            "output": { "shape": "TagrisVerifyResourcesExistOutput" },
-            "errors": [
-                { "shape": "ThrottlingException" },
-                { "shape": "InternalServerError" },
-                { "shape": "TagrisInvalidParameterException" },
-                { "shape": "TagrisAccessDeniedException" },
-                { "shape": "TagrisInvalidArnException" },
-                { "shape": "ResourceNotFoundException" },
-                { "shape": "TagrisInternalServiceException" },
-                { "shape": "ServiceQuotaExceededException" },
-                { "shape": "AccessDeniedException" },
-                { "shape": "TagrisPartialResourcesExistResultsException" },
-                { "shape": "TagrisThrottledException" },
-                { "shape": "ConflictException" },
-                { "shape": "ValidationException" }
-            ]
-        }
-    },
-    "shapes": {
-        "AccessDeniedException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": { "shape": "String" },
-                "code": { "shape": "ErrorCode" }
-            },
-            "error": {
-                "httpStatusCode": 403,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "AckIds": {
-            "type": "list",
-            "member": { "shape": "AckIdsMemberString" }
-        },
-        "AckIdsMemberString": {
-            "type": "string",
-            "max": 100,
-            "min": 0
-        },
-        "Arn": {
-            "type": "string",
-            "max": 1011,
-            "min": 20
-        },
-        "AvailableConnectionConfigurationOptions": {
-            "type": "list",
-            "member": { "shape": "AvailableConnectionConfigurationOptionsMemberString" }
-        },
-        "AvailableConnectionConfigurationOptionsMemberString": {
-            "type": "string",
-            "max": 50,
-            "min": 0
-        },
-        "BadRequestError": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": { "shape": "String" },
-                "code": { "shape": "ErrorCode" }
-            },
-            "error": {
-                "httpStatusCode": 400,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "Boolean": {
-            "type": "boolean",
-            "box": true
-        },
-        "CancelQueriesRequest": {
-            "type": "structure",
-            "required": ["queryExecutionIds", "databaseType"],
-            "members": {
-                "sqlworkbenchSource": {
-                    "shape": "SqlworkbenchSource",
-                    "location": "header",
-                    "locationName": "sqlworkbench-source"
-                },
-                "accountSettings": { "shape": "DatabaseConnectionAccountSettings" },
-                "queryExecutionIds": { "shape": "CancelQueriesRequestQueryExecutionIdsList" },
-                "databaseType": {
-                    "shape": "DatabaseType",
-                    "location": "querystring",
-                    "locationName": "databaseType"
-                }
-            }
-        },
-        "CancelQueriesRequestQueryExecutionIdsList": {
-            "type": "list",
-            "member": { "shape": "CancelQueriesRequestQueryExecutionIdsListMemberString" },
-            "max": 100,
-            "min": 1
-        },
-        "CancelQueriesRequestQueryExecutionIdsListMemberString": {
-            "type": "string",
-            "max": 100,
-            "min": 1
-        },
-        "CancelQueriesResponse": {
-            "type": "structure",
-            "required": ["cancelQueryResponses"],
-            "members": {
-                "cancelQueryResponses": { "shape": "CancelQueryResponses" }
-            }
-        },
-        "CancelQueryResponse": {
-            "type": "structure",
-            "required": ["queryExecutionId"],
-            "members": {
-                "queryExecutionId": { "shape": "CancelQueryResponseQueryExecutionIdString" },
-                "queryCancellationStatus": { "shape": "QueryCancellationStatus" }
-            }
-        },
-        "CancelQueryResponseQueryExecutionIdString": {
-            "type": "string",
-            "max": 1000,
-            "min": 0
-        },
-        "CancelQueryResponses": {
-            "type": "list",
-            "member": { "shape": "CancelQueryResponse" }
-        },
-        "ChildObjectTypes": {
-            "type": "list",
-            "member": { "shape": "ChildObjectTypesMemberString" }
-        },
-        "ChildObjectTypesMemberString": {
-            "type": "string",
-            "max": 50,
-            "min": 0
-        },
-        "Columns": {
-            "type": "list",
-            "member": { "shape": "QueryResultCellValue" }
-        },
-        "ConflictException": {
-            "type": "structure",
-            "required": ["message"],
-            "members": {
-                "message": { "shape": "String" },
-                "code": { "shape": "ErrorCode" }
-            },
-            "error": {
-                "httpStatusCode": 409,
-                "senderFault": true
-            },
-            "exception": true
-        },
-        "ConnectableResource": {
-            "type": "structure",
-            "required": ["displayName", "identifier", "childObjectTypes", "availableConnectionConfigurationOptions"],
-            "members": {
-                "displayName": { "shape": "ResourceDisplayName" },
-                "identifier": { "shape": "ResourceIdentifier" },
-                "type": { "shape": "ConnectableResourceTypeString" },
-                "unavailable": { "shape": "Boolean" },
-                "tooltipTranslationKey": { "shape": "ConnectableResourceTooltipTranslationKeyString" },
-                "childObjectTypes": { "shape": "ChildObjectTypes" },
-                "availableConnectionConfigurationOptions": { "shape": "AvailableConnectionConfigurationOptions" }
-            }
-        },
-        "ConnectableResourceTooltipTranslationKeyString": {
-            "type": "string",
-            "max": 50,
-            "min": 0
-        },
-        "ConnectableResourceTypeString": {
-            "type": "string",
-            "max": 50,
-            "min": 0
-        },
-        "ConnectableResourceTypes": {
-            "type": "list",
-            "member": { "shape": "ConnectableResourceTypesMemberString" }
-        },
-        "ConnectableResourceTypesMemberString": {
-            "type": "string",
-            "max": 50,
-            "min": 0
-        },
-        "ConnectableResources": {
-            "type": "list",
-            "member": { "shape": "ConnectableResource" }
-        },
-        "Connection": {
-            "type": "structure",
-            "members": {
-                "id": {
-                    "shape": "String",
-                    "documentation": "

Id of the connection

" - }, - "name": { - "shape": "ConnectionName", - "documentation": "

Name of the connection

" - }, - "authenticationType": { - "shape": "ConnectionAuthenticationTypes", - "documentation": "

Number representing the type of authentication to use (2 = IAM, 3 = Username and Password). Today we only support the types 2 and 3

" - }, - "secretArn": { - "shape": "String", - "documentation": "

Secret that is linked to this connection

" - }, - "databaseName": { - "shape": "DatabaseName", - "documentation": "

Name of the database where the query is run

" - }, - "clusterId": { - "shape": "String", - "documentation": "

Id of the cluster of the connection

" - }, - "dbUser": { - "shape": "DbUser", - "documentation": "

User of the database

" - }, - "isServerless": { "shape": "Boolean" }, - "isProd": { "shape": "String" }, - "isEnabled": { "shape": "String" }, - "userSettings": { "shape": "UserSettings" }, - "recordDate": { "shape": "String" }, - "updatedDate": { "shape": "String" }, - "tags": { "shape": "Tags" }, - "databaseType": { "shape": "DatabaseType" }, - "connectableResourceType": { "shape": "String" }, - "connectableResourceIdentifier": { "shape": "ResourceIdentifier" } - } - }, - "ConnectionAuthenticationTypes": { - "type": "string", - "enum": ["2", "3", "4", "5", "6", "7", "8"], - "sensitive": true - }, - "ConnectionName": { - "type": "string", - "sensitive": true - }, - "ConnectionProperties": { - "type": "map", - "key": { "shape": "ConnectionPropertyKey" }, - "value": { "shape": "ConnectionPropertyValue" }, - "max": 50, - "min": 1 - }, - "ConnectionPropertyKey": { - "type": "string", - "max": 1000, - "min": 1 - }, - "ConnectionPropertyValue": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionRequest": { - "type": "structure", - "required": ["name", "databaseName", "authenticationType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "name": { - "shape": "CreateConnectionRequestNameString", - "documentation": "

Name of the connection

" - }, - "databaseName": { - "shape": "CreateConnectionRequestDatabaseNameString", - "documentation": "

Name of the database used for this connection

" - }, - "authenticationType": { - "shape": "CreateConnectionRequestAuthenticationTypeEnum", - "documentation": "

Number representing the type of authentication to use (2 = IAM, 3 = Username and Password, 4 = Federated connection)

" - }, - "isProd": { "shape": "CreateConnectionRequestIsProdString" }, - "userSettings": { "shape": "UserSettings" }, - "secretArn": { - "shape": "CreateConnectionRequestSecretArnString", - "documentation": "

secretArn for redshift cluster

" - }, - "clusterId": { - "shape": "CreateConnectionRequestClusterIdString", - "documentation": "

Id of the cluster used for this connection

" - }, - "isServerless": { - "shape": "Boolean", - "documentation": "

Is serverless connection

" - }, - "dbUser": { - "shape": "DbUser", - "documentation": "

User of the database used for this connection

" - }, - "isStoreNewSecret": { "shape": "CreateConnectionRequestIsStoreNewSecretString" }, - "username": { - "shape": "DbUser", - "documentation": "

Username used in the Username_Password connection type

" - }, - "password": { - "shape": "CreateConnectionRequestPasswordString", - "documentation": "

Password of the user used for this connection

" - }, - "tags": { "shape": "Tags" }, - "host": { - "shape": "CreateConnectionRequestHostString", - "documentation": "

Host address used for creating secret for Username_Password connection type

" - }, - "secretName": { "shape": "CreateConnectionRequestSecretNameString" }, - "description": { "shape": "CreateConnectionRequestDescriptionString" }, - "databaseType": { "shape": "DatabaseType" }, - "connectableResourceIdentifier": { - "shape": "CreateConnectionRequestConnectableResourceIdentifierString", - "documentation": "

Id of the connectable resource used for this connection

" - }, - "connectableResourceType": { - "shape": "CreateConnectionRequestConnectableResourceTypeString", - "documentation": "

Type of the connectable resource used for this connection

" - } - } - }, - "CreateConnectionRequestAuthenticationTypeEnum": { - "type": "string", - "enum": ["2", "3", "4", "5", "6", "7", "8"], - "max": 1, - "min": 1, - "sensitive": true - }, - "CreateConnectionRequestClusterIdString": { - "type": "string", - "max": 63, - "min": 1 - }, - "CreateConnectionRequestConnectableResourceIdentifierString": { - "type": "string", - "max": 63, - "min": 1, - "sensitive": true - }, - "CreateConnectionRequestConnectableResourceTypeString": { - "type": "string", - "max": 63, - "min": 1 - }, - "CreateConnectionRequestDatabaseNameString": { - "type": "string", - "max": 64, - "min": 1, - "sensitive": true - }, - "CreateConnectionRequestDescriptionString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionRequestHostString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionRequestIsProdString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionRequestIsStoreNewSecretString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionRequestNameString": { - "type": "string", - "max": 512, - "min": 1, - "sensitive": true - }, - "CreateConnectionRequestPasswordString": { - "type": "string", - "max": 64, - "min": 8, - "sensitive": true - }, - "CreateConnectionRequestSecretArnString": { - "type": "string", - "max": 1000, - "min": 1 - }, - "CreateConnectionRequestSecretNameString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "CreateConnectionResponse": { - "type": "structure", - "members": { - "data": { "shape": "Connection" } - } - }, - "DatabaseAuthenticationMethod": { - "type": "string", - "enum": ["USERNAME_PASSWORD", "TEMPORARY_CREDENTIALS_WITH_IAM"] - }, - "DatabaseAuthenticationMethods": { - "type": "list", - "member": { "shape": "DatabaseAuthenticationMethod" } - }, - "DatabaseAuthenticationOption": { - "type": "structure", - "required": ["connectableResourceType", "authenticationMethods"], - "members": { - "connectableResourceType": { "shape": "String" }, - "authenticationMethods": { "shape": "DatabaseAuthenticationMethods" } - } - }, - "DatabaseAuthenticationOptions": { - "type": "list", - "member": { "shape": "DatabaseAuthenticationOption" } - }, - "DatabaseConfiguration": { - "type": "structure", - "required": [ - "databaseType", - "authenticationOptions", - "connectableResourceTypes", - "sessionSupported", - "eventAcknowledgementSupported", - "appendingLimitToQuerySupported", - "queryStatsSupported" - ], - "members": { - "databaseType": { "shape": "DatabaseType" }, - "authenticationOptions": { "shape": "DatabaseAuthenticationOptions" }, - "connectableResourceTypes": { "shape": "ConnectableResourceTypes" }, - "sessionSupported": { "shape": "Boolean" }, - "eventAcknowledgementSupported": { "shape": "Boolean" }, - "appendingLimitToQuerySupported": { "shape": "Boolean" }, - "queryStatsSupported": { "shape": "Boolean" } - } - }, - "DatabaseConfigurations": { - "type": "list", - "member": { "shape": "DatabaseConfiguration" } - }, - "DatabaseConnectionAccountSettings": { - "type": "structure", - "members": { - "masterKeyArn": { "shape": "KmsKeyArn" } - } - }, - "DatabaseConnectionConfiguration": { - "type": "structure", - "required": ["id", "type", "databaseType", "connectableResourceIdentifier", "connectableResourceType"], - "members": { - "id": { "shape": "DatabaseConnectionConfigurationIdString" }, - "type": { "shape": "DatabaseIntegrationConnectionAuthenticationTypes" }, - "auth": { "shape": "DatabaseConnectionConfigurationAuth" }, - "databaseType": { "shape": "DatabaseType" }, - "connectableResourceIdentifier": { "shape": "ResourceIdentifier" }, - "connectableResourceType": { "shape": "DatabaseConnectionConfigurationConnectableResourceTypeString" }, - "database": { "shape": "DatabaseName" } - } - }, - "DatabaseConnectionConfigurationAuth": { - "type": "structure", - "members": { - "secretArn": { "shape": "SecretKeyArn" }, - "username": { "shape": "DatabaseConnectionConfigurationAuthUsernameString" }, - "password": { "shape": "DatabaseConnectionConfigurationAuthPasswordString" } - } - }, - "DatabaseConnectionConfigurationAuthPasswordString": { - "type": "string", - "max": 1000, - "min": 0, - "sensitive": true - }, - "DatabaseConnectionConfigurationAuthUsernameString": { - "type": "string", - "max": 1000, - "min": 0, - "sensitive": true - }, - "DatabaseConnectionConfigurationConnectableResourceTypeString": { - "type": "string", - "max": 50, - "min": 0 - }, - "DatabaseConnectionConfigurationIdString": { - "type": "string", - "max": 2048, - "min": 32 - }, - "DatabaseIntegrationConnectionAuthenticationTypes": { - "type": "string", - "enum": ["4", "5", "6", "8"], - "sensitive": true - }, - "DatabaseName": { - "type": "string", - "max": 150, - "min": 0, - "sensitive": true - }, - "DatabaseType": { - "type": "string", - "enum": ["REDSHIFT", "ATHENA"] - }, - "DbUser": { - "type": "string", - "max": 127, - "min": 1, - "pattern": "[a-zA-Z0-9_][a-zA-Z_0-9+.@$-]*", - "sensitive": true - }, - "DeleteConnectionRequest": { - "type": "structure", - "required": ["connectionId"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "connectionId": { - "shape": "DeleteConnectionRequestConnectionIdString", - "documentation": "

Id of connection to delete

", - "location": "uri", - "locationName": "connectionId" - } - } - }, - "DeleteConnectionRequestConnectionIdString": { - "type": "string", - "max": 1000, - "min": 1 - }, - "DeleteConnectionResponse": { - "type": "structure", - "members": {} - }, - "ErrorCode": { - "type": "string", - "enum": ["QUERY_EXECUTION_NOT_FOUND", "QUERY_EXECUTION_ACCESS_DENIED"] - }, - "ExecuteQueryRequest": { - "type": "structure", - "required": ["query", "queryExecutionType", "queryResponseDeliveryType", "maxItems"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "connectionId": { "shape": "ExecuteQueryRequestConnectionIdString" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - }, - "connection": { "shape": "DatabaseConnectionConfiguration" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "tabId": { "shape": "ExecuteQueryRequestTabIdString" }, - "executionContext": { "shape": "ExecuteQueryRequestExecutionContextList" }, - "query": { "shape": "ExecuteQueryRequestQueryString" }, - "queryExecutionType": { "shape": "QueryExecutionType" }, - "sessionId": { "shape": "ExecuteQueryRequestSessionIdString" }, - "queryResponseDeliveryType": { "shape": "QueryResponseDeliveryType" }, - "maxItems": { "shape": "ExecuteQueryRequestMaxItemsInteger" }, - "limitQueryResults": { "shape": "ExecuteQueryRequestLimitQueryResultsInteger" }, - "isExplain": { "shape": "Boolean" }, - "ignoreHistory": { "shape": "Boolean" }, - "timeoutMillis": { "shape": "ExecuteQueryRequestTimeoutMillisInteger" } - } - }, - "ExecuteQueryRequestConnectionIdString": { - "type": "string", - "max": 2048, - "min": 32 - }, - "ExecuteQueryRequestExecutionContextList": { - "type": "list", - "member": { "shape": "ParentResource" }, - "max": 100, - "min": 0 - }, - "ExecuteQueryRequestLimitQueryResultsInteger": { - "type": "integer", - "box": true, - "max": 1000, - "min": 0 - }, - "ExecuteQueryRequestMaxItemsInteger": { - "type": "integer", - "box": true, - "max": 100, - "min": 20 - }, - "ExecuteQueryRequestQueryString": { - "type": "string", - "max": 1000000, - "min": 0, - "sensitive": true - }, - "ExecuteQueryRequestSessionIdString": { - "type": "string", - "max": 100, - "min": 0 - }, - "ExecuteQueryRequestTabIdString": { - "type": "string", - "max": 100, - "min": 1 - }, - "ExecuteQueryRequestTimeoutMillisInteger": { - "type": "integer", - "box": true, - "max": 120000, - "min": 0 - }, - "ExecuteQueryResponse": { - "type": "structure", - "required": ["queryExecutions"], - "members": { - "sessionId": { "shape": "ExecuteQueryResponseSessionIdString" }, - "queryExecutions": { "shape": "QueryExecutions" }, - "statusCode": { - "shape": "statusCode", - "location": "statusCode" - } - } - }, - "ExecuteQueryResponseSessionIdString": { - "type": "string", - "max": 100, - "min": 0 - }, - "ExportQueryResultsRequest": { - "type": "structure", - "required": ["queryExecutionId", "databaseType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "queryExecutionId": { "shape": "ExportQueryResultsRequestQueryExecutionIdString" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - }, - "fileType": { "shape": "FileType" } - } - }, - "ExportQueryResultsRequestQueryExecutionIdString": { - "type": "string", - "max": 100, - "min": 1 - }, - "ExportQueryResultsResponse": { - "type": "structure", - "required": ["queryResult", "contentType", "fileName"], - "members": { - "queryResult": { "shape": "StreamingBlob" }, - "contentType": { - "shape": "String", - "location": "header", - "locationName": "Content-Type" - }, - "fileName": { - "shape": "String", - "location": "header", - "locationName": "Content-Disposition" - } - }, - "payload": "queryResult" - }, - "FileType": { - "type": "string", - "enum": ["JSON", "CSV"] - }, - "FullQueryText": { - "type": "string", - "max": 1000000, - "min": 0, - "sensitive": true - }, - "GetConnectableResourcesRequest": { - "type": "structure", - "required": ["type", "maxItems", "databaseType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "type": { "shape": "GetConnectableResourcesRequestTypeString" }, - "maxItems": { "shape": "GetConnectableResourcesRequestMaxItemsInteger" }, - "pageToken": { "shape": "PageToken" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - } - } - }, - "GetConnectableResourcesRequestMaxItemsInteger": { - "type": "integer", - "box": true, - "max": 50, - "min": 20 - }, - "GetConnectableResourcesRequestTypeString": { - "type": "string", - "max": 150, - "min": 0 - }, - "GetConnectableResourcesResponse": { - "type": "structure", - "required": ["connectableResources"], - "members": { - "connectableResources": { "shape": "ConnectableResources" }, - "nextToken": { "shape": "String" } - } - }, - "GetConnectionRequest": { - "type": "structure", - "required": ["connectionId"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "connectionId": { - "shape": "GetConnectionRequestConnectionIdString", - "documentation": "

Id of connection to delete

", - "location": "uri", - "locationName": "connectionId" - } - } - }, - "GetConnectionRequestConnectionIdString": { - "type": "string", - "max": 1000, - "min": 1 - }, - "GetConnectionResponse": { - "type": "structure", - "members": { - "data": { "shape": "Connection" } - } - }, - "GetDatabaseConfigurationsRequest": { - "type": "structure", - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" } - } - }, - "GetDatabaseConfigurationsResponse": { - "type": "structure", - "members": { - "configurations": { "shape": "DatabaseConfigurations" } - } - }, - "GetQueryExecutionHistoryRequest": { - "type": "structure", - "required": ["queryExecutionId"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "queryExecutionId": { "shape": "GetQueryExecutionHistoryRequestQueryExecutionIdString" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" } - } - }, - "GetQueryExecutionHistoryRequestQueryExecutionIdString": { - "type": "string", - "max": 100, - "min": 1 - }, - "GetQueryExecutionHistoryResponse": { - "type": "structure", - "members": { - "id": { "shape": "String" }, - "querySourceId": { "shape": "String" }, - "queryStartTime": { "shape": "Long" }, - "queryEndTime": { "shape": "Long" }, - "status": { "shape": "QueryExecutionStatus" }, - "queryText": { "shape": "FullQueryText" }, - "serializedMetadata": { "shape": "SerializedMetadata" }, - "serializedQueryStats": { "shape": "SerializedQueryStats" }, - "databaseType": { "shape": "DatabaseType" } - } - }, - "GetQueryResultRequest": { - "type": "structure", - "required": ["queryExecutionId", "databaseType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "queryExecutionId": { "shape": "GetQueryResultRequestQueryExecutionIdString" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "pageToken": { "shape": "PageToken" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - }, - "pageSize": { "shape": "GetQueryResultRequestPageSizeInteger" } - } - }, - "GetQueryResultRequestPageSizeInteger": { - "type": "integer", - "box": true, - "min": 0 - }, - "GetQueryResultRequestQueryExecutionIdString": { - "type": "string", - "max": 100, - "min": 1 - }, - "GetQueryResultResponse": { - "type": "structure", - "members": { - "queryResult": { "shape": "QueryResult" }, - "nextToken": { "shape": "String" }, - "previousToken": { "shape": "String" } - } - }, - "GetResourcesRequest": { - "type": "structure", - "required": ["parents", "type", "maxItems"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "connectionId": { "shape": "GetResourcesRequestConnectionIdString" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - }, - "connection": { "shape": "DatabaseConnectionConfiguration" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "parents": { "shape": "ParentResources" }, - "type": { "shape": "GetResourcesRequestTypeString" }, - "maxItems": { "shape": "GetResourcesRequestMaxItemsInteger" }, - "pageToken": { "shape": "PageToken" }, - "forceRefresh": { "shape": "Boolean" }, - "forceRefreshRecursive": { "shape": "Boolean" } - } - }, - "GetResourcesRequestConnectionIdString": { - "type": "string", - "max": 2048, - "min": 32 - }, - "GetResourcesRequestMaxItemsInteger": { - "type": "integer", - "box": true, - "max": 100, - "min": 20 - }, - "GetResourcesRequestTypeString": { - "type": "string", - "max": 150, - "min": 0 - }, - "GetResourcesResponse": { - "type": "structure", - "members": { - "resources": { "shape": "Resources" }, - "nextToken": { "shape": "String" }, - "statusCode": { - "shape": "statusCode", - "location": "statusCode" - }, - "connectionProperties": { "shape": "ConnectionProperties" } - } - }, - "GetTabStatesRequest": { - "type": "structure", - "required": ["tabId"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "tabId": { "shape": "String" } - } - }, - "GetTabStatesResponse": { - "type": "structure", - "required": ["queryExecutionStates"], - "members": { - "queryExecutionStates": { "shape": "QueryExecutionStates" }, - "sessionId": { "shape": "String" } - } - }, - "Integer": { - "type": "integer", - "box": true - }, - "InternalServerError": { - "type": "structure", - "required": ["message"], - "members": { - "message": { "shape": "String" }, - "code": { "shape": "ErrorCode" } - }, - "error": { "httpStatusCode": 500 }, - "exception": true, - "fault": true - }, - "KmsKeyArn": { - "type": "string", - "max": 1000, - "min": 0, - "pattern": "arn:.*" - }, - "ListQueryExecutionHistoryRequest": { - "type": "structure", - "required": ["maxItems"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "maxItems": { "shape": "ListQueryExecutionHistoryRequestMaxItemsInteger" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "pageToken": { "shape": "ListQueryExecutionHistoryRequestPageTokenString" }, - "querySourceId": { "shape": "ListQueryExecutionHistoryRequestQuerySourceIdString" }, - "databaseType": { "shape": "DatabaseType" }, - "status": { "shape": "QueryExecutionStatus" }, - "startTime": { "shape": "QueryHistoryTimestamp" }, - "endTime": { "shape": "QueryHistoryTimestamp" }, - "containsText": { "shape": "ListQueryExecutionHistoryRequestContainsTextString" } - } - }, - "ListQueryExecutionHistoryRequestContainsTextString": { - "type": "string", - "max": 100, - "min": 0 - }, - "ListQueryExecutionHistoryRequestMaxItemsInteger": { - "type": "integer", - "box": true, - "max": 100, - "min": 1 - }, - "ListQueryExecutionHistoryRequestPageTokenString": { - "type": "string", - "max": 10000, - "min": 0 - }, - "ListQueryExecutionHistoryRequestQuerySourceIdString": { - "type": "string", - "max": 100, - "min": 0 - }, - "ListQueryExecutionHistoryResponse": { - "type": "structure", - "required": ["items"], - "members": { - "items": { "shape": "QueryExecutionHistoryPreviews" }, - "nextToken": { "shape": "ListQueryExecutionHistoryResponseNextTokenString" } - } - }, - "ListQueryExecutionHistoryResponseNextTokenString": { - "type": "string", - "max": 1000, - "min": 0 - }, - "ListTagsForResourceRequest": { - "type": "structure", - "required": ["resourceArn"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "resourceArn": { - "shape": "Arn", - "location": "uri", - "locationName": "resourceArn" - } - } - }, - "ListTagsForResourceResponse": { - "type": "structure", - "required": ["tags"], - "members": { - "tags": { "shape": "Tags" } - } - }, - "Long": { - "type": "long", - "box": true - }, - "PageToken": { - "type": "string", - "max": 1000, - "min": 0 - }, - "ParentResource": { - "type": "structure", - "required": ["parentId", "parentType"], - "members": { - "parentId": { "shape": "ParentResourceParentIdString" }, - "parentType": { "shape": "ParentResourceParentTypeString" } - } - }, - "ParentResourceParentIdString": { - "type": "string", - "max": 1000, - "min": 1, - "sensitive": true - }, - "ParentResourceParentTypeString": { - "type": "string", - "max": 100, - "min": 1 - }, - "ParentResources": { - "type": "list", - "member": { "shape": "ParentResource" } - }, - "PollQueryExecutionEventsRequest": { - "type": "structure", - "required": ["queryExecutionIds", "databaseType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "queryExecutionIds": { "shape": "PollQueryExecutionEventsRequestQueryExecutionIdsList" }, - "accountSettings": { "shape": "DatabaseConnectionAccountSettings" }, - "databaseType": { - "shape": "DatabaseType", - "location": "querystring", - "locationName": "databaseType" - }, - "ackIds": { "shape": "AckIds" } - } - }, - "PollQueryExecutionEventsRequestQueryExecutionIdsList": { - "type": "list", - "member": { "shape": "PollQueryExecutionEventsRequestQueryExecutionIdsListMemberString" }, - "max": 100, - "min": 1 - }, - "PollQueryExecutionEventsRequestQueryExecutionIdsListMemberString": { - "type": "string", - "max": 100, - "min": 1 - }, - "PollQueryExecutionEventsResponse": { - "type": "structure", - "members": { - "events": { "shape": "QueryExecutionEvents" } - } - }, - "QueryCancellationStatus": { - "type": "string", - "enum": ["CANCELLED", "DOES_NOT_EXISTS", "ALREADY_FINISHED", "CANCELLATION_FAILED"] - }, - "QueryExecution": { - "type": "structure", - "required": ["queryExecutionId"], - "members": { - "queryExecutionStatus": { "shape": "QueryExecutionStatus" }, - "queryExecutionId": { "shape": "QueryExecutionQueryExecutionIdString" }, - "queryResult": { "shape": "QueryResult" }, - "queryText": { "shape": "QueryText" } - } - }, - "QueryExecutionEvent": { - "type": "structure", - "required": ["queryExecutionEventType", "queryExecutionId"], - "members": { - "queryExecutionEventType": { "shape": "QueryExecutionEventType" }, - "queryExecutionId": { "shape": "QueryExecutionEventQueryExecutionIdString" }, - "queryExecutionStatus": { "shape": "QueryExecutionStatus" }, - "queryResult": { "shape": "QueryResult" }, - "nextToken": { "shape": "String" }, - "ackId": { "shape": "String" } - } - }, - "QueryExecutionEventQueryExecutionIdString": { - "type": "string", - "max": 100, - "min": 0 - }, - "QueryExecutionEventType": { - "type": "string", - "enum": ["QUERY_EXECUTION_STATUS", "QUERY_EXECUTION_RESULT"] - }, - "QueryExecutionEvents": { - "type": "list", - "member": { "shape": "QueryExecutionEvent" } - }, - "QueryExecutionHistoryPreview": { - "type": "structure", - "members": { - "id": { "shape": "String" }, - "querySourceId": { "shape": "String" }, - "queryStartTime": { "shape": "Long" }, - "queryEndTime": { "shape": "Long" }, - "status": { "shape": "QueryExecutionStatus" }, - "queryTextPreview": { "shape": "QueryTextPreview" }, - "serializedMetadata": { "shape": "SerializedMetadata" }, - "databaseType": { "shape": "DatabaseType" } - } - }, - "QueryExecutionHistoryPreviews": { - "type": "list", - "member": { "shape": "QueryExecutionHistoryPreview" } - }, - "QueryExecutionQueryExecutionIdString": { - "type": "string", - "max": 100, - "min": 0 - }, - "QueryExecutionState": { - "type": "structure", - "required": ["queryExecutionId", "status", "databaseType"], - "members": { - "queryExecutionId": { "shape": "String" }, - "status": { "shape": "String" }, - "databaseType": { "shape": "DatabaseType" } - } - }, - "QueryExecutionStates": { - "type": "list", - "member": { "shape": "QueryExecutionState" } - }, - "QueryExecutionStatus": { - "type": "string", - "enum": ["SCHEDULED", "RUNNING", "FAILED", "CANCELLED", "FINISHED"] - }, - "QueryExecutionType": { - "type": "string", - "enum": ["PERSIST_SESSION", "NO_SESSION"] - }, - "QueryExecutionWarning": { - "type": "structure", - "members": { - "message": { "shape": "QueryExecutionWarningMessage" }, - "level": { "shape": "QueryExecutionWarningLevel" } - } - }, - "QueryExecutionWarningLevel": { - "type": "string", - "enum": ["INFO", "WARNING"] - }, - "QueryExecutionWarningMessage": { - "type": "string", - "max": 1000, - "min": 0, - "sensitive": true - }, - "QueryExecutionWarnings": { - "type": "list", - "member": { "shape": "QueryExecutionWarning" } - }, - "QueryExecutions": { - "type": "list", - "member": { "shape": "QueryExecution" } - }, - "QueryHistoryTimestamp": { - "type": "long", - "box": true - }, - "QueryResponseDeliveryType": { - "type": "string", - "enum": ["SYNC", "ASYNC"] - }, - "QueryResult": { - "type": "structure", - "members": { - "queryExecutionStatus": { "shape": "QueryExecutionStatus" }, - "headers": { "shape": "QueryResultHeaders" }, - "rows": { "shape": "Rows" }, - "affectedRows": { "shape": "Integer" }, - "totalRowCount": { "shape": "Integer" }, - "elapsedTime": { "shape": "Long" }, - "errorMessage": { "shape": "QueryResultErrorMessage" }, - "errorPosition": { "shape": "Integer" }, - "queryResultWarningCode": { "shape": "QueryResultQueryResultWarningCodeString" }, - "warnings": { "shape": "QueryExecutionWarnings" }, - "queryExecutionId": { "shape": "String" }, - "sessionId": { "shape": "String" }, - "queryText": { "shape": "QueryText" }, - "statementType": { "shape": "StatementType" }, - "serializedMetadata": { "shape": "SerializedMetadata" }, - "connectionProperties": { "shape": "ConnectionProperties" } - } - }, - "QueryResultCellType": { - "type": "string", - "enum": ["STRING", "BOOLEAN", "INTEGER", "BIG_INTEGER", "FLOAT", "BIG_DECIMAL", "DATE", "TIME", "DATETIME"] - }, - "QueryResultCellValue": { - "type": "string", - "sensitive": true - }, - "QueryResultErrorMessage": { - "type": "string", - "max": 1000, - "min": 0, - "sensitive": true - }, - "QueryResultHeader": { - "type": "structure", - "required": ["displayName", "type"], - "members": { - "displayName": { "shape": "QueryResultHeaderDisplayName" }, - "type": { "shape": "QueryResultCellType" } - } - }, - "QueryResultHeaderDisplayName": { - "type": "string", - "sensitive": true - }, - "QueryResultHeaders": { - "type": "list", - "member": { "shape": "QueryResultHeader" } - }, - "QueryResultQueryResultWarningCodeString": { - "type": "string", - "max": 100, - "min": 0 - }, - "QueryText": { - "type": "string", - "sensitive": true - }, - "QueryTextPreview": { - "type": "string", - "max": 150, - "min": 0, - "sensitive": true - }, - "Resource": { - "type": "structure", - "required": ["displayName", "identifier", "childObjectTypes"], - "members": { - "displayName": { "shape": "ResourceDisplayName" }, - "identifier": { "shape": "ResourceIdentifier" }, - "type": { "shape": "ResourceTypeString" }, - "unavailable": { "shape": "Boolean" }, - "tooltipTranslationKey": { "shape": "ResourceTooltipTranslationKeyString" }, - "childObjectTypes": { "shape": "ChildObjectTypes" }, - "allowedActions": { "shape": "ResourceActions" }, - "resourceMetadata": { "shape": "ResourceMetadataItems" } - } - }, - "ResourceAction": { - "type": "string", - "enum": ["Drop", "Truncate", "GenerateDefinition", "GenerateSelectQuery"] - }, - "ResourceActions": { - "type": "list", - "member": { "shape": "ResourceAction" } - }, - "ResourceDisplayName": { - "type": "string", - "max": 150, - "min": 0, - "sensitive": true - }, - "ResourceIdentifier": { - "type": "string", - "max": 150, - "min": 0, - "sensitive": true - }, - "ResourceMetadata": { - "type": "structure", - "members": { - "key": { "shape": "String" }, - "value": { "shape": "String" } - } - }, - "ResourceMetadataItems": { - "type": "list", - "member": { "shape": "ResourceMetadata" } - }, - "ResourceNotFoundException": { - "type": "structure", - "required": ["message"], - "members": { - "message": { "shape": "String" }, - "code": { "shape": "ErrorCode" } - }, - "error": { - "httpStatusCode": 404, - "senderFault": true - }, - "exception": true - }, - "ResourceTooltipTranslationKeyString": { - "type": "string", - "max": 50, - "min": 0 - }, - "ResourceTypeString": { - "type": "string", - "max": 50, - "min": 0 - }, - "Resources": { - "type": "list", - "member": { "shape": "Resource" } - }, - "Row": { - "type": "structure", - "members": { - "row": { "shape": "Columns" } - } - }, - "Rows": { - "type": "list", - "member": { "shape": "Row" } - }, - "SecretKeyArn": { - "type": "string", - "max": 1000, - "min": 0, - "pattern": "arn:.*" - }, - "SerializedMetadata": { - "type": "string", - "max": 1000000, - "min": 0, - "sensitive": true - }, - "SerializedQueryStats": { - "type": "string", - "max": 1000000, - "min": 0, - "sensitive": true - }, - "ServiceQuotaExceededException": { - "type": "structure", - "required": ["message"], - "members": { - "message": { "shape": "String" }, - "code": { "shape": "ErrorCode" } - }, - "error": { - "httpStatusCode": 402, - "senderFault": true - }, - "exception": true - }, - "SqlworkbenchSource": { - "type": "string", - "enum": ["SUS", "RQEV2"] - }, - "StatementType": { - "type": "string", - "enum": ["DQL", "DML", "DDL", "DCL", "Utility"] - }, - "StreamingBlob": { - "type": "blob", - "streaming": true - }, - "String": { "type": "string" }, - "TagKey": { - "type": "string", - "max": 128, - "min": 1, - "pattern": "([\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*)" - }, - "TagKeyList": { - "type": "list", - "member": { "shape": "TagKey" }, - "max": 6500, - "min": 1 - }, - "TagResourceRequest": { - "type": "structure", - "required": ["resourceArn", "tags"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "resourceArn": { - "shape": "Arn", - "location": "uri", - "locationName": "resourceArn" - }, - "tags": { "shape": "Tags" } - } - }, - "TagResourceResponse": { - "type": "structure", - "members": {} - }, - "TagValue": { - "type": "string", - "max": 256, - "min": 0, - "pattern": "([\\p{L}\\p{Z}\\p{N}_.:/=+\\-@]*)" - }, - "TagrisAccessDeniedException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" } - }, - "exception": true - }, - "TagrisAccountId": { - "type": "string", - "max": 12, - "min": 12 - }, - "TagrisAmazonResourceName": { - "type": "string", - "max": 1011, - "min": 1 - }, - "TagrisExceptionMessage": { - "type": "string", - "max": 2048, - "min": 0 - }, - "TagrisInternalId": { - "type": "string", - "max": 64, - "min": 0 - }, - "TagrisInternalServiceException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" } - }, - "exception": true, - "fault": true - }, - "TagrisInvalidArnException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" }, - "sweepListItem": { "shape": "TagrisSweepListItem" } - }, - "exception": true - }, - "TagrisInvalidParameterException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" } - }, - "exception": true - }, - "TagrisPartialResourcesExistResultsException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" }, - "resourceExistenceInformation": { "shape": "TagrisSweepListResult" } - }, - "exception": true - }, - "TagrisStatus": { - "type": "string", - "enum": ["ACTIVE", "NOT_ACTIVE"] - }, - "TagrisSweepList": { - "type": "list", - "member": { "shape": "TagrisSweepListItem" } - }, - "TagrisSweepListItem": { - "type": "structure", - "members": { - "TagrisAccountId": { "shape": "TagrisAccountId" }, - "TagrisAmazonResourceName": { "shape": "TagrisAmazonResourceName" }, - "TagrisInternalId": { "shape": "TagrisInternalId" }, - "TagrisVersion": { "shape": "TagrisVersion" } - } - }, - "TagrisSweepListResult": { - "type": "map", - "key": { "shape": "TagrisAmazonResourceName" }, - "value": { "shape": "TagrisStatus" } - }, - "TagrisThrottledException": { - "type": "structure", - "members": { - "message": { "shape": "TagrisExceptionMessage" } - }, - "exception": true - }, - "TagrisVerifyResourcesExistInput": { - "type": "structure", - "required": ["TagrisSweepList"], - "members": { - "TagrisSweepList": { "shape": "TagrisSweepList" } - } - }, - "TagrisVerifyResourcesExistOutput": { - "type": "structure", - "required": ["TagrisSweepListResult"], - "members": { - "TagrisSweepListResult": { "shape": "TagrisSweepListResult" } - } - }, - "TagrisVersion": { "type": "long" }, - "Tags": { - "type": "map", - "key": { "shape": "TagKey" }, - "value": { "shape": "TagValue" }, - "max": 50, - "min": 1 - }, - "ThrottlingException": { - "type": "structure", - "required": ["message"], - "members": { - "message": { "shape": "String" }, - "code": { "shape": "ErrorCode" } - }, - "error": { - "httpStatusCode": 429, - "senderFault": true - }, - "exception": true - }, - "UntagResourceRequest": { - "type": "structure", - "required": ["resourceArn", "tagKeys"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "resourceArn": { - "shape": "Arn", - "location": "uri", - "locationName": "resourceArn" - }, - "tagKeys": { - "shape": "TagKeyList", - "location": "querystring", - "locationName": "tagKeys" - } - } - }, - "UntagResourceResponse": { - "type": "structure", - "members": {} - }, - "UpdateConnectionRequest": { - "type": "structure", - "required": ["id", "authenticationType"], - "members": { - "sqlworkbenchSource": { - "shape": "SqlworkbenchSource", - "location": "header", - "locationName": "sqlworkbench-source" - }, - "id": { - "shape": "UpdateConnectionRequestIdString", - "documentation": "

Id of the connection to update

" - }, - "name": { - "shape": "UpdateConnectionRequestNameString", - "documentation": "

Name of the connection

" - }, - "databaseName": { - "shape": "UpdateConnectionRequestDatabaseNameString", - "documentation": "

Name of the database used for this connection

" - }, - "authenticationType": { - "shape": "UpdateConnectionRequestAuthenticationTypeEnum", - "documentation": "

Number representing the type of authentication to use (2 = IAM, 3 = Username and Password, 4 = Federated connection)

" - }, - "secretArn": { - "shape": "UpdateConnectionRequestSecretArnString", - "documentation": "

secretArn for redshift cluster

" - }, - "clusterId": { - "shape": "UpdateConnectionRequestClusterIdString", - "documentation": "

Id of the cluster used for this connection

" - }, - "isServerless": { - "shape": "Boolean", - "documentation": "

Is serverless connection

" - }, - "dbUser": { - "shape": "DbUser", - "documentation": "

User of the database used for this connection

" - }, - "username": { - "shape": "DbUser", - "documentation": "

Username used in the Username_Password connection type

" - }, - "password": { - "shape": "UpdateConnectionRequestPasswordString", - "documentation": "

Password of the user used for this connection

" - }, - "host": { - "shape": "String", - "documentation": "

Host address used for creating secret for Username_Password connection type

" - }, - "databaseType": { "shape": "DatabaseType" }, - "connectableResourceIdentifier": { - "shape": "UpdateConnectionRequestConnectableResourceIdentifierString", - "documentation": "

Id of the connectable resource used for this connection

" - }, - "connectableResourceType": { - "shape": "UpdateConnectionRequestConnectableResourceTypeString", - "documentation": "

Type of the connectable resource used for this connection

" - } - } - }, - "UpdateConnectionRequestAuthenticationTypeEnum": { - "type": "string", - "enum": ["2", "3", "4", "5", "6", "7", "8"], - "max": 1, - "min": 1, - "sensitive": true - }, - "UpdateConnectionRequestClusterIdString": { - "type": "string", - "max": 63, - "min": 1 - }, - "UpdateConnectionRequestConnectableResourceIdentifierString": { - "type": "string", - "max": 63, - "min": 1, - "sensitive": true - }, - "UpdateConnectionRequestConnectableResourceTypeString": { - "type": "string", - "max": 63, - "min": 1 - }, - "UpdateConnectionRequestDatabaseNameString": { - "type": "string", - "max": 64, - "min": 1, - "sensitive": true - }, - "UpdateConnectionRequestIdString": { - "type": "string", - "max": 2048, - "min": 32 - }, - "UpdateConnectionRequestNameString": { - "type": "string", - "max": 512, - "min": 1, - "sensitive": true - }, - "UpdateConnectionRequestPasswordString": { - "type": "string", - "max": 64, - "min": 8, - "sensitive": true - }, - "UpdateConnectionRequestSecretArnString": { - "type": "string", - "max": 1000, - "min": 1 - }, - "UpdateConnectionResponse": { - "type": "structure", - "members": { - "data": { "shape": "Connection" } - } - }, - "UserSettings": { - "type": "string", - "sensitive": true - }, - "ValidationException": { - "type": "structure", - "required": ["message"], - "members": { - "message": { "shape": "String" }, - "code": { "shape": "ErrorCode" } - }, - "error": { - "httpStatusCode": 400, - "senderFault": true - }, - "exception": true - }, - "statusCode": { - "type": "integer", - "box": true, - "max": 500, - "min": 100 - } - } -} diff --git a/packages/core/src/sagemakerunifiedstudio/shared/credentialExpiryHandler.ts b/packages/core/src/sagemakerunifiedstudio/shared/credentialExpiryHandler.ts deleted file mode 100644 index e516920797..0000000000 --- a/packages/core/src/sagemakerunifiedstudio/shared/credentialExpiryHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { isCredentialExpirationError } from './smusUtils' -import { SmusAuthenticationProvider } from '../auth/providers/smusAuthenticationProvider' - -/** - * - * If the provided error indicates expired credentials, it marks the connection as invalid. - * This refreshes the SmusAuthInfo node to reflect the updated authentication state. - * - * @param err The error - * @param showError If true, shows error message to user. If false, silently handles the error. - */ -export async function handleCredExpiredError(err: any, showError: boolean = false): Promise { - const errorMessage = (err as Error).message - if (isCredentialExpirationError(err)) { - if (showError) { - void vscode.window.showErrorMessage( - 'Connection to SageMaker Unified Studio has expired. Please try again after reauthentication.' - ) - } - const smusAuthProvider = SmusAuthenticationProvider.fromContext() - await smusAuthProvider.invalidateConnection() - smusAuthProvider.dispose() - } else { - if (showError) { - void vscode.window.showErrorMessage(errorMessage) - } - } -} diff --git a/packages/core/src/sagemakerunifiedstudio/shared/smusUtils.ts b/packages/core/src/sagemakerunifiedstudio/shared/smusUtils.ts deleted file mode 100644 index f59da3b76e..0000000000 --- a/packages/core/src/sagemakerunifiedstudio/shared/smusUtils.ts +++ /dev/null @@ -1,669 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger } from '../../shared/logger/logger' -import { ToolkitError } from '../../shared/errors' -import { isSageMaker } from '../../shared/extensionUtilities' -import { getResourceMetadata } from './utils/resourceMetadataUtils' -import fetch from 'node-fetch' -import { CredentialsProvider, CredentialsProviderType } from '../../auth/providers/credentials' -import { CredentialType } from '../../shared/telemetry/telemetry' -import { AwsCredentialIdentity } from '@aws-sdk/types' - -/** - * Represents SSO instance information retrieved from DataZone - */ -export interface SsoInstanceInfo { - issuerUrl: string - ssoInstanceId: string - clientId: string - region: string -} - -/** - * Response from DataZone /sso/login endpoint - */ -interface DataZoneSsoLoginResponse { - redirectUrl: string -} - -/** - * Credential expiry time constants for SMUS providers (in milliseconds) - */ -export const SmusCredentialExpiry = { - /** Domain Execution Role (DER) credentials expiry time: 10 minutes */ - derExpiryMs: 10 * 60 * 1000, - /** Project Role credentials expiry time: 10 minutes */ - projectExpiryMs: 10 * 60 * 1000, - /** Connection credentials expiry time: 10 minutes */ - connectionExpiryMs: 10 * 60 * 1000, -} as const - -/** - * Error codes for SMUS-related operations - */ -export const SmusErrorCodes = { - /** Error code for when no active SMUS connection is available */ - NoActiveConnection: 'NoActiveConnection', - /** Error code for when API calls timeout */ - ApiTimeout: 'ApiTimeout', - /** Error code for when SMUS login fails */ - SmusLoginFailed: 'SmusLoginFailed', - /** Error code for when redeeming access token fails */ - RedeemAccessTokenFailed: 'RedeemAccessTokenFailed', - /** Error code for when connection establish fails */ - FailedAuthConnecton: 'FailedAuthConnecton', - /** Error code for when user cancels an operation */ - UserCancelled: 'UserCancelled', - /** Error code for when domain account Id is missing */ - AccountIdNotFound: 'AccountIdNotFound', - /** Error code for when resource ARN is missing */ - ResourceArnNotFound: 'ResourceArnNotFound', - /** Error code for when fails to get domain account Id */ - GetDomainAccountIdFailed: 'GetDomainAccountIdFailed', - /** Error code for when fails to get project account Id */ - GetProjectAccountIdFailed: 'GetProjectAccountIdFailed', - /** Error code for when region is missing */ - RegionNotFound: 'RegionNotFound', - /** Error code for when IAM-based domain is not found in the specified region */ - IamDomainNotFound: 'IamDomainNotFound', - /** Error code for when IAM profile is not found */ - ProfileNotFound: 'ProfileNotFound', - /** Error code for when IAM credential retrieval fails */ - CredentialRetrievalFailed: 'CredentialRetrievalFailed', - /** Error code for when IAM credential provider initialization fails */ - CredentialProviderInitFailed: 'CredentialProviderInitFailed', - /** Error code for when IAM profile type is invalid */ - InvalidProfileType: 'InvalidProfileType', - /** Error code for when IAM credential validation fails */ - IamValidationFailed: 'IamValidationFailed', - /** Error code for when sign out operation fails */ - SignOutFailed: 'SignOutFailed', - /** Error code for when domain URL format is invalid */ - InvalidDomainUrl: 'InvalidDomainUrl', - /** Error code for when connection to SMUS fails */ - FailedToConnect: 'FailedToConnect', - /** Error code for when connection is not found */ - ConnectionNotFound: 'ConnectionNotFound', - /** Error code for when connection type is invalid for the operation */ - InvalidConnectionType: 'InvalidConnectionType', - /** Error code for when no group profile is found for IAM role */ - NoGroupProfileFound: 'NoGroupProfileFound', - /** Error code for when no user profile is found for IAM principal */ - NoUserProfileFound: 'NoUserProfileFound', -} as const - -/** - * Timeout constants for SMUS API calls (in milliseconds) - */ -export const SmusTimeouts = { - /** Default timeout for API calls: 10 seconds */ - apiCallTimeoutMs: 10 * 1000, -} as const - -/** - * DataZone service ID used for filtering regions - */ -export const DataZoneServiceId = 'datazone' - -/** - * Domain version constants - */ -export const DomainVersionV1 = 'V1' -export const DomainVersionV2 = 'V2' - -/** - * IAM sign-in type constants - */ -export const IamSignInRole = 'IAM_ROLE' -export const IamSignInUser = 'IAM_USER' - -/** - * Input interface for IAM domain check function - */ -export interface IamDomainCheckInput { - domainVersion: string | undefined - iamSignIns?: string[] | undefined - domainId?: string -} - -/** - * Interface for AWS credential objects that need validation - */ -interface CredentialObject { - accessKeyId?: unknown - secretAccessKey?: unknown - sessionToken?: unknown - expiration?: unknown -} - -/** - * Validates AWS credential fields and throws appropriate errors if invalid - * @param credentials The credential object to validate - * @param errorCode The error code to use in ToolkitError - * @param contextMessage The context message for error messages (e.g., "API response", "project credential response") - * @throws ToolkitError if any credential field is invalid - */ -export function validateCredentialFields( - credentials: CredentialObject, - errorCode: string, - contextMessage: string, - validateExpireTime: boolean = false -): void { - if (!credentials.accessKeyId || typeof credentials.accessKeyId !== 'string') { - throw new ToolkitError(`Invalid accessKeyId in ${contextMessage}: ${typeof credentials.accessKeyId}`, { - code: errorCode, - }) - } - if (!credentials.secretAccessKey || typeof credentials.secretAccessKey !== 'string') { - throw new ToolkitError(`Invalid secretAccessKey in ${contextMessage}: ${typeof credentials.secretAccessKey}`, { - code: errorCode, - }) - } - if (!credentials.sessionToken || typeof credentials.sessionToken !== 'string') { - throw new ToolkitError(`Invalid sessionToken in ${contextMessage}: ${typeof credentials.sessionToken}`, { - code: errorCode, - }) - } - if (validateExpireTime) { - if (!credentials.expiration || !(credentials.expiration instanceof Date)) { - throw new ToolkitError(`Invalid expireTime in ${contextMessage}: ${typeof credentials.expiration}`, { - code: errorCode, - }) - } - } -} - -/** - * Utility class for SageMaker Unified Studio domain URL parsing and validation - */ -export class SmusUtils { - private static readonly logger = getLogger('smus') - - /** - * Extracts the domain ID from a SageMaker Unified Studio domain URL - * @param domainUrl The SageMaker Unified Studio domain URL - * @returns The extracted domain ID or undefined if not found - */ - public static extractDomainIdFromUrl(domainUrl: string): string | undefined { - try { - // Domain URL format: https://dzd_d3hr1nfjbtwui1.sagemaker.us-east-2.on.aws - const url = new URL(domainUrl) - const hostname = url.hostname - - // Extract domain ID from hostname (dzd_d3hr1nfjbtwui1 or dzd-d3hr1nfjbtwui1) - const domainIdMatch = hostname.match(/^(dzd[-_][a-zA-Z0-9_-]{1,36})\./) - return domainIdMatch?.[1] - } catch (error) { - this.logger.error('Failed to extract domain ID from URL: %s', error as Error) - return undefined - } - } - - /** - * Extracts the AWS region from a SageMaker Unified Studio domain URL - * @param domainUrl The SageMaker Unified Studio domain URL - * @param fallbackRegion Fallback region if extraction fails (default: 'us-east-1') - * @returns The extracted AWS region or the fallback region if not found - */ - public static extractRegionFromUrl(domainUrl: string, fallbackRegion: string = 'us-east-1'): string { - try { - // Domain URL formats: - // - https://dzd_d3hr1nfjbtwui1.sagemaker.us-east-2.on.aws - // - https://dzd_4gickdfsxtoxg0.sagemaker-gamma.us-west-2.on.aws - const url = new URL(domainUrl) - const hostname = url.hostname - - // Extract region from hostname, handling both prod and non-prod stages - // Pattern matches: .sagemaker[-stage].{region}.on.aws - const regionMatch = hostname.match(/\.sagemaker(?:-[a-z]+)?\.([a-z0-9-]+)\.on\.aws$/) - return regionMatch?.[1] || fallbackRegion - } catch (error) { - this.logger.error('Failed to extract region from URL: %s', error as Error) - return fallbackRegion - } - } - - /** - * Extracts both domain ID and region from a SageMaker Unified Studio domain URL - * @param domainUrl The SageMaker Unified Studio domain URL - * @param fallbackRegion Fallback region if extraction fails (default: 'us-east-1') - * @returns Object containing domainId and region - */ - public static extractDomainInfoFromUrl( - domainUrl: string, - fallbackRegion: string = 'us-east-1' - ): { domainId: string | undefined; region: string } { - return { - domainId: this.extractDomainIdFromUrl(domainUrl), - region: this.extractRegionFromUrl(domainUrl, fallbackRegion), - } - } - - /** - * Validates the domain URL format for SageMaker Unified Studio - * @param value The URL to validate - * @returns Error message if invalid, undefined if valid - */ - public static validateDomainUrl(value: string): string | undefined { - if (!value || value.trim() === '') { - return 'Domain URL is required' - } - - const trimmedValue = value.trim() - - // Check HTTPS requirement - if (!trimmedValue.startsWith('https://')) { - return 'Domain URL must use HTTPS (https://)' - } - - // Check basic URL format - try { - const url = new URL(trimmedValue) - - // Check if it looks like a SageMaker Unified Studio domain - if (!url.hostname.includes('sagemaker') || !url.hostname.includes('on.aws')) { - return 'URL must be a valid SageMaker Unified Studio domain (e.g., https://dzd_xxxxxxxxx.sagemaker.us-east-1.on.aws)' - } - - // Extract domain ID to validate - const domainId = this.extractDomainIdFromUrl(trimmedValue) - - if (!domainId) { - return 'URL must contain a valid domain ID (starting with dzd- or dzd_)' - } - - return undefined // Valid - } catch (err) { - return 'Invalid URL format' - } - } - - /** - * Makes HTTP call to DataZone /sso/login endpoint - * @param domainUrl The SageMaker Unified Studio domain URL - * @param domainId The extracted domain ID - * @returns Promise resolving to the login response - * @throws ToolkitError if the API call fails - */ - private static async callDataZoneLogin(domainUrl: string, domainId: string): Promise { - const loginUrl = new URL('/sso/login', domainUrl) - const requestBody = { - domainId: domainId, - } - - try { - const response = await fetch(loginUrl.toString(), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - Accept: 'application/json', - 'User-Agent': 'aws-toolkit-vscode', - }, - body: JSON.stringify(requestBody), - timeout: SmusTimeouts.apiCallTimeoutMs, - }) - - if (!response.ok) { - throw new ToolkitError(`SMUS login failed: ${response.status} ${response.statusText}`, { - code: SmusErrorCodes.SmusLoginFailed, - }) - } - - return (await response.json()) as DataZoneSsoLoginResponse - } catch (error) { - // Handle timeout errors specifically - if (error instanceof Error && (error.name === 'AbortError' || error.message.includes('timeout'))) { - throw new ToolkitError( - `DataZone login request timed out after ${SmusTimeouts.apiCallTimeoutMs / 1000} seconds`, - { - code: SmusErrorCodes.ApiTimeout, - cause: error, - } - ) - } - // Re-throw other errors as-is - throw error - } - } - - /** - * Gets SSO instance information by calling DataZone /sso/login endpoint - * This extracts the proper SSO instance ID and issuer URL needed for OAuth client registration - * - * @param domainUrl The SageMaker Unified Studio domain URL - * @returns Promise resolving to SSO instance information - * @throws ToolkitError if the API call fails or response is invalid - */ - public static async getSsoInstanceInfo(domainUrl: string): Promise { - try { - this.logger.info(`Getting SSO instance info from DataZone for domainurl: ${domainUrl}`) - - // Extract domain ID from the domain URL - const domainId = this.extractDomainIdFromUrl(domainUrl) - if (!domainId) { - throw new ToolkitError('Invalid domain URL format', { code: 'InvalidDomainUrl' }) - } - - // Call DataZone /sso/login endpoint to get redirect URL with SSO instance info - const loginData = await this.callDataZoneLogin(domainUrl, domainId) - if (!loginData.redirectUrl) { - throw new ToolkitError('No redirect URL received from DataZone login', { code: 'InvalidLoginResponse' }) - } - - // Parse the redirect URL to extract SSO instance information - const redirectUrl = new URL(loginData.redirectUrl) - const clientIdParam = redirectUrl.searchParams.get('client_id') - if (!clientIdParam) { - throw new ToolkitError('No client_id found in DataZone redirect URL', { code: 'InvalidRedirectUrl' }) - } - - // Decode the client_id ARN: arn:aws:sso::785498918019:application/ssoins-6684636af7e1a207/apl-5f60548b7f5677a2 - const decodedClientId = decodeURIComponent(clientIdParam) - const arnParts = decodedClientId.split('/') - if (arnParts.length < 2) { - throw new ToolkitError('Invalid client_id ARN format', { code: 'InvalidArnFormat' }) - } - - const ssoInstanceId = arnParts[1] // Extract ssoins-6684636af7e1a207 - const issuerUrl = `https://identitycenter.amazonaws.com/${ssoInstanceId}` - - // Extract region from domain URL - const region = this.extractRegionFromUrl(domainUrl) - - this.logger.info('Extracted SSO instance info: %s', ssoInstanceId) - - return { - issuerUrl, - ssoInstanceId, - clientId: decodedClientId, - region, - } - } catch (error) { - const errorMsg = error instanceof Error ? error.message : 'Unknown error' - this.logger.error('Failed to get SSO instance info: %s', errorMsg) - - if (error instanceof ToolkitError) { - throw error - } - - throw new ToolkitError(`Failed to get SSO instance info: ${errorMsg}`, { - code: 'SsoInstanceInfoFailed', - cause: error instanceof Error ? error : undefined, - }) - } - } - /** - * Extracts SSO ID from a user ID in the format "user-" - * @param userId The user ID to extract SSO ID from - * @returns The extracted SSO ID - * @throws Error if the userId format is invalid - */ - public static extractSSOIdFromUserId(userId: string): string { - const match = userId.match(/user-(.+)$/) - if (!match) { - this.logger.error(`Invalid UserId format: ${userId}`) - throw new Error(`Invalid UserId format: ${userId}`) - } - return match[1] - } - - /** - * Checks if we're in SMUS space environment (should hide certain UI elements) - * @returns True if in SMUS space environment with DataZone domain ID - */ - public static isInSmusSpaceEnvironment(): boolean { - const isSMUSspace = isSageMaker('SMUS') || isSageMaker('SMUS-SPACE-REMOTE-ACCESS') - const resourceMetadata = getResourceMetadata() - return isSMUSspace && !!resourceMetadata?.AdditionalMetadata?.DataZoneDomainId - } - - /** - * Extracts the session name from an assumed role ARN. - * - * Note: This function ONLY works for assumed role ARNs (arn:aws:sts::*:assumed-role/*). - * It will return undefined for other IAM principal types such as: - * - IAM users (arn:aws:iam::*:user/*) - * - IAM roles (arn:aws:iam::*:role/*) - * - * @param arn The assumed role ARN (format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME) - * @returns The session name if the ARN is a valid assumed role ARN, undefined otherwise - */ - public static extractSessionNameFromArn(arn: string): string | undefined { - try { - // Expected format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME - const parts = arn.split(':') - if (parts.length < 6) { - return undefined - } - - // The resource part is after the 5th colon - const resourcePart = parts.slice(5).join(':') - - // Split by '/' to get assumed-role, ROLE_NAME, and SESSION_NAME - const resourceParts = resourcePart.split('/') - if (resourceParts.length < 3 || resourceParts[0] !== 'assumed-role') { - return undefined - } - - // Session name is the last part - return resourceParts[2] - } catch (err) { - return undefined - } - } - - /** - * Determines if an ARN represents an IAM user (vs IAM role session) - * @param arn The ARN to check (format: arn:aws:iam::ACCOUNT:user/USER_NAME for IAM users, - * arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME for role sessions) - * @returns True if the ARN is an IAM user, false otherwise - */ - public static isIamUserArn(arn: string | undefined): boolean { - if (!arn) { - return false - } - - // IAM user ARN format: arn:aws:iam::ACCOUNT:user/USER_NAME - // IAM role session ARN format: arn:aws:sts::ACCOUNT:assumed-role/ROLE_NAME/SESSION_NAME - return arn.includes(':iam::') && arn.includes(':user/') - } - - /** - * Converts an STS assumed-role ARN to its corresponding IAM role ARN, or returns IAM user ARN as-is. - * Supports all AWS partitions (aws, aws-cn, aws-us-gov, etc.) - * Examples: - * Input: arn:aws:sts::123456789012:assumed-role/MyRole/MySession - * Output: arn:aws:iam::123456789012:role/MyRole - * - * Input: arn:aws:iam::123456789012:user/MyUser - * Output: arn:aws:iam::123456789012:user/MyUser - * - * Input: arn:aws-cn:sts::123456789012:assumed-role/MyRole/MySession - * Output: arn:aws-cn:iam::123456789012:role/MyRole - */ - public static convertAssumedRoleArnToIamRoleArn(stsArn: string): string { - // Check if it's already an IAM user ARN - return as-is - // Supports all AWS partitions: aws, aws-cn, aws-us-gov, etc. - const iamUserRegex = /^arn:(aws[a-z-]*):iam::(\d{12}):user\/([A-Za-z0-9+=,.@_\/-]+)$/ - if (iamUserRegex.test(stsArn)) { - return stsArn - } - - // Check if it's already an IAM role ARN - return as-is - const iamRoleRegex = /^arn:(aws[a-z-]*):iam::(\d{12}):role\/([A-Za-z0-9+=,.@_\/-]+)$/ - if (iamRoleRegex.test(stsArn)) { - return stsArn - } - - // Try to convert STS assumed-role ARN to IAM role ARN - const arnRegex = /^arn:(aws[a-z-]*):sts::(\d{12}):assumed-role\/([A-Za-z0-9+=,.@_\/-]+)\/([A-Za-z0-9+=,.@_-]+)$/ - const match = stsArn.match(arnRegex) - if (!match) { - throw new Error(`Invalid STS ARN format: ${stsArn}`) - } - - const [, partition, accountId, roleName] = match - - return `arn:${partition}:iam::${accountId}:role/${roleName}` - } -} - -/** - * Determines if a domain is an IAM domain based on IamSignIns field. - * - * IAM domains are V2 domains that support both IAM role and IAM user authentication. - * A domain is considered an IAM domain if its IamSignIns array contains both: - * - IAM_ROLE - * - IAM_USER - * - * @param input - Object containing domain version, IamSignIns, and optional domainId for logging - * @returns true if the domain is an IAM domain, false otherwise - */ -export function isIamDomain(input: IamDomainCheckInput): boolean { - const logger = getLogger('smus') - const domainIdLog = input.domainId ? ` for domain ${input.domainId}` : '' - - // Only V2 domains can be IAM domains - if (input.domainVersion !== DomainVersionV2) { - logger.debug( - `IAM domain check${domainIdLog}: Domain version is not V2 (value: ${input.domainVersion}), returning false` - ) - return false - } - - // Check if IamSignIns contains both IAM_ROLE and IAM_USER - if (!input.iamSignIns || !Array.isArray(input.iamSignIns)) { - logger.debug(`IAM domain check${domainIdLog}: IamSignIns is missing or invalid, returning false`) - return false - } - - const hasIamRole = input.iamSignIns.includes(IamSignInRole) - const hasIamUser = input.iamSignIns.includes(IamSignInUser) - - if (hasIamRole && hasIamUser) { - logger.debug(`IAM domain check${domainIdLog}: IAM domain detected via IamSignIns`) - return true - } - - logger.debug( - `IAM domain check${domainIdLog}: IamSignIns does not contain both IAM_ROLE and IAM_USER, returning false` - ) - return false -} - -/** - * Extracts the account ID from a SageMaker ARN. - * Supports formats like: - * arn:aws:sagemaker:::app/* - * - * @param arn - The full SageMaker ARN string - * @returns The account ID from the ARN - * @throws If the ARN format is invalid - */ -export function extractAccountIdFromSageMakerArn(arn: string): string { - // Match the ARN components to extract account ID - const regex = /^arn:aws:sagemaker:(?[^:]+):(?\d+):(app|space)\/.+$/i - const match = arn.match(regex) - - if (!match?.groups) { - throw new ToolkitError(`Invalid SageMaker ARN format: "${arn}"`) - } - - return match.groups.accountId -} - -/** - * Extracts account ID from ResourceArn in SMUS space environment - * @returns Promise resolving to the account ID - * @throws ToolkitError if unable to extract account ID - */ -export async function extractAccountIdFromResourceMetadata(): Promise { - const logger = getLogger('smus') - - try { - logger.debug('Extracting account ID from ResourceArn in resource-metadata file') - - const resourceMetadata = getResourceMetadata()! - const resourceArn = resourceMetadata.ResourceArn - - if (!resourceArn) { - throw new Error('ResourceArn not found in metadata file') - } - - const accountId = extractAccountIdFromSageMakerArn(resourceArn) - logger.debug(`Successfully extracted account ID from resource-metadata file: ${accountId}`) - - return accountId - } catch (err) { - logger.error(`Failed to extract account ID from ResourceArn: %s`, err) - throw new Error('Failed to extract AWS account ID from ResourceArn in SMUS space environment') - } -} - -/** - * Creates a CredentialsProvider from an AWS credentials function - * @param credentialsFunction Function that returns AWS credentials - * @param credentialTypeId Identifier for the credential type - * @param hashCode Unique hash code for caching - * @param region Domain region - * @returns Complete CredentialsProvider object - */ -export function convertToToolkitCredentialProvider( - credentialsFunction: () => Promise, - credentialTypeId: string, - hashCode: string, - region: string -): CredentialsProvider { - return { - getCredentials: credentialsFunction, - getCredentialsId: () => ({ credentialSource: 'temp' as const, credentialTypeId }), - getProviderType: () => 'temp' as CredentialsProviderType, - getTelemetryType: () => 'other' as CredentialType, - getDefaultRegion: () => region, - getHashCode: () => hashCode, - canAutoConnect: () => Promise.resolve(false), - isAvailable: () => Promise.resolve(true), - } -} - -/** - * Checks if an error indicates credential/token expiration - * - * @param error The error to check (can be any type) - * @returns true if the error indicates expired credentials, false otherwise - * - */ -export function isCredentialExpirationError(error: any): boolean { - if (!error) { - return false - } - - const errorName = (error.name || '') as string - const errorMessage = (error.message || '') as string - const errorNameLower = errorName.toLowerCase() - const errorMessageLower = errorMessage.toLowerCase() - - const expirationErrorNames = ['ExpiredTokenException'] - - const expirationErrorMessages = ['The security token included in the request is expired'] - - // Return true if error name matches any expiration error names (case-insensitive) - if (expirationErrorNames.some((name) => name.toLowerCase() === errorNameLower)) { - return true - } - - // Return true if error message contains any expiration error names (case-insensitive) - if (expirationErrorNames.some((errorName) => errorMessageLower.includes(errorName.toLowerCase()))) { - return true - } - - // Return true if error message contains any expiration error messages - if (expirationErrorMessages.some((keyword) => errorMessageLower.includes(keyword.toLowerCase()))) { - return true - } - - return false -} diff --git a/packages/core/src/sagemakerunifiedstudio/shared/telemetry.ts b/packages/core/src/sagemakerunifiedstudio/shared/telemetry.ts deleted file mode 100644 index ca86c0f53f..0000000000 --- a/packages/core/src/sagemakerunifiedstudio/shared/telemetry.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - SmusLogin, - SmusOpenRemoteConnection, - SmusRenderLakehouseNode, - SmusRenderS3Node, - SmusSignOut, - SmusStopSpace, - Span, -} from '../../shared/telemetry/telemetry' -import { SagemakerUnifiedStudioSpaceNode } from '../explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' -import { SmusAuthenticationProvider } from '../auth/providers/smusAuthenticationProvider' -import { getLogger } from '../../shared/logger/logger' -import { getContext } from '../../shared/vscode/setContext' -import { ConnectionCredentialsProvider } from '../auth/providers/connectionCredentialsProvider' -import { DataZoneConnection } from './client/datazoneClient' -import { createDZClientBaseOnDomainMode } from '../explorer/nodes/utils' - -const notSet = 'not-set' -/** - * Records space telemetry - */ -export async function recordSpaceTelemetry( - span: Span | Span, - node: SagemakerUnifiedStudioSpaceNode -) { - const logger = getLogger('smus') - - const parent = node.resource.getParent() as SageMakerUnifiedStudioSpacesParentNode - const authProvider = SmusAuthenticationProvider.fromContext() - const projectId = parent?.getProjectId() - const domainId = parent?.getAuthProvider()?.getDomainId() - - span.record({ - smusAuthMode: authProvider.activeConnection?.type, - smusSpaceKey: node.resource.DomainSpaceKey, - smusDomainRegion: node.resource.regionCode, - smusDomainId: domainId, - smusProjectId: projectId, - }) - - try { - const accountId = await authProvider.getDomainAccountId() - span.record({ smusDomainAccountId: accountId }) - } catch (err) { - span.record({ smusDomainAccountId: notSet }) - logger.warn(`Failed to record domain account Id for telemetry in domain ${domainId}: ${(err as Error).message}`) - } - - if (projectId) { - try { - const projectAccountId = await authProvider.getProjectAccountId(projectId) - span.record({ smusProjectAccountId: projectAccountId }) - } catch (err) { - span.record({ smusProjectAccountId: notSet }) - logger.warn( - `Failed to record project account Id for telemetry in domain ${domainId}: ${(err as Error).message}` - ) - } - - try { - const dzClient = await createDZClientBaseOnDomainMode(authProvider) - const toolingEnv = await dzClient.getToolingEnvironment(projectId) - span.record({ smusProjectRegion: toolingEnv.awsAccountRegion }) - } catch (err) { - span.record({ smusProjectRegion: notSet }) - logger.warn(`Failed to get project region for telemetry: ${(err as Error).message}`) - } - } -} - -/** - * Records auth telemetry - */ -export async function recordAuthTelemetry( - span: Span | Span, - authProvider: SmusAuthenticationProvider, - domainId: string | undefined, - region: string | undefined -) { - const logger = getLogger('smus') - - span.record({ - smusAuthMode: authProvider.activeConnection?.type, - smusDomainId: domainId, - awsRegion: region, - }) - - try { - if (!region) { - throw new Error(`Region is undefined for domain ${domainId}`) - } - const accountId = await authProvider.getDomainAccountId() - span.record({ - smusDomainAccountId: accountId, - }) - } catch (err) { - span.record({ smusDomainAccountId: notSet }) - logger.error( - `Failed to record Domain AccountId in data connection telemetry for domain ${domainId} in region ${region}: ${err}` - ) - } -} - -/** - * Records data connection telemetry for SMUS nodes - */ -export async function recordDataConnectionTelemetry( - span: Span | Span, - connection: DataZoneConnection, - connectionCredentialsProvider: ConnectionCredentialsProvider -) { - const logger = getLogger('smus') - - const isInSmusSpace = getContext('aws.smus.inSmusSpaceEnvironment') - const authProvider = SmusAuthenticationProvider.fromContext() - - span.record({ - smusAuthMode: authProvider.activeConnection?.type, - smusToolkitEnv: isInSmusSpace ? 'smus_space' : 'local', - smusDomainId: connection.domainId, - smusProjectId: connection.projectId, - smusConnectionId: connection.connectionId, - smusConnectionType: connection.type, - smusProjectRegion: connection.location?.awsRegion, - smusProjectAccountId: connection.location?.awsAccountId, - }) - - try { - const accountId = await connectionCredentialsProvider.getDomainAccountId() - span.record({ smusDomainAccountId: accountId }) - } catch (err) { - span.record({ smusDomainAccountId: notSet }) - logger.warn(`Failed to record domain account ID for data connection telemetry: ${(err as Error).message}`) - } -} diff --git a/packages/core/src/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.ts b/packages/core/src/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.ts deleted file mode 100644 index 29c372b796..0000000000 --- a/packages/core/src/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fs } from '../../../shared/fs/fs' -import { getLogger } from '../../../shared/logger/logger' -import { isSageMaker } from '../../../shared/extensionUtilities' - -/** - * Resource metadata schema used by `resource-metadata.json` in SageMaker Unified Studio spaces - */ -export type ResourceMetadata = { - AppType?: string - DomainId?: string - SpaceName?: string - UserProfileName?: string - ExecutionRoleArn?: string - ResourceArn?: string - ResourceName?: string - AppImageVersion?: string - AdditionalMetadata?: { - DataZoneDomainId?: string - DataZoneDomainRegion?: string - DataZoneEndpoint?: string - DataZoneEnvironmentId?: string - DataZoneProjectId?: string - DataZoneScopeName?: string - DataZoneStage?: string - DataZoneUserId?: string - PrivateSubnets?: string - ProjectS3Path?: string - SecurityGroup?: string - } - ResourceArnCaseSensitive?: string - IpAddressType?: string -} & Record - -const resourceMetadataPath = '/opt/ml/metadata/resource-metadata.json' -let resourceMetadata: ResourceMetadata | undefined = undefined - -/** - * Gets the cached resource metadata (must be initialized with `initializeResourceMetadata()` first) - * @returns ResourceMetadata object or undefined if not yet initialized - */ -export function getResourceMetadata(): ResourceMetadata | undefined { - return resourceMetadata -} - -/** - * Initializes resource metadata by reading and parsing the resource-metadata.json file - */ -export async function initializeResourceMetadata(): Promise { - const logger = getLogger('smus') - - if (!isSageMaker('SMUS') && !isSageMaker('SMUS-SPACE-REMOTE-ACCESS')) { - logger.debug(`Not in SageMaker Unified Studio space, skipping initialization of resource metadata`) - return - } - - try { - if (!(await resourceMetadataFileExists())) { - logger.debug(`Resource metadata file not found at: ${resourceMetadataPath}`) - } - - const fileContent = await fs.readFileText(resourceMetadataPath) - resourceMetadata = JSON.parse(fileContent) as ResourceMetadata - logger.debug(`Successfully read resource metadata from: ${resourceMetadataPath}`) - } catch (error) { - logger.error(`Failed to read or parse resource metadata file: ${error as Error}`) - } -} - -/** - * Checks if the resource-metadata.json file exists - * @returns True if the file exists, false otherwise - */ -export async function resourceMetadataFileExists(): Promise { - try { - return await fs.existsFile(resourceMetadataPath) - } catch (error) { - const logger = getLogger('smus') - logger.error(`Failed to check if resource metadata file exists: ${error as Error}`) - return false - } -} - -/** - * Resets the cached resource metadata - */ -export function resetResourceMetadata(): void { - resourceMetadata = undefined -} diff --git a/packages/core/src/sagemakerunifiedstudio/uriHandlers.ts b/packages/core/src/sagemakerunifiedstudio/uriHandlers.ts deleted file mode 100644 index ec313d5015..0000000000 --- a/packages/core/src/sagemakerunifiedstudio/uriHandlers.ts +++ /dev/null @@ -1,132 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { SearchParams } from '../shared/vscode/uriHandler' -import { ExtContext } from '../shared/extensions' -import { deeplinkConnect } from '../awsService/sagemaker/commands' -import { telemetry } from '../shared/telemetry/telemetry' -import { SmusAuthMode } from '../shared/telemetry/telemetry.gen' -/** - * Registers the SMUS deeplink URI handler at path `/connect/smus`. - * - * This handler processes deeplink URLs from the SageMaker Unified Studio console - * to establish remote connections to SMUS spaces. - * - * @param ctx Extension context containing the URI handler - * @returns Disposable for cleanup - */ -export function register(ctx: ExtContext) { - async function connectHandler(params: ReturnType) { - await telemetry.smus_deeplinkConnect.run(async (span) => { - span.record(extractTelemetryMetadata(params)) - - // WORKAROUND: The ws_url from the startSession API call contains a query parameter - // 'cell-number' within itself. When the entire deeplink URL is processed by the URI - // handler, 'cell-number' is parsed as a standalone query parameter at the top level - // instead of remaining part of the ws_url. This causes the ws_url to lose the - // cell-number context it needs. To fix this, we manually re-append the cell-number - // query parameter back to the ws_url to restore the original intended URL structure. - await deeplinkConnect( - ctx, - params.connection_identifier, - params.session, - `${params.ws_url}&cell-number=${params['cell-number']}`, // Re-append cell-number to ws_url - params.token, - params.domain, - params.app_type, - undefined, - undefined, - undefined, - true // isSMUS=true for SMUS connections - ) - }) - } - - return vscode.Disposable.from(ctx.uriHandler.onPath('/connect/smus', connectHandler, parseConnectParams)) -} - -/** - * Parses and validates SMUS deeplink URI parameters. - * - * Required parameters: - * - connection_identifier: Space ARN identifying the SMUS space - * - domain: Domain ID for the SMUS space (SM AI side) - * - user_profile: User profile name - * - session: SSM session ID - * - ws_url: WebSocket URL for SSM connection (originally contains cell-number as a query param) - * - cell-number: extracted from ws_url during URI parsing - * - token: Authentication token - * - * Optional parameters: - * - app_type: Application type (e.g., JupyterLab, CodeEditor) - * - smus_domain_id: SMUS domain identifier - * - smus_domain_account_id: SMUS domain account ID - * - smus_project_id: SMUS project identifier - * - smus_domain_region: SMUS domain region - * - smus_auth_mode: Authentication mode (sso or iam) - * - * Note: The ws_url from startSession API originally includes cell-number as a query parameter. - * However, when the deeplink URL is processed, the URI handler extracts cell-number as a - * separate top-level parameter. This is why we need to re-append it in the connectHandler. - * - * @param query URI query parameters - * @returns Parsed parameters object - * @throws Error if required parameters are missing - */ -export function parseConnectParams(query: SearchParams) { - const requiredParams = query.getFromKeysOrThrow( - 'connection_identifier', - 'domain', - 'user_profile', - 'session', - 'ws_url', - 'cell-number', - 'token' - ) - const optionalParams = query.getFromKeys( - 'app_type', - 'smus_domain_id', - 'smus_domain_account_id', - 'smus_project_id', - 'smus_domain_region', - 'smus_auth_mode' - ) - - return { ...requiredParams, ...optionalParams } -} - -/** - * Extracts telemetry metadata from URI parameters and space ARN. - * - * @param params Parsed URI parameters - * @returns Telemetry metadata object - */ -function extractTelemetryMetadata(params: ReturnType) { - // Extract metadata from space ARN - // ARN format: arn:aws:sagemaker:region:account-id:space/domain-id/space-name - const arnParts = params.connection_identifier.split(':') - const resourceParts = arnParts[5]?.split('/') // Gets "space/domain-id/space-name" - - const projectRegion = arnParts[3] // region from ARN - const projectAccountId = arnParts[4] // account-id from ARN - const domainIdFromArn = resourceParts?.[1] // domain-id from ARN - const spaceName = resourceParts?.[2] // space-name from ARN - - // Validate and cast smusAuthMode to the expected type - const authMode = params.smus_auth_mode - const smusAuthMode: SmusAuthMode | undefined = authMode === 'sso' || authMode === 'iam' ? authMode : undefined - - return { - smusDomainId: params.smus_domain_id, - smusDomainAccountId: params.smus_domain_account_id, - smusProjectId: params.smus_project_id, - smusDomainRegion: params.smus_domain_region, - smusProjectRegion: projectRegion, - smusProjectAccountId: projectAccountId, - smusSpaceKey: domainIdFromArn && spaceName ? `${domainIdFromArn}/${spaceName}` : undefined, - smusAuthMode: smusAuthMode, - } -} diff --git a/packages/core/src/shared/awsConsole.ts b/packages/core/src/shared/awsConsole.ts deleted file mode 100644 index db12e66727..0000000000 --- a/packages/core/src/shared/awsConsole.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSResourceNode } from './treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from './treeview/nodes/awsTreeNodeBase' -import { StackNameNode } from '../awsService/appBuilder/explorer/nodes/deployedStack' -import { openUrl } from './utilities/vsCodeUtils' - -export function getAwsConsoleUrl( - service: 'ecr' | 'cloudformation' | 'ec2-launch' | 'docdb', - region: string -): vscode.Uri { - switch (service) { - case 'ecr': - return vscode.Uri.parse(`https://${region}.console.aws.amazon.com/ecr/repositories?region=${region}`) - case 'cloudformation': - return vscode.Uri.parse(`https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}`) - case 'ec2-launch': - return vscode.Uri.parse( - `https://${region}.console.aws.amazon.com/ec2/home?region=${region}#LaunchInstances:` - ) - case 'docdb': - return vscode.Uri.parse(`https://${region}.console.aws.amazon.com/docdb/home?region=${region}`) - default: - throw Error() - } -} - -export async function openAwsConsoleCommand(node: AWSResourceNode) { - // All AWSResourceNodes that this function will receive are also AWSTreeNodeBase, - // so we should be able to get the region from them. - const regionCode = (node as unknown as AWSTreeNodeBase).regionCode - const arn = node.arn - await openUrlInConsole(regionCode, arn) -} - -export async function openAwsCFNConsoleCommand(node: StackNameNode) { - const regionCode = node.resource.regionCode - const arn = node.resource.arn - await openUrlInConsole(regionCode, arn) -} - -async function openUrlInConsole(regionCode: string | undefined, arn: string | undefined) { - const regionQuery = regionCode ? `region=${regionCode}&` : '' - const url = `https://console.aws.amazon.com/go/view?${regionQuery}arn=${arn}&source=aws-toolkit-vscode` - await openUrl(vscode.Uri.parse(url), 'AppBuilderOpenConsole') -} diff --git a/packages/core/src/shared/clients/apiGateway.ts b/packages/core/src/shared/clients/apiGateway.ts deleted file mode 100644 index eeae714471..0000000000 --- a/packages/core/src/shared/clients/apiGateway.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { ClientWrapper } from './clientWrapper' -import { - APIGatewayClient as ApiGatewayClientSDK, - GetResourcesCommand, - GetResourcesRequest, - GetRestApisCommand, - GetRestApisRequest, - GetStagesCommand, - Resource, - Resources, - RestApi, - RestApis, - Stages, - TestInvokeMethodCommand, - TestInvokeMethodRequest, - TestInvokeMethodResponse, -} from '@aws-sdk/client-api-gateway' - -export class ApiGatewayClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, ApiGatewayClientSDK) - } - - public async *getResourcesForApi(apiId: string): AsyncIterableIterator { - const request: GetResourcesRequest = { - restApiId: apiId, - } - - do { - const response: Resources = await this.makeRequest(GetResourcesCommand, request) - - if (response.items !== undefined && response.items.length > 0) { - yield* response.items - } - - request.position = response.position - } while (request.position !== undefined) - } - - public async getStages(apiId: string): Promise { - return this.makeRequest(GetStagesCommand, { - restApiId: apiId, - }) - } - - public async *listApis(): AsyncIterableIterator { - const request: GetRestApisRequest = {} - - do { - const response: RestApis = await this.makeRequest(GetRestApisCommand, request) - - if (response.items !== undefined && response.items.length > 0) { - yield* response.items - } - - request.position = response.position - } while (request.position !== undefined) - } - - public async testInvokeMethod( - apiId: string, - resourceId: string, - method: string, - body: string, - pathWithQueryString: string | undefined - ): Promise { - const request: TestInvokeMethodRequest = { - restApiId: apiId, - resourceId: resourceId, - httpMethod: method, - body: body, - } - if (pathWithQueryString) { - request.pathWithQueryString = pathWithQueryString - } - - return this.makeRequest(TestInvokeMethodCommand, request) - } -} diff --git a/packages/core/src/shared/clients/apprunner.ts b/packages/core/src/shared/clients/apprunner.ts deleted file mode 100644 index a2179167c2..0000000000 --- a/packages/core/src/shared/clients/apprunner.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as AppRunner from '@aws-sdk/client-apprunner' -import { ClientWrapper } from './clientWrapper' -import { hasProps, RequiredProps } from '../utilities/tsUtils' -import { AsyncCollection } from '../utilities/asyncCollection' - -export type ServiceSummary = RequiredProps< - AppRunner.ServiceSummary, - 'ServiceName' | 'ServiceArn' | 'Status' | 'ServiceId' -> -export type ImageRepository = RequiredProps - -export type CodeConfigurationValues = RequiredProps -interface CodeConfiguration extends RequiredProps { - CodeConfigurationValues: CodeConfigurationValues -} -export interface CodeRepository extends RequiredProps { - SourceCodeVersion: RequiredProps - CodeConfiguration: CodeConfiguration -} -export interface SourceConfiguration extends AppRunner.SourceConfiguration { - CodeRepository?: CodeRepository - ImageRepository?: RequiredProps -} -export interface CreateServiceRequest extends RequiredProps { - SourceConfiguration: SourceConfiguration -} - -// Note: Many of the requests return a type of Service, but Service <: ServiceSummary. -type WithServiceSummary = Omit & { Service: ServiceSummary } -export class AppRunnerClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, AppRunner.AppRunnerClient) - } - - public async createService( - request: CreateServiceRequest - ): Promise> { - return await this.makeRequest(AppRunner.CreateServiceCommand, request) - } - - public async listServices(request: AppRunner.ListServicesRequest): Promise { - return await this.makeRequest(AppRunner.ListServicesCommand, request) - } - - public paginateServices(request: AppRunner.ListServicesRequest): AsyncCollection { - return this.makePaginatedRequest( - AppRunner.paginateListServices, - request, - (page) => page.ServiceSummaryList - ).map((summaries) => summaries.filter(isServiceSummary)) - - function isServiceSummary(s: AppRunner.ServiceSummary): s is ServiceSummary { - return hasProps(s, 'ServiceName', 'ServiceArn', 'Status', 'ServiceId') - } - } - - public async pauseService( - request: AppRunner.PauseServiceRequest - ): Promise> { - return await this.makeRequest(AppRunner.PauseServiceCommand, request) - } - - public async resumeService( - request: AppRunner.ResumeServiceRequest - ): Promise> { - return await this.makeRequest(AppRunner.ResumeServiceCommand, request) - } - - public async updateService( - request: AppRunner.UpdateServiceRequest - ): Promise> { - return await this.makeRequest(AppRunner.UpdateServiceCommand, request) - } - - public async createConnection( - request: AppRunner.CreateConnectionRequest - ): Promise { - return await this.makeRequest(AppRunner.CreateConnectionCommand, request) - } - - public async listConnections( - request: AppRunner.ListConnectionsRequest = {} - ): Promise { - const result: AppRunner.ListConnectionsResponse = await this.makeRequest( - AppRunner.ListConnectionsCommand, - request - ) - return result.ConnectionSummaryList ?? [] - } - - public async describeService( - request: AppRunner.DescribeServiceRequest - ): Promise> { - return await this.makeRequest(AppRunner.DescribeServiceCommand, request) - } - - public async startDeployment( - request: AppRunner.StartDeploymentRequest - ): Promise { - return await this.makeRequest(AppRunner.StartDeploymentCommand, request) - } - - public async listOperations(request: AppRunner.ListOperationsRequest): Promise { - return await this.makeRequest(AppRunner.ListOperationsCommand, request) - } - - public async deleteService( - request: AppRunner.DeleteServiceRequest - ): Promise> { - return this.makeRequest(AppRunner.DeleteServiceCommand, request) - } -} diff --git a/packages/core/src/shared/clients/cloudControl.ts b/packages/core/src/shared/clients/cloudControl.ts deleted file mode 100644 index 664ae2db70..0000000000 --- a/packages/core/src/shared/clients/cloudControl.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as CloudControl from '@aws-sdk/client-cloudcontrol' -import globals from '../extensionGlobals' -import { localize } from '../utilities/vsCodeUtils' -import { ClientWrapper } from './clientWrapper' - -export class CloudControlClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, CloudControl.CloudControlClient) - } - - public async createResource(request: CloudControl.CreateResourceInput): Promise { - const createResponse: CloudControl.CreateResourceOutput = await this.makeRequest( - CloudControl.CreateResourceCommand, - request - ) - - await this.pollForCompletion(createResponse.ProgressEvent!) - return createResponse - } - - public async deleteResource(request: CloudControl.DeleteResourceInput): Promise { - const deleteResponse: CloudControl.DeleteResourceOutput = await this.makeRequest( - CloudControl.DeleteResourceCommand, - request - ) - - await this.pollForCompletion(deleteResponse.ProgressEvent!) - } - - public async listResources(request: CloudControl.ListResourcesInput): Promise { - return await this.makeRequest(CloudControl.ListResourcesCommand, request) - } - - public async getResource(request: CloudControl.GetResourceInput): Promise { - return await this.makeRequest(CloudControl.GetResourceCommand, request) - } - - public async updateResource(request: CloudControl.UpdateResourceInput): Promise { - const updateResponse: CloudControl.UpdateResourceOutput = await this.makeRequest( - CloudControl.UpdateResourceCommand, - request - ) - - await this.pollForCompletion(updateResponse.ProgressEvent!) - } - - private async getResourceRequestStatus( - request: CloudControl.GetResourceRequestStatusInput - ): Promise { - return await this.makeRequest(CloudControl.GetResourceRequestStatusCommand, request) - } - - private async pollForCompletion( - progressEvent: CloudControl.ProgressEvent, - baseDelay: number = 500, - maxRetries: number = 10 - ): Promise { - for (let i = 0; i < maxRetries; i++) { - const operationStatus = progressEvent.OperationStatus - - switch (operationStatus) { - case 'SUCCESS': - return - case 'FAILED': - throw new Error( - localize( - 'AWS.message.error.cloudControl.pollResourceStatus.failed', - 'Resource operation failed: {0} ({1})', - progressEvent.StatusMessage, - progressEvent.ErrorCode - ) - ) - case 'CANCEL_COMPLETE': - throw new Error( - localize( - 'AWS.message.error.cloudControl.pollResourceStatus.cancelled', - 'Resource operation cancelled: {0}', - progressEvent.StatusMessage - ) - ) - case 'IN_PROGRESS': - case 'CANCEL_IN_PROGRESS': - case 'PENDING': - break - default: - throw new Error( - localize( - 'AWS.message.error.cloudControl.pollResourceStatus.invalidOperationStatus', - 'Invalid resource operation status: {0}', - operationStatus - ) - ) - } - - if (i + 1 < maxRetries) { - await new Promise((resolve) => globals.clock.setTimeout(resolve, baseDelay * 2 ** i)) - const resourceRequestStatus = await this.getResourceRequestStatus({ - RequestToken: progressEvent.RequestToken, - }) - progressEvent = resourceRequestStatus.ProgressEvent! - } - } - throw new Error( - localize( - 'AWS.message.error.cloudControl.pollResourceStatus.timeout', - 'Failed to get terminal resource operation status for {0} before timeout. Please try again later', - progressEvent.Identifier - ) - ) - } -} diff --git a/packages/core/src/shared/clients/cloudFormation.ts b/packages/core/src/shared/clients/cloudFormation.ts deleted file mode 100644 index f04d2fdeb0..0000000000 --- a/packages/core/src/shared/clients/cloudFormation.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as CloudFormation from '@aws-sdk/client-cloudformation' -import { AsyncCollection } from '../utilities/asyncCollection' -import { hasProps, isNonNullable, RequiredProps } from '../utilities/tsUtils' -import { ClientWrapper } from './clientWrapper' - -export interface StackSummary - extends RequiredProps { - DriftInformation: RequiredProps -} - -export type StackResource = RequiredProps - -export interface DescribeStackResourcesOutput extends CloudFormation.DescribeStackResourcesOutput { - StackResources: StackResource[] -} -export class CloudFormationClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, CloudFormation.CloudFormationClient) - } - - public async deleteStack(name: string): Promise { - return await this.makeRequest(CloudFormation.DeleteStackCommand, { StackName: name }) - } - - public async describeType(typeName: string): Promise { - return await this.makeRequest(CloudFormation.DescribeTypeCommand, { TypeName: typeName }) - } - - public async *listStacks( - statusFilter: CloudFormation.StackStatus[] = ['CREATE_COMPLETE', 'UPDATE_COMPLETE'] - ): AsyncIterableIterator { - const request: CloudFormation.ListStacksInput = { - StackStatusFilter: statusFilter, - } - - do { - const response: CloudFormation.ListStacksOutput = await this.makeRequest( - CloudFormation.ListStacksCommand, - request - ) - - const filteredResponse = response.StackSummaries?.filter(isStackSummary) - if (filteredResponse && filteredResponse.length > 0) { - yield* filteredResponse - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public listAllStacks(request: CloudFormation.ListStacksInput = {}): AsyncCollection { - return this.makePaginatedRequest(CloudFormation.paginateListStacks, request, (page) => page.StackSummaries).map( - (s) => s.filter(isStackSummary) - ) - } - - public async *listTypes(): AsyncIterableIterator { - const request: CloudFormation.ListTypesInput = { - DeprecatedStatus: 'LIVE', - Type: 'RESOURCE', - Visibility: 'PUBLIC', - } - - do { - const response: CloudFormation.ListTypesOutput = await this.makeRequest( - CloudFormation.ListTypesCommand, - request - ) - - if (response.TypeSummaries) { - yield* response.TypeSummaries - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async describeStackResources(name: string): Promise { - return await this.makeRequest(CloudFormation.DescribeStackResourcesCommand, { StackName: name }) - } -} - -function isStackSummary(s: CloudFormation.StackSummary | undefined): s is StackSummary { - return ( - isNonNullable(s) && - hasProps(s, 'StackName', 'CreationTime', 'StackStatus', 'DriftInformation') && - hasProps(s.DriftInformation, 'StackDriftStatus') - ) -} diff --git a/packages/core/src/shared/clients/cloudWatchLogs.ts b/packages/core/src/shared/clients/cloudWatchLogs.ts deleted file mode 100644 index 5e43470fa3..0000000000 --- a/packages/core/src/shared/clients/cloudWatchLogs.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as CloudWatchLogs from '@aws-sdk/client-cloudwatch-logs' -import { ClientWrapper } from './clientWrapper' - -export class CloudWatchLogsClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, CloudWatchLogs.CloudWatchLogsClient) - } - - public async *describeLogGroups( - request: CloudWatchLogs.DescribeLogGroupsRequest = {} - ): AsyncIterableIterator { - do { - const response: CloudWatchLogs.DescribeLogGroupsResponse = await this.makeRequest( - CloudWatchLogs.DescribeLogGroupsCommand, - request - ) - if (response.logGroups) { - yield* response.logGroups - } - request.nextToken = response.nextToken - } while (request.nextToken) - } - - public async describeLogStreams( - request: CloudWatchLogs.DescribeLogStreamsRequest - ): Promise { - return await this.makeRequest(CloudWatchLogs.DescribeLogStreamsCommand, request) - } - - public async getLogEvents( - request: CloudWatchLogs.GetLogEventsRequest - ): Promise { - return await this.makeRequest(CloudWatchLogs.GetLogEventsCommand, request) - } - - public async filterLogEvents( - request: CloudWatchLogs.FilterLogEventsRequest - ): Promise { - return await this.makeRequest(CloudWatchLogs.FilterLogEventsCommand, request) - } -} diff --git a/packages/core/src/shared/clients/codecatalystClient.ts b/packages/core/src/shared/clients/codecatalystClient.ts deleted file mode 100644 index b7618fe01b..0000000000 --- a/packages/core/src/shared/clients/codecatalystClient.ts +++ /dev/null @@ -1,971 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../extensionGlobals' - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as logger from '../logger/logger' -import { CancellationError, Timeout, waitTimeout, waitUntil } from '../utilities/timeoutUtils' -import { isUserCancelledError } from '../../shared/errors' -import { showMessageWithCancel } from '../utilities/messages' -import { - assertHasProps, - ClassToInterfaceType, - hasProps, - isDefined, - isNonNullable, - RequiredProps, -} from '../utilities/tsUtils' -import { AsyncCollection, toCollection } from '../utilities/asyncCollection' -import { joinAll, pageableToCollection } from '../utilities/collectionUtils' -import { ToolkitError } from '../errors' -import { Uri } from 'vscode' -import { - CodeCatalystClient as CodeCatalystSDKClient, - CreateAccessTokenCommand, - CreateAccessTokenRequest, - CreateAccessTokenResponse, - CreateDevEnvironmentCommand, - CreateDevEnvironmentCommandOutput, - CreateDevEnvironmentRequest, - CreateProjectCommand, - CreateProjectRequest, - CreateSourceRepositoryBranchCommand, - CreateSourceRepositoryBranchRequest, - CreateSourceRepositoryBranchResponse, - DeleteDevEnvironmentCommand, - DeleteDevEnvironmentRequest, - DeleteDevEnvironmentResponse, - DevEnvironmentRepositorySummary, - DevEnvironmentSummary, - GetDevEnvironmentCommand, - GetDevEnvironmentRequest, - GetDevEnvironmentResponse, - GetProjectCommand, - GetProjectCommandOutput, - GetProjectRequest, - GetSourceRepositoryCloneUrlsCommand, - GetSourceRepositoryCloneUrlsRequest, - GetSourceRepositoryCloneUrlsResponse, - GetSpaceCommand, - GetSpaceCommandOutput, - GetSpaceRequest, - GetSubscriptionCommand, - GetSubscriptionRequest, - GetSubscriptionResponse, - GetUserDetailsCommand, - GetUserDetailsCommandOutput, - GetUserDetailsRequest, - GetUserDetailsResponse, - ListDevEnvironmentsCommand, - ListDevEnvironmentsRequest, - ListDevEnvironmentsResponse, - ListProjectsCommand, - ListProjectsRequest, - ListProjectsResponse, - ListSourceRepositoriesCommand, - ListSourceRepositoriesItem, - ListSourceRepositoriesRequest, - ListSourceRepositoriesResponse, - ListSourceRepositoryBranchesCommand, - ListSourceRepositoryBranchesItem, - ListSourceRepositoryBranchesRequest, - ListSpacesCommand, - ListSpacesRequest, - ListSpacesResponse, - PersistentStorage, - ProjectSummary, - SpaceSummary, - StartDevEnvironmentCommand, - StartDevEnvironmentRequest, - StartDevEnvironmentResponse, - StartDevEnvironmentSessionCommand, - StartDevEnvironmentSessionRequest, - StartDevEnvironmentSessionResponse, - StopDevEnvironmentCommand, - StopDevEnvironmentRequest, - StopDevEnvironmentResponse, - UpdateDevEnvironmentCommand, - UpdateDevEnvironmentRequest, - UpdateDevEnvironmentResponse, - VerifySessionCommand, - VerifySessionCommandOutput, -} from '@aws-sdk/client-codecatalyst' -import { SsoConnection } from '../../auth/connection' -import { DevSettings } from '../settings' -import { getServiceEnvVarConfig } from '../vscode/env' -import { AwsCommand, AwsCommandConstructor } from '../awsClientBuilderV3' -import { ClientWrapper } from './clientWrapper' -import { ServiceException } from '@aws-sdk/smithy-client' -import { AccessDeniedException } from '@aws-sdk/client-sso-oidc' -import { TokenIdentityProvider } from '@aws-sdk/types' - -const requiredDevEnvProps = [ - 'id', - 'status', - 'inactivityTimeoutMinutes', - 'repositories', - 'instanceType', - 'lastUpdatedTime', -] as const -type RequiredDevEnvProps = (typeof requiredDevEnvProps)[number] - -export interface CodeCatalystConfig { - readonly region: string - readonly endpoint: string - readonly hostname: string - readonly gitHostname: string -} - -export const defaultServiceConfig: CodeCatalystConfig = { - region: 'us-east-1', - endpoint: 'https://codecatalyst.global.api.aws', - hostname: 'codecatalyst.aws', - gitHostname: 'codecatalyst.aws', -} - -export function getCodeCatalystConfig(): CodeCatalystConfig { - return { - ...DevSettings.instance.getServiceConfig('codecatalystService', defaultServiceConfig), - - // Environment variable overrides - ...getServiceEnvVarConfig('codecatalyst', Object.keys(defaultServiceConfig)), - } -} - -interface CodeCatalystDevEnvironmentSummary extends RequiredProps { - readonly persistentStorage: RequiredProps - readonly repositories: RequiredProps[] -} - -export interface DevEnvironment extends CodeCatalystDevEnvironmentSummary { - readonly type: 'devEnvironment' - readonly id: string - readonly org: Pick - readonly project: Pick - readonly repositories: RequiredProps[] -} - -/** CodeCatalyst developer environment session. */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface CodeCatalystDevEnvSession extends StartDevEnvironmentResponse {} - -export interface CodeCatalystOrg extends SpaceSummary { - readonly type: 'org' - readonly name: string -} - -export interface CodeCatalystProject extends ProjectSummary { - readonly type: 'project' - readonly name: string - readonly org: Pick -} - -export interface CodeCatalystRepo extends ListSourceRepositoriesItem { - readonly type: 'repo' - readonly name: string - readonly org: Pick - readonly project: Pick -} - -export interface CodeCatalystBranch extends ListSourceRepositoryBranchesItem { - readonly type: 'branch' - readonly name: string - readonly repo: Pick - readonly org: Pick - readonly project: Pick -} - -export type CodeCatalystResource = - | CodeCatalystOrg - | CodeCatalystProject - | CodeCatalystRepo - | CodeCatalystBranch - | DevEnvironment - -function toDevEnv(spaceName: string, projectName: string, summary: CodeCatalystDevEnvironmentSummary): DevEnvironment { - return { - type: 'devEnvironment', - org: { name: spaceName }, - project: { name: projectName }, - ...summary, - } -} - -function toBranch( - org: string, - project: string, - repo: string, - branch: ListSourceRepositoryBranchesItem -): CodeCatalystBranch { - assertHasProps(branch, 'name') - - return { - type: 'branch', - repo: { name: repo }, - org: { name: org }, - project: { name: project }, - ...branch, - } -} - -function createCodeCatalystClient( - tokenProvider: TokenIdentityProvider, - regionCode: string, - endpoint: string -): CodeCatalystSDKClient { - // Avoid using cached client so that we can inject fresh bearer token provider. - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: CodeCatalystSDKClient, - clientOptions: { - region: regionCode, - endpoint: endpoint, - token: tokenProvider, - }, - }) -} - -export type UserDetails = RequiredProps - -// CodeCatalyst client has two variants: 'logged-in' and 'not logged-in' -// The 'not logged-in' variant is a subtype and has restricted functionality -// These characteristics appear in the Smithy model, but the SDK codegen is unable to model this - -export interface CodeCatalystClient extends ClassToInterfaceType { - readonly identity: { readonly id: string; readonly name: string } -} - -const onAccessDeniedExceptionEmitter = new vscode.EventEmitter() -export const onAccessDeniedException = onAccessDeniedExceptionEmitter.event - -interface AuthOptions { - showReauthPrompt?: boolean -} -export type CodeCatalystClientFactory = () => Promise -/** - * Factory to create a new `CodeCatalystClient`. Call `onCredentialsChanged()` before making requests. - */ -export async function createClient( - connection: SsoConnection, - regionCode = getCodeCatalystConfig().region, - endpoint = getCodeCatalystConfig().endpoint, - authOptions: AuthOptions = {} -): Promise { - const sdkv3Client = createCodeCatalystClient(getTokenProvider(connection), regionCode, endpoint) - const c = new CodeCatalystClientInternal(connection, sdkv3Client, regionCode) - try { - await c.verifySession() - } catch (e) { - if (!(e instanceof ToolkitError) || e.code !== 'AccessDeniedException') { - throw e - } - onAccessDeniedExceptionEmitter.fire(authOptions.showReauthPrompt ?? true) - - // Throw a "cancel" error to prevent further execution. - throw new ToolkitError('CodeCatalyst scope is expired', { code: 'ScopeExpiration', cancelled: true }) - } - - return c -} - -// TODO: move this to sso auth folder? -function getTokenProvider(connection: SsoConnection): TokenIdentityProvider { - return async (_) => { - const token = await connection.getToken() - return { - token: token.accessToken, - expiration: token.expiresAt, - } - } -} - -// XXX: the backend currently rejects empty strings for `alias` so the field must be removed if falsey -function fixAliasInRequest(request: T): T { - if (!request.alias) { - delete request.alias - } - - return request -} - -class CodeCatalystClientInternal extends ClientWrapper { - private userDetails?: UserDetails - private readonly log: logger.Logger - - /** - * Maps bearer tokens to CAWS identities via `verifySession` - * - * It's assumed that an identity will never change over the lifetime of a token - */ - private static identityCache = new Map() - - /** - * Maps CAWS identities to user details via `getUserDetails` - * - * User details _might_ change at some point, however, this is an uncommon occurence. - * Cached user details are cleared when the access token is refreshed. - */ - private static userDetailsCache = new Map() - - public constructor( - private readonly connection: SsoConnection, - private readonly sdkClientV3: CodeCatalystSDKClient, - regionCode: string - ) { - super(regionCode, CodeCatalystSDKClient) - this.log = logger.getLogger() - } - - protected override getClient(): CodeCatalystSDKClient { - return this.sdkClientV3 - } - - public get identity(): CodeCatalystClient['identity'] { - if (!this.userDetails) { - throw new Error('CodeCatalyst client is not connected') - } - - return { id: this.userDetails.userId, name: this.userDetails.userName } - } - - private async call< - CommandInput extends object, - CommandOutput extends object, - CommandOptions extends CommandInput, - Command extends AwsCommand, - >( - cmd: AwsCommandConstructor, - commandOptions: CommandOptions, - silent: true, - defaultVal: CommandOutput - ): Promise - private async call< - CommandInput extends object, - CommandOutput extends object, - CommandOptions extends CommandInput, - Command extends AwsCommand, - >( - cmd: AwsCommandConstructor, - commandOptions: CommandOptions, - silent: false - ): Promise - private async call< - CommandInput extends object, - CommandOutput extends object, - CommandOptions extends CommandInput, - Command extends AwsCommand, - >( - cmd: AwsCommandConstructor, - commandOptions: CommandOptions, - silent: boolean, - defaultVal?: CommandOutput - ): Promise { - const bearerToken = (await this.connection.getToken()).accessToken - - return new Promise(async (resolve, reject) => { - try { - const data = await this.makeRequest( - cmd, - commandOptions - ) - resolve(data) - } catch (e) { - const error = e as Error - if (error instanceof ServiceException && isAccessDeniedError(error)) { - CodeCatalystClientInternal.identityCache.delete(bearerToken) - } - if (silent && defaultVal !== undefined) { - resolve(defaultVal) - } else { - // SDKv3 does not have error codes so we use the name instead (see: https://github.com/aws/aws-sdk-js-v3/issues/759) - reject( - new ToolkitError(`CodeCatalyst: request failed with error`, { cause: error, code: error.name }) - ) - } - } - }) - - function isAccessDeniedError(e: ServiceException): boolean { - return ( - e.$response?.statusCode === 403 || - e.$response?.statusCode === 401 || - e.name === AccessDeniedException.name - ) - } - } - - /** - * Creates a PAT. - * - * @param args.name Name of the token - * @param args.expires PAT expires on this date, or undefined. - * @returns PAT secret - */ - public async createAccessToken( - args: CreateAccessTokenRequest - ): Promise> { - try { - return await this.call(CreateAccessTokenCommand, args, false) - } catch (e) { - if ((e as Error).name === 'ServiceQuotaExceededException') { - throw new ToolkitError('Access token limit exceeded', { cause: e as Error }) - } - throw e - } - } - - public async getSubscription(request: GetSubscriptionRequest): Promise { - return this.call(GetSubscriptionCommand, request, false) - } - - /** - * Gets identity properties of the current authenticated principal, and - * stores the id for use in later calls. - */ - public async verifySession(): Promise { - const id = await this.getUserId() - this.userDetails = CodeCatalystClientInternal.userDetailsCache.get(id) ?? (await this.getUserDetails({ id })) - CodeCatalystClientInternal.userDetailsCache.set(id, this.userDetails) - - return { ...this.userDetails } - } - - private async getUserId(): Promise { - const { accessToken, expiresAt } = await this.connection.getToken() - if (CodeCatalystClientInternal.identityCache.has(accessToken)) { - return CodeCatalystClientInternal.identityCache.get(accessToken)! - } - - const r: VerifySessionCommandOutput = await this.call(VerifySessionCommand, {}, false) - assertHasProps(r, 'identity') - - CodeCatalystClientInternal.identityCache.set(accessToken, r.identity) - setTimeout(() => { - CodeCatalystClientInternal.identityCache.delete(accessToken) - CodeCatalystClientInternal.userDetailsCache.delete(r.identity) - }, expiresAt.getTime() - Date.now()) - - return r.identity - } - - public async getBearerToken(): Promise { - return (await this.connection.getToken()).accessToken - } - - private async getUserDetails(args: GetUserDetailsRequest) { - const resp: GetUserDetailsCommandOutput = await this.call(GetUserDetailsCommand, args, false) - assertHasProps(resp, 'userId', 'userName', 'displayName', 'primaryEmail') - - return { ...resp, version: resp.version } as const - } - - public async getSpace(request: GetSpaceRequest): Promise { - const resp: GetSpaceCommandOutput = await this.call(GetSpaceCommand, request, false) - assertHasProps(resp, 'name', 'regionName') - return { ...resp, type: 'org' } - } - - public async getProject(request: RequiredProps): Promise { - const resp: GetProjectCommandOutput = await this.call(GetProjectCommand, request, false) - assertHasProps(resp, 'name') - return { ...resp, type: 'project', org: { name: request.spaceName } } - } - - /** - * Gets a list of all spaces (orgs) for the current CodeCatalyst user. - */ - public listSpaces(request: ListSpacesRequest = {}): AsyncCollection { - const requester: (request: ListSpacesRequest) => Promise = async (request) => - this.call(ListSpacesCommand, request, true, { items: [] }) - const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(isDefined) - // ts doesn't recognize nested assertion, so we add cast.This is safe because we assert it in the same line. - return collection.map((summaries) => summaries.filter(hasName).map(toOrg)) - - function toOrg>(s: T): CodeCatalystOrg { - return { - type: 'org', - ...s, - } - } - } - - /** - * Gets a list of all projects for the given CodeCatalyst user. - */ - public listProjects( - request: RequiredProps - ): AsyncCollection { - // Only get projects the user is a member of. - request.filters = [ - ...(request.filters ?? []), - { - key: 'hasAccessTo', - values: ['true'], - }, - ] - - const requester: (request: ListProjectsRequest) => Promise = (request) => - this.call(ListProjectsCommand, request, true, { items: [] }) - - const collection = pageableToCollection(requester, request, 'nextToken', 'items') - return collection.filter(isDefined).map((summaries) => summaries.filter(hasName).map(toProject)) - - function toProject>(s: T): CodeCatalystProject { - return { - type: 'project', - org: { name: request.spaceName }, - ...s, - } - } - } - - /** - * Gets a flat list of all devenvs for the given CodeCatalyst project. - */ - public listDevEnvironments(proj: CodeCatalystProject): AsyncCollection { - const initRequest = { spaceName: proj.org.name, projectName: proj.name } - const requester: (request: ListDevEnvironmentsRequest) => Promise = (request) => - this.call(ListDevEnvironmentsCommand, request, true, { items: [] }) - const collection = pageableToCollection(requester, initRequest, 'nextToken' as never, 'items').filter(isDefined) - // ts unable to recognize nested assertion here, so we need to cast. This is safe because we assert it in the same line. - return collection.map((envs) => { - const filteredEnvs = envs.filter(isValidEnvSummary) - const mappedEnvs = filteredEnvs.map((s) => toDevEnv(proj.org.name, proj.name, s)) - return mappedEnvs - }) - } - - /** - * Gets a flat list of all repos for the given CodeCatalyst user. - * @param thirdParty If you want to include 3P (eg github) results in - * your output. - */ - public listSourceRepositories( - request: RequiredProps, - thirdParty: boolean = true - ): AsyncCollection { - const requester = async (r: typeof request) => { - const allRepositories: Promise = this.call( - ListSourceRepositoriesCommand, - r, - true, - { items: [] } - ) - let finalRepositories = allRepositories - - // Filter out 3P repos - if (!thirdParty) { - finalRepositories = allRepositories.then(async (repos) => { - repos.items = await excludeThirdPartyRepos( - this, - request.spaceName, - request.projectName, - repos.items?.filter(hasName) - ) - return repos - }) - } - - return finalRepositories - } - - const collection = pageableToCollection(requester, request, 'nextToken', 'items') - return collection.filter(isDefined).map((summaries) => summaries.filter(hasName).map(toCodeCatalystRepo)) - - function toCodeCatalystRepo(s: RequiredProps): CodeCatalystRepo { - return { - type: 'repo', - org: { name: request.spaceName }, - project: { name: request.projectName }, - ...s, - } - } - } - - public listBranches( - request: RequiredProps< - ListSourceRepositoryBranchesRequest, - 'spaceName' | 'projectName' | 'sourceRepositoryName' - > - ): AsyncCollection { - const requester = async (r: typeof request) => - this.call(ListSourceRepositoryBranchesCommand, r, true, { items: [] }) - const collection = pageableToCollection(requester, request, 'nextToken' as never, 'items') - - return collection - .filter(isNonNullable) - .map((items) => - items.map((b) => toBranch(request.spaceName, request.projectName, request.sourceRepositoryName, b)) - ) - } - - /** - * Lists ALL of the given resource in the current account - * - * @param thirdParty If you want 3P repos in the result. - */ - public listResources(resourceType: 'org'): AsyncCollection - public listResources(resourceType: 'project'): AsyncCollection - public listResources(resourceType: 'repo', thirdParty?: boolean): AsyncCollection - public listResources(resourceType: 'devEnvironment'): AsyncCollection - public listResources( - resourceType: CodeCatalystResource['type'], - ...args: any[] - ): AsyncCollection { - function mapInner( - collection: AsyncCollection, - fn: (element: T) => AsyncCollection - ): AsyncCollection { - return toCollection(() => joinAll(collection.flatten().map(fn))) - } - - switch (resourceType) { - case 'org': - return this.listSpaces() - case 'project': - return mapInner(this.listResources('org'), (o) => this.listProjects({ spaceName: o.name })) - case 'repo': - return mapInner(this.listResources('project'), (p) => - this.listSourceRepositories({ projectName: p.name, spaceName: p.org.name }, ...args) - ) - case 'branch': - throw new Error('Listing branches is not currently supported') - case 'devEnvironment': - return mapInner(this.listResources('project'), (p) => this.listDevEnvironments(p)) - } - } - - public async createSourceBranch( - args: CreateSourceRepositoryBranchRequest - ): Promise { - return this.call(CreateSourceRepositoryBranchCommand, args, false) - } - - /** - * Gets the git source host URL for the given CodeCatalyst or third-party repo. - */ - public async getRepoCloneUrl(args: GetSourceRepositoryCloneUrlsRequest): Promise { - const r: GetSourceRepositoryCloneUrlsResponse = await this.call( - GetSourceRepositoryCloneUrlsCommand, - args, - false - ) - assertHasProps(r, 'https') - // The git extension skips over credential providers if the username is included in the authority - const uri = Uri.parse(r.https) - return uri.with({ authority: uri.authority.replace(/.*@/, '') }).toString() - } - - public async createDevEnvironment( - args: RequiredProps - ): Promise { - const request = fixAliasInRequest(args) - const response: CreateDevEnvironmentCommandOutput = await this.call(CreateDevEnvironmentCommand, request, false) - assertHasProps(response, 'id') - - return this.getDevEnvironment({ - id: response.id, - projectName: args.projectName, - spaceName: args.spaceName, - }) - } - - public async startDevEnvironment(args: StartDevEnvironmentRequest): Promise { - return this.call(StartDevEnvironmentCommand, args, false) - } - - public async createProject( - args: RequiredProps - ): Promise { - await this.call(CreateProjectCommand, args, false) - - return { ...args, name: args.displayName, type: 'project', org: { name: args.spaceName } } - } - - public async startDevEnvironmentSession( - args: StartDevEnvironmentSessionRequest - ): Promise { - const r: StartDevEnvironmentSessionResponse = await this.call(StartDevEnvironmentSessionCommand, args, false) - if (!r.sessionId) { - throw new TypeError('got falsy dev environment "sessionId"') - } - return { ...r, sessionId: r.sessionId } - } - - public async stopDevEnvironment(args: StopDevEnvironmentRequest): Promise { - return this.call(StopDevEnvironmentCommand, args, false) - } - - public async getDevEnvironment( - args: RequiredProps - ): Promise { - const a = { ...args } - delete (a as any).ides - delete (a as any).repositories - - const r: GetDevEnvironmentResponse = await this.call(GetDevEnvironmentCommand, a, false) - const summary = { ...args, ...r } - if (!isValidEnvSummary(summary)) { - throw new ToolkitError(`GetDevEnvironment failed due to response missing required properties`) - } - - return toDevEnv(args.spaceName, args.projectName, summary) - } - - public async deleteDevEnvironment(args: DeleteDevEnvironmentRequest): Promise { - return this.call(DeleteDevEnvironmentCommand, args, false) - } - - public updateDevEnvironment(args: UpdateDevEnvironmentRequest): Promise { - const request = fixAliasInRequest(args) - return this.call(UpdateDevEnvironmentCommand, request, false) - } - - /** - * Best-effort attempt to start a devenv given an ID, showing a progress notification with a cancel button - * TODO: may combine this progress stuff into some larger construct - * - * The cancel button does not abort the start, but rather alerts any callers that any operations that rely - * on the dev environment starting should not progress. - */ - public async startDevEnvironmentWithProgress( - args: RequiredProps, - timeout: Timeout = new Timeout(1000 * 60 * 60) - ): Promise { - // Track the status changes chronologically so that we can - // 1. reason about hysterisis (weird flip-flops) - // 2. have visibility in the logs - const statuses = new Array<{ status: string; start: number }>() - let alias: string | undefined - let startAttempts = 0 - - function statusesToString() { - let s = '' - for (let i = 0; i < statuses.length; i++) { - const item = statuses[i] - const nextItem = i < statuses.length - 1 ? statuses[i + 1] : undefined - const nextTime = nextItem ? nextItem.start : Date.now() - const elapsed = nextTime - item.start - s += `${s ? ' ' : ''}${item.status}/${elapsed}ms` - } - return `[${s}]` - } - - function getName(): string { - const fullname = alias ? alias : args.id - const shortname = fullname.substring(0, 19) + (fullname.length > 20 ? '…' : '') - return shortname - } - - function failedStartMsg(serviceMsg?: string) { - const LastSeenStatus = statuses[statuses.length - 1]?.status - const serviceMsg_ = serviceMsg ? `${serviceMsg}: ` : '' - return `Dev Environment failed to start (${LastSeenStatus}): ${serviceMsg_}${getName()}` - } - - const doLog = (kind: 'debug' | 'error' | 'info', msg: string) => { - const fmt = `${msg} (time: %ds${ - startAttempts <= 1 ? '' : ', startAttempts: ' + startAttempts.toString() - }): %s %s` - if (kind === 'debug') { - this.log.debug(fmt, timeout.elapsedTime / 1000, getName(), statusesToString()) - } else if (kind === 'error') { - this.log.error(fmt, timeout.elapsedTime / 1000, getName(), statusesToString()) - } else { - this.log.info(fmt, timeout.elapsedTime / 1000, getName(), statusesToString()) - } - } - - const progress = await showMessageWithCancel( - localize('AWS.CodeCatalyst.devenv.message', 'CodeCatalyst'), - timeout - ) - progress.report({ message: localize('AWS.CodeCatalyst.devenv.checking', 'Checking status...') }) - - try { - const devenv = await this.getDevEnvironment(args) - alias = devenv.alias - statuses.push({ status: devenv.status, start: Date.now() }) - if (devenv.status === 'RUNNING') { - doLog('debug', 'devenv RUNNING') - timeout.cancel() - // "Debounce" in case caller did not check if the environment was already running. - return devenv - } - } catch { - // Continue. - } - - doLog('debug', 'devenv not started, waiting') - - const pollDevEnv = waitUntil( - async () => { - if (timeout.completed) { - // TODO: need a better way to "cancel" a `waitUntil`. - throw new CancellationError('user') - } - - const LastSeenStatus = statuses[statuses.length - 1] - const elapsed = Date.now() - LastSeenStatus.start - const resp = await this.getDevEnvironment(args) - const serviceReason = (resp.statusReason ?? '').trim() - alias = resp.alias - - if ( - startAttempts > 2 && - elapsed > 10000 && - ['STOPPED', 'FAILED'].includes(LastSeenStatus.status) && - ['STOPPED', 'FAILED'].includes(resp.status) - ) { - const fails = statuses.filter((o) => o.status === 'FAILED').length - const code = fails === 0 ? 'BadDevEnvState' : 'FailedDevEnv' - - if (serviceReason !== '') { - // Service gave a status reason like "Compute limit exceeded", show it to the user. - throw new ToolkitError(failedStartMsg(resp.statusReason), { code: code }) - } - - // If still STOPPED/FAILED after 10+ seconds, don't keep retrying for 1 hour... - throw new ToolkitError(failedStartMsg(), { code: code }) - } else if (['STOPPED', 'FAILED'].includes(resp.status)) { - progress.report({ - message: localize('AWS.CodeCatalyst.devenv.resuming', 'Resuming Dev Environment...'), - }) - try { - startAttempts++ - await this.startDevEnvironment(args) - } catch (e) { - const err = e as ServiceException - // - ServiceQuotaExceededException: account billing limit reached - // - ValidationException: "… creation has failed, cannot start" - // - ConflictException: "Cannot start … because update process is still going on" - // (can happen after "Update Dev Environment") - if (err.name === 'ServiceQuotaExceededException') { - throw new ToolkitError('Dev Environment failed: quota exceeded', { - code: 'ServiceQuotaExceeded', - cause: err, - }) - } - doLog('info', `devenv not started (${err.name}), waiting`) - // Continue retrying... - } - } else if (resp.status === 'STOPPING') { - progress.report({ - message: localize('AWS.CodeCatalyst.devenv.stopping', 'Waiting for Dev Environment to stop...'), - }) - } else { - progress.report({ - message: localize('AWS.CodeCatalyst.devenv.starting', 'Opening Dev Environment...'), - }) - } - - if (LastSeenStatus?.status !== resp.status) { - statuses.push({ status: resp.status, start: Date.now() }) - if (resp.status !== 'RUNNING') { - doLog('debug', `devenv not started, waiting`) - } - } - return resp.status === 'RUNNING' ? resp : undefined - }, - // note: the `waitUntil` will resolve prior to the real timeout if it is refreshed - { interval: 1000, timeout: timeout.remainingTime, truthy: true } - ) - - const devenv = await waitTimeout(pollDevEnv, timeout).catch((e) => { - if (isUserCancelledError(e)) { - doLog('info', 'devenv failed to start (user cancelled)') - e.message = failedStartMsg() - throw e - } else if (e instanceof ToolkitError) { - doLog('error', 'devenv failed to start') - throw e - } - - doLog('error', 'devenv failed to start') - throw new ToolkitError(failedStartMsg(), { code: 'Unknown', cause: e }) - }) - - if (!devenv) { - doLog('error', 'devenv failed to start (timeout)') - throw new ToolkitError(failedStartMsg(), { code: 'Timeout' }) - } - doLog('info', 'devenv started') - - return devenv - } -} - -/** - * Returns only the first-party repos from the given - * list of repository items. - */ -export async function excludeThirdPartyRepos( - client: CodeCatalystClient, - spaceName: CodeCatalystOrg['name'], - projectName: CodeCatalystProject['name'], - items?: RequiredProps[] -): Promise { - if (items === undefined) { - return items - } - - // Filter out 3P repos. - return ( - await Promise.all( - items.map(async (item) => { - return (await isThirdPartyRepo(client, { - spaceName, - projectName, - sourceRepositoryName: item.name, - })) - ? undefined - : item - }) - ) - ).filter(isDefined) -} - -/** - * Determines if a repo is third party (3P) compared to first party (1P). - * - * 1P is CodeCatalyst, 3P is something like Github. - */ -export async function isThirdPartyRepo( - client: CodeCatalystClient, - codeCatalystRepo: GetSourceRepositoryCloneUrlsRequest -): Promise { - const url = await client.getRepoCloneUrl(codeCatalystRepo) - // TODO: Make more robust to work with getCodeCatalystConfig(). - return ( - !Uri.parse(url).authority.endsWith('codecatalyst.aws') && - !Uri.parse(url).authority.endsWith('caws.dev-tools.aws.dev') - ) -} - -// These type guard wrappers are needed because type assertions fail -// to travel up function scope (see: https://github.com/microsoft/TypeScript/issues/9998) - -function hasPersistentStorage( - s: T -): s is T & { persistentStorage: { sizeInGiB: number } } { - return hasProps(s, 'persistentStorage') && hasProps(s.persistentStorage, 'sizeInGiB') -} - -function hasRepositories( - s: T -): s is T & { repositories: RequiredProps[] } { - return hasProps(s, 'repositories') && s.repositories.every((r) => hasProps(r, 'repositoryName')) -} - -function hasName(s: T): s is RequiredProps { - return hasProps(s, 'name') -} - -function isValidEnvSummary(s: DevEnvironmentSummary): s is CodeCatalystDevEnvironmentSummary { - return hasProps(s, ...requiredDevEnvProps) && hasPersistentStorage(s) && hasRepositories(s) -} diff --git a/packages/core/src/shared/clients/devenvClient.ts b/packages/core/src/shared/clients/devenvClient.ts deleted file mode 100644 index c62708b0b0..0000000000 --- a/packages/core/src/shared/clients/devenvClient.ts +++ /dev/null @@ -1,276 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import got, { HTTPError } from 'got' -import globals from '../extensionGlobals' -import { getLogger } from '../logger/logger' -import { getCodeCatalystDevEnvId } from '../vscode/env' -import { UserActivity } from '../extensionUtilities' - -const environmentAuthToken = '__MDE_ENV_API_AUTHORIZATION_TOKEN' -const environmentEndpoint = process.env['__MDE_ENVIRONMENT_API'] ?? 'http://127.0.0.1:1339' - -/** - * Client to the MDE quasi-IMDS localhost endpoint. - */ -export class DevEnvClient implements vscode.Disposable { - static #instance: DevEnvClient - private readonly timer - private LastSeenStatus = '' - private onStatusChangeFn: undefined | ((oldStatus: string, newStatus: string) => void) - - /** Singleton instance (to avoid multiple polling workers). */ - public static get instance() { - return (this.#instance ??= new this()) - } - - /** @internal */ - public constructor(private readonly endpoint: string = environmentEndpoint) { - if (!this.id) { - getLogger().debug('codecatalyst: DevEnvClient skipped (local)') - this.timer = undefined - } else { - getLogger().debug('codecatalyst: DevEnvClient started') - this.timer = globals.clock.setInterval(async () => { - const r = await this.getStatus() - if (this.LastSeenStatus !== r.status) { - const newStatus = r.status ?? 'NULL' - getLogger().info( - 'codecatalyst: DevEnvClient: status change (old=%s new=%s)%s%s', - this.LastSeenStatus, - newStatus, - r.actionId ? ` action=${r.actionId}` : '', - r.message ? `: "${r.message}"` : '' - ) - if (this.onStatusChangeFn) { - this.onStatusChangeFn(this.LastSeenStatus, newStatus) - } - this.LastSeenStatus = newStatus ?? 'NULL' - } - }, 1000) - } - } - - public onStatusChange(fn: (oldStatus: string, newStatus: string) => void) { - this.onStatusChangeFn = fn - } - - public dispose() { - if (this.timer) { - globals.clock.clearInterval(this.timer) - } - } - - public get id(): string | undefined { - return getCodeCatalystDevEnvId() - } - - public isCodeCatalystDevEnv(): boolean { - return !!this.id - } - - // Start an action - public async startDevfile(request: StartDevfileRequest): Promise { - await this.got.post('start', { json: request }) - } - - // Create a devfile for the project - public async createDevfile(request: CreateDevfileRequest): Promise { - const response = await this.got.post('devfile/create', { json: request }) - - return response.body - } - - // Get status and action type - // - // Example: - // { status: 'IMAGES-UPDATE-AVAILABLE', location: 'devfile.yaml' } - public async getStatus(): Promise { - const response = await this.got('status') - - return response.body - } - - private get authToken(): string | undefined { - return process.env[environmentAuthToken] - } - - private readonly got = got.extend({ - prefixUrl: this.endpoint, - responseType: 'json', - // `Authorization` _should_ have two parameters (RFC 7235), MDE should probably fix that - headers: { Authorization: this.authToken }, - }) - - /** - * Notifies the MDE API of user activity. - * - * WARNING: Use {@link DevEnvActivity} instead of calling this directly. - */ - async updateActivity(timestamp: number = Date.now()): Promise { - await this.got.put('activity', { json: { timestamp: timestamp.toString() } }) - return timestamp - } - - /** - * Gets the latest user activity timestamp from MDE API. - * - * WARNING: Use {@link DevEnvActivity} instead of calling this directly. - */ - async getActivity(): Promise { - const response = await this.got('activity') - return response.body.timestamp ? parseInt(response.body.timestamp) : undefined - } -} - -/** - * Posts user activity timestamps to the dev env "/activity" API, which are used by MDE to decide - * when the user was last active, and thus prevent auto-shutdown of the dev env. - */ -export class DevEnvActivity implements vscode.Disposable { - private activityUpdatedEmitter = new vscode.EventEmitter() - private ideActivityListener: vscode.Disposable | undefined - /** The last known activity timestamp, but there could be a newer one on the server. */ - private lastLocalActivity: number | undefined - private extensionUserActivity: UserActivity - private static _defaultUserActivity: UserActivity | undefined - - static readonly activityUpdateDelay = 10_000 - - /** Gets a new DevEnvActivity, or undefined if service is failed. */ - static async create( - client: DevEnvClient, - extensionUserActivity?: UserActivity - ): Promise { - try { - await client.getActivity() - getLogger().debug('codecatalyst: DevEnvActivity: Activity API is enabled') - } catch (e) { - const error = e instanceof HTTPError ? e.response.body : e - getLogger().error(`codecatalyst: DevEnvActivity: Activity API failed: %s`, error) - return undefined - } - - return new DevEnvActivity(client, extensionUserActivity) - } - - private constructor( - private readonly client: DevEnvClient, - extensionUserActivity?: UserActivity - ) { - this.extensionUserActivity = extensionUserActivity ?? this.defaultUserActivity - } - - private get defaultUserActivity(): UserActivity { - return (DevEnvActivity._defaultUserActivity ??= new UserActivity(DevEnvActivity.activityUpdateDelay)) - } - - /** Send activity timestamp to the MDE environment endpoint. */ - async sendActivityUpdate(timestamp: number = Date.now()): Promise { - await this.client.updateActivity() - getLogger().debug('codecatalyst: DevEnvActivity: heartbeat sent') - this.lastLocalActivity = timestamp - this.activityUpdatedEmitter.fire(timestamp) - return timestamp - } - - /** Get the latest activity timestamp from the Dev Env */ - async getLatestActivity(): Promise { - const lastServerActivity = await this.client.getActivity() - - // A single Dev Env can have multiple clients connected to it. - // So if one client updates the timestamp, it will be different from what the - // other clients assumed the last activity was. - if (lastServerActivity && lastServerActivity !== this.lastLocalActivity) { - this.activityUpdatedEmitter.fire(lastServerActivity) - } - - this.lastLocalActivity = lastServerActivity - return this.lastLocalActivity - } - - /** true, if the latest activity on the server is different from what this client has as the latest */ - async isLocalActivityStale(): Promise { - return (await this.getLatestActivity()) !== this.lastLocalActivity - } - - /** - * Subscribes to the "user activity sent" event. - * - * @param callback Called when event is fired. - * @param callback.timestamp Timestamp (milliseconds since 1970), see {@link Date.now} - */ - onActivityUpdate(callback: (timestamp: number) => any) { - this.activityUpdatedEmitter.event(callback) - } - - /** - * Sends an activity timestamp to Dev Env when there is user activity, throttled to once every {@link DevEnvActivity.activityUpdateDelay}. - */ - setUpdateActivityOnIdeActivity(doUpdate: boolean) { - this.ideActivityListener?.dispose() - this.ideActivityListener = undefined - - if (!doUpdate) { - // Stop updating the activity heartbeat - return - } - - if (this.ideActivityListener) { - return - } - - this.ideActivityListener = this.extensionUserActivity.onUserActivity(async () => { - await this.sendActivityUpdate() - }) - } - - dispose() { - this.setUpdateActivityOnIdeActivity(false) - } -} - -export interface GetActivityResponse { - timestamp?: string -} - -export interface UpdateActivityRequest { - timestamp?: string -} - -export interface GetStatusResponse { - actionId?: string - message?: string - status?: Status - location?: string // relative to the currently mounted project -} - -export interface CreateDevfileRequest { - path?: string -} - -export interface CreateDevfileResponse { - // Location of the created devfile. - location?: string -} - -export interface StartDevfileRequest { - // The devfile.yaml file path relative to /projects/ - location?: string - - // The home volumes will be deleted and created again with the content of the '/home' folder of each component container. - recreateHomeVolumes?: boolean -} - -export type Status = - | 'PENDING' - | 'STABLE' - | 'CHANGED' - /** - * The image on-disk in the DE is different from the one in the container registry. - * Client should call "/devfile/pull" to pull the latest image from the registry. - */ - | 'IMAGES-UPDATE-AVAILABLE' diff --git a/packages/core/src/shared/clients/docdbClient.ts b/packages/core/src/shared/clients/docdbClient.ts deleted file mode 100644 index 5405010114..0000000000 --- a/packages/core/src/shared/clients/docdbClient.ts +++ /dev/null @@ -1,312 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../extensionGlobals' -import { getLogger } from '../logger/logger' -import { getUserAgent } from '../telemetry/util' -import { ToolkitError } from '../errors' -import { InterfaceNoSymbol } from '../utilities/tsUtils' -import * as DocDB from '@aws-sdk/client-docdb' -import * as DocDBElastic from '@aws-sdk/client-docdb-elastic' - -function isElasticCluster(clusterId: string | undefined): boolean | undefined { - return clusterId?.includes(':docdb-elastic:') -} - -export const DocDBEngine = 'docdb' -export const DBStorageType = { Standard: 'standard', IOpt1: 'iopt1' } as const -export const MaxInstanceCount = 16 - -/** A list of Amazon DocumentDB clusters. */ -export type DBElasticCluster = DocDBElastic.Cluster & DocDBElastic.ClusterInList - -export interface DBInstance extends DocDB.DBInstance { - IsClusterWriter?: boolean -} - -export type DocumentDBClient = InterfaceNoSymbol - -export class DefaultDocumentDBClient { - static create(regionCode: string): DocumentDBClient { - return new DefaultDocumentDBClient(regionCode) - } - - private constructor(public readonly regionCode: string) {} - - private async getSdkConfig() { - const credentials = await globals.awsContext.getCredentials() - const endpointUrl = globals.awsContext.getCredentialEndpointUrl() - const config = { - customUserAgent: getUserAgent({ includePlatform: true, includeClientId: true }), - credentials: credentials, - region: this.regionCode, - } - if (endpointUrl !== undefined) { - return { ...config, endpoint: endpointUrl } - } - return config - } - - public async getClient(): Promise { - const config = await this.getSdkConfig() - return new DocDB.DocDBClient(config) - } - - public async getElasticClient(): Promise { - const config = await this.getSdkConfig() - return new DocDBElastic.DocDBElasticClient(config) - } - - private async executeCommand(command: any): Promise { - getLogger().debug(`docdbClient: ${command.constructor.name} called`) - const client = await this.getClient() - try { - return await client.send(command) - } finally { - client.destroy() - } - } - - private async executeElasticCommand( - command: any - ): Promise { - getLogger().debug(`docdbClient: ${command.constructor.name} called`) - const client = await this.getElasticClient() - try { - return await client.send(command) - } finally { - client.destroy() - } - } - - // Ideally, we would return AsyncCollection or iterator - public async listInstanceClassOptions( - engineVersion: string | undefined, - storageType: string | undefined - ): Promise { - getLogger().debug('docdbClient: ListInstanceClassOptions called') - const client = await this.getClient() - - try { - const instanceClasses: DocDB.OrderableDBInstanceOption[] = [] - const input = { - Engine: DocDBEngine, - EngineVersion: engineVersion, - } - const paginator = DocDB.paginateDescribeOrderableDBInstanceOptions({ client }, input) - for await (const page of paginator) { - instanceClasses.push(...(page.OrderableDBInstanceOptions ?? [])) - } - - return instanceClasses.filter((ic) => storageType === ic.StorageType || storageType === undefined) - } catch (e) { - throw ToolkitError.chain(e, 'Failed to get instance classes') - } finally { - client.destroy() - } - } - - public async listEngineVersions(): Promise { - const command = new DocDB.DescribeDBEngineVersionsCommand({ Engine: DocDBEngine }) - const response = await this.executeCommand(command) - return response.DBEngineVersions ?? [] - } - - public async listGlobalClusters(clusterId: string | undefined = undefined): Promise { - const input: DocDB.DescribeGlobalClustersCommandInput = { - Filters: [], - GlobalClusterIdentifier: clusterId, - } - const command = new DocDB.DescribeGlobalClustersCommand(input) - const response = await this.executeCommand(command) - return response.GlobalClusters ?? [] - } - - public async listElasticClusters(): Promise { - const command = new DocDBElastic.ListClustersCommand() - const response = await this.executeElasticCommand(command) - return response.clusters ?? [] - } - - public async listClusters(clusterId: string | undefined = undefined): Promise { - const input: DocDB.DescribeDBClustersCommandInput = { - Filters: [{ Name: 'engine', Values: [DocDBEngine] }], - DBClusterIdentifier: clusterId, - } - const command = new DocDB.DescribeDBClustersCommand(input) - const response = await this.executeCommand(command) - return response.DBClusters ?? [] - } - - public async listInstances(clusters: string[] = []): Promise { - const input: DocDB.DescribeDBInstancesCommandInput = {} - if (clusters?.length > 0) { - input.Filters = [{ Name: 'db-cluster-id', Values: clusters }] - } - const command = new DocDB.DescribeDBInstancesCommand(input) - const response = await this.executeCommand(command) - return response.DBInstances ?? [] - } - - public async getElasticCluster(clusterArn: string): Promise { - const command = new DocDBElastic.GetClusterCommand({ clusterArn }) - const response = await this.executeElasticCommand(command) - return response.cluster - } - - public async createCluster(input: DocDB.CreateDBClusterMessage): Promise { - const command = new DocDB.CreateDBClusterCommand(input) - const response = await this.executeCommand(command) - return response.DBCluster - } - - public async createElasticCluster( - input: DocDBElastic.CreateClusterInput - ): Promise { - const command = new DocDBElastic.CreateClusterCommand(input) - const response = await this.executeElasticCommand(command) - return response.cluster - } - - public async createGlobalCluster( - input: DocDB.CreateGlobalClusterCommandInput - ): Promise { - const command = new DocDB.CreateGlobalClusterCommand(input) - const response = await this.executeCommand(command) - return response.GlobalCluster - } - - public async modifyGlobalCluster( - input: DocDB.ModifyGlobalClusterCommandInput - ): Promise { - const command = new DocDB.ModifyGlobalClusterCommand(input) - const response = await this.executeCommand(command) - return response.GlobalCluster - } - - public async createClusterSnapshot( - input: DocDBElastic.CreateClusterSnapshotInput - ): Promise { - const command = new DocDBElastic.CreateClusterSnapshotCommand(input) - const response = await this.executeElasticCommand(command) - return response.snapshot - } - - public async modifyCluster(input: DocDB.ModifyDBClusterMessage): Promise { - const command = new DocDB.ModifyDBClusterCommand(input) - const response = await this.executeCommand(command) - return response.DBCluster - } - - public async deleteCluster(input: DocDB.DeleteDBClusterMessage): Promise { - const command = new DocDB.DeleteDBClusterCommand(input) - const response = await this.executeCommand(command) - return response.DBCluster - } - - public async deleteElasticCluster(clusterArn: string): Promise { - const command = new DocDBElastic.DeleteClusterCommand({ clusterArn }) - const response = await this.executeElasticCommand(command) - return response.cluster - } - - public async getInstance(instanceId: string): Promise { - const input: DocDB.DescribeDBInstancesCommandInput = { - DBInstanceIdentifier: instanceId, - } - const command = new DocDB.DescribeDBInstancesCommand(input) - const response = await this.executeCommand(command) - return response.DBInstances ? response.DBInstances[0] : undefined - } - - public async createInstance(input: DocDB.CreateDBInstanceMessage): Promise { - const command = new DocDB.CreateDBInstanceCommand(input) - const response = await this.executeCommand(command) - return response.DBInstance - } - - public async modifyInstance(input: DocDB.ModifyDBInstanceMessage): Promise { - const command = new DocDB.ModifyDBInstanceCommand(input) - const response = await this.executeCommand(command) - return response.DBInstance - } - - public async deleteInstance(input: DocDB.DeleteDBInstanceMessage): Promise { - const command = new DocDB.DeleteDBInstanceCommand(input) - const response = await this.executeCommand(command) - return response.DBInstance - } - - public async rebootInstance(instanceId: string): Promise { - const command = new DocDB.RebootDBInstanceCommand({ DBInstanceIdentifier: instanceId }) - const response = await this.executeCommand(command) - return !!response.DBInstance - } - - public async listResourceTags(arn: string): Promise | undefined> { - if (isElasticCluster(arn)) { - const command = new DocDBElastic.ListTagsForResourceCommand({ resourceArn: arn }) - const response = await this.executeElasticCommand(command) - return response.tags - } else { - const command = new DocDB.ListTagsForResourceCommand({ ResourceName: arn }) - const response = await this.executeCommand(command) - const tagMap = response.TagList?.reduce( - (map, tag) => { - map[tag.Key!] = tag.Value - return map - }, - {} as Record - ) - return tagMap - } - } - - public async addResourceTags(input: DocDBElastic.TagResourceCommandInput): Promise { - if (isElasticCluster(input.resourceArn)) { - const command = new DocDBElastic.TagResourceCommand(input) - await this.executeElasticCommand(command) - } else { - const command = new DocDB.AddTagsToResourceCommand({ - ResourceName: input.resourceArn, - Tags: Object.entries(input.tags ?? {}).map(([Key, Value]) => ({ Key, Value })), - }) - await this.executeCommand(command) - } - } - - public async removeResourceTags(input: DocDBElastic.UntagResourceCommandInput): Promise { - if (isElasticCluster(input.resourceArn)) { - const command = new DocDBElastic.UntagResourceCommand(input) - await this.executeElasticCommand(command) - } else { - const command = new DocDB.RemoveTagsFromResourceCommand({ - ResourceName: input.resourceArn, - TagKeys: input.tagKeys, - }) - await this.executeCommand(command) - } - } - - public async startCluster(clusterId: string) { - if (isElasticCluster(clusterId)) { - const command = new DocDBElastic.StartClusterCommand({ clusterArn: clusterId }) - await this.executeElasticCommand(command) - } else { - const command = new DocDB.StartDBClusterCommand({ DBClusterIdentifier: clusterId }) - await this.executeCommand(command) - } - } - - public async stopCluster(clusterId: string) { - if (isElasticCluster(clusterId)) { - const command = new DocDBElastic.StopClusterCommand({ clusterArn: clusterId }) - await this.executeElasticCommand(command) - } else { - const command = new DocDB.StopDBClusterCommand({ DBClusterIdentifier: clusterId }) - await this.executeCommand(command) - } - } -} diff --git a/packages/core/src/shared/clients/ecrClient.ts b/packages/core/src/shared/clients/ecrClient.ts deleted file mode 100644 index f5e03d4db7..0000000000 --- a/packages/core/src/shared/clients/ecrClient.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - ECRClient, - DescribeImagesCommand, - DescribeRepositoriesCommand, - CreateRepositoryCommand, - DeleteRepositoryCommand, - BatchDeleteImageCommand, -} from '@aws-sdk/client-ecr' -import type { DescribeImagesRequest, DescribeRepositoriesRequest, Repository } from '@aws-sdk/client-ecr' -import globals from '../extensionGlobals' -import { AsyncCollection } from '../utilities/asyncCollection' -import { pageableToCollection } from '../utilities/collectionUtils' -import { assertHasProps, ClassToInterfaceType, isNonNullable, RequiredProps } from '../utilities/tsUtils' - -export type EcrRepository = RequiredProps - -export type EcrClient = ClassToInterfaceType -export class DefaultEcrClient { - public constructor(public readonly regionCode: string) {} - - public async *describeTags(repositoryName: string): AsyncIterableIterator { - const sdkClient = this.createSdkClient() - const request: DescribeImagesRequest = { repositoryName: repositoryName } - do { - const response = await sdkClient.send(new DescribeImagesCommand(request)) - if (response.imageDetails) { - for (const item of response.imageDetails) { - if (item.imageTags !== undefined) { - for (const tag of item.imageTags) { - yield tag - } - } - } - } - request.nextToken = response.nextToken - } while (request.nextToken) - } - - public async *describeRepositories(): AsyncIterableIterator { - const sdkClient = this.createSdkClient() - const request: DescribeRepositoriesRequest = {} - do { - const response = await sdkClient.send(new DescribeRepositoriesCommand(request)) - if (response.repositories) { - yield* response.repositories - .map((repo: Repository) => { - // If any of these are not present, the repo returned is not valid. repositoryUri/Arn - // are both based on name, and it's not possible to not have a name - if (!repo.repositoryArn || !repo.repositoryName || !repo.repositoryUri) { - return undefined - } else { - return { - repositoryName: repo.repositoryName, - repositoryUri: repo.repositoryUri, - repositoryArn: repo.repositoryArn, - } - } - }) - .filter((item: EcrRepository | undefined) => item !== undefined) as EcrRepository[] - } - request.nextToken = response.nextToken - } while (request.nextToken) - } - - public listAllRepositories(): AsyncCollection { - const requester = async (req: DescribeRepositoriesRequest) => - this.createSdkClient().send(new DescribeRepositoriesCommand(req)) - const collection = pageableToCollection(requester, {}, 'nextToken', 'repositories') - - return collection - .filter(isNonNullable) - .map((list: Repository[]) => list.map((repo: Repository) => (assertHasProps(repo), repo))) - } - - public async createRepository(repositoryName: string) { - const sdkClient = this.createSdkClient() - return sdkClient.send(new CreateRepositoryCommand({ repositoryName: repositoryName })) - } - - public async deleteRepository(repositoryName: string): Promise { - const sdkClient = this.createSdkClient() - await sdkClient.send(new DeleteRepositoryCommand({ repositoryName: repositoryName })) - } - - public async deleteTag(repositoryName: string, tag: string): Promise { - const sdkClient = this.createSdkClient() - await sdkClient.send( - new BatchDeleteImageCommand({ repositoryName: repositoryName, imageIds: [{ imageTag: tag }] }) - ) - } - - protected createSdkClient(): ECRClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: ECRClient, - clientOptions: { region: this.regionCode }, - }) - } -} diff --git a/packages/core/src/shared/clients/ecsClient.ts b/packages/core/src/shared/clients/ecsClient.ts deleted file mode 100644 index 818cdc0dec..0000000000 --- a/packages/core/src/shared/clients/ecsClient.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - Cluster, - DescribeClustersCommand, - DescribeServicesCommand, - DescribeTaskDefinitionCommand, - DescribeTaskDefinitionResponse, - DescribeTasksCommand, - DescribeTasksRequest, - ECSClient, - ExecuteCommandCommand, - ExecuteCommandRequest, - ExecuteCommandResponse, - ListClustersCommand, - ListClustersRequest, - ListServicesCommand, - ListServicesRequest, - ListTasksCommand, - ListTasksRequest, - RegisterTaskDefinitionCommand, - RegisterTaskDefinitionRequest, - Service, - Task, - UpdateServiceCommand, - UpdateServiceRequest, -} from '@aws-sdk/client-ecs' -import globals from '../extensionGlobals' -import { AsyncCollection } from '../utilities/asyncCollection' -import { pageableToCollection } from '../utilities/collectionUtils' -import { ClassToInterfaceType, isNonNullable } from '../utilities/tsUtils' - -export type EcsClient = ClassToInterfaceType - -export type EcsResourceAndToken = { - resource: Cluster[] | Service[] - nextToken?: string -} - -const maxResultsPerResponse = 10 -export class DefaultEcsClient { - public constructor(public readonly regionCode: string) {} - - public async getClusters(nextToken?: string): Promise { - const sdkClient = this.createSdkClient() - const clusterArnList = await sdkClient.send( - new ListClustersCommand({ maxResults: maxResultsPerResponse, nextToken }) - ) - if (clusterArnList.clusterArns?.length === 0) { - return { resource: [] } - } - const clusterResponse = await sdkClient.send( - new DescribeClustersCommand({ clusters: clusterArnList.clusterArns }) - ) - const response: EcsResourceAndToken = { - resource: clusterResponse.clusters!, - nextToken: clusterArnList.nextToken, - } - return response - } - - public listClusters(request: ListClustersRequest = {}): AsyncCollection { - const client = this.createSdkClient() - const requester = async (req: ListClustersRequest) => client.send(new ListClustersCommand(req)) - const collection = pageableToCollection(requester, request, 'nextToken', 'clusterArns') - - return collection.filter(isNonNullable).map(async (clusters) => { - if (clusters.length === 0) { - return [] - } - - const resp = await client.send(new DescribeClustersCommand({ clusters })) - return resp.clusters! - }) - } - - public async getServices(cluster: string, nextToken?: string): Promise { - const sdkClient = this.createSdkClient() - const serviceArnList = await sdkClient.send( - new ListServicesCommand({ cluster: cluster, maxResults: maxResultsPerResponse, nextToken }) - ) - if (serviceArnList.serviceArns?.length === 0) { - return { resource: [] } - } - const services = await this.describeServices(cluster, serviceArnList.serviceArns!) - const response: EcsResourceAndToken = { - resource: services, - nextToken: serviceArnList.nextToken, - } - return response - } - - public listServices(request: ListServicesRequest = {}): AsyncCollection { - const client = this.createSdkClient() - const requester = async (req: ListServicesRequest) => client.send(new ListServicesCommand(req)) - const collection = pageableToCollection(requester, request, 'nextToken', 'serviceArns') - - return collection.filter(isNonNullable).map(async (services) => { - if (services.length === 0) { - return [] - } - - const resp = await client.send(new DescribeServicesCommand({ cluster: request.cluster, services })) - return resp.services! - }) - } - - public async describeTaskDefinition(taskDefinition: string): Promise { - const sdkClient = this.createSdkClient() - return await sdkClient.send(new DescribeTaskDefinitionCommand({ taskDefinition })) - } - - public async listTasks(args: ListTasksRequest): Promise { - const sdkClient = this.createSdkClient() - const listTasksResponse = await sdkClient.send(new ListTasksCommand(args)) - return listTasksResponse.taskArns ?? [] - } - - public async updateService(request: UpdateServiceRequest): Promise { - const sdkClient = this.createSdkClient() - await sdkClient.send(new UpdateServiceCommand(request)) - } - - public async describeTasks(cluster: string, tasks: string[]): Promise { - const sdkClient = this.createSdkClient() - - const params: DescribeTasksRequest = { cluster, tasks } - const describedTasks = await sdkClient.send(new DescribeTasksCommand(params)) - return describedTasks.tasks ?? [] - } - - public async describeServices(cluster: string, services: string[]): Promise { - const sdkClient = this.createSdkClient() - return (await sdkClient.send(new DescribeServicesCommand({ cluster, services }))).services ?? [] - } - - protected createSdkClient(): ECSClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: ECSClient, - clientOptions: { region: this.regionCode }, - }) - } - - public async executeCommand(request: Omit): Promise { - const sdkClient = this.createSdkClient() - - // Currently the 'interactive' flag is required and needs to be true for ExecuteCommand: https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ExecuteCommand.html - // This may change 'in the near future' as explained here: https://aws.amazon.com/blogs/containers/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/ - return await sdkClient.send(new ExecuteCommandCommand({ ...request, interactive: true })) - } - - public async registerTaskDefinition(request: RegisterTaskDefinitionRequest) { - const sdkClient = this.createSdkClient() - return sdkClient.send(new RegisterTaskDefinitionCommand(request)) - } -} diff --git a/packages/core/src/shared/clients/iotClient.ts b/packages/core/src/shared/clients/iotClient.ts deleted file mode 100644 index fc5581fffa..0000000000 --- a/packages/core/src/shared/clients/iotClient.ts +++ /dev/null @@ -1,580 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as _ from 'lodash' -import { - AttachPolicyCommand, - AttachPolicyRequest, - AttachThingPrincipalCommand, - AttachThingPrincipalRequest, - CertificateDescription, - CreateKeysAndCertificateCommand, - CreateKeysAndCertificateRequest, - CreateKeysAndCertificateResponse, - CreatePolicyCommand, - CreatePolicyRequest, - CreatePolicyResponse, - CreatePolicyVersionCommand, - CreatePolicyVersionRequest, - CreateThingCommand, - CreateThingRequest, - CreateThingResponse, - DeleteCertificateCommand, - DeleteCertificateRequest, - DeletePolicyCommand, - DeletePolicyRequest, - DeletePolicyVersionCommand, - DeletePolicyVersionRequest, - DeleteThingCommand, - DeleteThingRequest, - DescribeCertificateCommand, - DescribeCertificateRequest, - DescribeCertificateResponse, - DescribeEndpointCommand, - DetachPolicyCommand, - DetachPolicyRequest, - DetachThingPrincipalCommand, - DetachThingPrincipalRequest, - GetPolicyVersionCommand, - GetPolicyVersionRequest, - GetPolicyVersionResponse, - IoTClient, - ListCertificatesCommand, - ListCertificatesRequest, - ListCertificatesResponse, - ListPoliciesCommand, - ListPoliciesRequest, - ListPoliciesResponse, - ListPolicyVersionsCommand, - ListPolicyVersionsRequest, - ListPrincipalPoliciesCommand, - ListPrincipalPoliciesRequest, - ListPrincipalPoliciesResponse, - ListPrincipalThingsCommand, - ListPrincipalThingsRequest, - ListTargetsForPolicyCommand, - ListTargetsForPolicyRequest, - ListThingPrincipalsCommand, - ListThingPrincipalsRequest, - ListThingPrincipalsResponse, - ListThingsCommand, - ListThingsRequest, - ListThingsResponse, - PolicyVersion, - SetDefaultPolicyVersionCommand, - SetDefaultPolicyVersionRequest, - UpdateCertificateCommand, - UpdateCertificateRequest, -} from '@aws-sdk/client-iot' -import { parse } from '@aws-sdk/util-arn-parser' -import { getLogger } from '../logger/logger' -import { InterfaceNoSymbol } from '../utilities/tsUtils' -import globals from '../extensionGlobals' - -const defaultMaxThings = 250 // 250 is the maximum allowed by the API - -/* ATS is recommended over the deprecated Verisign certificates */ -const iotEndpointType = 'iot:Data-ATS' - -export type IotThing = { readonly name: string; readonly arn: string } -export type IotCertificate = { - readonly id: string - readonly arn: string - readonly activeStatus: string - readonly creationDate: Date -} -export type IotPolicy = IotThing -export type IotClient = InterfaceNoSymbol - -const iotServiceArn = 'iot' -// Pattern to extract the certificate ID from the parsed ARN resource. -const certArnResourcePattern = /cert\/(\w+)/ - -export interface ListThingCertificatesResponse { - readonly certificates: CertificateDescription[] - readonly nextToken: string | undefined -} - -export class DefaultIotClient { - public constructor( - private readonly regionCode: string, - private readonly iotProvider: (regionCode: string) => IoTClient = createSdkClient - ) {} - - /** - * Lists Things owned by the client. - * - * @throws Error if there is an error calling IoT. - */ - public async listThings(request?: ListThingsRequest): Promise { - getLogger().debug('ListThings called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: ListThingsResponse = await iot.send( - new ListThingsCommand({ - maxResults: request?.maxResults ?? defaultMaxThings, - nextToken: request?.nextToken, - }) - ) - - getLogger().debug('ListThings returned response: %O', output) - return output - } - - /** - * Creates an IoT Thing. - * - * @throws Error if there is an error calling IoT. - */ - public async createThing(request: CreateThingRequest): Promise { - getLogger().debug('CreateThing called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: CreateThingResponse = await iot.send(new CreateThingCommand({ thingName: request.thingName })) - - getLogger().debug('CreateThing returned response: %O', output) - return output - } - - /** - * Deletes an IoT Thing. - * - * @throws Error if there is an error calling IoT. - */ - public async deleteThing(request: DeleteThingRequest): Promise { - getLogger().debug('DeleteThing called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DeleteThingCommand({ thingName: request.thingName })) - - getLogger().debug('DeleteThing successful') - } - - /** - * Lists all IoT certificates in account. - * - * @throws Error if there is an error calling IoT. - */ - public async listCertificates(request: ListCertificatesRequest): Promise { - getLogger().debug('ListCertificates called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: ListCertificatesResponse = await iot.send( - new ListCertificatesCommand({ - pageSize: request.pageSize ?? defaultMaxThings, - marker: request.marker, - ascendingOrder: request.ascendingOrder, - }) - ) - - getLogger().debug('ListCertificates returned response: %O', output) - return output - } - - /** - * Lists all principals attached to IoT Thing. - * - * Returns ARNS of principals that may be X.509 certificates, IAM - * users/groups/roles, or Amazon Cognito identities. - * - * @throws Error if there is an error calling IoT. - */ - public async listThingPrincipals(request: ListThingPrincipalsRequest): Promise { - const iot = this.iotProvider(this.regionCode) - - const output: ListThingPrincipalsResponse = await iot.send( - new ListThingPrincipalsCommand({ - thingName: request.thingName, - maxResults: request.maxResults ?? defaultMaxThings, - nextToken: request.nextToken, - }) - ) - return output - } - - /** - * Describes a certificate given the certificate ID. - * - * @throws Error if there is an error calling IoT. - */ - private async describeCertificate(request: DescribeCertificateRequest): Promise { - const iot = this.iotProvider(this.regionCode) - - const output: DescribeCertificateResponse = await iot.send(new DescribeCertificateCommand(request)) - - return output - } - - /** - * Lists all IoT certificates attached to IoT Thing. - * - * listThingPrincipals() returns ARNS of principals that may be X.509 - * certificates, IAM users/groups/roles, or Amazon Cognito identities. - * The list is filtered for certificates only, and describeCertificate() - * is called to get the information for each certificate. - * - * @throws Error if there is an error calling IoT. - */ - public async listThingCertificates(request: ListThingPrincipalsRequest): Promise { - getLogger().debug('ListThingCertificates called with request: %O', request) - - const output = await this.listThingPrincipals(request) - const iotPrincipals: string[] = output.principals ?? [] - const nextToken = output.nextToken - - const describedCerts = iotPrincipals.map(async (iotPrincipal) => { - const principalArn = parse(iotPrincipal) - const certIdFound = principalArn.resource.match(certArnResourcePattern) - if (principalArn.service !== iotServiceArn || !certIdFound) { - return undefined - } - const certId = certIdFound[1] - return this.describeCertificate({ certificateId: certId }) - }) - - const resolvedCerts = (await Promise.all(describedCerts)) - .filter((cert) => cert?.certificateDescription !== undefined) - .map((cert) => cert?.certificateDescription as CertificateDescription) - - const response: ListThingCertificatesResponse = { certificates: resolvedCerts, nextToken: nextToken } - getLogger().debug('ListThingCertificates returned response: %O', response) - return { certificates: resolvedCerts, nextToken: nextToken } - } - - /** - * Lists Things attached to specified certificate. - * - * The nextToken output is discarded, since this function is only used - * to determine if the number of attached Things is nonzero. - * - * @throws Error if there is an error calling IoT. - */ - public async listThingsForCert(request: ListPrincipalThingsRequest): Promise { - getLogger().debug('ListThingsForCert called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output = await iot.send( - new ListPrincipalThingsCommand({ - maxResults: request.maxResults ?? defaultMaxThings, - nextToken: request.nextToken, - principal: request.principal, - }) - ) - const iotThings: string[] = output.things ?? [] - - getLogger().debug('ListThingsForCert returned response: %O', iotThings) - return iotThings - } - - /** - * Creates an X.509 certificate with a 2048 bit RSA keypair. - * - * @throws Error if there is an error calling IoT. - */ - public async createCertificateAndKeys( - request: CreateKeysAndCertificateRequest - ): Promise { - getLogger().debug('CreateCertificate called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: CreateKeysAndCertificateResponse = await iot.send(new CreateKeysAndCertificateCommand(request)) - - getLogger().debug('CreateCertificate succeeded') - return output - } - - /** - * Activates, deactivates, or revokes an IoT Certificate. - * - * @throws Error if there is an error calling IoT. - */ - public async updateCertificate(request: UpdateCertificateRequest): Promise { - getLogger().debug('UpdateCertificate called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send( - new UpdateCertificateCommand({ certificateId: request.certificateId, newStatus: request.newStatus }) - ) - - getLogger().debug('UpdateCertificate successful') - } - - /** - * Deletes the specified IoT Certificate. - * - * Note that a certificate cannot be deleted if it is ACTIVE, or has attached - * Things or policies. A certificate may be force deleted if it is INACTIVE - * and has no attached Things. - * - * @throws Error if there is an error calling IoT. - */ - public async deleteCertificate(request: DeleteCertificateRequest): Promise { - getLogger().debug('DeleteCertificate called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DeleteCertificateCommand(request)) - - getLogger().debug('DeleteCertificate successful') - } - - /** - * Attaches the certificate specified by the principal to the specified Thing. - * - * @throws Error if there is an error calling IoT. - */ - public async attachThingPrincipal(request: AttachThingPrincipalRequest): Promise { - getLogger().debug('AttachThingPrincipal called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new AttachThingPrincipalCommand({ thingName: request.thingName, principal: request.principal })) - - getLogger().debug('AttachThingPrincipal successful') - } - - /** - * Detaches the certificate specified by the principal from the specified Thing. - * - * @throws Error if there is an error calling IoT. - */ - public async detachThingPrincipal(request: DetachThingPrincipalRequest): Promise { - getLogger().debug('DetachThingPrincipal called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DetachThingPrincipalCommand({ thingName: request.thingName, principal: request.principal })) - - getLogger().debug('DetachThingPrincipal successful') - } - - /** - * Lists all IoT Policies. - * - * @throws Error if there is an error calling IoT. - */ - public async listPolicies(request: ListPoliciesRequest): Promise { - getLogger().debug('ListPolicies called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: ListPoliciesResponse = await iot.send( - new ListPoliciesCommand({ - pageSize: request.pageSize ?? defaultMaxThings, - marker: request.marker, - ascendingOrder: request.ascendingOrder, - }) - ) - - getLogger().debug('ListPolicies returned response: %O', output) - return output - } - - /** - * Lists IoT policies for principal. - * - * @throws Error if there is an error calling IoT. - */ - public async listPrincipalPolicies(request: ListPrincipalPoliciesRequest): Promise { - getLogger().debug('ListPrincipalPolicies called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: ListPrincipalPoliciesResponse = await iot.send( - new ListPrincipalPoliciesCommand({ - principal: request.principal, - pageSize: request.pageSize ?? defaultMaxThings, - marker: request.marker, - ascendingOrder: request.ascendingOrder, - }) - ) - - getLogger().debug('ListPrincipalPolicies returned response: %O', output) - return output - } - - /** - * Lists certificates attached to specified policy. - * - * The nextMarker output value is discarded, since this function is only - * used to determine if the number of attached targets is nonzero. - * - * @throws Error if there is an error calling IoT. - */ - public async listPolicyTargets(request: ListTargetsForPolicyRequest): Promise { - getLogger().debug('ListPolicyTargets called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output = await iot.send( - new ListTargetsForPolicyCommand({ - pageSize: request.pageSize ?? defaultMaxThings, - marker: request.marker, - policyName: request.policyName, - }) - ) - const arns: string[] = output.targets ?? [] - - getLogger().debug('ListPolicyTargets returned response: %O', arns) - return arns - } - - /** - * Attaches the specified policy to the specified target certificate. - * - * @throws Error if there is an error calling IoT. - */ - public async attachPolicy(request: AttachPolicyRequest): Promise { - getLogger().debug('AttachPolicy called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new AttachPolicyCommand({ policyName: request.policyName, target: request.target })) - - getLogger().debug('AttachPolicy successful') - } - - /** - * Detaches the specified policy to the specified target certificate. - * - * @throws Error if there is an error calling IoT. - */ - public async detachPolicy(request: DetachPolicyRequest): Promise { - getLogger().debug('DetachPolicy called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DetachPolicyCommand({ policyName: request.policyName, target: request.target })) - - getLogger().debug('DetachPolicy successful') - } - - /** - * Creates an policy from the given policy document. - * - * @throws Error if there is an error calling IoT. - */ - public async createPolicy(request: CreatePolicyRequest): Promise { - getLogger().debug('CreatePolicy called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: CreatePolicyResponse = await iot.send(new CreatePolicyCommand(request)) - getLogger().info(`Created policy: ${output.policyArn}`) - - getLogger().debug('CreatePolicy successful') - } - - /** - * Deletes an IoT Policy. - * - * Note that a policy cannot be deleted if it is attached to a certificate, - * or has non-default versions. A policy with non default versions must first - * delete versions with deletePolicyVersions() - * - * @throws Error if there is an error calling IoT. - */ - public async deletePolicy(request: DeletePolicyRequest): Promise { - getLogger().debug('DeletePolicy called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DeletePolicyCommand({ policyName: request.policyName })) - - getLogger().debug('DeletePolicy successful') - } - - /** - * Retrieves the account's IoT device data endpoint. - * - * @throws Error if there is an error calling IoT. - */ - public async getEndpoint(): Promise { - getLogger().debug('GetEndpoint called') - const iot = this.iotProvider(this.regionCode) - - const output = await iot.send(new DescribeEndpointCommand({ endpointType: iotEndpointType })) - if (!output.endpointAddress) { - throw new Error('Failed to retrieve endpoint') - } - - getLogger().debug('GetEndpoint successful') - return output.endpointAddress - } - - /** - * Lists versions for an IoT Policy - * - * @throws Error if there is an error calling IoT. - */ - public async *listPolicyVersions(request: ListPolicyVersionsRequest): AsyncIterableIterator { - const iot = this.iotProvider(this.regionCode) - - const response = await iot.send(new ListPolicyVersionsCommand(request)) - - if (response.policyVersions) { - yield* response.policyVersions - } - } - - /** - * Creates a new version for an IoT policy - * - * @throws Error if there is an error calling IoT. - */ - public async createPolicyVersion(request: CreatePolicyVersionRequest): Promise { - getLogger().debug('CreatePolicyVersion called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output = await iot.send(new CreatePolicyVersionCommand(request)) - getLogger().info(`Created new version ${output.policyVersionId} of ${request.policyName}`) - - getLogger().debug('CreatePolicyVersion successful') - } - - /** - * Deletes an IoT Policy version. - * - * Note that a policy cannot be deleted if it is attached to a certificate, - * or has non-default versions. A policy with non default versions must first - * delete versions with deletePolicyVersions() - * - * @throws Error if there is an error calling IoT. - */ - public async deletePolicyVersion(request: DeletePolicyVersionRequest): Promise { - getLogger().debug('DeletePolicyVersion called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new DeletePolicyVersionCommand(request)) - - getLogger().debug('DeletePolicyVersion successful') - } - - /** - * Sets a default version for an Iot Policy. - * - * @throws Error if there is an error calling IoT. - */ - public async setDefaultPolicyVersion(request: SetDefaultPolicyVersionRequest): Promise { - getLogger().debug('SetDefaultPolicyVersion called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - await iot.send(new SetDefaultPolicyVersionCommand(request)) - - getLogger().debug('SetDefaultPolicyVersion successful') - } - - /** - * Downloads information including document for a specified policy version. - * - * @throws Error if there is an error calling IoT. - */ - public async getPolicyVersion(request: GetPolicyVersionRequest): Promise { - getLogger().debug('GetPolicyVersion called with request: %O', request) - const iot = this.iotProvider(this.regionCode) - - const output: GetPolicyVersionResponse = await iot.send(new GetPolicyVersionCommand(request)) - - getLogger().debug('GetPolicyVersion successful') - return output - } -} - -function createSdkClient(regionCode: string): IoTClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: IoTClient, - clientOptions: { region: regionCode }, - }) -} diff --git a/packages/core/src/shared/clients/kubectlClient.ts b/packages/core/src/shared/clients/kubectlClient.ts deleted file mode 100644 index a374927bda..0000000000 --- a/packages/core/src/shared/clients/kubectlClient.ts +++ /dev/null @@ -1,270 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as k8s from '@kubernetes/client-node' -import { getLogger } from '../logger/logger' -import { Cluster } from '@aws-sdk/client-eks' -import { SagemakerDevSpaceNode } from '../../awsService/sagemaker/explorer/sagemakerDevSpaceNode' -import globals from '../extensionGlobals' - -export interface HyperpodDevSpace { - name: string - namespace: string - cluster: string - group: string - version: string - plural: string - status: string - appType: string - creator: string - accessType: string -} - -export interface HyperpodCluster { - clusterName: string - clusterArn: string - status: string - eksClusterName?: string - eksClusterArn?: string - regionCode: string -} - -export class KubectlClient { - private kubeConfig: k8s.KubeConfig - private k8sApi: k8s.CustomObjectsApi - - public constructor(eksCluster: Cluster, hyperpodCluster: HyperpodCluster) { - this.kubeConfig = new k8s.KubeConfig() - this.loadKubeConfig(eksCluster, hyperpodCluster) - this.k8sApi = this.kubeConfig.makeApiClient(k8s.CustomObjectsApi) - } - - async getSpacesForCluster(eksCluster: Cluster): Promise { - try { - const group = 'workspace.jupyter.org' - const version = 'v1alpha1' - const plural = 'workspaces' - - const res = await this.k8sApi!.listClusterCustomObject(group, version, plural) - if (!res) { - getLogger().info(`No cluster custom object found`) - return [] - } - - if ((res as any).body?.items) { - return (res as any).body.items.map((space: any) => ({ - name: space.metadata?.name, - namespace: space.metadata?.namespace, - cluster: eksCluster.name, - status: this.getStatusFromConditions(space.status?.conditions, space.spec?.desiredStatus), - group, - version, - plural, - appType: space.spec?.appType, - creator: space.metadata?.annotations['workspace.jupyter.org/created-by'], - accessType: space.spec?.accessType, - })) - } - } catch (error: any) { - if (error.statusCode === 403 || error.statusCode === 401) { - void vscode.window.showErrorMessage( - `You do not have permission to view ${eksCluster.name} or its spaces. Please contact your administrator.` - ) - getLogger().warn( - `[Warning]: User has insufficient permissions to view EKS cluster (${eksCluster.name}) or its spaces.` - ) - } - - getLogger().warn( - `[Warning]: Unavailable spaces for EKS Cluster (${eksCluster.name}): ${error}\nStack trace: ${(error as Error).stack}` - ) - } - return [] - } - - private loadKubeConfig(eksCluster: Cluster, hyperpodCluster: HyperpodCluster): void { - if (eksCluster.name && eksCluster.endpoint) { - const credentialId = globals.awsContext.getCredentialProfileName() - const awsProfile = credentialId?.startsWith('profile:') ? credentialId.split('profile:')[1] : credentialId - this.kubeConfig.loadFromOptions({ - clusters: [ - { - name: eksCluster.name, - server: eksCluster.endpoint, - caData: eksCluster.certificateAuthority?.data, - skipTLSVerify: false, - }, - ], - users: [ - { - name: eksCluster.name, - exec: { - apiVersion: 'client.authentication.k8s.io/v1beta1', - command: 'aws', - args: [ - 'eks', - 'get-token', - '--cluster-name', - eksCluster.name, - '--region', - hyperpodCluster.regionCode, - ], - env: [ - { - name: 'AWS_PROFILE', - value: awsProfile, - }, - ], - interactiveMode: 'Never', - }, - }, - ], - contexts: [ - { - name: eksCluster.name, - cluster: eksCluster.name, - user: eksCluster.name, - }, - ], - currentContext: eksCluster.name, - }) - } - } - - async getHyperpodSpaceStatus(devSpace: HyperpodDevSpace): Promise { - try { - const response = await this.k8sApi!.getNamespacedCustomObject( - devSpace.group, - devSpace.version, - devSpace.namespace, - devSpace.plural, - devSpace.name - ) - - const statusObj = (response.body as any).status - const desiredStatus = (response.body as any).spec?.desiredStatus - const conditions = statusObj?.conditions - const currentStatus = this.getStatusFromConditions(conditions, desiredStatus) - - return currentStatus - } catch (error) { - throw new Error(`[Hyperpod] Failed to get status for devSpace: ${devSpace.name}`) - } - } - - private getStatusFromConditions(conditions: any[], desiredStatus?: string): string { - if (!conditions) { - return 'Unknown' - } - const getCondition = (type: string) => conditions.find((c: any) => c.type === type)?.status === 'True' - - const available = getCondition('Available') - const progressing = getCondition('Progressing') - const stopped = getCondition('Stopped') - const degraded = getCondition('Degraded') - - if (degraded) { - return 'Error' - } else if (!available && progressing && desiredStatus === 'Running') { - return 'Starting' - } else if (!available && progressing && desiredStatus === 'Stopped') { - return 'Stopping' - } else if (available && !progressing && !stopped) { - return 'Running' - } else if (!available && !progressing && stopped) { - return 'Stopped' - } else { - return 'Unknown' - } - } - - async startHyperpodDevSpace(node: SagemakerDevSpaceNode): Promise { - getLogger().info(`[Hyperpod] Starting devSpace: %s`, node.devSpace.name) - await this.patchDevSpaceStatus(node.devSpace, 'Running') - node.devSpace.status = await this.getHyperpodSpaceStatus(node.devSpace) - node.getParent().trackPendingNode(node.getDevSpaceKey()) - } - - async stopHyperpodDevSpace(node: SagemakerDevSpaceNode): Promise { - getLogger().info(`[Hyperpod] Stopping devSpace: %s`, node.devSpace.name) - await this.patchDevSpaceStatus(node.devSpace, 'Stopped') - node.devSpace.status = await this.getHyperpodSpaceStatus(node.devSpace) - node.getParent().trackPendingNode(node.getDevSpaceKey()) - } - - async patchDevSpaceStatus(devSpace: HyperpodDevSpace, desiredStatus: 'Running' | 'Stopped'): Promise { - try { - const patchBody = { - spec: { - desiredStatus: desiredStatus, - }, - } - - await this.k8sApi!.patchNamespacedCustomObject( - devSpace.group, - devSpace.version, - devSpace.namespace, - devSpace.plural, - devSpace.name, - patchBody, - undefined, - undefined, - undefined, - { headers: { 'Content-Type': 'application/merge-patch+json' } } - ) - } catch (error) { - throw new Error( - `[Hyperpod] Failed to update transitional status for devSpace ${devSpace.name}: ${(error as Error).message}` - ) - } - } - - async createWorkspaceConnection(devSpace: HyperpodDevSpace): Promise<{ type: string; url: string }> { - try { - getLogger().info(`[Hyperpod] Creating workspace connection for space: ${devSpace.name}`) - - const group = 'connection.workspace.jupyter.org' - const version = 'v1alpha1' - const plural = 'workspaceconnections' - - const workspaceConnection = { - apiVersion: `${group}/${version}`, - kind: 'WorkspaceConnection', - metadata: { - namespace: devSpace.namespace, - }, - spec: { - workspaceName: devSpace.name, - workspaceConnectionType: 'vscode-remote', - }, - } - - getLogger().info(`[Hyperpod] Creating WorkspaceConnection: %O`, workspaceConnection) - - const response = await this.k8sApi!.createNamespacedCustomObject( - group, - version, - devSpace.namespace, - plural, - workspaceConnection - ) - - const body = response.body as any - const presignedUrl = body.status?.workspaceConnectionUrl - const connectionType = body.status?.workspaceConnectionType - - getLogger().info(`Connection Type: ${connectionType}`) - getLogger().info(`Presigned URL: ${presignedUrl}`) - - return { type: connectionType || 'vscode-remote', url: presignedUrl } - } catch (error) { - getLogger().error(`[Hyperpod] Failed to create workspace connection: ${error}`) - throw new Error( - `Failed to create workspace connection: ${error instanceof Error ? error.message : String(error)}` - ) - } - } -} diff --git a/packages/core/src/shared/clients/lambdaClient.ts b/packages/core/src/shared/clients/lambdaClient.ts deleted file mode 100644 index 11ce274c44..0000000000 --- a/packages/core/src/shared/clients/lambdaClient.ts +++ /dev/null @@ -1,356 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { BlobPayloadInputTypes } from '@smithy/types' -import { ToolkitError } from '../errors' -import globals from '../extensionGlobals' -import { getLogger } from '../logger/logger' -import { ClassToInterfaceType } from '../utilities/tsUtils' - -import { - LambdaClient as LambdaSdkClient, - GetFunctionCommand, - GetFunctionCommandOutput, - FunctionConfiguration, - InvocationResponse, - ListFunctionsRequest, - ListFunctionsResponse, - GetFunctionResponse, - GetLayerVersionResponse, - ListLayerVersionsRequest, - LayerVersionsListItem, - ListLayerVersionsResponse, - UpdateFunctionConfigurationRequest, - FunctionUrlConfig, - GetFunctionConfigurationCommand, - PublishVersionCommand, - UpdateFunctionConfigurationCommand, - UpdateFunctionCodeCommand, - ListFunctionUrlConfigsCommand, - ListLayerVersionsCommand, - GetLayerVersionCommand, - ListFunctionsCommand, - DeleteFunctionCommand, - InvokeCommand, - waitUntilFunctionUpdatedV2, - waitUntilFunctionActiveV2, -} from '@aws-sdk/client-lambda' -import { CancellationError } from '../utilities/timeoutUtils' -import { fromSSO } from '@aws-sdk/credential-provider-sso' -import { getIAMConnection } from '../../auth/utils' -import { NodeHttpHandler } from '@smithy/node-http-handler' -import type { UserAgent } from '@aws-sdk/types' - -export type LambdaClient = ClassToInterfaceType - -export class DefaultLambdaClient { - private readonly defaultTimeoutInMs: number - - public constructor( - public readonly regionCode: string, - public readonly userAgent: UserAgent | undefined = undefined - ) { - this.defaultTimeoutInMs = 5 * 60 * 1000 // 5 minutes (SDK default is 2 minutes) - } - - public async deleteFunction(name: string, qualifier?: string): Promise { - const sdkClient = await this.createSdkClient() - - await sdkClient.send( - new DeleteFunctionCommand({ - FunctionName: name, - Qualifier: qualifier, - }) - ) - } - - public async invoke( - name: string, - payload?: BlobPayloadInputTypes, - version?: string, - logtype: 'Tail' | 'None' = 'Tail' - ): Promise { - const sdkClient = await this.createSdkClient() - - const response = await sdkClient.send( - new InvokeCommand({ - FunctionName: name, - LogType: logtype, - Payload: payload, - Qualifier: version, - }) - ) - - return response - } - - public async *listFunctions(): AsyncIterableIterator { - const client = await this.createSdkClient() - - const request: ListFunctionsRequest = {} - do { - const response: ListFunctionsResponse = await client.send(new ListFunctionsCommand(request)) - - if (response.Functions) { - yield* response.Functions - } - - request.Marker = response.NextMarker - } while (request.Marker) - } - - public async getFunction(name: string): Promise { - getLogger().debug(`GetFunction called for function: ${name}`) - const client = await this.createSdkClient() - - try { - const response = await client.send(new GetFunctionCommand({ FunctionName: name })) - // prune `Code` from logs so we don't reveal a signed link to customer resources. - getLogger().debug('GetFunction returned response (code section pruned): %O', { - ...response, - Code: 'Pruned', - }) - return response - } catch (e) { - getLogger().error('Failed to get function: %s', e) - throw e - } - } - - public async getLayerVersion(name: string, version: number): Promise { - getLogger().debug(`getLayerVersion called for LayerName: ${name}, VersionNumber ${version}`) - const client = await this.createSdkClient() - - try { - const response = await client.send(new GetLayerVersionCommand({ LayerName: name, VersionNumber: version })) - // prune `Code` from logs so we don't reveal a signed link to customer resources. - getLogger().debug('getLayerVersion returned response (code section pruned): %O', { - ...response, - Code: 'Pruned', - }) - return response - } catch (e) { - getLogger().error('Failed to get function: %s', e) - throw e - } - } - - public async *listLayerVersions(name: string): AsyncIterableIterator { - const client = await this.createSdkClient() - - const request: ListLayerVersionsRequest = { LayerName: name } - do { - const response: ListLayerVersionsResponse = await client.send(new ListLayerVersionsCommand(request)) - - if (response.LayerVersions) { - yield* response.LayerVersions - } - - request.Marker = response.NextMarker - } while (request.Marker) - } - - public async getFunctionUrlConfigs(name: string): Promise { - getLogger().debug(`GetFunctionUrlConfig called for function: ${name}`) - const client = await this.createSdkClient() - - try { - const response = await client.send(new ListFunctionUrlConfigsCommand({ FunctionName: name })) - // prune `Code` from logs so we don't reveal a signed link to customer resources. - getLogger().debug('GetFunctionUrlConfig returned response (code section pruned): %O', { - ...response, - Code: 'Pruned', - }) - return response.FunctionUrlConfigs ?? [] - } catch (e) { - throw ToolkitError.chain(e, 'Failed to get Lambda function URLs') - } - } - - public async updateFunctionCode(name: string, zipFile: Uint8Array): Promise { - getLogger().debug(`updateFunctionCode called for function: ${name}`) - const client = await this.createSdkClient() - - try { - const response = await client.send( - new UpdateFunctionCodeCommand({ - FunctionName: name, - ZipFile: zipFile, - }) - ) - getLogger().debug('updateFunctionCode returned response: %O', response) - await waitUntilFunctionUpdatedV2({ client, maxWaitTime: 300 }, { FunctionName: name }) - - return response - } catch (e) { - getLogger().error('Failed to run updateFunctionCode: %s', e) - throw e - } - } - - public async updateFunctionConfiguration( - params: UpdateFunctionConfigurationRequest, - options: { - maxRetries?: number - initialDelayMs?: number - backoffMultiplier?: number - waitForUpdate?: boolean - } = {} - ): Promise { - const client = await this.createSdkClient() - const maxRetries = options.maxRetries ?? 5 - const initialDelayMs = options.initialDelayMs ?? 1000 - const backoffMultiplier = options.backoffMultiplier ?? 2 - // return until lambda update is completed - const waitForUpdate = options.waitForUpdate ?? false - - let retryCount = 0 - let lastError: any - - // there could be race condition, if function is being updated, wait and retry - while (retryCount <= maxRetries) { - try { - const response = await client.send(new UpdateFunctionConfigurationCommand(params)) - getLogger().debug('updateFunctionConfiguration returned response: %O', response) - if (waitForUpdate) { - // don't return if wait for result - break - } - return response - } catch (e) { - lastError = e - - // Check if this is an "update in progress" error - if (this.isUpdateInProgressError(e) && retryCount < maxRetries) { - const delayMs = initialDelayMs * Math.pow(backoffMultiplier, retryCount) - getLogger().info( - `Update in progress for Lambda function ${params.FunctionName}. ` + - `Retrying in ${delayMs}ms (attempt ${retryCount + 1}/${maxRetries})` - ) - - await new Promise((resolve) => setTimeout(resolve, delayMs)) - retryCount++ - } else { - getLogger().error('Failed to run updateFunctionConfiguration: %s', e) - throw e - } - } - } - - // check if lambda update is completed, use client.getFunctionConfiguration to poll until - // LastUpdateStatus is Successful or Failed - if (waitForUpdate) { - let lastUpdateStatus = 'InProgress' - while (lastUpdateStatus === 'InProgress') { - await new Promise((resolve) => setTimeout(resolve, 1000)) - const response = await client.send( - new GetFunctionConfigurationCommand({ FunctionName: params.FunctionName }) - ) - lastUpdateStatus = response.LastUpdateStatus ?? 'Failed' - if (lastUpdateStatus === 'Successful') { - return response - } else if (lastUpdateStatus === 'Failed') { - getLogger().error('Failed to update function configuration: %O', response) - throw new Error(`Failed to update function configuration: ${response.LastUpdateStatusReason}`) - } - } - } - - getLogger().error(`Failed to update function configuration after ${maxRetries} retries: %s`, lastError) - throw lastError - } - - public async publishVersion( - name: string, - options: { waitForUpdate?: boolean } = {} - ): Promise { - const client = await this.createSdkClient() - // return until lambda update is completed - const waitForUpdate = options.waitForUpdate ?? false - const response = await client.send( - new PublishVersionCommand({ - FunctionName: name, - }) - ) - - if (waitForUpdate) { - let state = 'Pending' - while (state === 'Pending') { - await new Promise((resolve) => setTimeout(resolve, 1000)) - const statusResponse = await client.send( - new GetFunctionConfigurationCommand({ FunctionName: name, Qualifier: response.Version }) - ) - state = statusResponse.State ?? 'Failed' - if (state === 'Active' || state === 'InActive') { - // version creation finished - return statusResponse - } else if (state === 'Failed') { - getLogger().error('Failed to create Version: %O', statusResponse) - throw new Error(`Failed to create Version: ${statusResponse.LastUpdateStatusReason}`) - } - } - } - - return response - } - - private isUpdateInProgressError(error: any): boolean { - return ( - error?.message && - error.message.includes( - 'The operation cannot be performed at this time. An update is in progress for resource:' - ) - ) - } - - public async waitForActive( - functionName: string, - waiter?: { maxWaitTime?: number; minDelay?: number; maxDelay?: number } - ): Promise { - const sdkClient = await this.createSdkClient() - - await waitUntilFunctionActiveV2( - { - client: sdkClient, - maxWaitTime: waiter?.maxWaitTime ?? 600, - minDelay: waiter?.minDelay ?? 1, - maxDelay: waiter?.maxDelay ?? 120, - }, - { FunctionName: functionName } - ) - } - - private async createSdkClient(): Promise { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: LambdaSdkClient, - userAgent: !this.userAgent, - clientOptions: { - customUserAgent: this.userAgent, - region: this.regionCode, - requestHandler: new NodeHttpHandler({ - requestTimeout: this.defaultTimeoutInMs, - }), - }, - }) - } -} - -export async function getFunctionWithCredentials(region: string, name: string): Promise { - const connection = await getIAMConnection({ - prompt: true, - messageText: 'Opening a Lambda Function requires you to be authenticated.', - }) - - if (!connection) { - throw new CancellationError('user') - } - - const credentials = - connection.type === 'iam' ? await connection.getCredentials() : fromSSO({ profile: connection.id }) - const client = new LambdaSdkClient({ region, credentials }) - - const command = new GetFunctionCommand({ FunctionName: name }) - return client.send(command) -} diff --git a/packages/core/src/shared/clients/redshiftClient.ts b/packages/core/src/shared/clients/redshiftClient.ts deleted file mode 100644 index 5464f58f1d..0000000000 --- a/packages/core/src/shared/clients/redshiftClient.ts +++ /dev/null @@ -1,300 +0,0 @@ -/* eslint-disable header/header */ -/*! - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - ClusterCredentials, - ClustersMessage, - DescribeClustersCommand, - DescribeClustersMessage, - GetClusterCredentialsCommand, - GetClusterCredentialsMessage, - RedshiftClient, -} from '@aws-sdk/client-redshift' -import { - DescribeStatementCommand, - DescribeStatementRequest, - ExecuteStatementCommand, - GetStatementResultCommand, - GetStatementResultRequest, - GetStatementResultResponse, - ListDatabasesCommand, - ListDatabasesRequest, - ListDatabasesResponse, - ListSchemasCommand, - ListSchemasRequest, - ListSchemasResponse, - ListTablesCommand, - ListTablesRequest, - ListTablesResponse, - RedshiftDataClient, -} from '@aws-sdk/client-redshift-data' -import { - GetCredentialsCommand, - GetCredentialsRequest, - GetCredentialsResponse, - ListWorkgroupsCommand, - ListWorkgroupsRequest, - ListWorkgroupsResponse, - RedshiftServerlessClient, -} from '@aws-sdk/client-redshift-serverless' -import globals from '../extensionGlobals' -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../../awsService/redshift/models/models' -import { sleep } from '../utilities/timeoutUtils' -import { SecretsManagerClient } from './secretsManagerClient' -import { ToolkitError } from '../errors' -import { getLogger } from '../logger/logger' - -export interface ExecuteQueryResponse { - statementResultResponse: GetStatementResultResponse - executionId: string -} - -// Type definition for Provisioned and Serverless -export class DefaultRedshiftClient { - public constructor( - public readonly regionCode: string, - private readonly redshiftDataClientProvider: ( - regionCode: string - ) => RedshiftDataClient = createRedshiftDataClient, - private readonly redshiftClientProvider: (regionCode: string) => RedshiftClient = createRedshiftSdkClient, - private readonly redshiftServerlessClientProvider: ( - regionCode: string - ) => RedshiftServerlessClient = createRedshiftServerlessSdkClient - ) {} - - // eslint-disable-next-line require-yield - public async describeProvisionedClusters(nextToken?: string): Promise { - const redshiftClient = this.redshiftClientProvider(this.regionCode) - const request: DescribeClustersMessage = { - Marker: nextToken, - MaxRecords: 20, - } - const response = await redshiftClient.send(new DescribeClustersCommand(request)) - if (response.Clusters) { - response.Clusters = response.Clusters.filter( - (cluster) => cluster.ClusterAvailabilityStatus?.toLowerCase() === 'available' - ) - } - return response - } - - public async listServerlessWorkgroups(nextToken?: string): Promise { - const redshiftServerlessClient = this.redshiftServerlessClientProvider(this.regionCode) - const request: ListWorkgroupsRequest = { - nextToken: nextToken, - maxResults: 20, - } - const response = await redshiftServerlessClient.send(new ListWorkgroupsCommand(request)) - if (response.workgroups) { - response.workgroups = response.workgroups.filter( - (workgroup) => workgroup.status?.toLowerCase() === 'available' - ) - } - return response - } - - public async listDatabases(connectionParams: ConnectionParams, nextToken?: string): Promise { - const redshiftDataClient = this.redshiftDataClientProvider(this.regionCode) - const warehouseType = connectionParams.warehouseType - const warehouseIdentifier = connectionParams.warehouseIdentifier - const input: ListDatabasesRequest = { - ClusterIdentifier: warehouseType === RedshiftWarehouseType.PROVISIONED ? warehouseIdentifier : undefined, - Database: connectionParams.database, - DbUser: - warehouseType === RedshiftWarehouseType.PROVISIONED && - connectionParams.connectionType !== ConnectionType.DatabaseUser - ? connectionParams.username - : undefined, - WorkgroupName: warehouseType === RedshiftWarehouseType.SERVERLESS ? warehouseIdentifier : undefined, - NextToken: nextToken, - SecretArn: - connectionParams.connectionType === ConnectionType.DatabaseUser || connectionParams.secret - ? connectionParams.secret - : undefined, - } - return redshiftDataClient.send(new ListDatabasesCommand(input)) - } - public async listSchemas(connectionParams: ConnectionParams, nextToken?: string): Promise { - const redshiftDataClient = this.redshiftDataClientProvider(this.regionCode) - const warehouseType = connectionParams.warehouseType - const warehouseIdentifier = connectionParams.warehouseIdentifier - const input: ListSchemasRequest = { - ClusterIdentifier: warehouseType === RedshiftWarehouseType.PROVISIONED ? warehouseIdentifier : undefined, - Database: connectionParams.database, - DbUser: - connectionParams.username && connectionParams.connectionType !== ConnectionType.DatabaseUser - ? connectionParams.username - : undefined, - WorkgroupName: warehouseType === RedshiftWarehouseType.SERVERLESS ? warehouseIdentifier : undefined, - NextToken: nextToken, - SecretArn: - connectionParams.connectionType === ConnectionType.DatabaseUser || connectionParams.secret - ? connectionParams.secret - : undefined, - } - return redshiftDataClient.send(new ListSchemasCommand(input)) - } - - public async listTables( - connectionParams: ConnectionParams, - schemaName: string, - nextToken?: string - ): Promise { - const redshiftDataClient = this.redshiftDataClientProvider(this.regionCode) - const warehouseType = connectionParams.warehouseType - const warehouseIdentifier = connectionParams.warehouseIdentifier - const input: ListTablesRequest = { - ClusterIdentifier: warehouseType === RedshiftWarehouseType.PROVISIONED ? warehouseIdentifier : undefined, - DbUser: - connectionParams.username && connectionParams.connectionType !== ConnectionType.DatabaseUser - ? connectionParams.username - : undefined, - Database: connectionParams.database, - WorkgroupName: warehouseType === RedshiftWarehouseType.SERVERLESS ? warehouseIdentifier : undefined, - SchemaPattern: schemaName, - NextToken: nextToken, - SecretArn: - connectionParams.connectionType === ConnectionType.DatabaseUser || connectionParams.secret - ? connectionParams.secret - : undefined, - } - const ListTablesResponse = redshiftDataClient.send(new ListTablesCommand(input)) - return ListTablesResponse - } - - public async executeQuery( - connectionParams: ConnectionParams, - queryToExecute: string, - nextToken?: string, - executionId?: string - ): Promise { - const redshiftData = this.redshiftDataClientProvider(this.regionCode) - // if executionId is not passed in, that means that we're executing and retrieving the results of the query for the first time. - if (!executionId) { - const execution = await redshiftData.send( - new ExecuteStatementCommand({ - ClusterIdentifier: - connectionParams.warehouseType === RedshiftWarehouseType.PROVISIONED - ? connectionParams.warehouseIdentifier - : undefined, - WorkgroupName: - connectionParams.warehouseType === RedshiftWarehouseType.SERVERLESS - ? connectionParams.warehouseIdentifier - : undefined, - Database: connectionParams.database, - Sql: queryToExecute, - DbUser: - connectionParams.username && connectionParams.connectionType !== ConnectionType.DatabaseUser - ? connectionParams.username - : undefined, - SecretArn: - connectionParams.connectionType === ConnectionType.DatabaseUser || connectionParams.secret - ? connectionParams.secret - : undefined, - }) - ) - - executionId = execution.Id - type Status = 'RUNNING' | 'FAILED' | 'FINISHED' - let status: Status = 'RUNNING' - while (status === 'RUNNING') { - const describeStatementResponse = await redshiftData.send( - new DescribeStatementCommand({ Id: executionId } as DescribeStatementRequest) - ) - if (describeStatementResponse.Status === 'FAILED' || describeStatementResponse.Status === 'FINISHED') { - status = describeStatementResponse.Status - if (status === 'FAILED') { - throw new Error( - `Failed to run query: '${queryToExecute}': '${describeStatementResponse.Error}'` - ) - } else if (status === 'FINISHED' && !describeStatementResponse.HasResultSet) { - return undefined - } - break - } else { - await sleep(1000) - } - } - } - const result = await redshiftData.send( - new GetStatementResultCommand({ Id: executionId, NextToken: nextToken } as GetStatementResultRequest) - ) - - return { statementResultResponse: result, executionId: executionId } as ExecuteQueryResponse - } - - public async getTempCredentials( - warehouseType: RedshiftWarehouseType, - connectionParams: ConnectionParams - ): Promise { - if (warehouseType === RedshiftWarehouseType.PROVISIONED) { - const redshiftClient = this.redshiftClientProvider(this.regionCode) - const getClusterCredentialsRequest: GetClusterCredentialsMessage = { - DbUser: connectionParams.username!, - DbName: connectionParams.database, - ClusterIdentifier: connectionParams.warehouseIdentifier, - } - return redshiftClient.send(new GetClusterCredentialsCommand(getClusterCredentialsRequest)) - } else { - const redshiftServerless = this.redshiftServerlessClientProvider(this.regionCode) - const getCredentialsRequest: GetCredentialsRequest = { - dbName: connectionParams.database, - workgroupName: connectionParams.warehouseIdentifier, - } - return redshiftServerless.send(new GetCredentialsCommand(getCredentialsRequest)) - } - } - public genUniqueId(connectionParams: ConnectionParams): string { - const epochDate = Date.now() - return `${epochDate}-${connectionParams.warehouseIdentifier}` - } - - public async createSecretFromConnectionParams(connectionParams: ConnectionParams): Promise { - /* - create a secrete arn for the username and password entered through the Database Username and Password authentication - */ - const secretsManagerClient = new SecretsManagerClient(this.regionCode) - const username = connectionParams.username - const password = connectionParams.password - if (username && password) { - const secretString = this.genUniqueId(connectionParams) - try { - const response = await secretsManagerClient?.createSecret(secretString, username, password) - if (response && response.ARN) { - return response.ARN - } - throw new ToolkitError('Secret Arn not created') - } catch (error) { - getLogger().error( - `Redshift: Error creating secret in AWS Secrets Manager - ${(error as Error).message}` - ) - throw error - } - } else { - throw new ToolkitError('Username or Password not present') - } - } -} - -function createRedshiftSdkClient(regionCode: string): RedshiftClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: RedshiftClient, - clientOptions: { region: regionCode }, - }) -} - -function createRedshiftServerlessSdkClient(regionCode: string): RedshiftServerlessClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: RedshiftServerlessClient, - clientOptions: { region: regionCode }, - }) -} -function createRedshiftDataClient(regionCode: string): RedshiftDataClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: RedshiftDataClient, - clientOptions: { region: regionCode }, - }) -} diff --git a/packages/core/src/shared/clients/sagemaker.ts b/packages/core/src/shared/clients/sagemaker.ts deleted file mode 100644 index 3b68c86cfd..0000000000 --- a/packages/core/src/shared/clients/sagemaker.ts +++ /dev/null @@ -1,481 +0,0 @@ -/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { - AppDetails, - AppType, - CreateAppCommand, - CreateAppCommandInput, - CreateAppCommandOutput, - DeleteAppCommand, - DeleteAppCommandInput, - DeleteAppCommandOutput, - DescribeAppCommand, - DescribeAppCommandInput, - DescribeAppCommandOutput, - DescribeDomainCommand, - DescribeDomainCommandInput, - DescribeDomainCommandOutput, - DescribeDomainResponse, - DescribeSpaceCommand, - DescribeSpaceCommandInput, - DescribeSpaceCommandOutput, - ListAppsCommandInput, - ListSpacesCommandInput, - ResourceSpec, - SageMakerClient, - SpaceDetails, - UpdateSpaceCommand, - UpdateSpaceCommandInput, - UpdateSpaceCommandOutput, - paginateListApps, - paginateListSpaces, - ListClustersCommandInput, - DescribeClusterCommand, - DescribeClusterCommandInput, - DescribeClusterCommandOutput, - ClusterSummary, - paginateListClusters, -} from '@amzn/sagemaker-client' -import { isEmpty } from 'lodash' -import { sleep } from '../utilities/timeoutUtils' -import { ClientWrapper } from './clientWrapper' -import { AsyncCollection } from '../utilities/asyncCollection' -import { - InstanceTypeError, - InstanceTypeMinimum, - InstanceTypeInsufficientMemory, - InstanceTypeInsufficientMemoryMessage, - InstanceTypeNotSelectedMessage, - RemoteAccess, -} from '../../awsService/sagemaker/constants' -import { getDomainSpaceKey } from '../../awsService/sagemaker/utils' -import { getLogger } from '../logger/logger' -import { ToolkitError } from '../errors' -import { continueText, cancel } from '../localizedText' -import { showConfirmationMessage } from '../utilities/messages' -import { AwsCredentialIdentity } from '@aws-sdk/types' -import globals from '../extensionGlobals' -import { HyperpodCluster } from './kubectlClient' -import { EKSClient } from '@aws-sdk/client-eks' -import { DevSettings } from '../settings' - -const appTypeSettingsMap: Record = { - [AppType.JupyterLab as string]: 'JupyterLabAppSettings', - [AppType.CodeEditor as string]: 'CodeEditorAppSettings', -} as const - -export const waitForAppConfig = { - softTimeoutRetries: 12, - hardTimeoutRetries: 120, - intervalMs: 5000, -} - -export interface SagemakerSpaceApp extends SpaceDetails { - App?: AppDetails - DomainSpaceKey: string -} - -export class SagemakerClient extends ClientWrapper { - public constructor( - public override readonly regionCode: string, - private readonly credentialsProvider?: () => Promise - ) { - super(regionCode, SageMakerClient) - } - - protected override getClient(ignoreCache: boolean = false) { - if (!this.client || ignoreCache) { - const devSettings = DevSettings.instance - const customEndpoint = devSettings.get('endpoints', {})['sagemaker'] - const endpoint = customEndpoint || `https://sagemaker.${this.regionCode}.amazonaws.com` - const args = { - serviceClient: SageMakerClient, - region: this.regionCode, - clientOptions: { - endpoint: endpoint, - region: this.regionCode, - ...(this.credentialsProvider && { credentials: this.credentialsProvider }), - }, - } - this.client = globals.sdkClientBuilderV3.createAwsService(args) - } - return this.client - } - - public override dispose() { - getLogger().debug('SagemakerClient: Disposing client %O', this.client) - this.client?.destroy() - this.client = undefined - } - - public listSpaces(request: ListSpacesCommandInput = {}): AsyncCollection { - // @ts-ignore: Suppressing type mismatch on paginator return type - return this.makePaginatedRequest(paginateListSpaces, request, (page) => page.Spaces) - } - - public listApps(request: ListAppsCommandInput = {}): AsyncCollection { - // @ts-ignore: Suppressing type mismatch on paginator return type - return this.makePaginatedRequest(paginateListApps, request, (page) => page.Apps) - } - - public describeApp(request: DescribeAppCommandInput): Promise { - return this.makeRequest(DescribeAppCommand, request) - } - - public describeDomain(request: DescribeDomainCommandInput): Promise { - return this.makeRequest(DescribeDomainCommand, request) - } - - public describeSpace(request: DescribeSpaceCommandInput): Promise { - return this.makeRequest(DescribeSpaceCommand, request) - } - - public updateSpace(request: UpdateSpaceCommandInput): Promise { - return this.makeRequest(UpdateSpaceCommand, request) - } - - public createApp(request: CreateAppCommandInput): Promise { - return this.makeRequest(CreateAppCommand, request) - } - - public deleteApp(request: DeleteAppCommandInput): Promise { - return this.makeRequest(DeleteAppCommand, request) - } - - public async listAppForSpace(domainId: string, spaceName: string): Promise { - const appsList = await this.listApps({ DomainIdEquals: domainId, SpaceNameEquals: spaceName }) - .flatten() - .promise() - return appsList[0] // At most one App for one SagemakerSpace - } - - public async startSpace(spaceName: string, domainId: string, skipInstanceTypePrompts: boolean = false) { - let spaceDetails: DescribeSpaceCommandOutput - - // Get existing space details - try { - spaceDetails = await this.describeSpace({ - DomainId: domainId, - SpaceName: spaceName, - }) - } catch (err) { - throw this.handleStartSpaceError(err) - } - - // Get app type - const appType = spaceDetails.SpaceSettings?.AppType - if (!appType || !(appType in appTypeSettingsMap)) { - throw new ToolkitError(`Unsupported AppType "${appType}" for space "${spaceName}"`) - } - - // Get app resource spec - const requestedResourceSpec = - appType === AppType.JupyterLab - ? spaceDetails.SpaceSettings?.JupyterLabAppSettings?.DefaultResourceSpec - : spaceDetails.SpaceSettings?.CodeEditorAppSettings?.DefaultResourceSpec - - let instanceType = requestedResourceSpec?.InstanceType - - // Is InstanceType defined and has enough memory? - if (instanceType && instanceType in InstanceTypeInsufficientMemory) { - if (skipInstanceTypePrompts) { - // User already consented, upgrade automatically - instanceType = InstanceTypeInsufficientMemory[instanceType] - } else { - // Prompt user to select one with sufficient memory (1 level up from their chosen one) - const confirmed = await showConfirmationMessage({ - prompt: InstanceTypeInsufficientMemoryMessage( - spaceDetails.SpaceName || '', - instanceType, - InstanceTypeInsufficientMemory[instanceType] - ), - confirm: 'Restart Space and Connect', - cancel: 'Cancel', - type: 'warning', - }) - - if (!confirmed) { - throw new ToolkitError('InstanceType has insufficient memory.', { code: InstanceTypeError }) - } - - instanceType = InstanceTypeInsufficientMemory[instanceType] - } - } else if (!instanceType) { - if (skipInstanceTypePrompts) { - // User already consented, use minimum - instanceType = InstanceTypeMinimum - } else { - // Prompt user to select the minimum supported instance type - const confirmed = await showConfirmationMessage({ - prompt: InstanceTypeNotSelectedMessage(spaceDetails.SpaceName || ''), - confirm: continueText, - cancel: cancel, - type: 'warning', - }) - - if (!confirmed) { - throw new ToolkitError('InstanceType not defined.', { code: InstanceTypeError }) - } - - instanceType = InstanceTypeMinimum - } - } - - // First, update the space if needed - const needsRemoteAccess = - !spaceDetails.SpaceSettings?.RemoteAccess || - spaceDetails.SpaceSettings?.RemoteAccess === RemoteAccess.DISABLED - const instanceTypeChanged = requestedResourceSpec?.InstanceType !== instanceType - - if (needsRemoteAccess || instanceTypeChanged) { - const updateSpaceRequest: UpdateSpaceCommandInput = { - DomainId: domainId, - SpaceName: spaceName, - SpaceSettings: { - ...(needsRemoteAccess && { RemoteAccess: RemoteAccess.ENABLED }), - ...(instanceTypeChanged && { - [appTypeSettingsMap[appType]]: { - DefaultResourceSpec: { - InstanceType: instanceType, - }, - }, - }), - }, - } - - try { - getLogger().debug('SagemakerClient: Updating space: domainId=%s, spaceName=%s', domainId, spaceName) - await this.updateSpace(updateSpaceRequest) - await this.waitForSpaceInService(spaceName, domainId) - } catch (err) { - throw this.handleStartSpaceError(err) - } - } - - const resourceSpec: ResourceSpec = { - // Default values - SageMakerImageArn: 'arn:aws:sagemaker:us-west-2:542918446943:image/sagemaker-distribution-cpu', - SageMakerImageVersionAlias: '3.2.0', - - // The existing resource spec - ...requestedResourceSpec, - - // The instance type user has chosen - InstanceType: instanceType, - } - - const cleanedResourceSpec = - resourceSpec && 'EnvironmentArn' in resourceSpec - ? { ...resourceSpec, EnvironmentArn: undefined, EnvironmentVersionArn: undefined } - : resourceSpec - - // Second, create the App - const createAppRequest: CreateAppCommandInput = { - DomainId: domainId, - SpaceName: spaceName, - AppType: appType, - AppName: 'default', - ResourceSpec: cleanedResourceSpec, - } - - try { - getLogger().debug('SagemakerClient: Creating app: domainId=%s, spaceName=%s', domainId, spaceName) - await this.createApp(createAppRequest) - } catch (err) { - throw this.handleStartSpaceError(err) - } - } - - public async listSpaceApps(domainId?: string): Promise> { - // Create options object conditionally if domainId is provided - const options = domainId ? { DomainIdEquals: domainId } : undefined - - const appMap: Map = await this.listApps(options) - .flatten() - .filter((app) => !!app.DomainId && !!app.SpaceName) - .filter((app) => app.AppType === AppType.JupyterLab || app.AppType === AppType.CodeEditor) - .toMap((app) => getDomainSpaceKey(app.DomainId || '', app.SpaceName || '')) - - const spaceApps: Map = await this.listSpaces(options) - .flatten() - .filter((space) => !!space.DomainId && !!space.SpaceName) - .map((space) => { - const key = getDomainSpaceKey(space.DomainId || '', space.SpaceName || '') - return { ...space, App: appMap.get(key), DomainSpaceKey: key } - }) - .toMap((space) => getDomainSpaceKey(space.DomainId || '', space.SpaceName || '')) - return spaceApps - } - - public async fetchSpaceAppsAndDomains( - domainId?: string, - filterSmusDomains: boolean = true - ): Promise<[Map, Map]> { - try { - const spaceApps = await this.listSpaceApps(domainId) - // Get de-duped list of domain IDs for all of the spaces - const domainIds: string[] = domainId - ? [domainId] - : [...new Set([...spaceApps].map(([_, spaceApp]) => spaceApp.DomainId || ''))] - - // Get details for each domain - const domains: [string, DescribeDomainResponse][] = await Promise.all( - domainIds.map(async (domainId, index) => { - await sleep(index * 100) - const response = await this.describeDomain({ DomainId: domainId }) - return [domainId, response] - }) - ) - - const domainsMap = new Map(domains) - - const filteredSpaceApps = new Map( - [...spaceApps] - // Filter out SageMaker Unified Studio domains only if filterSmusDomains is true - .filter( - ([_, spaceApp]) => - !filterSmusDomains || - isEmpty(domainsMap.get(spaceApp.DomainId || '')?.DomainSettings?.UnifiedStudioSettings) - ) - ) - - return [filteredSpaceApps, domainsMap] - } catch (err) { - const error = err as Error - getLogger().error('Failed to fetch space apps: %s', err) - if (error.name === 'AccessDeniedException') { - void vscode.window.showErrorMessage( - 'AccessDeniedException: You do not have permission to view spaces. Please contact your administrator', - { modal: false, detail: 'AWS Toolkit' } - ) - } - throw err - } - } - - private async waitForSpaceInService( - spaceName: string, - domainId: string, - maxRetries = 30, - intervalMs = 5000 - ): Promise { - for (let attempt = 0; attempt < maxRetries; attempt++) { - const result = await this.describeSpace({ SpaceName: spaceName, DomainId: domainId }) - - if (result.Status === 'InService') { - return - } - - await sleep(intervalMs) - } - - throw new ToolkitError( - `Timed out waiting for space "${spaceName}" in domain "${domainId}" to reach "InService" status.` - ) - } - - public async waitForAppInService( - domainId: string, - spaceName: string, - appType: string, - progress?: vscode.Progress<{ message?: string; increment?: number }> - ): Promise { - for (let attempt = 0; attempt < waitForAppConfig.hardTimeoutRetries; attempt++) { - const { Status } = await this.describeApp({ - DomainId: domainId, - SpaceName: spaceName, - AppType: appType as any, - AppName: 'default', - }) - - if (Status === 'InService') { - return - } - - if (['Failed', 'DeleteFailed'].includes(Status ?? '')) { - throw new ToolkitError(`App failed to start. Status: ${Status}`) - } - - if (attempt === waitForAppConfig.softTimeoutRetries) { - progress?.report({ - message: `Starting the space is taking longer than usual. The space will connect when ready`, - }) - } - - await sleep(waitForAppConfig.intervalMs) - } - - throw new ToolkitError(`Timed out waiting for app "${spaceName}" to reach "InService" status.`) - } - - private handleStartSpaceError(err: unknown) { - const error = err as Error - if (error.name === 'AccessDeniedException') { - throw new ToolkitError('You do not have permission to start spaces. Please contact your administrator', { - cause: error, - }) - } else { - throw err - } - } - - public listClusters(request: ListClustersCommandInput = {}): AsyncCollection { - // @ts-ignore: Suppressing type mismatch on paginator return type - return this.makePaginatedRequest(paginateListClusters, request, (page) => page.ClusterSummaries) - } - - public describeCluster(request: DescribeClusterCommandInput): Promise { - return this.makeRequest(DescribeClusterCommand, request) - } - - public async listHyperpodClusters(): Promise { - const clusterSummaries = await this.listClusters().flatten().promise() - const clusters: HyperpodCluster[] = [] - - for (const summary of clusterSummaries) { - clusters.push(await this.getHyperpodCluster(summary.ClusterName!)) - } - return clusters - } - - async getHyperpodCluster(clusterName: string): Promise { - const response = await this.describeCluster({ ClusterName: clusterName }) - - if (!response.ClusterArn) { - throw new Error(`Cluster ${clusterName} not found`) - } - - const orchestrator = response.Orchestrator - let eksClusterName: string | undefined - let eksClusterArn: string | undefined - - if (orchestrator?.Eks) { - eksClusterName = orchestrator.Eks.ClusterArn?.split('/').pop() - eksClusterArn = orchestrator.Eks.ClusterArn - } - - return { - clusterName: response.ClusterName!, - clusterArn: response.ClusterArn, - status: response.ClusterStatus!, - eksClusterName, - eksClusterArn, - regionCode: this.regionCode, - } - } - - public getEKSClient(ignoreCache: boolean = false) { - const args = { - serviceClient: EKSClient as any, - region: this.regionCode, - clientOptions: { - region: this.regionCode, - ...(this.credentialsProvider && { credentials: this.credentialsProvider }), - }, - } - return globals.sdkClientBuilderV3.createAwsService(args) as EKSClient - } -} diff --git a/packages/core/src/shared/clients/schemaClient.ts b/packages/core/src/shared/clients/schemaClient.ts deleted file mode 100644 index 238b0d4681..0000000000 --- a/packages/core/src/shared/clients/schemaClient.ts +++ /dev/null @@ -1,191 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - DescribeCodeBindingCommand, - DescribeCodeBindingResponse, - DescribeSchemaCommand, - DescribeSchemaResponse, - GetCodeBindingSourceCommand, - GetCodeBindingSourceResponse, - ListRegistriesCommand, - ListRegistriesRequest, - ListRegistriesResponse, - ListSchemasCommand, - ListSchemasRequest, - ListSchemasResponse, - ListSchemaVersionsCommand, - ListSchemaVersionsRequest, - ListSchemaVersionsResponse, - PutCodeBindingCommand, - PutCodeBindingResponse, - RegistrySummary, - SchemasClient, - SchemaSummary, - SchemaVersionSummary, - SearchSchemasCommand, - SearchSchemasRequest, - SearchSchemasResponse, - SearchSchemaSummary, -} from '@aws-sdk/client-schemas' -import globals from '../extensionGlobals' - -import { ClassToInterfaceType } from '../utilities/tsUtils' - -export type SchemaClient = ClassToInterfaceType -export class DefaultSchemaClient { - public constructor(public readonly regionCode: string) {} - - public async *listRegistries(): AsyncIterableIterator { - const client = this.createSdkClient() - - const request: ListRegistriesRequest = {} - - do { - const response: ListRegistriesResponse = await client.send(new ListRegistriesCommand(request)) - - if (response.Registries) { - yield* response.Registries - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async *listSchemas(registryName: string): AsyncIterableIterator { - const client = this.createSdkClient() - - const request: ListSchemasRequest = { - RegistryName: registryName, - } - - do { - const response: ListSchemasResponse = await client.send(new ListSchemasCommand(request)) - - if (response.Schemas) { - yield* response.Schemas - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async describeSchema( - registryName: string, - schemaName: string, - schemaVersion?: string - ): Promise { - const client = this.createSdkClient() - - return await client.send( - new DescribeSchemaCommand({ - RegistryName: registryName, - SchemaName: schemaName, - SchemaVersion: schemaVersion, - }) - ) - } - - public async *listSchemaVersions( - registryName: string, - schemaName: string - ): AsyncIterableIterator { - const client = this.createSdkClient() - - const request: ListSchemaVersionsRequest = { - RegistryName: registryName, - SchemaName: schemaName, - } - - do { - const response: ListSchemaVersionsResponse = await client.send(new ListSchemaVersionsCommand(request)) - - if (response.SchemaVersions) { - yield* response.SchemaVersions - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async *searchSchemas(keywords: string, registryName: string): AsyncIterableIterator { - const client = this.createSdkClient() - - const request: SearchSchemasRequest = { - Keywords: keywords, - RegistryName: registryName, - } - - do { - const response: SearchSchemasResponse = await client.send(new SearchSchemasCommand(request)) - - if (response.Schemas) { - yield* response.Schemas - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async getCodeBindingSource( - language: string, - registryName: string, - schemaName: string, - schemaVersion: string - ): Promise { - const client = this.createSdkClient() - - return await client.send( - new GetCodeBindingSourceCommand({ - Language: language, - RegistryName: registryName, - SchemaName: schemaName, - SchemaVersion: schemaVersion, - }) - ) - } - - public async putCodeBinding( - language: string, - registryName: string, - schemaName: string, - schemaVersion: string - ): Promise { - const client = this.createSdkClient() - - return await client.send( - new PutCodeBindingCommand({ - Language: language, - RegistryName: registryName, - SchemaName: schemaName, - SchemaVersion: schemaVersion, - }) - ) - } - public async describeCodeBinding( - language: string, - registryName: string, - schemaName: string, - schemaVersion: string - ): Promise { - const client = this.createSdkClient() - - return await client.send( - new DescribeCodeBindingCommand({ - Language: language, - RegistryName: registryName, - SchemaName: schemaName, - SchemaVersion: schemaVersion, - }) - ) - } - - private createSdkClient(): SchemasClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: SchemasClient, - clientOptions: { region: this.regionCode }, - }) - } -} diff --git a/packages/core/src/shared/clients/secretsManagerClient.ts b/packages/core/src/shared/clients/secretsManagerClient.ts deleted file mode 100644 index af9af46d55..0000000000 --- a/packages/core/src/shared/clients/secretsManagerClient.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - CreateSecretCommand, - CreateSecretRequest, - CreateSecretResponse, - ListSecretsCommand, - ListSecretsRequest, - ListSecretsResponse, - SecretsManagerClient as SecretsManagerSdkClient, -} from '@aws-sdk/client-secrets-manager' -import globals from '../extensionGlobals' -import { productName } from '../constants' - -export class SecretsManagerClient { - public constructor( - public readonly regionCode: string, - private readonly secretsManagerClientProvider: ( - regionCode: string - ) => SecretsManagerSdkClient = createSecretsManagerClient - ) {} - - /** - * Lists the secrets that are stored by Secrets Manager - * @param filter tagged key filter value - * @returns a list of the secrets - */ - public async listSecrets(filter: string): Promise { - const secretsManagerClient = this.secretsManagerClientProvider(this.regionCode) - const request: ListSecretsRequest = { - IncludePlannedDeletion: false, - Filters: [ - { - Key: 'tag-key', - Values: [filter], - }, - ], - SortOrder: 'desc', - } - return secretsManagerClient.send(new ListSecretsCommand(request)) - } - - public async createSecret(secretString: string, username: string, password: string): Promise { - const secretsManagerClient = this.secretsManagerClientProvider(this.regionCode) - const request: CreateSecretRequest = { - Description: `Database secret created with ${productName}`, - Name: secretString ? secretString : '', - SecretString: JSON.stringify({ username, password }), - Tags: [ - { - Key: 'Service', - Value: 'Redshift', - }, - { - Key: 'Request-Source', - Value: productName, - }, - ], - ForceOverwriteReplicaSecret: true, - } - return secretsManagerClient.send(new CreateSecretCommand(request)) - } -} - -function createSecretsManagerClient(regionCode: string): SecretsManagerSdkClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: SecretsManagerSdkClient, - clientOptions: { region: regionCode }, - }) -} diff --git a/packages/core/src/shared/clients/ssm.ts b/packages/core/src/shared/clients/ssm.ts deleted file mode 100644 index 3565e9c7a6..0000000000 --- a/packages/core/src/shared/clients/ssm.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - SSMClient, - Session, - StartSessionCommand, - TerminateSessionCommand, - TerminateSessionResponse, - StartSessionCommandOutput, - DescribeInstanceInformationCommandInput, - InstanceInformation, - SendCommandCommand, - SendCommandCommandOutput, - waitUntilCommandExecuted, - SessionState, - paginateDescribeInstanceInformation, - paginateDescribeSessions, -} from '@aws-sdk/client-ssm' -import { WaiterState } from '@smithy/util-waiter' -import { ToolkitError } from '../errors' -import { ClientWrapper } from './clientWrapper' - -export class SsmClient extends ClientWrapper { - public constructor(public override readonly regionCode: string) { - super(regionCode, SSMClient) - } - - public async terminateSession(session: Session): Promise { - const sessionId = session.SessionId! - return await this.terminateSessionFromId(sessionId) - } - - public async terminateSessionFromId(sessionId: string): Promise { - return await this.makeRequest(TerminateSessionCommand, { SessionId: sessionId }) - } - - public async startSession( - target: string, - document?: string, - reason?: string, - parameters?: Record - ): Promise { - return await this.makeRequest(StartSessionCommand, { - Target: target, - DocumentName: document, - Reason: reason, - Parameters: parameters, - }) - } - - public async describeInstance(target: string): Promise { - return await this.getFirst( - paginateDescribeInstanceInformation, - { - InstanceInformationFilterList: [{ key: 'InstanceIds', valueSet: [target] }], - } satisfies DescribeInstanceInformationCommandInput, - (page) => page.InstanceInformationList - ) - } - - public async getTargetPlatformName(target: string): Promise { - const instanceInformation = await this.describeInstance(target) - return instanceInformation.PlatformName! - } - - public async sendCommand( - target: string, - documentName: string, - parameters: Record - ): Promise { - return await this.makeRequest(SendCommandCommand, { - InstanceIds: [target], - DocumentName: documentName, - Parameters: parameters, - }) - } - - private async waitForCommand(commandId: string, target: string) { - const result = await waitUntilCommandExecuted( - { client: this.getClient(), maxWaitTime: 30 }, - { CommandId: commandId, InstanceId: target } - ) - if (result.state !== WaiterState.SUCCESS) { - throw new ToolkitError(`Command ${commandId} failed to execute on target ${target}`) - } - } - - public async sendCommandAndWait( - target: string, - documentName: string, - parameters: Record - ): Promise { - const response = await this.sendCommand(target, documentName, parameters) - try { - await this.waitForCommand(response.Command!.CommandId!, target) - return response - } catch (err) { - throw new ToolkitError(`Failed in sending command to target ${target}`, { cause: err as Error }) - } - } - - public async getInstanceAgentPingStatus(target: string): Promise { - const instanceInformation = await this.describeInstance(target) - return instanceInformation ? instanceInformation.PingStatus! : 'Inactive' - } - - public async describeSessions(state: SessionState) { - return this.makePaginatedRequest(paginateDescribeSessions, { State: state }, (page) => page.Sessions) - } -} diff --git a/packages/core/src/shared/clients/ssmDocumentClient.ts b/packages/core/src/shared/clients/ssmDocumentClient.ts deleted file mode 100644 index 581cb0bc21..0000000000 --- a/packages/core/src/shared/clients/ssmDocumentClient.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - CreateDocumentCommand, - CreateDocumentRequest, - CreateDocumentResult, - DeleteDocumentCommand, - DeleteDocumentRequest, - DeleteDocumentResult, - DescribeDocumentCommand, - DescribeDocumentRequest, - DescribeDocumentResult, - DocumentFormat, - DocumentIdentifier, - DocumentVersionInfo, - GetDocumentCommand, - GetDocumentRequest, - GetDocumentResult, - ListDocumentsCommand, - ListDocumentsRequest, - ListDocumentsResult, - ListDocumentVersionsCommand, - ListDocumentVersionsRequest, - ListDocumentVersionsResult, - SSMClient, - UpdateDocumentCommand, - UpdateDocumentDefaultVersionCommand, - UpdateDocumentDefaultVersionRequest, - UpdateDocumentDefaultVersionResult, - UpdateDocumentRequest, - UpdateDocumentResult, -} from '@aws-sdk/client-ssm' -import globals from '../extensionGlobals' - -import { ClassToInterfaceType } from '../utilities/tsUtils' - -export type SsmDocumentClient = ClassToInterfaceType -export class DefaultSsmDocumentClient { - public constructor(public readonly regionCode: string) {} - - public async deleteDocument(documentName: string): Promise { - const client = this.createSdkClient() - - const request: DeleteDocumentRequest = { - Name: documentName, - } - - return await client.send(new DeleteDocumentCommand(request)) - } - - public async *listDocuments(request: ListDocumentsRequest = {}): AsyncIterableIterator { - const client = this.createSdkClient() - - do { - const response: ListDocumentsResult = await client.send(new ListDocumentsCommand(request)) - - if (response.DocumentIdentifiers) { - yield* response.DocumentIdentifiers - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async *listDocumentVersions(documentName: string): AsyncIterableIterator { - const client = this.createSdkClient() - - const request: ListDocumentVersionsRequest = { - Name: documentName, - } - - do { - const response: ListDocumentVersionsResult = await client.send(new ListDocumentVersionsCommand(request)) - - if (response.DocumentVersions) { - yield* response.DocumentVersions - } - - request.NextToken = response.NextToken - } while (request.NextToken) - } - - public async describeDocument(documentName: string, documentVersion?: string): Promise { - const client = this.createSdkClient() - - const request: DescribeDocumentRequest = { - Name: documentName, - DocumentVersion: documentVersion, - } - - return await client.send(new DescribeDocumentCommand(request)) - } - - public async getDocument( - documentName: string, - documentVersion?: string, - documentFormat?: DocumentFormat - ): Promise { - const client = this.createSdkClient() - - const request: GetDocumentRequest = { - Name: documentName, - DocumentVersion: documentVersion, - DocumentFormat: documentFormat, - } - - return await client.send(new GetDocumentCommand(request)) - } - - public async createDocument(request: CreateDocumentRequest): Promise { - const client = this.createSdkClient() - - return await client.send(new CreateDocumentCommand(request)) - } - - public async updateDocument(request: UpdateDocumentRequest): Promise { - const client = this.createSdkClient() - - return await client.send(new UpdateDocumentCommand(request)) - } - - public async updateDocumentVersion( - documentName: string, - documentVersion: string - ): Promise { - const client = this.createSdkClient() - - const request: UpdateDocumentDefaultVersionRequest = { - Name: documentName, - DocumentVersion: documentVersion, - } - - return await client.send(new UpdateDocumentDefaultVersionCommand(request)) - } - - private createSdkClient(): SSMClient { - return globals.sdkClientBuilderV3.createAwsService({ - serviceClient: SSMClient, - clientOptions: { region: this.regionCode }, - }) - } -} diff --git a/packages/core/src/shared/clients/stepFunctions.ts b/packages/core/src/shared/clients/stepFunctions.ts deleted file mode 100644 index 2248330765..0000000000 --- a/packages/core/src/shared/clients/stepFunctions.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - CreateStateMachineCommand, - CreateStateMachineCommandInput, - CreateStateMachineCommandOutput, - DescribeStateMachineCommand, - DescribeStateMachineCommandInput, - DescribeStateMachineCommandOutput, - ListStateMachinesCommand, - ListStateMachinesCommandInput, - ListStateMachinesCommandOutput, - SFNClient, - StartExecutionCommand, - StartExecutionCommandInput, - StartExecutionCommandOutput, - StateMachineListItem, - TestStateCommand, - TestStateCommandInput, - TestStateCommandOutput, - UpdateStateMachineCommand, - UpdateStateMachineCommandInput, - UpdateStateMachineCommandOutput, -} from '@aws-sdk/client-sfn' -import { ClientWrapper } from './clientWrapper' - -export class StepFunctionsClient extends ClientWrapper { - public constructor(regionCode: string) { - super(regionCode, SFNClient) - } - - public async *listStateMachines( - request: ListStateMachinesCommandInput = {} - ): AsyncIterableIterator { - do { - const response: ListStateMachinesCommandOutput = await this.makeRequest(ListStateMachinesCommand, request) - if (response.stateMachines) { - yield* response.stateMachines - } - request.nextToken = response.nextToken - } while (request.nextToken) - } - - public async getStateMachineDetails( - request: DescribeStateMachineCommandInput - ): Promise { - return this.makeRequest(DescribeStateMachineCommand, request) - } - - public async executeStateMachine(request: StartExecutionCommandInput): Promise { - return this.makeRequest(StartExecutionCommand, request) - } - - public async createStateMachine(request: CreateStateMachineCommandInput): Promise { - return this.makeRequest(CreateStateMachineCommand, request) - } - - public async updateStateMachine(request: UpdateStateMachineCommandInput): Promise { - return this.makeRequest(UpdateStateMachineCommand, request) - } - - public async testState(request: TestStateCommandInput): Promise { - return this.makeRequest(TestStateCommand, request) - } -} diff --git a/packages/core/src/shared/cloudformation/activation.ts b/packages/core/src/shared/cloudformation/activation.ts index 86cbf05698..accce1ddfb 100644 --- a/packages/core/src/shared/cloudformation/activation.ts +++ b/packages/core/src/shared/cloudformation/activation.ts @@ -14,7 +14,6 @@ import { createStarterTemplateFile } from './cloudformation' import * as CloudFormation from './cloudformation' import { Commands } from '../vscode/commands2' import globals from '../extensionGlobals' -import { SamCliSettings } from '../sam/cli/samCliSettings' import { Timeout } from '../utilities/timeoutUtils' /** @@ -85,8 +84,7 @@ function setTemplateRegistryInGlobals(registry: CloudFormationTemplateRegistry) } // prevent eager load if codelenses are off - const config = SamCliSettings.instance - if (config.get('enableCodeLenses', false) || isToolkitActive()) { + if (isToolkitActive()) { return await asyncRegistry.getInstance() } diff --git a/packages/core/src/shared/codelens/codeLensUtils.ts b/packages/core/src/shared/codelens/codeLensUtils.ts deleted file mode 100644 index f39391dd6d..0000000000 --- a/packages/core/src/shared/codelens/codeLensUtils.ts +++ /dev/null @@ -1,424 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { RuntimeFamily } from '../../lambda/models/samLambdaRuntime' -import * as CloudFormation from '../cloudformation/cloudformation' -import { getResourcesForHandler } from '../fs/templateRegistry' -import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { getLogger } from '../logger/logger' -import { API_TARGET_TYPE, CODE_TARGET_TYPE, TEMPLATE_TARGET_TYPE } from '../sam/debugger/awsSamDebugConfiguration' -import { - addSamDebugConfiguration, - AddSamDebugConfigurationInput, -} from '../sam/debugger/commands/addSamDebugConfiguration' -import { createQuickPick, promptUser, verifySinglePickerOutput } from '../ui/picker' -import { activateExtension, localize } from '../utilities/vsCodeUtils' -import { getWorkspaceRelativePath } from '../utilities/workspaceUtils' -import * as csharpCodelens from './csharpCodeLensProvider' -import * as javaCodelens from './javaCodeLensProvider' -import * as pythonCodelens from './pythonCodeLensProvider' -import * as tsCodelens from './typescriptCodeLensProvider' -import * as goCodelens from './goCodeLensProvider' -import { VSCODE_EXTENSION_ID } from '../extensions' -import { getIdeProperties } from '../extensionUtilities' -import { SamCliSettings } from '../sam/cli/samCliSettings' -import globals from '../extensionGlobals' - -export type Language = 'python' | 'javascript' | 'csharp' | 'go' | 'java' | 'typescript' - -export async function makeCodeLenses({ - document, - token, - handlers, - runtimeFamily, -}: { - document: vscode.TextDocument - token: vscode.CancellationToken - handlers: LambdaHandlerCandidate[] - runtimeFamily: RuntimeFamily -}): Promise { - const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(document.uri) - - if (!workspaceFolder) { - getLogger().error(`makeCodeLenses: source file is external to workspace: ${document.uri}`) - - return [] - } - - const lenses: vscode.CodeLens[] = [] - for (const handler of handlers) { - // handler.range is a RangeOrCharOffset union type. Extract vscode.Range. - const range = - handler.range instanceof vscode.Range - ? handler.range - : new vscode.Range( - document.positionAt(handler.range.positionStart), - document.positionAt(handler.range.positionEnd) - ) - - try { - const registry = await globals.templateRegistry - const associatedResources = getResourcesForHandler(handler.filename, handler.handlerName, registry.items) - const templateConfigs: AddSamDebugConfigurationInput[] = [] - - if (associatedResources.length > 0) { - for (const resource of associatedResources) { - const isImage = CloudFormation.isImageLambdaResource(resource.resourceData.Properties) - templateConfigs.push({ - resourceName: resource.name, - rootUri: vscode.Uri.file(resource.templateDatum.path), - runtimeFamily: isImage ? runtimeFamily : undefined, - }) - const events = resource.resourceData.Properties?.Events - if (events) { - // Check for api events - for (const key in events) { - const value = events[key] - if (value.Type === 'Api') { - templateConfigs.push({ - resourceName: resource.name, - rootUri: vscode.Uri.file(resource.templateDatum.path), - apiEvent: { name: key, event: value }, - runtimeFamily: isImage ? runtimeFamily : undefined, - }) - } - } - } - } - } - const codeConfig: AddSamDebugConfigurationInput = { - resourceName: handler.handlerName, - rootUri: handler.rootUri, - runtimeFamily, - } - lenses.push( - makeAddCodeSamDebugCodeLens(range, codeConfig, templateConfigs, false), - makeAddCodeSamDebugCodeLens(range, codeConfig, templateConfigs, true) - ) - } catch (err) { - getLogger().error( - `Could not generate 'configure' code lens for handler '${handler.handlerName}': %O`, - err as Error - ) - } - } - - return lenses -} - -function makeAddCodeSamDebugCodeLens( - range: vscode.Range, - codeConfig: AddSamDebugConfigurationInput, - templateConfigs: AddSamDebugConfigurationInput[], - openWebview: boolean -): vscode.CodeLens { - const title = openWebview - ? `${getIdeProperties().company}: ${localize('AWS.codelens.lambda.configEditor', 'Edit Debug Configuration')}` - : `${getIdeProperties().company}: ${localize( - 'AWS.command.addSamDebugConfiguration', - 'Add Debug Configuration' - )}` - const command: vscode.Command = { - title, - command: 'aws.pickAddSamDebugConfiguration', - // Values provided by makeTypescriptCodeLensProvider(), - // makeCSharpCodeLensProvider(), makePythonCodeLensProvider(). - arguments: [codeConfig, templateConfigs, openWebview], - } - - return new vscode.CodeLens(range, command) -} - -/** - * Tied to the AWS.addSamDebugConfig command: lets a user create a config tied to a handler via command instead of codelens - * Renders a quick pick using the first 200 characters of the first line of the function declaration + function line number - * @param document Curr document - * @param lenses Codelenses returned via CodeLensProvider, which we extract the information from - */ -export async function invokeCodeLensCommandPalette( - document: Pick, - lenses: vscode.CodeLens[], - nextStep: ( - codeConfig: AddSamDebugConfigurationInput, - templateConfigs: AddSamDebugConfigurationInput[], - openWebview: boolean, - continuationStep?: boolean - ) => Promise = pickAddSamDebugConfiguration -): Promise { - const labelRenderRange = 200 - const handlers: (vscode.QuickPickItem & { lens?: vscode.CodeLens })[] = lenses - .filter((lens) => { - // remove codelenses that go to the invoker UI - // maybe move this into the workflow at some point (drop down to one) - return ( - lens && - lens.command && - lens.command.arguments && - lens.command.arguments.length === 3 && - lens.command.arguments![2] !== true - ) - }) - .map((lens) => { - return { - // lens is currently pulling the entire function, not just the declaration - label: document.getText( - new vscode.Range( - lens.range.start, - new vscode.Position(lens.range.start.line, lens.range.start.character + labelRenderRange) - ) - ), - detail: localize( - 'AWS.pickDebugHandler.range', - 'Function on line {0}', - (lens.range.start.line + 1).toString() - ), - lens, - } - }) - if (handlers.length === 0) { - handlers.push({ - label: localize('AWS.pickDebugHandler.noItems', 'No handlers found in current file'), - detail: localize('AWS.pickDebugHandler.noItems.detail', 'Ensure your language extension is working'), - description: localize('AWS.picker.dynamic.noItemsFound.detail', 'Click here to go back'), - }) - } - - const picker = createQuickPick({ - options: { - canPickMany: false, - ignoreFocusOut: false, - matchOnDetail: true, - title: localize('AWS.pickDebugHandler.prompt', 'Create a Debug Configuration from a valid handler'), - step: 1, - totalSteps: 2, - }, - items: handlers, - }) - - const choices = await promptUser({ picker }) - const val = verifySinglePickerOutput(choices) - - if (!val || !val.lens || !val.lens.command) { - return undefined - } - - // note: val.lens.command.arguments[2] should always be false (aka no invoke UI) based on the filter statement - await nextStep(val.lens.command.arguments![0], val.lens.command.arguments![1], val.lens.command.arguments![2], true) -} - -/** - * Wraps the addSamDebugConfiguration logic in a picker that lets the user choose to create - * a code-type debug config, template-type debug config, or an api-type debug config using a selected template - * TODO: Dedupe? Call out dupes at the quick pick level? - * @param codeConfig - * @param templateConfigs - */ -export async function pickAddSamDebugConfiguration( - codeConfig: AddSamDebugConfigurationInput, - templateConfigs: AddSamDebugConfigurationInput[], - openWebview: boolean, - continuationStep?: boolean -): Promise { - if (templateConfigs.length === 0) { - await addSamDebugConfiguration(codeConfig, CODE_TARGET_TYPE, openWebview) - - return - } - - const templateItemsMap = new Map() - const templateItems: vscode.QuickPickItem[] = [] - for (const templateConfig of templateConfigs) { - const label = `${ - getWorkspaceRelativePath(templateConfig.rootUri.fsPath)?.relativePath ?? templateConfig.rootUri.fsPath - }:${templateConfig.resourceName}` - - if (templateConfig.apiEvent) { - const apiLabel = `${label} (API Event: ${templateConfig.apiEvent.name})` - const eventDetail = `${templateConfig.apiEvent.event.Properties?.Method?.toUpperCase()} ${ - templateConfig.apiEvent.event.Properties?.Path - }` - templateItems.push({ label: apiLabel, detail: eventDetail }) - templateItemsMap.set(apiLabel, templateConfig) - } else { - templateItems.push({ label: label }) - templateItemsMap.set(label, templateConfig) - } - } - - const noTemplate = localize('AWS.pickDebugConfig.noTemplate', 'No Template') - const picker = createQuickPick({ - options: { - canPickMany: false, - ignoreFocusOut: false, - matchOnDetail: true, - title: localize( - 'AWS.pickDebugConfig.prompt', - 'Create a Debug Configuration from a CloudFormation Template' - ), - step: continuationStep ? 2 : 1, - totalSteps: continuationStep ? 2 : 1, - }, - items: [ - ...templateItems, - { - label: noTemplate, - detail: localize( - 'AWS.pickDebugConfig.noTemplate.detail', - 'Launch config will execute function in isolation, without referencing a CloudFormation template' - ), - }, - ], - }) - - const choices = await promptUser({ picker }) - const val = verifySinglePickerOutput(choices) - - if (!val) { - return undefined - } - if (val.label === noTemplate) { - await addSamDebugConfiguration(codeConfig, CODE_TARGET_TYPE, openWebview, { - step: continuationStep ? 3 : 2, - totalSteps: continuationStep ? 3 : 2, - }) - } else { - const templateItem = templateItemsMap.get(val.label) - if (!templateItem) { - return undefined - } - if (templateItem.apiEvent) { - await addSamDebugConfiguration(templateItem, API_TARGET_TYPE, openWebview) - } else { - await addSamDebugConfiguration(templateItem, TEMPLATE_TARGET_TYPE, openWebview) - } - } -} - -export interface OverridableCodeLensProvider extends vscode.CodeLensProvider { - provideCodeLenses: ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ) => Promise -} - -export async function makePythonCodeLensProvider(configuration: SamCliSettings): Promise { - return { - // CodeLensProvider - provideCodeLenses: async ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ): Promise => { - if (!forceProvide && !configuration.get('enableCodeLenses', false)) { - return [] - } - // Try to activate the Python Extension before requesting symbols from a python file - await activateExtension(VSCODE_EXTENSION_ID.python) - if (token.isCancellationRequested) { - return [] - } - - const handlers: LambdaHandlerCandidate[] = await pythonCodelens.getLambdaHandlerCandidates(document.uri) - return makeCodeLenses({ - document, - handlers, - token, - runtimeFamily: RuntimeFamily.Python, - }) - }, - } -} - -export async function makeCSharpCodeLensProvider(configuration: SamCliSettings): Promise { - return { - provideCodeLenses: async ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ): Promise => { - if (!forceProvide && !configuration.get('enableCodeLenses', false)) { - return [] - } - const handlers: LambdaHandlerCandidate[] = await csharpCodelens.getLambdaHandlerCandidates(document) - return makeCodeLenses({ - document, - handlers, - token, - runtimeFamily: RuntimeFamily.DotNet, - }) - }, - } -} - -export function makeTypescriptCodeLensProvider(configuration: SamCliSettings): OverridableCodeLensProvider { - return { - provideCodeLenses: async ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ): Promise => { - if (!forceProvide && !configuration.get('enableCodeLenses', false)) { - return [] - } - const handlers = await tsCodelens.getLambdaHandlerCandidates(document) - return makeCodeLenses({ - document, - handlers, - token, - runtimeFamily: RuntimeFamily.NodeJS, - }) - }, - } -} - -export async function makeGoCodeLensProvider(configuration: SamCliSettings): Promise { - return { - provideCodeLenses: async ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ): Promise => { - if (!forceProvide && !configuration.get('enableCodeLenses', false)) { - return [] - } - const handlers = await goCodelens.getLambdaHandlerCandidates(document) - return makeCodeLenses({ - document, - handlers, - token, - runtimeFamily: RuntimeFamily.Go, - }) - }, - } -} - -export async function makeJavaCodeLensProvider(configuration: SamCliSettings): Promise { - return { - provideCodeLenses: async ( - document: vscode.TextDocument, - token: vscode.CancellationToken, - forceProvide?: boolean - ): Promise => { - if (!forceProvide && !configuration.get('enableCodeLenses', false)) { - return [] - } - // Try to activate the Java Extension before requesting symbols from a java file - await activateExtension(VSCODE_EXTENSION_ID.java) - if (token.isCancellationRequested) { - return [] - } - - const handlers: LambdaHandlerCandidate[] = await javaCodelens.getLambdaHandlerCandidates(document) - return makeCodeLenses({ - document, - handlers, - token, - runtimeFamily: RuntimeFamily.Java, - }) - }, - } -} diff --git a/packages/core/src/shared/codelens/csharpCodeLensProvider.ts b/packages/core/src/shared/codelens/csharpCodeLensProvider.ts deleted file mode 100644 index a6481165b4..0000000000 --- a/packages/core/src/shared/codelens/csharpCodeLensProvider.ts +++ /dev/null @@ -1,198 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { findParentProjectFile } from '../utilities/workspaceUtils' - -export const csharpLanguage = 'csharp' -export const csharpAllfiles: vscode.DocumentFilter[] = [ - { - scheme: 'file', - language: csharpLanguage, - }, -] -export const csharpBasePattern = '**/*.csproj' - -const regexpReservedWordPublic = /\bpublic\b/ - -export interface DotNetLambdaHandlerComponents { - assembly: string - namespace: string - class: string - method: string - // Range of the function representing the Lambda Handler - handlerRange: vscode.Range -} - -export async function getLambdaHandlerCandidates(document: vscode.TextDocument): Promise { - // Limitation: If more than one .csproj file exists in the same directory, - // and the directory is the closest to the source file, the csproj file used will be random - - // TODO : Perform an XPATH parse on the project file - // If Project/PropertyGroup/AssemblyName exists, use that. Otherwise use the file name. - const assemblyUri = await findParentProjectFile(document.uri, /^.*\.csproj$/) - if (!assemblyUri) { - return [] - } - const assemblyName = path.parse(assemblyUri.fsPath).name - - const symbols: vscode.DocumentSymbol[] = - (await vscode.commands.executeCommand( - 'vscode.executeDocumentSymbolProvider', - document.uri - )) || [] - - return getLambdaHandlerComponents(document, symbols, assemblyName).map( - (lambdaHandlerComponents) => { - const handlerName = generateDotNetLambdaHandler(lambdaHandlerComponents) - - return { - filename: document.uri.fsPath, - handlerName, - rootUri: assemblyUri, - range: lambdaHandlerComponents.handlerRange, - } - } - ) -} - -export function getLambdaHandlerComponents( - document: vscode.TextDocument, - symbols: vscode.DocumentSymbol[], - assembly: string -): DotNetLambdaHandlerComponents[] { - return ( - symbols - .filter((symbol) => symbol.kind === vscode.SymbolKind.Namespace) - // Find relevant classes within the namespace - .reduce< - { - namespace: vscode.DocumentSymbol - class: vscode.DocumentSymbol - }[] - >((accumulator, namespaceSymbol: vscode.DocumentSymbol) => { - accumulator.push( - ...namespaceSymbol.children - .filter((namespaceChildSymbol) => namespaceChildSymbol.kind === vscode.SymbolKind.Class) - .filter((classSymbol) => isPublicClassSymbol(document, classSymbol)) - .map((classSymbol) => { - return { - namespace: namespaceSymbol, - class: classSymbol, - } - }) - ) - - return accumulator - }, []) - // Find relevant methods within each class - .reduce((accumulator, lambdaHandlerComponents) => { - accumulator.push( - ...lambdaHandlerComponents.class.children - .filter((classChildSymbol) => classChildSymbol.kind === vscode.SymbolKind.Method) - .filter((methodSymbol) => isValidLambdaHandler(document, methodSymbol)) - .map((methodSymbol) => { - return { - assembly, - namespace: lambdaHandlerComponents.namespace.name, - class: document.getText(lambdaHandlerComponents.class.selectionRange), - method: document.getText(methodSymbol.selectionRange), - handlerRange: methodSymbol.range, - } - }) - ) - - return accumulator - }, []) - ) -} - -export function isPublicClassSymbol( - document: Pick, - symbol: vscode.DocumentSymbol -): boolean { - if (symbol.kind === vscode.SymbolKind.Class) { - // from "public class Processor" pull "public class " - const classDeclarationBeforeNameRange = new vscode.Range(symbol.range.start, symbol.selectionRange.start) - const classDeclarationBeforeName: string = document.getText(classDeclarationBeforeNameRange) - - return regexpReservedWordPublic.test(classDeclarationBeforeName) - } - - return false -} - -/** - * Returns whether or not a method is a valid Lambda handler - * @param document VS Code document - * @param symbol VS Code DocumentSymbol to evaluate - */ -export function isValidLambdaHandler( - document: Pick, - symbol: vscode.DocumentSymbol -): boolean { - if (symbol.kind === vscode.SymbolKind.Method) { - // from "public async Task foo()" pull "public async Task " - const signatureBeforeMethodNameRange = new vscode.Range(symbol.range.start, symbol.selectionRange.start) - const signatureBeforeMethodName: string = document.getText(signatureBeforeMethodNameRange) - - if (regexpReservedWordPublic.test(signatureBeforeMethodName)) { - return isValidMethodSignature(symbol) - } - } - - return false -} - -/** - * Returns whether or not a VS Code DocumentSymbol is a method that could be a Lambda handler - * * has one or more parameters - * * if there is more than one parameter, the second parameter is an ILambdaContext object - * * does not check for extension/implementation of ILambdaContext - * @param symbol VS Code DocumentSymbol to analyze - */ -export function isValidMethodSignature(symbol: vscode.DocumentSymbol): boolean { - const parametersRegExp = /\(.*\)/ - const lambdaContextType = 'ILambdaContext ' - - if (symbol.kind === vscode.SymbolKind.Method) { - // public void methodName(Foo x, ILambdaContext y) -> (Foo x, ILambdaContext y) - const parametersArr = parametersRegExp.exec(symbol.name) - // reject if there are no parameters - if (!parametersArr) { - return false - } - // remove generics from parameter string so we can do a predictable split on comma - const strippedStr = stripGenericsFromParams(parametersArr[0]) - const individualParams = strippedStr.split(',') - if (individualParams.length === 1 || individualParams[1].valueOf().trimStart().startsWith(lambdaContextType)) { - return true - } - } - - return false -} - -/** - * Strips any generics from a string in order to ensure predictable commas for a string of parameters. - * e.g.: `'(Foo x, ILambdaContext y)' -> '(Foo x, ILambdaContext y)'` - * Implements a fairly rough English-centric approximation of the C# identifier spec: - * * can start with a letter, underscore, or @ sign - * * all other characters are letters, numbers, underscores, or periods - * - * Actual spec: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/lexical-structure#identifiers - * @param input String to remove generics from - */ -function stripGenericsFromParams(input: string): string { - const cSharpGenericIdentifierRegex = /(?:<{1}(?:\s*[a-zA-Z_@][a-zA-Z0-9._]*[\s,]?)*>{1})/g - - return input.replace(cSharpGenericIdentifierRegex, '') -} - -export function generateDotNetLambdaHandler(components: DotNetLambdaHandlerComponents): string { - return `${components.assembly}::${components.namespace}.${components.class}::${components.method}` -} diff --git a/packages/core/src/shared/codelens/goCodeLensProvider.ts b/packages/core/src/shared/codelens/goCodeLensProvider.ts deleted file mode 100644 index 3d3bf1c194..0000000000 --- a/packages/core/src/shared/codelens/goCodeLensProvider.ts +++ /dev/null @@ -1,166 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { VSCODE_EXTENSION_ID } from '../extensions' -import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { getLogger } from '../logger/logger' -import { stripNewLinesAndComments } from '../../shared/utilities/textUtilities' -import { findParentProjectFile } from '../utilities/workspaceUtils' -import { basename, dirname } from 'path' -import { activateExtension } from '../utilities/vsCodeUtils' - -export const goLanguage = 'go' -export const goAllfiles: vscode.DocumentFilter[] = [ - { - scheme: 'file', - language: goLanguage, - }, -] - -// Go modules were introduced in Go 1.11 -// Before that, $GOPATH had to be set properly to find dependencies -// We currently just ignore projects without a Go module file -export const goBasePattern = '**/go.mod' - -export async function getLambdaHandlerCandidates(document: vscode.TextDocument): Promise { - const modFile = await findParentProjectFile(document.uri, /go\.mod$/) - const hasGoExtension = !!(await activateExtension(VSCODE_EXTENSION_ID.go)) - - if (!modFile || !hasGoExtension) { - return [] - } - - // We'll check that the Go lambda module is required, otherwise showing debug configs does not make much sense - try { - const modDoc: vscode.TextDocument = await vscode.workspace.openTextDocument(modFile!) - // TODO: Check require and import statements for the correct modules/packages (very low priority) - - if ( - !modDoc.getText().includes('github.com/aws/aws-lambda-go') || - !document.getText().includes('github.com/aws/aws-lambda-go/lambda') - ) { - return [] - } - } catch (err) { - // No need to throw an error - getLogger().verbose( - `Go CodeLens: not enabled for "${document.fileName}". Verify that a go.mod file exists and is within the module directory` - ) - - return [] - } - - const filename = document.uri.fsPath - const symbols: vscode.DocumentSymbol[] = - (await vscode.commands.executeCommand( - 'vscode.executeDocumentSymbolProvider', - document.uri - )) ?? [] - - return symbols - .filter((symbol) => isValidFuncSignature(document, symbol)) - .map((symbol) => { - return { - filename, - handlerName: basename(dirname(filename)), - rootUri: modFile, - range: symbol.range, - } - }) -} - -/** - * Checks for a valid lamba function signature for Go - * - * @param document VS Code Document that contains the symbol - * @param symbol VS Code DocumentSymbol to analyze - */ -export function isValidFuncSignature(document: vscode.TextDocument, symbol: vscode.DocumentSymbol): boolean { - if (symbol.name === 'main') { - return false - } - - // Documentation for valid lambda handler: https://docs.aws.amazon.com/lambda/latest/dg/golang-handler.html - // The LSP doesn't expose any low-level type-checking functionality unfortunately. If we want to perform - // type-checking on our lambdas, we need to write the code for that ourselves using 'executeTypeDefinitionProvider'. - if (symbol.kind === vscode.SymbolKind.Function && symbol.range.start.character === 0) { - // vscode details does not include the return type in the signature :( - // otherwise we would just use symbol.details instead of parsing it ourselves - const funcWithBody: string = document.getText(symbol.range) - - const bodyStart: number = funcWithBody.search(/\{/) - const sigStart: number = funcWithBody.search(/\(/) - - if (bodyStart === -1 || sigStart === -1) { - return false - } - - const funcSig: string = stripNewLinesAndComments(funcWithBody.substring(sigStart, bodyStart)) - - const funcSigParts: string[] = funcSig.split(/\)/) - const argTypes: string[] = parseTypes(funcSigParts[0]) - const retTypes: string[] = funcSigParts.length > 1 ? parseTypes(funcSigParts[1]) : [] - - return validateArgumentTypes(argTypes) && validateReturnTypes(retTypes) - } - - return false -} - -/** - * Finds all types of the parameter list, stripping out names if they exist. - * Parenthesis are stripped from the list if they exist. - * - * @param params List of parameters delimeted by commas - * - * @returns A list of types mapping 1:1 with the input - */ -function parseTypes(params: string): string[] { - params = params.replace(/\(|\)/, '') - const types: string[] = [] - - let lastType: number = 0 - const paramParts = params.split(',') - - // Names of parameters must either be all present or all absent: https://golang.org/ref/spec#Function_types - // eslint-disable-next-line unicorn/no-array-for-each - paramParts.forEach((element: string, i: number) => { - const parts: string[] = element.trim().split(/\s+/) - const type: string = parts.length > 1 ? parts[1].trim() : parts[0].trim() - - // Go allows types to be assigned to multiple parameters, e.g. (x, y, z int) === (x int, y int, z int) - if (parts.length > 1) { - for (let j = lastType; j < i; j++) { - types[j] = type - } - lastType = i + 1 - } - - if (type !== '') { - types.push(type) - } - }) - - return types -} - -function validateArgumentTypes(argTypes: string[]): boolean { - if (argTypes.length > 2) { - return false - } else if (argTypes.length === 2) { - return argTypes[0].includes('Context') - } else { - return true - } -} - -function validateReturnTypes(retTypes: string[]): boolean { - if (retTypes.length > 2) { - return false - } else { - return retTypes.length === 0 ? true : retTypes.pop()!.includes('error') - } -} diff --git a/packages/core/src/shared/codelens/javaCodeLensProvider.ts b/packages/core/src/shared/codelens/javaCodeLensProvider.ts deleted file mode 100644 index 58eda7fc4b..0000000000 --- a/packages/core/src/shared/codelens/javaCodeLensProvider.ts +++ /dev/null @@ -1,201 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { findParentProjectFile } from '../utilities/workspaceUtils' - -export const javaLanguage = 'java' -export const javaAllfiles: vscode.DocumentFilter[] = [ - { - scheme: 'file', - language: javaLanguage, - }, -] -export const gradleBasePattern = '**/build.gradle' -export const mavenBasePattern = '**/pom.xml' - -const regexpReservedWordPublic = /\bpublic \b/ -const regexpReservedWordAbstract = /\b abstract \b/ -const regexpParameters = /\(.*\)/ - -export interface JavaLambdaHandlerComponents { - package: string - class: string - method: string - // Range of the function representing the Lambda Handler - handlerRange: vscode.Range -} - -export async function getLambdaHandlerCandidates(document: vscode.TextDocument): Promise { - const rootUri = - (await findParentProjectFile(document.uri, /^.*pom.xml$/)) ?? - (await findParentProjectFile(document.uri, /^.*build.gradle$/)) - if (!rootUri) { - return [] - } - - const symbols: vscode.DocumentSymbol[] = - (await vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri)) || [] - - return getLambdaHandlerComponents(document, symbols).map((lambdaHandlerComponents) => { - const handlerName = generateJavaLambdaHandler(lambdaHandlerComponents) - - return { - filename: document.uri.fsPath, - handlerName, - rootUri: rootUri, - range: lambdaHandlerComponents.handlerRange, - } - }) -} - -export function getLambdaHandlerComponents( - document: vscode.TextDocument, - symbols: vscode.DocumentSymbol[] -): JavaLambdaHandlerComponents[] { - const packageSymbols = symbols.filter((symbol) => symbol.kind === vscode.SymbolKind.Package) - if (packageSymbols.length !== 1) { - return [] - } - const packageName = packageSymbols[0].name - - return ( - symbols - .filter((symbol) => symbol.kind === vscode.SymbolKind.Class) - .filter((classSymbol) => isValidClassSymbol(document, classSymbol)) - // Find relevant methods within each class - .reduce((accumulator, lambdaHandlerComponent) => { - accumulator.push( - ...lambdaHandlerComponent.children - .filter((classChildSymbol) => classChildSymbol.kind === vscode.SymbolKind.Method) - .filter((methodSymbol) => isValidLambdaHandler(document, methodSymbol)) - .map((methodSymbol) => { - return { - package: packageName, - class: document.getText(lambdaHandlerComponent.selectionRange), - method: document.getText(methodSymbol.selectionRange), - handlerRange: methodSymbol.range, - } - }) - ) - - return accumulator - }, []) - ) -} - -export function isValidClassSymbol( - document: Pick, - symbol: vscode.DocumentSymbol -): boolean { - if (symbol.kind === vscode.SymbolKind.Class) { - // from "public abstract class Processor" pull "public abstract class " - const classDeclarationBeforeNameRange = new vscode.Range(symbol.range.start, symbol.selectionRange.start) - const classDeclarationBeforeName: string = document.getText(classDeclarationBeforeNameRange) - - return ( - regexpReservedWordPublic.test(classDeclarationBeforeName) && - !regexpReservedWordAbstract.test(classDeclarationBeforeName) - ) - } - - return false -} - -/** - * Returns whether or not a method is a valid Lambda handler - * @param document VS Code document - * @param symbol VS Code DocumentSymbol to evaluate - */ -export function isValidLambdaHandler( - document: Pick, - symbol: vscode.DocumentSymbol -): boolean { - if (symbol.kind === vscode.SymbolKind.Method) { - // from "public async Task foo()" pull "public async Task " - const signatureBeforeMethodNameRange = new vscode.Range(symbol.range.start, symbol.selectionRange.start) - const signatureBeforeMethodName: string = document.getText(signatureBeforeMethodNameRange) - - if (regexpReservedWordPublic.test(signatureBeforeMethodName)) { - return isValidMethodSignature(symbol) - } - } - - return false -} - -/** - * Returns whether or not a VS Code DocumentSymbol is a method that could be a Lambda handler - * * has one parameter - * * has two parameters where the first params are an InputStream and OutputStream OR the last param is a Context - * * TODO?: Notably, we are not checking specifically for a `com.amazonaws.services.lambda.runtime.Context`, or `java.io` streams - * * has three parameters where both conditions from two parameters are met - * @param symbol VS Code DocumentSymbol to analyze - */ -export function isValidMethodSignature(symbol: vscode.DocumentSymbol): boolean { - if (symbol.kind === vscode.SymbolKind.Method) { - // The `redhat.java` extension appears to strip a fair amount from this signature: - // from source function `public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context)` - // redhat extension returns: symbol.name = `'handleRequest(APIGatewayProxyRequestEvent, Context)'` - const parametersArr = regexpParameters.exec(symbol.name) - // reject if there are no parameters - if (!parametersArr) { - return false - } - // remove generics from parameter string so we can do a predictable split on comma - const strippedStr = stripGenericsFromParams(parametersArr[0]) - const individualParams = strippedStr.split(',') - switch (individualParams.length) { - case 1: - return individualParams[0] === '()' ? false : true - case 2: - return lastParamIsContext(individualParams) || firstTwoParamsAreStreams(individualParams) - case 3: - return lastParamIsContext(individualParams) && firstTwoParamsAreStreams(individualParams) - default: - return false - } - } - - return false -} - -function lastParamIsContext(paramArr: string[]): boolean { - // TODO: handle different kinds of imported context objects in case user is importing a non-Lambda context - const lambdaContextType = /[\.\b]{0,1}Context\b/ - return lambdaContextType.test(paramArr[paramArr.length - 1].valueOf().trimStart()) -} - -function firstTwoParamsAreStreams(paramArr: string[]): boolean { - const inputStreamType = /[\.\b]{0,1}InputStream\b/ - const outputStreamType = /[\.\b]{0,1}OutputStream\b/ - return inputStreamType.test(paramArr[0].valueOf().trim()) && outputStreamType.test(paramArr[1].valueOf().trim()) -} - -/** - * Strips any generics from a string in order to ensure predictable commas for a string of parameters. - * e.g.: `'(Foo x, Context y)' -> '(Foo x, Context y)'` - * Implements a fairly rough English-centric approximation of the Java identifier spec: - * * isJavaIdentifierStart(firstCharacter) is true: https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#isJavaIdentifierStart(char) - * * all other characters are true for isJavaIdentifierPart: https://docs.oracle.com/javase/7/docs/api/java/lang/Character.html#isJavaIdentifierPart(char) - * * For now, cover this from an English-centric point of view (a-zA-Z) and add unicode ranges if necessary - * - * @param input String to remove generics from - */ -function stripGenericsFromParams(input: string): string { - const javaGenericIdentifierRegex = /<\s*(?:[a-zA-Z_$]{1}[a-zA-Z0-9_$]*?[\s,]*?)+>/g - - return input.replace(javaGenericIdentifierRegex, '') -} - -/** - * - * @param components Components to generate handler from - * @returns String representation of the Lambda Java handler. Always provides method (even if it corrrectly implements a Java Lambda interface) - */ -export function generateJavaLambdaHandler(components: JavaLambdaHandlerComponents): string { - return `${components.package}.${components.class}::${components.method}` -} diff --git a/packages/core/src/shared/codelens/pythonCodeLensProvider.ts b/packages/core/src/shared/codelens/pythonCodeLensProvider.ts deleted file mode 100644 index f9466189fc..0000000000 --- a/packages/core/src/shared/codelens/pythonCodeLensProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { LambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { findParentProjectFile } from '../utilities/workspaceUtils' - -export const pythonLanguage = 'python' -export const pythonAllfiles: vscode.DocumentFilter[] = [ - { - scheme: 'file', - language: pythonLanguage, - }, -] - -export const pythonBasePattern = '**/requirements.txt' - -export async function getLambdaHandlerCandidates(uri: vscode.Uri): Promise { - const rootUri = (await findParentProjectFile(uri, /^requirements\.txt$/)) || uri - - const filename = uri.fsPath - const parsedPath = path.parse(filename) - // Python handler paths are slash separated and don't include the file extension - const handlerPath = path - .relative(path.parse(rootUri.fsPath).dir, path.join(parsedPath.dir, parsedPath.name)) - .split(path.sep) - .join('/') - - const symbols: vscode.DocumentSymbol[] = - (await vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', uri)) ?? - [] - - return symbols.filter(isTopLevelFunction).map((symbol) => { - return { - filename, - handlerName: `${handlerPath}.${symbol.name}`, - rootUri: rootUri, - range: symbol.range, - } - }) -} - -function isTopLevelFunction(symbol: vscode.DocumentSymbol) { - // if the function is indented at all, it is not classified as being a top-level function in the eyes of Python - return symbol.kind === vscode.SymbolKind.Function && symbol.range.start.character === 0 -} diff --git a/packages/core/src/shared/codelens/resourceCodeLensProvider.ts b/packages/core/src/shared/codelens/resourceCodeLensProvider.ts deleted file mode 100644 index 648a3c9c5f..0000000000 --- a/packages/core/src/shared/codelens/resourceCodeLensProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { AwsResourceManager } from '../../dynamicResources/awsResourceManager' -import { ResourceNode } from '../../dynamicResources/explorer/nodes/resourceNode' - -export class ResourceCodeLensProvider implements vscode.CodeLensProvider { - private readonly _onDidChangeCodeLenses = new vscode.EventEmitter() - public get onDidChangeCodeLenses(): vscode.Event { - return this._onDidChangeCodeLenses.event - } - - public constructor(public readonly resourceManager: AwsResourceManager) {} - - public provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken - ): vscode.ProviderResult { - const uri = document.uri - const resource = this.resourceManager.fromUri(uri) - if (resource) { - const type = resource instanceof ResourceNode ? resource.parent : resource - const codelenses = [ - { - range: new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 0)), - isResolved: true, - command: { - title: localize( - 'aws.resources.codelens.docs', - 'View resource documentation ({0})', - type.typeName - ), - command: 'aws.resources.viewDocs', - arguments: [type], - }, - }, - ] - - return uri.scheme === 'file' ? codelenses : [] - } - return [] - } -} diff --git a/packages/core/src/shared/codelens/samTemplateCodeLensProvider.ts b/packages/core/src/shared/codelens/samTemplateCodeLensProvider.ts deleted file mode 100644 index d4074ecf48..0000000000 --- a/packages/core/src/shared/codelens/samTemplateCodeLensProvider.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import _ from 'lodash' -import * as vscode from 'vscode' -import { TemplateFunctionResource, TemplateSymbolResolver } from '../cloudformation/templateSymbolResolver' -import { getConfigsMappedToTemplates, LaunchConfiguration } from '../debug/launchConfiguration' -import { getIdeProperties } from '../extensionUtilities' -import { TEMPLATE_TARGET_TYPE, API_TARGET_TYPE } from '../sam/debugger/awsSamDebugConfiguration' -import { AddSamDebugConfigurationInput } from '../sam/debugger/commands/addSamDebugConfiguration' -import { localize } from '../utilities/vsCodeUtils' -import { SamCliSettings } from '../sam/cli/samCliSettings' - -/** - * Provides "Add Debug Configuration" CodeLenses to SAM template.yaml files, - * for resources that do not already have a mapped config in launch.json. - */ -export class SamTemplateCodeLensProvider implements vscode.CodeLensProvider { - public async provideCodeLenses( - document: vscode.TextDocument, - token: vscode.CancellationToken, - symbolResolver = new TemplateSymbolResolver(document), - launchConfig = new LaunchConfiguration(document.uri), - waitForSymbols: boolean = false - ): Promise { - const config = SamCliSettings.instance - if (!config.get('enableCodeLenses', false)) { - return [] - } - const apiResources = await symbolResolver.getResourcesOfKind('api', waitForSymbols) - const funResources = await symbolResolver.getResourcesOfKind('function', waitForSymbols) - if (_(funResources).isEmpty() && _(apiResources).isEmpty()) { - return [] - } - - // User already has launch configs for: - const mappedApiConfigs = Array.from(await getConfigsMappedToTemplates(launchConfig, 'api')) - const mappedFunConfigs = Array.from(await getConfigsMappedToTemplates(launchConfig, 'template')) - - const unmappedApis = apiResources.filter( - (r) => - undefined === - mappedApiConfigs.find( - (o) => r.name === (o.invokeTarget as any).logicalId && o.invokeTarget.target === 'api' - ) - ) - const unmappedFuns = funResources.filter( - (r) => - undefined === - mappedFunConfigs.find( - (o) => r.name === (o.invokeTarget as any).logicalId && o.invokeTarget.target === 'template' - ) - ) - const codelensInfo = [...unmappedApis, ...unmappedFuns].map((r) => this.createCodeLens(r, document.uri)) - return codelensInfo - } - - private createCodeLens(resource: TemplateFunctionResource, templateUri: vscode.Uri): vscode.CodeLens { - const target = resource.kind === 'api' ? API_TARGET_TYPE : TEMPLATE_TARGET_TYPE - const input: AddSamDebugConfigurationInput = { - resourceName: resource.name, - rootUri: templateUri, - } - const title = - resource.kind === 'api' - ? `${getIdeProperties().company}: ${localize( - 'AWS.command.addSamApiDebugConfiguration', - 'Add API Debug Configuration' - )}` - : `${getIdeProperties().company}: ${localize( - 'AWS.command.addSamDebugConfiguration', - 'Add Debug Configuration' - )}` - return new vscode.CodeLens(resource.range, { - title: title, - command: 'aws.addSamDebugConfiguration', - arguments: [input, target], - }) - } -} diff --git a/packages/core/src/shared/codelens/typescriptCodeLensProvider.ts b/packages/core/src/shared/codelens/typescriptCodeLensProvider.ts deleted file mode 100644 index 9cdfe96318..0000000000 --- a/packages/core/src/shared/codelens/typescriptCodeLensProvider.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { LambdaHandlerCandidate, RootlessLambdaHandlerCandidate } from '../lambdaHandlerSearch' -import { TypescriptLambdaHandlerSearch } from '../typescriptLambdaHandlerSearch' -import { normalizeSeparator } from '../utilities/pathUtils' -import { findParentProjectFile } from '../utilities/workspaceUtils' - -export const javascriptLanguage = 'javascript' - -export const typescriptLanguage = 'typescript' - -export const typescriptAllFiles: vscode.DocumentFilter[] = [ - { - language: javascriptLanguage, - scheme: 'file', - }, - { - language: typescriptLanguage, - scheme: 'file', - }, -] - -export const javascriptBasePattern = '**/package.json' - -export async function getLambdaHandlerCandidates(document: vscode.TextDocument): Promise { - const rootUri = (await findParentProjectFile(document.uri, /^package\.json$/)) || document.uri - - const search: TypescriptLambdaHandlerSearch = new TypescriptLambdaHandlerSearch( - document.uri.fsPath, - document.getText() - ) - const unprocessedHandlers: RootlessLambdaHandlerCandidate[] = await search.findCandidateLambdaHandlers() - - // For Javascript CodeLenses, store the complete relative pathed handler name - // (eg: src/app.handler) instead of only the pure handler name (eg: app.handler) - // Without this, the CodeLens command is unable to resolve a match back to a sam template. - // This is done to address https://github.com/aws/aws-toolkit-vscode/issues/757 - return await finalizeTsHandlers(unprocessedHandlers, document.uri, rootUri) -} - -/** - * Applies a full relative path to the Javascript handler that will be stored in the CodeLens commands. - * Also adds `package.json` path - * @param handlers Rootless handlers to apply relative paths to - * @param uri URI of the file containing these Lambda Handlers - * @param rootUri URI of `package.json` file or the source file if there isn't one found. - */ -async function finalizeTsHandlers( - handlers: RootlessLambdaHandlerCandidate[], - fileUri: vscode.Uri, - rootUri: vscode.Uri -): Promise { - const relativePath = path.relative(path.dirname(rootUri.fsPath), path.dirname(fileUri.fsPath)) - - return handlers.map((handler) => { - return { - filename: handler.filename, - handlerName: normalizeSeparator(path.join(relativePath, handler.handlerName)), - rootUri: rootUri, - range: handler.range, - } - }) -} diff --git a/packages/core/src/shared/debug/launchConfiguration.ts b/packages/core/src/shared/debug/launchConfiguration.ts deleted file mode 100644 index 69ea55366b..0000000000 --- a/packages/core/src/shared/debug/launchConfiguration.ts +++ /dev/null @@ -1,208 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import _ from 'lodash' -import * as nls from 'vscode-nls' -import * as path from 'path' -import * as vscode from 'vscode' -import { - AwsSamDebuggerConfiguration, - CodeTargetProperties, - ensureRelativePaths, - isAwsSamDebugConfiguration, - isCodeTargetProperties, - isTemplateTargetProperties, - AwsSamTargetType, -} from '../sam/debugger/awsSamDebugConfiguration' -import { - AwsSamDebugConfigurationValidator, - DefaultAwsSamDebugConfigurationValidator, -} from '../sam/debugger/awsSamDebugConfigurationValidator' -import * as pathutils from '../utilities/pathUtils' -import { tryGetAbsolutePath } from '../utilities/workspaceUtils' -import { getLogger } from '../logger/logger' -import { makeFailedWriteMessage, showViewLogsMessage } from '../utilities/messages' -import { openUrl } from '../utilities/vsCodeUtils' -import globals from '../extensionGlobals' -import { getLaunchConfigDocUrl } from '../extensionUtilities' - -const localize = nls.loadMessageBundle() - -/** - * Reads and writes DebugConfigurations. - */ -export interface DebugConfigurationSource { - getDebugConfigurations(): vscode.DebugConfiguration[] - setDebugConfigurations(value: vscode.DebugConfiguration[]): Promise -} - -/** - * Wraps read and write operations on launch.json. - */ -export class LaunchConfiguration { - public readonly workspaceFolder: vscode.WorkspaceFolder | undefined - /** - * Creates a Launch Configuration scoped to the given resource. - */ - public constructor( - public readonly scopedResource: vscode.Uri, - private readonly configSource: DebugConfigurationSource = new DefaultDebugConfigSource(scopedResource), - private readonly samValidator: AwsSamDebugConfigurationValidator = new DefaultAwsSamDebugConfigurationValidator( - vscode.workspace.getWorkspaceFolder(scopedResource) - ) - ) { - this.workspaceFolder = vscode.workspace.getWorkspaceFolder(scopedResource) - } - - public getDebugConfigurations(): vscode.DebugConfiguration[] { - const configs = this.configSource.getDebugConfigurations() - return configs.map((o) => { - if (isAwsSamDebugConfiguration(o)) { - ensureRelativePaths(undefined, o) - } - return o - }) - } - - /** - * Returns all valid Sam Debug Configurations. - */ - public async getSamDebugConfigurations(): Promise { - const configs = this.getDebugConfigurations().filter((o) => - isAwsSamDebugConfiguration(o) - ) as AwsSamDebuggerConfiguration[] - const registry = await globals.templateRegistry - // XXX: can't use filter() with async predicate. - const validConfigs: AwsSamDebuggerConfiguration[] = [] - for (const c of configs) { - if ((await this.samValidator.validate(c, registry))?.isValid) { - validConfigs.push(c) - } - } - return validConfigs - } - - /** - * Adds a collection of debug configurations to the top of the list - */ - public async addDebugConfigurations(debugConfigs: vscode.DebugConfiguration[]): Promise { - await this.configSource.setDebugConfigurations([...debugConfigs, ...this.getDebugConfigurations()]) - } - - /** - * Adds a debug configuration to the top of the list. - */ - public async addDebugConfiguration(debugConfig: vscode.DebugConfiguration): Promise { - await this.configSource.setDebugConfigurations([debugConfig, ...this.getDebugConfigurations()]) - } - - /** - * Edits a debug configuration at a specfic index by replacing it with a new one. - * @param editedDebugConfig Full edited debug configuration - * @param index Index in `launch.json` configurations to replace - */ - public async editDebugConfiguration(editedDebugConfig: vscode.DebugConfiguration, index: number): Promise { - const configs = this.getDebugConfigurations() - configs[index] = editedDebugConfig - await this.configSource.setDebugConfigurations(configs) - } -} - -class DefaultDebugConfigSource implements DebugConfigurationSource { - private readonly launch: vscode.WorkspaceConfiguration - - public constructor(resource: vscode.Uri) { - this.launch = vscode.workspace.getConfiguration('launch', resource) - } - - public getDebugConfigurations(): vscode.DebugConfiguration[] { - return this.launch.get('configurations') ?? [] - } - - public async setDebugConfigurations(value: vscode.DebugConfiguration[]): Promise { - try { - await this.launch.update('configurations', value) - } catch (e) { - const helpText = localize('AWS.generic.message.getHelp', 'Get Help...') - getLogger().error('setDebugConfigurations failed: %O', e as Error) - await showViewLogsMessage(makeFailedWriteMessage('launch.json'), 'error', [helpText]).then( - async (buttonText) => { - if (buttonText === helpText) { - await openUrl(getLaunchConfigDocUrl()) - } - } - ) - } - } -} - -async function getSamCodeTargets(launchConfig: LaunchConfiguration): Promise { - const debugConfigs = await launchConfig.getSamDebugConfigurations() - return _(debugConfigs) - .map((samConfig) => samConfig.invokeTarget) - .filter(isCodeTargetProperties) - .value() -} - -/** - * Gets configs associated with the template.yaml to which `launchConfig` is - * scoped. - * - * @param launchConfig Launch config to check - * @param type target type ('api' or 'template'), or undefined for 'both' - */ -export async function getConfigsMappedToTemplates( - launchConfig: LaunchConfiguration, - type: AwsSamTargetType | undefined -): Promise> { - if (type === 'code') { - throw Error() - } - const folder = launchConfig.workspaceFolder - // Launch configs with target=template or target=api. - const templateConfigs = (await launchConfig.getSamDebugConfigurations()).filter((o) => - isTemplateTargetProperties(o.invokeTarget) - ) - const filtered = templateConfigs.filter( - (t) => - (type === undefined || t.invokeTarget.target === type) && - pathutils.areEqual( - folder?.uri.fsPath, - (t.invokeTarget as any).templatePath, - launchConfig.scopedResource.fsPath - ) - ) - return _(filtered) - .thru((array) => new Set(array)) - .value() -} - -/** - * Gets a set of filepaths pointing to the handler source file for each - * `target=code` config in launch.json. - * - * Each path is the absolute path resolved against `projectRoot`. - * - * @param launchConfig Launch config to check - */ -export async function getReferencedHandlerPaths(launchConfig: LaunchConfiguration): Promise> { - const existingSamCodeTargets = await getSamCodeTargets(launchConfig) - - return _(existingSamCodeTargets) - .map((target) => { - if (path.isAbsolute(target.projectRoot)) { - return pathutils.normalize(path.join(target.projectRoot, target.lambdaHandler)) - } - return pathutils.normalize( - path.join( - tryGetAbsolutePath(launchConfig.workspaceFolder, ''), - target.projectRoot, - target.lambdaHandler - ) - ) - }) - .thru((array) => new Set(array)) - .value() -} diff --git a/packages/core/src/shared/extensionGlobals.ts b/packages/core/src/shared/extensionGlobals.ts index 6e495339de..9940e4d481 100644 --- a/packages/core/src/shared/extensionGlobals.ts +++ b/packages/core/src/shared/extensionGlobals.ts @@ -5,7 +5,6 @@ import { ExtensionContext, LogOutputChannel, OutputChannel } from 'vscode' import { LoginManager } from '../auth/deprecated/loginManager' -import { AwsResourceManager } from '../dynamicResources/awsResourceManager' import { AWSClientBuilder } from './awsClientBuilder' import { AwsContext } from './awsContext' import { AwsContextCommands } from './awsContextCommands' @@ -203,7 +202,6 @@ export interface ToolkitGlobals { templateRegistry: Promise schemaService: SchemaService codelensRootRegistry: CodelensRootRegistry - resourceManager: AwsResourceManager uriHandler: UriHandler /** An id to differentiate the current machine being run on. Can help distinguish a remote from a local machine. */ machineId: string diff --git a/packages/core/src/shared/extensions.ts b/packages/core/src/shared/extensions.ts index cd14a9f599..d8674f1e8b 100644 --- a/packages/core/src/shared/extensions.ts +++ b/packages/core/src/shared/extensions.ts @@ -8,7 +8,6 @@ import { AwsContext } from './awsContext' import { RegionProvider } from './regions/regionProvider' import { TelemetryService } from './telemetry/telemetryService' import { CredentialsStore } from '../auth/credentials/store' -import { SamCliContext } from './sam/cli/samCliContext' import { UriHandler } from './vscode/uriHandler' // eslint-disable-next-line @typescript-eslint/naming-convention @@ -35,7 +34,6 @@ export const vscodeExtensionMinVersion = { export interface ExtContext { extensionContext: vscode.ExtensionContext awsContext: AwsContext - samCliContext: () => SamCliContext regionProvider: RegionProvider outputChannel: vscode.OutputChannel telemetryService: TelemetryService diff --git a/packages/core/src/shared/filetypes.ts b/packages/core/src/shared/filetypes.ts index 9457592c8b..78dcb0e605 100644 --- a/packages/core/src/shared/filetypes.ts +++ b/packages/core/src/shared/filetypes.ts @@ -6,7 +6,13 @@ import * as vscode from 'vscode' import * as path from 'path' import * as constants from '../shared/constants' -import * as aslFormats from '../stepFunctions/constants/aslFormats' +// ASL format constants (previously from stepFunctions/constants/aslFormats, which was removed) +const aslFormats = { + JSON_ASL: 'asl', + YAML_ASL: 'asl-yaml', + JSON_TYPE: 'json', + YAML_TYPE: 'yaml', +} as const import * as fsutil from '../shared/filesystemUtilities' import fs from '../shared/fs/fs' import globals from './extensionGlobals' diff --git a/packages/core/src/shared/fs/templateRegistry.ts b/packages/core/src/shared/fs/templateRegistry.ts index a9ec3a66ac..fb37d3d4a6 100644 --- a/packages/core/src/shared/fs/templateRegistry.ts +++ b/packages/core/src/shared/fs/templateRegistry.ts @@ -4,21 +4,14 @@ */ import * as vscode from 'vscode' -import { readFileSync } from 'fs' // eslint-disable-line no-restricted-imports import * as CloudFormation from '../cloudformation/cloudformation' -import * as pathutils from '../utilities/pathUtils' -import * as path from 'path' -import { isInDirectory } from '../filesystemUtilities' -import { dotNetRuntimes, goRuntimes, javaRuntimes } from '../../lambda/models/samLambdaRuntime' -import { getLambdaDetails } from '../../lambda/utils' -import { WatchedFiles, WatchedItem } from './watchedFiles' +import { WatchedFiles } from './watchedFiles' import { getLogger } from '../logger/logger' import globals from '../extensionGlobals' import { Timeout } from '../utilities/timeoutUtils' import { localize } from '../utilities/vsCodeUtils' import { PerfLog } from '../logger/perfLogger' import { showMessageWithCancel } from '../utilities/messages' -import { Runtime } from '@aws-sdk/client-lambda' export class CloudFormationTemplateRegistry extends WatchedFiles { public name: string = 'CloudFormationTemplateRegistry' @@ -125,184 +118,3 @@ export class AsyncCloudFormationTemplateRegistry { return this.setupPromise } } - -/** - * Gets resources and additional metadata for resources tied to a filepath and handler. - * Checks all registered templates by default; otherwise can operate on a subset TemplateDatum[] - * @param filepath Handler file's path - * @param handler Handler function from aforementioned file - * @param unfilteredTemplates Array containing TemplateDatum objects to filter - */ -export function getResourcesForHandler( - filepath: string, - handler: string, - unfilteredTemplates: WatchedItem[] -): { templateDatum: WatchedItem; name: string; resourceData: CloudFormation.Resource }[] { - // TODO: Array.flat and Array.flatMap not introduced until >= Node11.x -- migrate when VS Code updates Node ver - const o = unfilteredTemplates.map((templateDatum) => { - return getResourcesForHandlerFromTemplateDatum(filepath, handler, templateDatum).map((resource) => { - return { - ...resource, - templateDatum, - } - }) - }) - if (o.length === 0) { - return [] - } - return o.reduce((acc, cur) => [...acc, ...cur]) -} - -/** - * Returns an array of Cloudformation Resources in a TemplateDatum that is tied to the filepath and handler given. - * @param filepath Handler file's path - * @param handler Handler function from aforementioned file - * @param templateDatum TemplateDatum object to search through - */ -export function getResourcesForHandlerFromTemplateDatum( - filepath: string, - handler: string, - templateDatum: WatchedItem -): { name: string; resourceData: CloudFormation.Resource }[] { - const matchingResources: { name: string; resourceData: CloudFormation.Resource }[] = [] - const templateDirname = path.dirname(templateDatum.path) - // template isn't a parent or sibling of file - if (!isInDirectory(templateDirname, path.dirname(filepath))) { - return [] - } - - // no resources - const resources = templateDatum.item.Resources - if (!resources) { - return [] - } - - for (const key of Object.keys(resources)) { - const resource = resources[key] - // check if some sort of serverless function - if ( - resource && - [CloudFormation.SERVERLESS_FUNCTION_TYPE, CloudFormation.LAMBDA_FUNCTION_TYPE].includes(resource.Type) - ) { - // parse template values that could potentially be refs - const registeredRuntime = CloudFormation.getStringForProperty( - resource.Properties, - 'Runtime', - templateDatum.item - ) as Runtime - const registeredCodeUri = CloudFormation.getStringForProperty( - resource.Properties, - 'CodeUri', - templateDatum.item - ) - const registeredHandler = CloudFormation.getStringForProperty( - resource.Properties, - 'Handler', - templateDatum.item - ) - - // properties for image type templates - const registeredPackageType = CloudFormation.getStringForProperty( - resource.Properties, - 'PackageType', - templateDatum.item - ) - const registeredDockerContext = CloudFormation.getStringForProperty( - resource.Metadata, - 'DockerContext', - templateDatum.item - ) - const registeredDockerFile = CloudFormation.getStringForProperty( - resource.Metadata, - 'Dockerfile', - templateDatum.item - ) - - if (registeredRuntime && registeredHandler && registeredCodeUri) { - // Java and .NET are currently special cases in that the filepath and handler aren't specific. - // For now: check if handler matches and check if the code URI contains the filepath. - // TODO: Can we use Omnisharp or some sort of Java tooling to help guide us better? - if (dotNetRuntimes.includes(registeredRuntime) || javaRuntimes.includes(registeredRuntime)) { - if ( - handler === registeredHandler && - isInDirectory( - pathutils.normalize(path.join(templateDirname, registeredCodeUri)), - pathutils.normalize(filepath) - ) - ) { - matchingResources.push({ name: key, resourceData: resource }) - } - } else if (goRuntimes.includes(registeredRuntime)) { - // Go is another special case. The handler merely refers to the compiled binary. - // We ignore checking for a handler name match, since it is not relevant - // See here: https://github.com/aws/aws-lambda-go - if ( - isInDirectory( - pathutils.normalize(path.join(templateDirname, registeredCodeUri)), - pathutils.normalize(filepath) - ) - ) { - matchingResources.push({ name: key, resourceData: resource }) - } - } else { - // Interpreted languages all follow the same spec: - // ./path/to/handler/without/file/extension.handlerName - // Check to ensure filename and handler both match. - try { - const parsedLambda = getLambdaDetails({ - Handler: registeredHandler, - Runtime: registeredRuntime, - }) - const functionName = handler.split('.').pop() - if ( - pathutils.normalize(filepath) === - pathutils.normalize( - path.join(templateDirname, registeredCodeUri, parsedLambda.fileName) - ) && - functionName === parsedLambda.functionName - ) { - matchingResources.push({ name: key, resourceData: resource }) - } - } catch (e) { - getLogger().warn( - `Resource ${key} in template ${templateDirname} has invalid runtime for handler ${handler}: ${registeredRuntime}` - ) - } - } - // not direct-invoke type, attempt image type - } else if (registeredPackageType === 'Image' && registeredDockerContext && registeredDockerFile) { - // path must be inside dockerDir - const dockerDir = path.join(templateDirname, registeredDockerContext) - if (isInDirectory(dockerDir, filepath)) { - let adjustedHandler: string = handler - if (!filepath.endsWith('.cs')) { - // reframe path to be relative to dockerDir instead of package.json - // omit filename and append filename + function name from handler - const relPath = path.relative(dockerDir, path.dirname(filepath)) - const handlerParts = pathutils.normalizeSeparator(handler).split('/') - adjustedHandler = pathutils.normalizeSeparator( - path.join(relPath, handlerParts[handlerParts.length - 1]) - ) - } - try { - // open dockerfile and see if it has a handler that matches the handler represented by this file - const fileText = readFileSync(path.join(dockerDir, registeredDockerFile)).toString() - // exact match within quotes to avoid shorter paths being picked up - if ( - new RegExp(`['"]${adjustedHandler}['"]`, 'g').test(pathutils.normalizeSeparator(fileText)) - ) { - matchingResources.push({ name: key, resourceData: resource }) - } - } catch (e) { - // file read error - getLogger().error(e as Error) - } - } - } else { - getLogger().verbose(`Resource ${key} in template ${templateDirname} does not match handler ${handler}`) - } - } - } - - return matchingResources -} diff --git a/packages/core/src/shared/globalState.ts b/packages/core/src/shared/globalState.ts index 9e68836cbf..db8ec05d55 100644 --- a/packages/core/src/shared/globalState.ts +++ b/packages/core/src/shared/globalState.ts @@ -5,7 +5,6 @@ import * as vscode from 'vscode' import { getLogger } from './logger/logger' -import * as redshift from '../awsService/redshift/models/models' import { TypeConstructor, cast } from './utilities/typeConstructors' type ToolId = 'codecatalyst' | 'codewhisperer' | 'testId' | 'smus' @@ -196,51 +195,6 @@ export class GlobalState implements vscode.Memento { return Promise.allSettled(this.memento.keys().map((k) => this.memento.update(k, undefined))) } - /** - * Stores Redshift connection info for the specified warehouse ARN. - * - * TODO: this never garbage-collects old connections, so the state will grow forever... - * - * @param warehouseArn redshift warehouse ARN - * @param cxnInfo Connection info. Value is 'DELETE_CONNECTION' when the connection is deleted - * but the explorer node is not refreshed yet. - */ - async saveRedshiftConnection( - warehouseArn: string, - cxnInfo: redshift.ConnectionParams | undefined | 'DELETE_CONNECTION' - ) { - const allCxns = this.tryGet('aws.redshift.connections', Object, {}) - await this.update('aws.redshift.connections', { - ...allCxns, - [warehouseArn]: cxnInfo, - }) - } - - /** - * Get the Redshift connection info for the specified warehouse ARN. - * - * @param warehouseArn redshift warehouse ARN - * @returns Connection info. Value is 'DELETE_CONNECTION' when the connection is deleted but the - * explorer node is not refreshed yet. - */ - getRedshiftConnection(warehouseArn: string): redshift.ConnectionParams | undefined | 'DELETE_CONNECTION' { - const all = this.tryGet>( - 'aws.redshift.connections', - (v) => { - if (v !== undefined && typeof v !== 'object') { - throw new Error() - } - const item = (v as any)?.[warehouseArn] - // Requested item must be object or 'DELETE_CONNECTION'. - if (item !== undefined && typeof item !== 'object' && item !== 'DELETE_CONNECTION') { - throw new Error() - } - return v - } - ) - return all?.[warehouseArn] - } - /** * Sets SSO session creation timestamp for the given session `id`. * diff --git a/packages/core/src/shared/sam/activation.ts b/packages/core/src/shared/sam/activation.ts deleted file mode 100644 index c3fd137671..0000000000 --- a/packages/core/src/shared/sam/activation.ts +++ /dev/null @@ -1,284 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import globals from '../extensionGlobals' - -import * as vscode from 'vscode' -import { createNewSamApplication, resumeCreateNewSamApp } from '../../lambda/commands/createNewSamApp' -import { SamParameterCompletionItemProvider } from '../../lambda/config/samParameterCompletionItemProvider' -import * as codelensUtils from '../codelens/codeLensUtils' -import * as csLensProvider from '../codelens/csharpCodeLensProvider' -import * as javaLensProvider from '../codelens/javaCodeLensProvider' -import * as pyLensProvider from '../codelens/pythonCodeLensProvider' -import * as goLensProvider from '../codelens/goCodeLensProvider' -import { SamTemplateCodeLensProvider } from '../codelens/samTemplateCodeLensProvider' -import * as jsLensProvider from '../codelens/typescriptCodeLensProvider' -import { ExtContext } from '../extensions' -import { getIdeProperties } from '../extensionUtilities' -import { getLogger } from '../logger/logger' -import { PerfLog } from '../logger/perfLogger' -import { NoopWatcher } from '../fs/watchedFiles' -import { detectSamCli } from './cli/samCliDetection' -import { CodelensRootRegistry } from '../fs/codelensRootRegistry' -import { AWS_SAM_DEBUG_TYPE } from './debugger/awsSamDebugConfiguration' -import { SamDebugConfigProvider } from './debugger/awsSamDebugger' -import { addSamDebugConfiguration } from './debugger/commands/addSamDebugConfiguration' -import { shared } from '../utilities/functionUtils' -import { SamCliSettings } from './cli/samCliSettings' -import { Commands } from '../vscode/commands2' -import { runSync } from './sync' -import { runDeploy } from './deploy' -import { telemetry } from '../telemetry/telemetry' - -const sharedDetectSamCli = shared(detectSamCli) - -const supportedLanguages: { - [language: string]: codelensUtils.OverridableCodeLensProvider -} = {} - -/** - * Activate SAM-related functionality. - */ -export async function activate(ctx: ExtContext): Promise { - let didActivateCodeLensProviders = false - const config = SamCliSettings.instance - - // Do this "on-demand" because it is slow. - async function activateSlowCodeLensesOnce(): Promise { - if (!didActivateCodeLensProviders) { - didActivateCodeLensProviders = true - const disposeable = await activateCodefileOverlays(ctx, config) - ctx.extensionContext.subscriptions.push(...disposeable) - } - } - - if (config.get('enableCodeLenses', false)) { - await activateSlowCodeLensesOnce() - } - - await registerCommands(ctx, config) - Commands.register('aws.addSamDebugConfig', async () => { - if (!didActivateCodeLensProviders) { - await activateSlowCodeLensesOnce() - } - await samDebugConfigCmd() - }) - - ctx.extensionContext.subscriptions.push( - activateSamYamlOverlays(), - vscode.debug.registerDebugConfigurationProvider(AWS_SAM_DEBUG_TYPE, new SamDebugConfigProvider(ctx)) - ) - - ctx.extensionContext.subscriptions.push( - vscode.languages.registerCompletionItemProvider( - { - language: 'json', - scheme: 'file', - pattern: '**/.aws/parameters.json', - }, - new SamParameterCompletionItemProvider(), - '"' - ) - ) - - config.onDidChange(async (event) => { - switch (event.key) { - case 'location': - // This only shows a message (passive=true), does not set anything. - await sharedDetectSamCli({ passive: true, showMessage: true }) - break - case 'enableCodeLenses': - if (config.get(event.key, false) && !didActivateCodeLensProviders) { - await activateSlowCodeLensesOnce() - } - break - default: - break - } - }) - - const settings = SamCliSettings.instance - settings.onDidChange(({ key }) => { - if (key === 'legacyDeploy') { - telemetry.aws_modifySetting.run((span) => { - span.record({ settingId: 'sam_legacyDeploy' }) - const state = settings.get('legacyDeploy') - span.record({ settingState: state ? 'Enabled' : 'Disabled' }) - }) - } - }) - - ctx.extensionContext.subscriptions.push(config) - - if (globals.didReload) { - await resumeCreateNewSamApp(ctx) - } -} - -async function registerCommands(ctx: ExtContext, settings: SamCliSettings): Promise { - ctx.extensionContext.subscriptions.push( - Commands.register({ id: 'aws.samcli.detect', autoconnect: false }, () => - sharedDetectSamCli({ passive: false, showMessage: true }) - ), - Commands.register({ id: 'aws.lambda.createNewSamApp', autoconnect: false }, async () => { - await createNewSamApplication(ctx) - }), - Commands.register({ id: 'aws.addSamDebugConfiguration', autoconnect: false }, addSamDebugConfiguration), - Commands.register( - { id: 'aws.pickAddSamDebugConfiguration', autoconnect: false }, - codelensUtils.pickAddSamDebugConfiguration - ), - Commands.register( - { id: 'aws.deploySamApplication', autoconnect: true }, - async (arg) => - // `arg` is one of : - // - undefined - // - regionNode (selected from AWS Explorer) - // - Uri to template.yaml (selected from File Explorer) - // - TreeNode (selected from AppBuilder) - await runDeploy(arg) - ), - Commands.register({ id: 'aws.toggleSamCodeLenses', autoconnect: false }, async () => { - const toggled = !settings.get('enableCodeLenses', false) - await settings.update('enableCodeLenses', toggled) - }), - Commands.register( - { - id: 'aws.samcli.sync', - autoconnect: true, - }, - async (arg?, validate?: boolean) => await runSync('infra', arg, validate) - ) - ) -} - -async function activateCodeLensRegistry(context: ExtContext) { - try { - const registry = new CodelensRootRegistry() - globals.codelensRootRegistry = registry - - // - // "**/…" string patterns watch recursively across _all_ workspace - // folders (see documentation for addWatchPatterns()). - // - registry.addWatchPatterns([ - pyLensProvider.pythonBasePattern, - jsLensProvider.javascriptBasePattern, - csLensProvider.csharpBasePattern, - goLensProvider.goBasePattern, - javaLensProvider.gradleBasePattern, - javaLensProvider.mavenBasePattern, - ]) - await registry.rebuild() - } catch (e) { - void vscode.window.showErrorMessage( - localize( - 'AWS.codelens.failToInitializeCode', - 'Failed to activate Lambda handler {0}', - getIdeProperties().codelenses - ) - ) - getLogger().error('Failed to activate codelens registry %O', e) - // This prevents us from breaking for any reason later if it fails to load. Since - // Noop watcher is always empty, we will get back empty arrays with no issues. - globals.codelensRootRegistry = new NoopWatcher() as unknown as CodelensRootRegistry - } - context.extensionContext.subscriptions.push(globals.codelensRootRegistry) -} - -async function samDebugConfigCmd() { - const activeEditor = vscode.window.activeTextEditor - if (!activeEditor) { - getLogger().error(`aws.addSamDebugConfig was called without an active text editor`) - void vscode.window.showErrorMessage( - localize('AWS.pickDebugHandler.noEditor', 'Toolkit could not find an active editor') - ) - - return - } - const document = activeEditor.document - const provider = supportedLanguages[document.languageId] - if (!provider) { - getLogger().error(`aws.addSamDebugConfig called on a document with an invalid language: ${document.languageId}`) - void vscode.window.showErrorMessage( - localize( - 'AWS.pickDebugHandler.invalidLanguage', - 'Toolkit cannot detect handlers in language: {0}', - document.languageId - ) - ) - - return - } - - // TODO: No reason for this to depend on the codelense provider (which scans the whole workspace and creates filewatchers). - const lenses = (await provider.provideCodeLenses(document, new vscode.CancellationTokenSource().token, true)) ?? [] - await codelensUtils.invokeCodeLensCommandPalette(document, lenses) -} - -/** - * Creates vscode.CodeLensProvider for SAM "template.yaml" files. - * - * Used for: - * 1. showing codelenses in SAM template.yaml files - */ -function activateSamYamlOverlays(): vscode.Disposable { - return vscode.languages.registerCodeLensProvider( - [ - { - language: 'yaml', - scheme: 'file', - pattern: '**/*template.{yml,yaml}', - }, - ], - new SamTemplateCodeLensProvider() - ) -} - -/** - * EXPENSIVE AND SLOW. Creates filewatchers and vscode.CodeLensProvider objects - * for codefiles (as opposed to SAM template.yaml files). - * - * Used for: - * 1. showing codelenses - * 2. "Add Local Invoke and Debug Configuration" command (TODO: remove dependency on - * codelense provider (which scans the whole workspace and creates - * filewatchers)). - */ -async function activateCodefileOverlays( - context: ExtContext, - configuration: SamCliSettings -): Promise { - const perflog = new PerfLog('activateCodefileOverlays') - const disposables: vscode.Disposable[] = [] - const tsCodeLensProvider = codelensUtils.makeTypescriptCodeLensProvider(configuration) - const pyCodeLensProvider = await codelensUtils.makePythonCodeLensProvider(configuration) - const javaCodeLensProvider = await codelensUtils.makeJavaCodeLensProvider(configuration) - const csCodeLensProvider = await codelensUtils.makeCSharpCodeLensProvider(configuration) - const goCodeLensProvider = await codelensUtils.makeGoCodeLensProvider(configuration) - - // Ideally we should not need to `await` this Promise, but CodeLens providers are currently not implementing - // the event to notify on when their results change. - await activateCodeLensRegistry(context) - - supportedLanguages[jsLensProvider.javascriptLanguage] = tsCodeLensProvider - supportedLanguages[pyLensProvider.pythonLanguage] = pyCodeLensProvider - supportedLanguages[javaLensProvider.javaLanguage] = javaCodeLensProvider - supportedLanguages[csLensProvider.csharpLanguage] = csCodeLensProvider - supportedLanguages[goLensProvider.goLanguage] = goCodeLensProvider - supportedLanguages[jsLensProvider.typescriptLanguage] = tsCodeLensProvider - - disposables.push(vscode.languages.registerCodeLensProvider(jsLensProvider.typescriptAllFiles, tsCodeLensProvider)) - disposables.push(vscode.languages.registerCodeLensProvider(pyLensProvider.pythonAllfiles, pyCodeLensProvider)) - disposables.push(vscode.languages.registerCodeLensProvider(javaLensProvider.javaAllfiles, javaCodeLensProvider)) - disposables.push(vscode.languages.registerCodeLensProvider(csLensProvider.csharpAllfiles, csCodeLensProvider)) - disposables.push(vscode.languages.registerCodeLensProvider(goLensProvider.goAllfiles, goCodeLensProvider)) - - perflog.done() - return disposables -} diff --git a/packages/core/src/shared/sam/build.ts b/packages/core/src/shared/sam/build.ts deleted file mode 100644 index c8e81dce9f..0000000000 --- a/packages/core/src/shared/sam/build.ts +++ /dev/null @@ -1,346 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { TemplateItem, createTemplatePrompter } from '../ui/sam/templatePrompter' -import { ChildProcess } from '../utilities/processUtils' -import { addTelemetryEnvVar } from './cli/samCliInvokerUtils' -import { Wizard } from '../wizards/wizard' -import { CloudFormationTemplateRegistry } from '../fs/templateRegistry' -import { createExitPrompter } from '../ui/common/exitPrompter' -import { DataQuickPickItem, createMultiPick, createQuickPick } from '../ui/pickerPrompter' -import { createCommonButtons } from '../ui/buttons' -import { samBuildParamUrl, samBuildUrl } from '../constants' -import { CancellationError } from '../utilities/timeoutUtils' -import { ToolkitError } from '../errors' -import globals from '../extensionGlobals' -import { TreeNode } from '../treeview/resourceTreeDataProvider' -import { telemetry } from '../telemetry/telemetry' -import { getSpawnEnv } from '../env/resolveEnv' -import { - getErrorCode, - getProjectRoot, - getRecentResponse, - getSamCliPathAndVersion, - getTerminalFromError, - isDotnetRuntime, - updateRecentResponse, -} from './utils' -import { getConfigFileUri, validateSamBuildConfig } from './config' -import { runInTerminal } from './processTerminal' -import { buildMementoRootKey, buildProcessMementoRootKey, globalIdentifier } from './constants' -import { SemVer } from 'semver' - -export interface BuildParams { - readonly template: TemplateItem - readonly projectRoot: vscode.Uri - readonly paramsSource: ParamsSource.Specify | ParamsSource.SamConfig | ParamsSource.DefaultValues - readonly buildFlags?: string -} - -export enum ParamsSource { - Specify, - SamConfig, - DefaultValues, -} - -export function createParamsSourcePrompter(existValidSamconfig: boolean) { - const items: DataQuickPickItem[] = [ - { - label: 'Specify build flags', - data: ParamsSource.Specify, - }, - ] - - items.push( - existValidSamconfig - ? { - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - } - : { - label: 'Use default values', - data: ParamsSource.DefaultValues, - description: 'cached = true, parallel = true, use_container = true', - } - ) - - return createQuickPick(items, { - title: 'Specify parameter source for build', - placeholder: 'Select configuration options for sam build', - buttons: createCommonButtons(samBuildUrl), - }) -} - -const buildFlagItems: DataQuickPickItem[] = [ - { - label: 'Beta features', - data: '--beta-features', - description: 'Enable beta features', - }, - { - label: 'Build in source', - data: '--build-in-source', - description: 'Opts in to build project in the source folder', - }, - { - label: 'Cached', - data: '--cached', - description: 'Reuse build artifacts that have not changed from previous builds', - }, - { - label: 'Debug', - data: '--debug', - description: 'Turn on debug logging to print debug messages and display timestamps', - }, - { - label: 'Parallel', - data: '--parallel', - description: 'Enable parallel builds for AWS SAM template functions and layers', - }, - { - label: 'Skip prepare infra', - data: '--skip-prepare-infra', - description: 'Skip preparation stage when there are no infrastructure changes', - }, - { - label: 'Skip pull image', - data: '--skip-pull-image', - description: 'Skip pulling down the latest Docker image for Lambda runtime', - }, - { - label: 'Use container', - data: '--use-container', - description: 'Build functions with an AWS Lambda-like container', - }, - { - label: 'Save parameters', - data: '--save-params', - description: 'Save to samconfig.toml as default parameters', - }, -] -export type SamBuildResult = { - isSuccess: boolean -} - -export class BuildWizard extends Wizard { - arg: TreeNode | undefined - registry: CloudFormationTemplateRegistry - public constructor( - state: Partial, - registry: CloudFormationTemplateRegistry, - arg?: TreeNode | undefined - ) { - super({ initState: state, exitPrompterProvider: createExitPrompter }) - this.registry = registry - this.arg = arg - if (this.arg === undefined) { - // "Build" command was invoked on the command palette. - this.form.template.bindPrompter(() => - createTemplatePrompter(this.registry, buildMementoRootKey, samBuildUrl) - ) - this.form.projectRoot.setDefault(({ template }) => getProjectRoot(template)) - this.form.paramsSource.bindPrompter(async ({ projectRoot }) => { - const existValidSamConfig: boolean | undefined = await validateSamBuildConfig(projectRoot) - return createParamsSourcePrompter(existValidSamConfig) - }) - } else { - // "Build" command was invoked from build icon from sidebar - const templateUri = (this.arg.getTreeItem() as vscode.TreeItem).resourceUri - const templateItem = { uri: templateUri, data: {} } as TemplateItem - this.form.template.setDefault(templateItem) - this.form.projectRoot.setDefault(({ template }) => getProjectRoot(template)) - this.form.paramsSource.bindPrompter(async ({ projectRoot }) => { - const existValidSamConfig: boolean | undefined = await validateSamBuildConfig(projectRoot) - return createParamsSourcePrompter(existValidSamConfig) - }) - this.form.projectRoot.setDefault(() => getProjectRoot(templateItem)) - } - - this.form.buildFlags.bindPrompter( - () => - createMultiPick(buildFlagItems, { - title: 'Select build flags', - buttons: createCommonButtons(samBuildParamUrl), - ignoreFocusOut: true, - }), - { - showWhen: ({ paramsSource }) => paramsSource === ParamsSource.Specify, - } - ) - } -} - -/** - * Get build flags based on user selection - * @param paramsSource - * @param projectRoot - * @param defaultFlags - * @returns - */ -export async function getBuildFlags( - paramsSource: ParamsSource, - projectRoot: vscode.Uri, - defaultFlags: string[] -): Promise { - if (paramsSource === ParamsSource.SamConfig) { - try { - const samConfigFile = await getConfigFileUri(projectRoot) - return ['--config-file', samConfigFile.fsPath] - } catch (error) { - return defaultFlags - } - } - return defaultFlags -} - -export async function runBuild(arg?: TreeNode): Promise { - const source = arg ? 'AppBuilderBuild' : 'CommandPalette' - telemetry.record({ source: source }) - - // Prepare Build params - const buildParams: Partial = {} - - const registry = await globals.templateRegistry - const params = await new BuildWizard(buildParams, registry, arg).run() - if (params === undefined) { - throw new CancellationError('user') - } - - throwIfTemplateIsBeingBuilt(params.template.uri.path) - await registerTemplateBuild(params.template.uri.path) - - const projectRoot = params.projectRoot - - const defaultFlags: string[] = ['--cached', '--parallel', '--save-params', '--use-container'] - - const { path: samCliPath, parsedVersion } = await getSamCliPathAndVersion() - - // refactor - const buildFlags: string[] = - params.paramsSource === ParamsSource.Specify && params.buildFlags - ? await resolveBuildFlags(JSON.parse(params.buildFlags), parsedVersion) - : await getBuildFlags(params.paramsSource, projectRoot, defaultFlags) - - // todo remove - if (await isDotnetRuntime(params.template.uri)) { - if (buildFlags.includes('--use-container')) { - buildFlags.push('--mount-with', 'WRITE') - } - } - - const templatePath = params.template.uri.fsPath - buildFlags.push('--template', `${templatePath}`) - - await updateRecentResponse(buildMementoRootKey, 'global', 'templatePath', templatePath) - - try { - await vscode.window.withProgress( - { - cancellable: true, - location: vscode.ProgressLocation.Notification, - }, - async (progress, token) => { - token.onCancellationRequested(async () => { - throw new CancellationError('user') - }) - - progress.report({ message: `Building SAM template at ${params.template.uri.path}` }) - - // Create a child process to run the SAM build command - const buildProcess = new ChildProcess(samCliPath, ['build', ...buildFlags], { - spawnOptions: await addTelemetryEnvVar({ - cwd: params.projectRoot.fsPath, - env: await getSpawnEnv(process.env), - }), - }) - - // Run SAM build in Terminal - await runInTerminal(buildProcess, 'build') - } - ) - - return { - isSuccess: true, - } - } catch (error) { - throw ToolkitError.chain(error, 'Failed to build SAM template', { - details: { terminal: getTerminalFromError(error), ...resolveBuildArgConflict(buildFlags) }, - code: getErrorCode(error), - }) - } finally { - await unregisterTemplateBuild(params.template.uri.path) - } -} - -function resolveBuildArgConflict(boundArgs: string[]): string[] { - const boundArgsSet = new Set(boundArgs) - if (boundArgsSet.has('--watch')) { - boundArgsSet.delete('--no-watch') - } - if (boundArgsSet.has('--dependency-layer')) { - boundArgsSet.delete('--no--dependency-layer') - } - if (boundArgsSet.has('--use-container') || boundArgsSet.has('-u')) { - boundArgsSet.delete('--build-in-source') - } - if (boundArgsSet.has('--build-in-source')) { - boundArgsSet.delete('--no-build-in-source') - } - - // TODO phase 2: add anti param - // apply anti param if param is not set - // if (!boundArgsSet.has('--cached')) { - // boundArgsSet.add('--no-cached') - // } - // if (!boundArgsSet.has('--build-in-source')) { - // boundArgsSet.add('--no-build-in-source') - // } - // if (!boundArgsSet.has('--beta-features')) { - // boundArgsSet.add('--no-beta-features') - // } - return Array.from(boundArgsSet) -} -export async function resolveBuildFlags(buildFlags: string[], samCliVersion: SemVer | null): Promise { - // --no-use-container was not added until v1.133.0 - if (samCliVersion?.compare('1.133.0') ?? -1 < 0) { - return buildFlags - } - if (!buildFlags.includes('--use-container')) { - buildFlags.push('--no-use-container') - } - return buildFlags -} - -/** - * Returns true if there's an ongoing build process for the provided template, false otherwise - * @Param templatePath The path to the template.yaml file - */ -function isBuildInProgress(templatePath: string): boolean { - const expirationDate = getRecentResponse(buildProcessMementoRootKey, globalIdentifier, templatePath) - if (expirationDate) { - return Date.now() < parseInt(expirationDate) - } - return false -} - -/** - * Throws an error if there's a build in progress for the provided template - * @Param templatePath The path to the template.yaml file - */ -export function throwIfTemplateIsBeingBuilt(templatePath: string) { - if (isBuildInProgress(templatePath)) { - throw new ToolkitError('This template is already being built', { code: 'BuildInProgress' }) - } -} - -export async function registerTemplateBuild(templatePath: string) { - const expirationDate = Date.now() + 5 * 60 * 1000 // five minutes - await updateRecentResponse(buildProcessMementoRootKey, globalIdentifier, templatePath, expirationDate.toString()) -} - -export async function unregisterTemplateBuild(templatePath: string) { - await updateRecentResponse(buildProcessMementoRootKey, globalIdentifier, templatePath, undefined) -} diff --git a/packages/core/src/shared/sam/cli/samCliBuild.ts b/packages/core/src/shared/sam/cli/samCliBuild.ts deleted file mode 100644 index 128249c09c..0000000000 --- a/packages/core/src/shared/sam/cli/samCliBuild.ts +++ /dev/null @@ -1,188 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fileExists } from '../../filesystemUtilities' -import { getLogger, Logger } from '../../logger/logger' -import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils' -import { pushIf } from '../../utilities/collectionUtils' -import { localize } from '../../utilities/vsCodeUtils' -import { Timeout, waitTimeout } from '../../utilities/timeoutUtils' -import { ChildProcessResult } from '../../utilities/processUtils' -import { dirname } from 'path' - -export interface SamCliBuildInvocationArguments { - /** - * The path to a folder where the built artifacts are stored. - */ - buildDir: string - /** - * Resolves relative paths to the function's source code with respect to this folder. - * If omitted, relative paths are resolved with respect to the template's location. - */ - baseDir?: string - /** - * Location of the SAM Template to build - */ - templatePath: string - /** - * Environment variables set when invoking the SAM process (NOT passed to the Lambda). - */ - environmentVariables?: NodeJS.ProcessEnv - /** - * Manages the sam cli execution. - */ - invoker: SamCliProcessInvoker - /** - * - true: If your Lambda depends on packages that need to be compiled natively, - * use this flag to build your function inside an AWS Lambda-like Docker container. - * - false: Lambda will be built on local machine instead of in a Docker image. - */ - useContainer?: boolean - /** sam cli "--mount-with" option. */ - mountWith?: 'read' | 'write' - /** - * Specifies the name or id of an existing Docker network to Lambda Docker containers should connect to, - * along with the default bridge network. - * If not specified, the Lambda containers will only connect to the default bridge Docker network. - */ - dockerNetwork?: string - /** - * - true: Do not pull the latest Docker image for Lambda runtime. - * - false: Pull the latest Docker image if necessary - */ - skipPullImage?: boolean - /** - * The path to a custom dependency manifest (ex: package.json) to use instead of the default one. - */ - manifestPath?: string - /** - * parameter overrides specified in the `sam.template.parameters` field - */ - parameterOverrides?: string[] - /** SAM args specified by user (`sam.buildArguments`). */ - extraArgs?: string[] -} - -export interface FileFunctions { - fileExists: typeof fileExists -} - -/** - * An elaborate way to run `sam build`. - */ -export class SamCliBuildInvocation { - private _failure: string | undefined - - public constructor( - private readonly args: SamCliBuildInvocationArguments, - private readonly context: { file: FileFunctions } = { file: getDefaultFileFunctions() } - ) { - this.args.useContainer = !!this.args.useContainer - this.args.skipPullImage = !!this.args.skipPullImage - } - - /** Gets the failure message, or undefined if no failure was detected. */ - public failure(): string | undefined { - return this._failure - } - - /** - * Invokes "sam build". - * - * @returns Process exit code, or -1 if `SamCliBuildInvocation` stopped the process and stored a failure message in `SamCliBuildInvocation.failure()`. - */ - public async execute(timer?: Timeout): Promise { - await this.validate() - - const invokeArgs: string[] = [ - 'build', - ...(getLogger().logLevelEnabled('debug') ? ['--debug'] : []), - '--build-dir', - this.args.buildDir, - '--template', - this.args.templatePath, - ] - - pushIf(invokeArgs, !!this.args.baseDir, '--base-dir', this.args.baseDir!) - pushIf(invokeArgs, !!this.args.dockerNetwork, '--docker-network', this.args.dockerNetwork!) - pushIf(invokeArgs, !!this.args.useContainer, '--use-container') - pushIf(invokeArgs, !!this.args.mountWith, '--mount-with', this.args.mountWith?.toUpperCase()) - pushIf(invokeArgs, !!this.args.skipPullImage, '--skip-pull-image') - pushIf(invokeArgs, !!this.args.manifestPath, '--manifest', this.args.manifestPath!) - pushIf( - invokeArgs, - !!this.args.parameterOverrides && this.args.parameterOverrides.length > 0, - '--parameter-overrides', - ...(this.args.parameterOverrides ?? []) - ) - invokeArgs.push(...(this.args.extraArgs ?? [])) - - const env: NodeJS.ProcessEnv = { - ...process.env, - ...this.args.environmentVariables, - } - - const onOutput = (text: string): void => { - if (text.match(/(RuntimeError: Container does not exist)/)) { - this.args.invoker.stop() - this._failure = localize( - 'AWS.sam.build.failure.diskSpace', - '"sam build" failed. Check system disk space.' - ) - } else if (timer !== undefined) { - timer.refresh() - } - } - - let childProcessResult: Promise = this.args.invoker.invoke({ - spawnOptions: { - env, - cwd: dirname(this.args.templatePath), - }, - arguments: invokeArgs, - onStdout: onOutput, - onStderr: onOutput, - }) - - // TODO: add `Timeout` support to `ChildProcess` itself instead of wrapping the promise - if (timer) { - childProcessResult = waitTimeout(childProcessResult, timer, { - completeTimeout: false, - onExpire: () => { - this.args.invoker.stop() - this._failure = localize( - 'AWS.sam.build.failure.timeout', - '"sam build" failed. Timed out waiting for build.' - ) - }, - }) - } - - const result = await childProcessResult - - if (this._failure || result === undefined) { - return -1 - } - - logAndThrowIfUnexpectedExitCode(result, 0) - return result.exitCode - } - - private async validate(): Promise { - if (!(await this.context.file.fileExists(this.args.templatePath))) { - const logger: Logger = getLogger() - - const err = new Error(`template path does not exist: ${this.args.templatePath}`) - logger.error(err) - throw err - } - } -} - -function getDefaultFileFunctions(): FileFunctions { - return { - fileExists, - } -} diff --git a/packages/core/src/shared/sam/cli/samCliContext.ts b/packages/core/src/shared/sam/cli/samCliContext.ts deleted file mode 100644 index 1416fe5f71..0000000000 --- a/packages/core/src/shared/sam/cli/samCliContext.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SamCliSettings } from './samCliSettings' -import { DefaultSamCliProcessInvoker } from './samCliInvoker' -import { SamCliProcessInvoker } from './samCliInvokerUtils' -import { throwAndNotifyIfInvalid } from './samCliValidationUtils' -import { DefaultSamCliValidator, DefaultSamCliValidatorContext, SamCliValidator } from './samCliValidator' - -export interface SamCliContext { - validator: SamCliValidator - invoker: SamCliProcessInvoker -} - -// Sam Cli Context is lazy loaded on first request to reduce the -// amount of work done during extension activation. -let samCliContext: SamCliContext | undefined - -/** - * Sam Cli Context is lazy loaded on first request - */ -export function getSamCliContext() { - return (samCliContext ??= makeSamCliContext()) -} - -export async function getSamCliVersion(context: SamCliContext): Promise { - const result = await context.validator.detectValidSamCli() - throwAndNotifyIfInvalid(result) - - return result.versionValidation!.version! -} - -function makeSamCliContext(): SamCliContext { - const samCliConfiguration = SamCliSettings.instance - const invoker = new DefaultSamCliProcessInvoker(samCliConfiguration) - - const validatorContext = new DefaultSamCliValidatorContext(samCliConfiguration) - const validator = new DefaultSamCliValidator(validatorContext) - - const context: SamCliContext = { - invoker, - validator, - } - - return context -} diff --git a/packages/core/src/shared/sam/cli/samCliDeploy.ts b/packages/core/src/shared/sam/cli/samCliDeploy.ts deleted file mode 100644 index 2bd805555e..0000000000 --- a/packages/core/src/shared/sam/cli/samCliDeploy.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger } from '../../logger/logger' -import { map } from '../../utilities/collectionUtils' -import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils' - -export interface SamCliDeployParameters { - templateFile: string - parameterOverrides: Map - environmentVariables: NodeJS.ProcessEnv - region: string - stackName: string - s3Bucket: string - ecrRepo?: string -} - -export async function runSamCliDeploy( - deployArguments: SamCliDeployParameters, - invoker: SamCliProcessInvoker -): Promise { - const args = [ - 'deploy', - ...(getLogger().logLevelEnabled('debug') ? ['--debug'] : []), - '--template-file', - deployArguments.templateFile, - '--stack-name', - deployArguments.stackName, - '--capabilities', - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - 'CAPABILITY_AUTO_EXPAND', - '--region', - deployArguments.region, - '--s3-bucket', - deployArguments.s3Bucket, - ] - - if (deployArguments.ecrRepo) { - args.push('--image-repository', deployArguments.ecrRepo) - } - - if (deployArguments.parameterOverrides.size > 0) { - const overrides = [...map(deployArguments.parameterOverrides.entries(), ([key, value]) => `${key}=${value}`)] - args.push('--parameter-overrides', ...overrides) - } - - const childProcessResult = await invoker.invoke({ - arguments: args, - spawnOptions: { env: deployArguments.environmentVariables }, - }) - - logAndThrowIfUnexpectedExitCode(childProcessResult, 0) -} diff --git a/packages/core/src/shared/sam/cli/samCliDetection.ts b/packages/core/src/shared/sam/cli/samCliDetection.ts deleted file mode 100644 index bdd50ea713..0000000000 --- a/packages/core/src/shared/sam/cli/samCliDetection.ts +++ /dev/null @@ -1,94 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { samInstallUrl } from '../../constants' -import { telemetry } from '../../telemetry/telemetry' -import { SamCliSettings } from './samCliSettings' - -const localize = nls.loadMessageBundle() - -const learnMore = localize('AWS.samcli.userChoice.visit.install.url', 'Install SAM CLI') -const browseToSamCli = localize('AWS.samcli.userChoice.browse', 'Locate SAM CLI...') -const settingsUpdated = localize('AWS.samcli.detect.settings.updated', 'Settings updated.') - -/** - * Searches for SAM CLI on the system, updates the user setting if - * `passive=false`, and shows a message if `showMessage=true`. - * - * @param args.passive Controls whether user setting is updated. Also sets the - * telemetry "passive" flag. - * @param args.showMessage true: always show message, false: never show - * message (except if SAM was not found) - */ -export async function detectSamCli(args: { passive: boolean; showMessage: boolean | undefined }): Promise { - const config = SamCliSettings.instance - const isConfigured = '' !== config.get('location', '') - - const sam = await config.getOrDetectSamCli(true) - const found = sam.path !== '' - - // Update the user setting. - // - // NOTE: We must NOT _passively_ auto-update the user setting, that - // conflicts with VSCode "remote": each VSCode instance will update the - // setting based on its local environment, but the user settings are - // shared across VSCode instances... - const update = !args.passive && sam.autoDetected && sam.path && (await config.update('location', sam.path)) - - if (args.showMessage !== false || !found) { - if (!found) { - notifyUserSamCliNotDetected(config) - } else if (update && args.showMessage === true) { - void vscode.window.showInformationMessage(getSettingsUpdatedMessage(sam.path ?? '?')) - } - } - - if (!args.passive) { - telemetry.sam_detect.emit({ - passive: args.passive, - result: found ? 'Succeeded' : 'Failed', - ...(found ? {} : { reason: isConfigured ? 'SamCliInvalid' : 'SamCliNotFound' }), - }) - } -} - -function notifyUserSamCliNotDetected(SamCliSettings: SamCliSettings): void { - // inform the user, but don't wait for this to complete - void vscode.window - .showErrorMessage( - localize( - 'AWS.samcli.error.notFound', - 'Cannot find SAM CLI, which is required to create and debug SAM applications. If you have SAM CLI in a custom location, set the "aws.samcli.location" user setting.' - ), - learnMore, - browseToSamCli - ) - .then(async (userResponse) => { - if (userResponse === learnMore) { - await vscode.commands.executeCommand('vscode.open', samInstallUrl) - } else if (userResponse === browseToSamCli) { - const location: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({ - canSelectFiles: true, - canSelectFolders: false, - canSelectMany: false, - openLabel: 'Set location in user settings', - }) - - if (!!location && location.length === 1) { - const path: string = location[0].fsPath - await SamCliSettings.update('location', path) - void vscode.window.showInformationMessage(getSettingsUpdatedMessage(path)) - } - } - }) -} - -function getSettingsUpdatedMessage(location: string): string { - const configuredLocation = localize('AWS.samcli.configured.location', 'SAM CLI Location: {0}', location) - - return `${settingsUpdated} ${configuredLocation}` -} diff --git a/packages/core/src/shared/sam/cli/samCliFeatureRegistry.ts b/packages/core/src/shared/sam/cli/samCliFeatureRegistry.ts deleted file mode 100644 index d491157388..0000000000 --- a/packages/core/src/shared/sam/cli/samCliFeatureRegistry.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as semver from 'semver' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { getSamCliPathAndVersion } from '../utils' -import { getLogger } from '../../logger/logger' -import { openUrl } from '../../utilities/vsCodeUtils' -import { awsClis } from '../../utilities/cliUtils' -import { ToolkitError } from '../../errors' - -/** - * Registry of SAM CLI features and their minimum required versions. - * This allows us to validate templates before invoking SAM CLI commands - * and provide clear error messages when features are not supported given - * current SAM CLI version. - */ - -/** - * Detection rule for identifying features in templates. - * Each rule specifies how to detect a feature in the template structure. - */ -export interface FeatureDetectionRule { - /** Check if a resource matches this feature */ - matchResource?: (resourceType: string, properties: any) => boolean - /** Check if globals section matches this feature */ - matchGlobals?: (globals: any) => boolean -} - -export interface SamCliFeature { - /** Unique identifier for the feature */ - id: string - /** Human-readable name */ - name: string - /** Minimum SAM CLI version required */ - minVersion: string - /** Description of the feature */ - description: string - /** Detection rules for this feature */ - detectionRule: FeatureDetectionRule -} - -/** - * Registry of SAM CLI features and their version requirements. - * Add new features here as they are introduced in SAM CLI. - * - * To add a new feature: - * 1. Add a new entry to this registry with detection rules - * 2. The detection logic will automatically pick it up - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export const SAM_CLI_FEATURE_REGISTRY: Record = { - CAPACITY_PROVIDER: { - id: 'CAPACITY_PROVIDER', - name: '`CapacityProvider`', - minVersion: '1.149.0', - description: 'AWS::Serverless::CapacityProvider resource', - detectionRule: { - matchResource: (resourceType: string) => resourceType === 'AWS::Serverless::CapacityProvider', - }, - }, - CAPACITY_PROVIDER_CONFIG: { - id: 'CAPACITY_PROVIDER_CONFIG', - name: 'CapacityProviderConfig', - minVersion: '1.149.0', - description: 'AWS::Serverless::Function CapacityProviderConfig property', - detectionRule: { - matchResource: (resourceType: string, properties: any) => - resourceType === 'AWS::Serverless::Function' && !!properties?.CapacityProviderConfig, - matchGlobals: (globals: any) => !!globals.Function?.CapacityProviderConfig, - }, - }, -} as const - -/** - * Detects SAM CLI features used in a CloudFormation template. - * This function is completely data-driven - it iterates through all features - * in the registry and applies their detection rules. - * - * Logic: - * - Automatically fetches SAM CLI version (SAM CLI must be available) - * - Filters features to only those unsupported by current version before searching - * - Returns early if no unsupported features exist - * - * @param template The parsed CloudFormation template - * @param samCliVersion Optional SAM CLI version (fetched automatically if not provided) - * @returns Object containing detected unsupported features and the SAM CLI version used - */ -export async function detectFeaturesInTemplate( - template: any, - samCliVersion?: string -): Promise<{ unsupported: SamCliFeature[]; version: string }> { - const allFeatures = Object.values(SAM_CLI_FEATURE_REGISTRY) - - // Fetch SAM CLI version if not provided - const version = samCliVersion ?? String((await getSamCliPathAndVersion()).parsedVersion ?? '0.0.0') - - // Step 1: Filter to only features that require a version greater than current - const unsupportedFeatures = allFeatures.filter((feature) => semver.gt(feature.minVersion, version)) - - // Step 2: Early exit if no unsupported features exist - if (unsupportedFeatures.length === 0) { - return { unsupported: [], version } - } - - // Step 3: Search template for only the unsupported features - const detected: SamCliFeature[] = [] - const resources = template?.Resources ?? {} - const globals = template?.Globals ?? {} - - for (const feature of unsupportedFeatures) { - let found = false - - // Check resources if matchResource rule exists - if (feature.detectionRule.matchResource && !found) { - for (const resource of Object.values(resources) as any[]) { - if (feature.detectionRule.matchResource(resource.Type, resource.Properties)) { - detected.push(feature) - found = true - break - } - } - } - - // Check globals if matchGlobals rule exists and not yet found - if (feature.detectionRule.matchGlobals && !found) { - if (feature.detectionRule.matchGlobals(globals)) { - detected.push(feature) - } - } - } - - return { unsupported: detected, version } -} - -/** - * Validates a template file against the current SAM CLI version. - * Shows a user prompt and throws an error if the template contains unsupported features. - * - * @param templateUri URI to the CloudFormation template file - * @throws Error if template contains features unsupported by current SAM CLI version - */ -export async function validateSamCliVersionForTemplateFile(templateUri: vscode.Uri): Promise { - const samTemplate = await CloudFormation.tryLoad(templateUri) - if (!samTemplate.template) { - return // Template couldn't be loaded, skip validation - } - - const { unsupported, version } = await detectFeaturesInTemplate(samTemplate.template) - - if (unsupported.length === 0) { - return // All features are supported - } - - // Calculate required version - const requiredVersion = unsupported.reduce( - (max, feature) => (semver.gt(feature.minVersion, max) ? feature.minVersion : max), - unsupported[0].minVersion - ) - - const featureList = unsupported.map((f) => `${f.description} (requires ${f.minVersion})`).join(' ') - const errorMessage = `Your SAM CLI version (${version}) does not support the following features: ${featureList}. Please upgrade to SAM CLI version ${requiredVersion} or higher.` - - getLogger().warn(`SAM CLI version check failed: ${errorMessage}`) - throw new ToolkitError(errorMessage) -} - -export async function showWarningWithSamCliUpdateInstruction(errorMessage: string): Promise { - // Show user prompt with option to view update instructions - const updateInstruction = 'View SAM CLI Update Instructions' - const selection = await vscode.window.showWarningMessage(errorMessage, updateInstruction) - - if (selection === updateInstruction) { - void openUrl(vscode.Uri.parse(awsClis['sam-cli'].manualInstallLink)) - } -} diff --git a/packages/core/src/shared/sam/cli/samCliInfo.ts b/packages/core/src/shared/sam/cli/samCliInfo.ts deleted file mode 100644 index ededaa33c4..0000000000 --- a/packages/core/src/shared/sam/cli/samCliInfo.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger, Logger } from '../../logger/logger' -import { mergeResolvedShellPath } from '../../env/resolveEnv' -import { ChildProcess } from '../../utilities/processUtils' - -export interface SamCliInfoResponse { - version: string -} - -export class SamCliInfoInvocation { - public constructor(private readonly samPath: string) {} - - public async execute(): Promise { - const r = await new ChildProcess(this.samPath, ['--info'], { logging: 'no' }).run({ - spawnOptions: { env: await mergeResolvedShellPath(process.env) }, - }) - - if (r.exitCode !== 0) { - // getVersionValidatorResult() will return `SamCliVersionValidation.VersionNotParseable`. - return { version: '' } - } - const response = this.convertOutput(r.stdout) - - if (!response) { - return { version: '' } - } - - // Special case: fix sam cli "nightly" invalid semver version string. - // Example: "1.86.1.dev202306120901" - // https://github.com/aws/aws-sam-cli/releases/tag/sam-cli-nightly - response.version = response.version.replace(/.dev/, '-dev') - - return response - } - - /** - * Parses the output into a typed object with expected data - * @param text output from a `sam --info` call - */ - protected convertOutput(text: string): SamCliInfoResponse | undefined { - const logger: Logger = getLogger() - try { - return JSON.parse(text) as SamCliInfoResponse - } catch (err) { - logger.error(err as Error) - - return undefined - } - } -} diff --git a/packages/core/src/shared/sam/cli/samCliInit.ts b/packages/core/src/shared/sam/cli/samCliInit.ts deleted file mode 100644 index 366c753951..0000000000 --- a/packages/core/src/shared/sam/cli/samCliInit.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Runtime } from '@aws-sdk/client-lambda' -import { SchemaTemplateExtraContext } from '../../../eventSchemas/templates/schemasAppTemplateUtils' -import { Architecture, DependencyManager } from '../../../lambda/models/samLambdaRuntime' -import { getSamCliTemplateParameter, SamTemplate } from '../../../lambda/models/samTemplates' -import { SamCliContext } from './samCliContext' -import { logAndThrowIfUnexpectedExitCode } from './samCliInvokerUtils' - -export interface SamCliInitArgs { - name: string - location: string - dependencyManager: DependencyManager - extraContent?: SchemaTemplateExtraContext - template?: SamTemplate - // zip-based lambdas; mutually exclusive with baseImage - runtime?: Runtime - // image-based lambdas; mutually exclusive with runtime - baseImage?: string - architecture?: Architecture -} - -export async function runSamCliInit(initArguments: SamCliInitArgs, context: SamCliContext): Promise { - const args = [ - 'init', - '--name', - initArguments.name, - '--no-interactive', - '--dependency-manager', - initArguments.dependencyManager, - ] - - if (initArguments.runtime) { - args.push('--runtime', initArguments.runtime) - } - - if (initArguments.template) { - args.push('--app-template', getSamCliTemplateParameter(initArguments.template)) - } - - if (initArguments.architecture) { - args.push('--architecture', initArguments.architecture) - } - - if (initArguments.baseImage) { - // specifying baseImage implies a packageType of image - args.push('--package-type', 'Image') - args.push('--base-image', initArguments.baseImage) - // TODO: Allow users to select app template for base image - args.push('--app-template', 'hello-world-lambda-image') - } - - if (initArguments.extraContent!) { - args.push('--extra-context', JSON.stringify(initArguments.extraContent)) - } - - const childProcessResult = await context.invoker.invoke({ - spawnOptions: { cwd: initArguments.location }, - arguments: args, - }) - - logAndThrowIfUnexpectedExitCode(childProcessResult, 0) -} diff --git a/packages/core/src/shared/sam/cli/samCliInvoker.ts b/packages/core/src/shared/sam/cli/samCliInvoker.ts deleted file mode 100644 index 03d6fa249d..0000000000 --- a/packages/core/src/shared/sam/cli/samCliInvoker.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as logger from '../../logger/logger' -import { ChildProcess, ChildProcessResult } from '../../utilities/processUtils' -import { - addTelemetryEnvVar, - makeRequiredSamCliProcessInvokeOptions, - SamCliProcessInvokeOptions, - SamCliProcessInvoker, -} from './samCliInvokerUtils' - -import * as nls from 'vscode-nls' -import { SamCliSettings } from './samCliSettings' -const localize = nls.loadMessageBundle() - -/** - * Yet another `sam` CLI wrapper. - * - * TODO: Merge this with `DefaultSamLocalInvokeCommand`. - */ -export class DefaultSamCliProcessInvoker implements SamCliProcessInvoker { - private childProcess?: ChildProcess - public constructor(private readonly context = SamCliSettings.instance) {} - - public stop(): void { - if (!this.childProcess) { - throw new Error('not started') - } - this.childProcess.stop() - } - - public async invoke(options?: SamCliProcessInvokeOptions): Promise { - const invokeOptions = makeRequiredSamCliProcessInvokeOptions(options) - const logging = options?.logging !== false - const getLogger = logging ? logger.getLogger : logger.getNullLogger - const getDebugConsoleLogger = logging ? logger.getDebugConsoleLogger : logger.getNullLogger - const log = getLogger() - - const sam = await this.context.getOrDetectSamCli() - if (!sam.path) { - log.warn('SAM CLI not found and not configured') - } else if (sam.autoDetected) { - log.info('SAM CLI not configured, using SAM found at: %O', sam.path) - } - - const samCommand = sam.path ? sam.path : 'sam' - this.childProcess = new ChildProcess(samCommand, invokeOptions.arguments, { - logging: options?.logging ? 'yes' : 'no', - spawnOptions: await addTelemetryEnvVar(options?.spawnOptions), - }) - - getLogger().info(localize('AWS.running.command', 'Command: {0}', `${this.childProcess}`)) - log.verbose(`running: ${this.childProcess}`) - return await this.childProcess.run({ - onStdout: (text, context) => { - getDebugConsoleLogger().info(text) - log.verbose(`stdout: ${text}`) - options?.onStdout?.(text, context) - }, - onStderr: (text, context) => { - getDebugConsoleLogger().info(text) - log.verbose(`stderr: ${text}`) - options?.onStderr?.(text, context) - }, - }) - } -} diff --git a/packages/core/src/shared/sam/cli/samCliInvokerUtils.ts b/packages/core/src/shared/sam/cli/samCliInvokerUtils.ts deleted file mode 100644 index a042d74215..0000000000 --- a/packages/core/src/shared/sam/cli/samCliInvokerUtils.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports -import { getLogger } from '../../logger/logger' -import { getUserAgent } from '../../telemetry/util' -import { ChildProcessResult, ChildProcessOptions } from '../../utilities/processUtils' -import { ErrorInformation, ToolkitError } from '../../errors' -import globals from '../../extensionGlobals' -import { isAutomation } from '../../vscode/env' - -/** Generic SAM CLI invocation error. */ -export class SamCliError extends ToolkitError.named('SamCliError') { - public constructor(message?: string, info?: ErrorInformation) { - super(message ?? 'SAM CLI failed', { ...info, code: 'SamCliFailed' }) - } -} - -export interface SamCliProcessInvokeOptions { - spawnOptions?: SpawnOptions - arguments?: string[] - onStdout?: ChildProcessOptions['onStdout'] - onStderr?: ChildProcessOptions['onStderr'] - /** Log command invocations (default: true). */ - logging?: boolean -} - -export function makeRequiredSamCliProcessInvokeOptions( - options?: SamCliProcessInvokeOptions -): Required> { - options = options || {} - - return { - spawnOptions: options.spawnOptions || {}, - arguments: options.arguments || [], - } -} - -export interface SamCliProcessInvoker { - invoke(options?: SamCliProcessInvokeOptions): Promise - stop(): void -} - -export function makeUnexpectedExitCodeError(message: string): Error { - const msg = message ? message : 'SAM CLI failed' - return new SamCliError(msg) -} - -export function logAndThrowIfUnexpectedExitCode(r: ChildProcessResult, expectedExitCode: number): void { - if (r.exitCode === expectedExitCode) { - return - } - - const errIndented = r.stderr.replace(/\n/g, '\n ').trim() - const outIndented = r.stdout.replace(/\n/g, '\n ').trim() - - getLogger().error(`SAM CLI failed (exitcode: ${r.exitCode}, expected ${expectedExitCode}): ${r.error?.message ?? ''} - stdout: - ${outIndented} - stderr: - ${errIndented} -`) - - const message = r.error instanceof Error ? r.error.message : collectSamErrors(r.stderr).join('\n') - throw makeUnexpectedExitCodeError(message) -} - -/** - * Collect known error messages from sam cli output, so they can be surfaced to the user. - * - * @param samOutput SAM CLI output containing potential error messages - */ -export function collectSamErrors(samOutput: string): string[] { - const lines = samOutput.split('\n') - const matchedLines: string[] = [] - const matchers = [matchSamError, matchAfterEscapeSeq] - for (const line of lines) { - for (const m of matchers) { - const match = m(line) - if (match && match.trim() !== '') { - matchedLines.push(match) - break // Skip remaining matchers, go to next line. - } - } - } - return matchedLines -} - -/** All accepted escape sequences. */ -const yellowForeground = '[33m' -const acceptedSequences = [yellowForeground] - -/** Returns text after a known escape sequence, or empty string. */ -function matchAfterEscapeSeq(text: string, sequences = acceptedSequences): string { - text = text.trim() - const escapeInDecimal = 27 - if (text.codePointAt(0) !== escapeInDecimal) { - return '' - } - - const remainingText = text.substring(1) - for (const seq of sequences) { - if (remainingText.startsWith(seq)) { - return remainingText.substring(seq.length).trim() - } - } - return '' -} - -function matchSamError(text: string): string { - // These should be ordered by "specificity", to make the result more relevant for users. - const patterns = [ - /\s*(Running.*requires Docker\.?)/, - /\s*(Docker.*not reachable\.?)/, - /\bError:\s*(.*)/, // Goes last because it is the least specific. - ] - for (const re of patterns) { - const match = text.match(re) - if (match?.[1]) { - // Capture group 1 is the first (…) group in the regex pattern. - return match[1].trim() // Return _only_ the matched text. The rest is noise. - } - } - return '' -} - -export async function addTelemetryEnvVar(options: SpawnOptions | undefined): Promise { - const telemetryEnabled = globals.telemetry.telemetryEnabled && !isAutomation() - // If AWS Toolkit telemetry was disabled, implicit dependencies (such as SAM CLI) should inherit that choice. - const samEnv = telemetryEnabled ? {} : { SAM_CLI_TELEMETRY: '0' } - return { - ...options, - env: { - AWS_TOOLING_USER_AGENT: getUserAgent({ includeClientId: false }), - ...samEnv, - ...options?.env, - }, - } -} diff --git a/packages/core/src/shared/sam/cli/samCliListResources.ts b/packages/core/src/shared/sam/cli/samCliListResources.ts deleted file mode 100644 index ed1eb9115d..0000000000 --- a/packages/core/src/shared/sam/cli/samCliListResources.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils' -import { getSpawnEnv } from '../../env/resolveEnv' -import { getLogger } from '../../logger/logger' -import { showWarningWithSamCliUpdateInstruction, validateSamCliVersionForTemplateFile } from './samCliFeatureRegistry' - -export interface SamCliListResourcesParameters { - templateFile: string - stackName: string - region?: string - projectRoot?: vscode.Uri -} - -export async function runSamCliListResource( - listStackResourcesArguments: SamCliListResourcesParameters, - invoker: SamCliProcessInvoker -): Promise { - // Validate template features before invoking SAM CLI - try { - const templateUri = vscode.Uri.file(listStackResourcesArguments.templateFile) - await validateSamCliVersionForTemplateFile(templateUri) - } catch (validationError: any) { - // Validation failed, show error with update instructions - getLogger().warn('SAM CLI feature validation failed: %O', validationError) - const errorMessage = `Failed to run SAM CLI list resources. ${validationError.message}` - void showWarningWithSamCliUpdateInstruction(errorMessage) - return [] - } - - const args = [ - 'list', - 'resources', - '--template-file', - listStackResourcesArguments.templateFile, - '--stack-name', - listStackResourcesArguments.stackName, - '--output', - 'json', - ] - - if (listStackResourcesArguments.region) { - args.push('--region', listStackResourcesArguments.region) - } - - try { - const childProcessResult = await invoker.invoke({ - arguments: args, - spawnOptions: { - env: await getSpawnEnv(process.env), - cwd: listStackResourcesArguments.projectRoot?.fsPath, - }, - }) - - logAndThrowIfUnexpectedExitCode(childProcessResult, 0) - return childProcessResult.stdout - } catch (error: any) { - const message = error.message - if (message.includes('does not exist on Cloudformation')) { - getLogger().info(message) - } else { - void vscode.window.showWarningMessage(`Failed to run SAM CLI list resources: ${message}`) - } - return [] - } -} diff --git a/packages/core/src/shared/sam/cli/samCliLocalInvoke.ts b/packages/core/src/shared/sam/cli/samCliLocalInvoke.ts deleted file mode 100644 index c1e60ebcb4..0000000000 --- a/packages/core/src/shared/sam/cli/samCliLocalInvoke.ts +++ /dev/null @@ -1,313 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports -import { pushIf } from '../../utilities/collectionUtils' -import * as nls from 'vscode-nls' -import { getLogger, getDebugConsoleLogger, Logger } from '../../logger/logger' -import { ChildProcess } from '../../utilities/processUtils' -import { Timeout } from '../../utilities/timeoutUtils' -import { removeAnsi } from '../../utilities/textUtilities' -import * as vscode from 'vscode' -import globals from '../../extensionGlobals' -import { SamCliSettings } from './samCliSettings' -import { addTelemetryEnvVar, collectSamErrors, SamCliError } from './samCliInvokerUtils' -import { fs } from '../../fs/fs' -import { Runtime } from '@aws-sdk/client-lambda' -import { getSamCliPathAndVersion } from '../utils' -import { deprecatedRuntimes } from '../../../lambda/models/samLambdaRuntime' - -const localize = nls.loadMessageBundle() - -export const waitForDebuggerMessages = { - PYTHON: 'Debugger waiting for client...', - NODEJS: 'Debugger listening on', - DOTNET: 'Waiting for the debugger to attach...', - GO_DELVE: 'launching process with args', // Comes from https://github.com/go-delve/delve/blob/f5d2e132bca763d222680815ace98601c2396517/service/debugger/debugger.go#L187 - JAVA: 'Picked up _JAVA_OPTIONS:', -} - -export interface SamLocalInvokeCommandArgs { - command: string - args: string[] - options?: SpawnOptions - /** Wait until strings specified in `debuggerAttachCues` appear in the process output. */ - waitForCues: boolean - timeout?: Timeout - /** Allows us to name debug sessions so we can terminate them later */ - name?: string -} - -/** - * Yet another `sam` CLI wrapper. - */ -export interface SamLocalInvokeCommand { - /** @returns `sam` process (may be running or stopped) */ - invoke(items: SamLocalInvokeCommandArgs): Promise -} - -/** - * Yet another `sam` CLI wrapper. - * - * TODO: Merge this with `DefaultSamCliProcessInvoker`. - */ -export class DefaultSamLocalInvokeCommand implements SamLocalInvokeCommand { - private readonly logger: Logger = getLogger() - - public constructor( - private readonly debuggerAttachCues: string[] = [waitForDebuggerMessages.PYTHON, waitForDebuggerMessages.NODEJS] - ) {} - - public async invoke({ options, ...params }: SamLocalInvokeCommandArgs): Promise { - const childProcess = new ChildProcess(params.command, params.args, { - spawnOptions: await addTelemetryEnvVar(options), - }) - getLogger().info('AWS.running.command: Command: %O', childProcess) - // "sam local invoke", "sam local start-api", etc. - const samCommandName = `sam ${params.args[0]} ${params.args[1]}` - - const checkForCues: boolean = params.waitForCues && this.debuggerAttachCues.length !== 0 - const runDebugger = new Promise(async (resolve, reject) => { - const result = await childProcess - .run({ - waitForStreams: true, - rejectOnError: true, - timeout: params.timeout, - onStdout: (text: string): void => { - getDebugConsoleLogger().info(text, { raw: true }) - // If we have a timeout (as we do on debug) refresh the timeout as we receive text - params.timeout?.refresh() - this.logger.verbose('SAM: pid %d: stdout: %s', childProcess.pid(), removeAnsi(text)) - }, - onStderr: (text: string): void => { - getDebugConsoleLogger().info(text, { raw: true }) - // If we have a timeout (as we do on debug) refresh the timeout as we receive text - params.timeout?.refresh() - this.logger.verbose('SAM: pid %d: stderr: %s', childProcess.pid(), removeAnsi(text)) - if (checkForCues) { - // Look for messages like "Debugger attached" before returning back to caller - if (this.debuggerAttachCues.some((cue) => text.includes(cue))) { - this.logger.verbose( - `SAM: pid ${childProcess.pid()}: local SAM app is ready for debugger to attach` - ) - // Process will continue running, while user debugs it. - resolve() - } - } - }, - }) - .catch((error) => { - getLogger().error( - localize('AWS.samcli.error', 'Error running command "{0}": {1}', samCommandName, error.message) - ) - reject(error) - }) - - if (result) { - const code = result.exitCode - - this.logger.verbose(`SAM: command exited (code: ${code}): ${childProcess}`) - // onStdout/onStderr may print partial lines. Force a newline - // to ensure "Command stopped" appears on its own line. - globals.outputChannel.appendLine('') - globals.outputChannel.appendLine( - localize('AWS.samcli.stopped', 'Command stopped: "{0}"', samCommandName) - ) - - // Process ended without emitting a known "cue" message. - // Possible causes: - // - User killed Docker or the process directly. - // - User manually attached before we found a "cue" message. - // - We need to update the list of "cue" messages. - if (code === 0) { - resolve() - } else if (code !== 0) { - const samErrors = collectSamErrors(result.stderr) - if (samErrors.length > 0) { - const e = new SamCliError(samErrors.join('\n')) - reject(e) - } else { - reject(new Error(`"${samCommandName}" command stopped (error code: ${code})`)) - } - } - - // Forces debugger to disconnect (sometimes it fails to disconnect on its own) - // Note that VSCode 1.42 only allows us to get the active debug session, so - // the user will have to manually disconnect if using multiple debug sessions - const debugSession = vscode.debug.activeDebugSession - if (debugSession && debugSession.name === params.name) { - getLogger().debug('forcing disconnect of debugger session "%s"', debugSession.name) - debugSession.customRequest('disconnect').then( - () => undefined, - (e) => - getLogger().warn( - 'failed to disconnect debugger session "%s": %s', - debugSession.name, - (e as Error).message - ) - ) - } - } - }) - - await runDebugger - - return childProcess - } -} - -export interface SamCliLocalInvokeInvocationArguments { - /** - * The name of the resource in the SAM Template to be invoked. - */ - templateResourceName: string - /** - * Location of the SAM Template to invoke locally against. - */ - templatePath: string - /** - * Location of the file containing the Lambda Function event payload. - */ - eventPath?: string - /** - * Location of the file containing the environment variables to invoke the Lambda Function against. - */ - environmentVariablePath?: string - /** - * Environment variables set when invoking the SAM process (NOT passed to the Lambda). - */ - environmentVariables?: NodeJS.ProcessEnv - /** - * When specified, starts the Lambda function container in debug mode and exposes this port on the local host. - */ - debugPort?: string - /** - * Manages the sam cli execution. - */ - invoker: SamLocalInvokeCommand - /** - * Specifies the name or id of an existing Docker network to Lambda Docker containers should connect to, - * along with the default bridge network. - * If not specified, the Lambda containers will only connect to the default bridge Docker network. - */ - dockerNetwork?: string - /** - * - true: Do not pull the latest Docker image for Lambda runtime. - * - false: Pull the latest Docker image if necessary - */ - skipPullImage?: boolean - /** - * Host path to a debugger that will be mounted into the Lambda container. - */ - debuggerPath?: string - /** - * Passed to be executed as the root process in the Lambda container - */ - debugArgs?: string[] - /** - * Passed to be executed as the root process in the Lambda container - */ - containerEnvFile?: string - /** - * parameter overrides specified in the `sam.template.parameters` field - */ - parameterOverrides?: string[] - /** SAM args specified by user (`sam.localArguments`). */ - extraArgs?: string[] - /** Debug session name */ - name?: string - /** AWS region */ - region?: string - /** Overrides the template-specified runtime. */ - runtime?: Runtime -} - -/** - * Yet another `sam` CLI wrapper. - */ -export class SamCliLocalInvokeInvocation { - private readonly config = SamCliSettings.instance - - public constructor(private readonly args: SamCliLocalInvokeInvocationArguments) { - this.args.skipPullImage = !!this.args.skipPullImage - } - - public async execute(timeout?: Timeout): Promise { - await this.validate() - const sam = await this.config.getOrDetectSamCli() - if (!sam.path) { - getLogger().warn('SAM CLI not found and not configured') - } else if (sam.autoDetected) { - getLogger().info('SAM CLI not configured, using SAM found at: %O', sam.path) - } - - const samCommand = sam.path ? sam.path : 'sam' - const invokeArgs = [ - 'local', - 'invoke', - ...(getLogger().logLevelEnabled('debug') ? ['--debug'] : []), - this.args.templateResourceName, - '--template', - this.args.templatePath, - ] - - pushIf(invokeArgs, !!this.args.eventPath, '--event', this.args.eventPath) - pushIf(invokeArgs, !!this.args.environmentVariablePath, '--env-vars', this.args.environmentVariablePath) - pushIf(invokeArgs, !!this.args.debugPort, '-d', this.args.debugPort!) - pushIf(invokeArgs, !!this.args.dockerNetwork, '--docker-network', this.args.dockerNetwork!) - pushIf(invokeArgs, !!this.args.skipPullImage, '--skip-pull-image') - pushIf(invokeArgs, !!this.args.debuggerPath, '--debugger-path', this.args.debuggerPath!) - pushIf(invokeArgs, !!this.args.debugArgs, '--debug-args', ...(this.args.debugArgs ?? [])) - pushIf(invokeArgs, !!this.args.containerEnvFile, '--container-env-vars', this.args.containerEnvFile) - pushIf(invokeArgs, !!this.args.region, '--region', this.args.region) - - pushIf( - invokeArgs, - !!this.args.parameterOverrides && this.args.parameterOverrides.length > 0, - '--parameter-overrides', - ...(this.args.parameterOverrides ?? []) - ) - invokeArgs.push(...(this.args.extraArgs ?? [])) - - // Get samcli version - const { parsedVersion } = await getSamCliPathAndVersion() - - // '--runtime' option for sam local invoke is only available in SAM CLI 1.135.0 and later - if ((parsedVersion?.compare('1.135.0') ?? -1) >= 0) { - // check if the runtime is valid (not deprecated) - if (this.args.runtime && !deprecatedRuntimes.has(this.args.runtime)) { - pushIf(invokeArgs, true, '--runtime', this.args.runtime) - } - } - - return await this.args.invoker.invoke({ - options: { - env: { - ...process.env, - ...this.args.environmentVariables, - }, - }, - command: samCommand, - args: invokeArgs, - waitForCues: !!this.args.debugPort, - timeout, - name: this.args.name, - }) - } - - protected async validate(): Promise { - if (!this.args.templateResourceName) { - throw new Error('template resource name is missing or empty') - } - - if (!(await fs.exists(this.args.templatePath))) { - throw new Error(`template path does not exist: ${this.args.templatePath}`) - } - - if (this.args.eventPath !== undefined && !(await fs.exists(this.args.eventPath))) { - throw new Error(`event path does not exist: ${this.args.eventPath}`) - } - } -} diff --git a/packages/core/src/shared/sam/cli/samCliLocator.ts b/packages/core/src/shared/sam/cli/samCliLocator.ts deleted file mode 100644 index d71110cc45..0000000000 --- a/packages/core/src/shared/sam/cli/samCliLocator.ts +++ /dev/null @@ -1,227 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import { EnvironmentVariables } from '../../environmentVariables' -import * as filesystemUtilities from '../../filesystemUtilities' -import { getLogger, Logger } from '../../logger/logger' -import { SamCliInfoInvocation } from './samCliInfo' -import { DefaultSamCliValidator, SamCliValidatorContext, SamCliVersionValidation } from './samCliValidator' -import { PerfLog } from '../../logger/perfLogger' -import { tryRun } from '../../utilities/pathFind' -import { mergeResolvedShellPath } from '../../env/resolveEnv' - -interface SamLocation { - path: string - version: string -} -export class SamCliLocationProvider { - private static samCliLocator: BaseSamCliLocator | undefined - protected static cachedSamLocation: SamLocation | undefined - private static samLocationValid: boolean = false - - /** Checks that the given `sam` actually works by invoking `sam --version`. */ - private static async isValidSamLocation(samPath: string) { - const isValid = await tryRun(samPath, ['--version'], 'no', 'SAM CLI') - this.samLocationValid = isValid - return isValid - } - - /** - * Gets the last-found `sam` location, or searches the system if a working - * `sam` wasn't previously found and cached. - */ - public async getLocation(forceSearch?: boolean): Promise<{ path: string; version: string } | undefined> { - const perflog = new PerfLog('samCliLocator: getLocation') - const cachedLoc = forceSearch ? undefined : SamCliLocationProvider.cachedSamLocation - - // Avoid searching the system for `sam` (especially slow on Windows). - if ( - cachedLoc && - (SamCliLocationProvider.samLocationValid || - (await SamCliLocationProvider.isValidSamLocation(cachedLoc.path))) - ) { - perflog.done() - return cachedLoc - } - SamCliLocationProvider.cachedSamLocation = await SamCliLocationProvider.getSamCliLocator().getLocation() - perflog.done() - return SamCliLocationProvider.cachedSamLocation - } - - public static getSamCliLocator(): SamCliLocationProvider { - if (!SamCliLocationProvider.samCliLocator) { - if (process.platform === 'win32') { - SamCliLocationProvider.samCliLocator = new WindowsSamCliLocator() - } else { - SamCliLocationProvider.samCliLocator = new UnixSamCliLocator() - } - } - - return SamCliLocationProvider.samCliLocator - } -} - -abstract class BaseSamCliLocator { - /** Indicates that findFileInFolders() returned at least once. */ - static didSearch = false - protected readonly logger: Logger = getLogger() - - public constructor() { - this.verifyOs() - } - - public async getLocation() { - let location = await this.findFileInFolders(this.getExecutableFilenames(), this.getExecutableFolders()) - - if (!location?.version) { - location = await this.getSystemPathLocation() - } - - return location - } - - protected abstract verifyOs(): void - protected abstract getExecutableFilenames(): string[] - protected abstract getExecutableFolders(): string[] - - /** - * Searches for `sam` in various places and the $PATH. - * - * If only a broken `sam` is found it is returned with an empty `version`. - */ - protected async findFileInFolders(files: string[], folders: string[]) { - // Keep the first found "sam" even if it is broken. - // This allows us to give a better message than "not found". - let brokenSam: string | undefined - - const fullPaths: string[] = files - .map((file) => folders.filter((folder) => !!folder).map((folder) => path.join(folder, file))) - .reduce((accumulator, paths) => { - accumulator.push(...paths) - - return accumulator - }) - - for (const fullPath of fullPaths) { - if (!BaseSamCliLocator.didSearch) { - this.logger.verbose(`samCliLocator: searching in: ${fullPath}`) - } - const context: SamCliValidatorContext = { - samCliLocation: async () => fullPath, - getSamCliInfo: async () => new SamCliInfoInvocation(fullPath).execute(), - } - const validator = new DefaultSamCliValidator(context) - if (await filesystemUtilities.fileExists(fullPath)) { - try { - const validationResult = await validator.getVersionValidatorResult() - if (validationResult.validation === SamCliVersionValidation.Valid) { - BaseSamCliLocator.didSearch = true - return { path: fullPath, version: validationResult.version } - } - this.logger.warn( - `samCliLocator: found invalid SAM CLI (${validationResult.validation}): ${fullPath}` - ) - brokenSam = brokenSam ?? fullPath - } catch (e) { - const err = e as Error - this.logger.error('samCliLocator failed: %s', err.message) - } - } - } - - BaseSamCliLocator.didSearch = true - return brokenSam ? { path: brokenSam, version: '' } : undefined - } - - /** - * Searches for `getExecutableFilenames()` in `$PATH` and returns the first - * path found on the filesystem, if any. - */ - private async getSystemPathLocation() { - const envVars = (await mergeResolvedShellPath(process.env)) as EnvironmentVariables - - if (envVars.PATH) { - const systemPaths: string[] = envVars.PATH.split(path.delimiter).filter((folder) => !!folder) - - return await this.findFileInFolders(this.getExecutableFilenames(), systemPaths) - } - - return undefined - } -} - -class WindowsSamCliLocator extends BaseSamCliLocator { - // Do not access locationPaths directly. Use getExecutableFolders() - private static locationPaths: string[] | undefined - - private static readonly executableFilenames: string[] = ['sam.cmd', 'sam.exe'] - - public constructor() { - super() - } - - protected verifyOs(): void { - if (process.platform !== 'win32') { - throw new Error('Wrong platform') - } - } - - protected getExecutableFilenames(): string[] { - return WindowsSamCliLocator.executableFilenames - } - - protected getExecutableFolders(): string[] { - if (!WindowsSamCliLocator.locationPaths) { - WindowsSamCliLocator.locationPaths = [] - - const envVars = process.env as EnvironmentVariables - - const programFiles = envVars.PROGRAMFILES - if (programFiles) { - WindowsSamCliLocator.locationPaths.push(String.raw`${programFiles}\Amazon\AWSSAMCLI\bin`) - } - - const programFilesX86 = envVars['PROGRAMFILES(X86)'] - if (programFilesX86) { - WindowsSamCliLocator.locationPaths.push(String.raw`${programFilesX86}\Amazon\AWSSAMCLI\bin`) - } - } - - return WindowsSamCliLocator.locationPaths - } -} - -class UnixSamCliLocator extends BaseSamCliLocator { - private static readonly locationPaths: string[] = [ - '/opt/homebrew/bin', - '/usr/local/bin', - '/usr/bin', - // WEIRD BUT TRUE: brew installs to /home/linuxbrew/.linuxbrew if - // possible, else to ~/.linuxbrew. https://docs.brew.sh/Homebrew-on-Linux - '/home/linuxbrew/.linuxbrew/bin', - `${process.env.HOME}/.linuxbrew/bin`, - ] - - private static readonly executableFilenames: string[] = ['sam'] - - public constructor() { - super() - } - - protected verifyOs(): void { - if (process.platform === 'win32') { - throw new Error('Wrong platform') - } - } - - protected getExecutableFilenames(): string[] { - return UnixSamCliLocator.executableFilenames - } - - protected getExecutableFolders(): string[] { - return UnixSamCliLocator.locationPaths - } -} diff --git a/packages/core/src/shared/sam/cli/samCliPackage.ts b/packages/core/src/shared/sam/cli/samCliPackage.ts deleted file mode 100644 index 2dbc7b9007..0000000000 --- a/packages/core/src/shared/sam/cli/samCliPackage.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils' - -export interface SamCliPackageParameters { - /** - * The SAM Template to package - */ - sourceTemplateFile: string - /** - * The SAM Template produced by SAM CLI's packaging - */ - destinationTemplateFile: string - environmentVariables: NodeJS.ProcessEnv - region: string - s3Bucket: string - - /** - * The URI of an ECR repository - */ - ecrRepo?: string -} - -export async function runSamCliPackage( - packageArguments: SamCliPackageParameters, - invoker: SamCliProcessInvoker -): Promise { - const args = [ - 'package', - '--template-file', - packageArguments.sourceTemplateFile, - '--s3-bucket', - packageArguments.s3Bucket, - '--output-template-file', - packageArguments.destinationTemplateFile, - '--region', - packageArguments.region, - ] - if (packageArguments.ecrRepo) { - args.push('--image-repository') - args.push(packageArguments.ecrRepo) - } - const childProcessResult = await invoker.invoke({ - arguments: args, - spawnOptions: { - env: packageArguments.environmentVariables, - }, - }) - - logAndThrowIfUnexpectedExitCode(childProcessResult, 0) -} diff --git a/packages/core/src/shared/sam/cli/samCliRemoteTestEvent.ts b/packages/core/src/shared/sam/cli/samCliRemoteTestEvent.ts deleted file mode 100644 index e17a5d49c9..0000000000 --- a/packages/core/src/shared/sam/cli/samCliRemoteTestEvent.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { logAndThrowIfUnexpectedExitCode, SamCliProcessInvoker } from './samCliInvokerUtils' -import path from 'path' -import * as os from 'os' -import { getSpawnEnv } from '../../env/resolveEnv' -import { getLogger } from '../../../shared/logger/logger' - -export const TestEventsOperation = { - List: 'list', - Get: 'get', - Put: 'put', -} -export interface SamCliRemoteTestEventsParameters { - operation: string - functionArn?: string - name?: string - region?: string - eventSample?: string - projectRoot?: vscode.Uri - stackName?: string - logicalId?: string - force?: boolean -} - -export async function runSamCliRemoteTestEvents( - remoteTestEventsParameters: SamCliRemoteTestEventsParameters, - invoker: SamCliProcessInvoker -): Promise { - const args = ['remote', 'test-event', remoteTestEventsParameters.operation] - if (remoteTestEventsParameters.stackName === '' && remoteTestEventsParameters.functionArn === '') { - getLogger().info('No remote test events found. This stack is not deployed.') - return 'No remote test events found. This stack is not deployed.' - } - if (remoteTestEventsParameters.functionArn) { - args.push(remoteTestEventsParameters.functionArn) - } else if (remoteTestEventsParameters.stackName && remoteTestEventsParameters.logicalId) { - args.push('--stack-name', remoteTestEventsParameters.stackName, remoteTestEventsParameters.logicalId) - } - - if (remoteTestEventsParameters.region) { - args.push('--region', remoteTestEventsParameters.region) - } - - if (remoteTestEventsParameters.name) { - args.push('--name', remoteTestEventsParameters.name) - } - - if (remoteTestEventsParameters.operation === TestEventsOperation.Put && remoteTestEventsParameters.eventSample) { - const tempFileUri = vscode.Uri.file(path.join(os.tmpdir(), 'event-sample.json')) - const encoder = new TextEncoder() - await vscode.workspace.fs.writeFile(tempFileUri, encoder.encode(remoteTestEventsParameters.eventSample)) - args.push('--file', tempFileUri.fsPath) - - // Add --force flag when updating existing events - if (remoteTestEventsParameters.force) { - args.push('--force') - } - } - - const childProcessResult = await invoker.invoke({ - arguments: args, - spawnOptions: { - env: await getSpawnEnv(process.env), - cwd: remoteTestEventsParameters.projectRoot?.fsPath, - }, - }) - - if (childProcessResult.stderr && childProcessResult.stderr.startsWith('Error: No events found for function')) { - return '' - } - logAndThrowIfUnexpectedExitCode(childProcessResult, 0) - - return childProcessResult.stdout -} diff --git a/packages/core/src/shared/sam/cli/samCliSettings.ts b/packages/core/src/shared/sam/cli/samCliSettings.ts deleted file mode 100644 index 693c3edb23..0000000000 --- a/packages/core/src/shared/sam/cli/samCliSettings.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { getLogger } from '../../logger/logger' -import { fromExtensionManifest, Settings } from '../../settings' -import { stripUndefined, toRecord } from '../../utilities/collectionUtils' -import { ClassToInterfaceType, keys } from '../../utilities/tsUtils' -import { SamCliLocationProvider } from './samCliLocator' -import { onceChanged } from '../../utilities/functionUtils' - -const localTimeoutDefaultMillis: number = 90000 -interface SavedBuckets { - [profile: string]: { [region: string]: string } -} - -function SavedBuckets(value: unknown): SavedBuckets { - // Legacy code used to save data as strings - const buckets = typeof value === 'string' ? JSON.parse(value) : value - - if (typeof buckets !== 'object' || !buckets) { - throw new TypeError('Value was not a non-null object') - } - - const result = toRecord(keys(buckets), (k) => { - const v = buckets[k] - - if (typeof v !== 'object' || !v) { - getLogger().warn(`Settings: removed invalid key "${k}" from saved buckets`) - return undefined - } - - return toRecord(keys(v), (p) => { - const bucket = v[p] - - if (typeof bucket !== 'string') { - getLogger().warn(`Settings: removed invalid key "${k}.${p}" from saved buckets`) - return undefined - } - - return bucket - }) - }) - - stripUndefined(result) - - return result as SavedBuckets -} - -const description = { - location: String, - lambdaTimeout: Number, - enableCodeLenses: Boolean, - manuallySelectedBuckets: SavedBuckets, - legacyDeploy: Boolean, -} - -export class SamCliSettings extends fromExtensionManifest('aws.samcli', description) { - protected static readonly logIfChanged = onceChanged((s: string) => getLogger().info(s)) - - public constructor( - private readonly locationProvider = new SamCliLocationProvider(), - settings: ClassToInterfaceType = Settings.instance - ) { - super(settings) - } - - /** - * Gets location of `sam` from: - * 1. user config (if any) - * 2. previous location cached in `getLocation` (if valid) - * 3. system search (done by `getLocation`) - * - * @returns `autoDetected=true` if auto-detection was _attempted_. - */ - public async getOrDetectSamCli( - forceSearch?: boolean - ): Promise<{ path: string | undefined; autoDetected: boolean }> { - const fromConfig = this.get('location', '') - - if (fromConfig) { - SamCliSettings.logIfChanged(`SAM CLI location (from settings): ${fromConfig}`) - return { path: fromConfig, autoDetected: false } - } - const fromSearch = await this.locationProvider.getLocation(forceSearch) - SamCliSettings.logIfChanged(`SAM CLI location (version: ${fromSearch?.version}): ${fromSearch?.path}`) - return { path: fromSearch?.path, autoDetected: true } - } - - public getSavedBuckets(): SavedBuckets | undefined { - try { - return this.get('manuallySelectedBuckets') - } catch (error) { - void this.delete('manuallySelectedBuckets') - } - } - - /** - * Writes a single new saved bucket to the stored buckets setting, combining previous saved data - * if it exists. One saved bucket is limited per region per profile. - */ - public async updateSavedBuckets(profile: string, region: string, bucket: string): Promise { - const oldBuckets = this.getSavedBuckets() - - return this.update('manuallySelectedBuckets', { - ...oldBuckets, - [profile]: { - ...(oldBuckets?.[profile] ?? {}), - [region]: bucket, - }, - }) - } - - public getLocalInvokeTimeout(): number { - return this.get('lambdaTimeout', localTimeoutDefaultMillis) - } - - static #instance: SamCliSettings - - public static get instance() { - return (this.#instance ??= new this()) - } -} diff --git a/packages/core/src/shared/sam/cli/samCliStartApi.ts b/packages/core/src/shared/sam/cli/samCliStartApi.ts deleted file mode 100644 index 3e0339ae25..0000000000 --- a/packages/core/src/shared/sam/cli/samCliStartApi.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fileExists } from '../../filesystemUtilities' -import { getLogger } from '../../logger/logger' -import { pushIf } from '../../utilities/collectionUtils' - -export interface SamCliStartApiArguments { - /** - * Location of the SAM Template to invoke locally against. - */ - templatePath: string - /** - * Location of the file containing the environment variables to invoke the Lambda Function against. - */ - environmentVariablePath?: string - /** - * Environment variables set when invoking the SAM process (NOT passed to the Lambda). - */ - environmentVariables?: NodeJS.ProcessEnv - /** Local API webserver port. */ - port?: string - /** - * When specified, starts the Lambda function container in debug mode and exposes this port on the local host. - */ - debugPort?: string - /** - * Specifies the name or id of an existing Docker network to Lambda Docker containers should connect to, - * along with the default bridge network. - * If not specified, the Lambda containers will only connect to the default bridge Docker network. - */ - dockerNetwork?: string - /** - * - true: Do not pull the latest Docker image for Lambda runtime. - * - false: Pull the latest Docker image if necessary - */ - skipPullImage?: boolean - /** - * Host path to a debugger that will be mounted into the Lambda container. - */ - debuggerPath?: string - /** - * Passed to be executed as the root process in the Lambda container - */ - debugArgs?: string[] - /** - * parameter overrides specified in the `sam.template.parameters` field - */ - parameterOverrides?: string[] - /** SAM args specified by user (`sam.localArguments`). */ - extraArgs?: string[] - /** Path to the container environment variable file */ - containerEnvFile?: string - /** Debug session name */ - name?: string - /** AWS region */ - region?: string -} - -/** - * Build and validate `sam local start-api` arguments - */ -export async function buildSamCliStartApiArguments(args: SamCliStartApiArguments): Promise { - args.skipPullImage = args.skipPullImage === true - - await validate(args.templatePath) - - const invokeArgs = [ - 'local', - 'start-api', - ...(getLogger().logLevelEnabled('debug') ? ['--debug'] : []), - '--template', - args.templatePath, - ] - - pushIf(invokeArgs, !!args.environmentVariablePath, '--env-vars', args.environmentVariablePath) - pushIf(invokeArgs, !!args.port, '--port', args.port!) - pushIf(invokeArgs, !!args.debugPort, '--debug-port', args.debugPort!) - pushIf(invokeArgs, !!args.dockerNetwork, '--docker-network', args.dockerNetwork!) - pushIf(invokeArgs, !!args.skipPullImage, '--skip-pull-image') - pushIf(invokeArgs, !!args.debuggerPath, '--debugger-path', args.debuggerPath!) - pushIf(invokeArgs, !!args.debugArgs, '--debug-args', ...(args.debugArgs ?? [])) - pushIf(invokeArgs, !!args.containerEnvFile, '--container-env-vars', args.containerEnvFile) - pushIf(invokeArgs, !!args.region, '--region', args.region) - pushIf( - invokeArgs, - !!args.parameterOverrides && args.parameterOverrides.length > 0, - '--parameter-overrides', - ...(args.parameterOverrides ?? []) - ) - invokeArgs.push(...(args.extraArgs ?? [])) - - return invokeArgs -} - -async function validate(templatePath: string): Promise { - if (!(await fileExists(templatePath))) { - throw new Error(`template path does not exist: ${templatePath}`) - } -} diff --git a/packages/core/src/shared/sam/cli/samCliValidationNotification.ts b/packages/core/src/shared/sam/cli/samCliValidationNotification.ts deleted file mode 100644 index 45862c7ffd..0000000000 --- a/packages/core/src/shared/sam/cli/samCliValidationNotification.ts +++ /dev/null @@ -1,163 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { openUrl, showExtensionPage } from '../../../shared/utilities/vsCodeUtils' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' - -import { samInstallUrl } from '../../constants' -import { getIdeProperties } from '../../extensionUtilities' -import { - InvalidSamCliError, - InvalidSamCliVersionError, - maxSamCliVersionExclusive, - minSamCliVersion, - SamCliNotFoundError, - SamCliVersionValidation, - SamCliVersionValidatorResult, -} from './samCliValidator' -import { VSCODE_EXTENSION_ID } from '../../extensions' - -const localize = nls.loadMessageBundle() - -// Notification Actions -export interface SamCliValidationNotificationAction { - label(): string - invoke(): Promise -} - -const actionGoToSamCli: SamCliValidationNotificationAction = { - label: () => localize('AWS.samcli.userChoice.visit.install.url', 'Install latest SAM CLI'), - invoke: async () => { - void openUrl(samInstallUrl) - }, -} - -const actionGoToVsCodeMarketplace: SamCliValidationNotificationAction = { - label: () => - localize( - 'AWS.samcli.userChoice.update.awstoolkit.url', - 'Install latest {0} Toolkit', - getIdeProperties().company - ), - invoke: async () => { - void showExtensionPage(VSCODE_EXTENSION_ID.awstoolkit) - }, -} - -// Notifications -export interface SamCliValidationNotification { - show(): Promise -} - -class DefaultSamCliValidationNotification implements SamCliValidationNotification { - public constructor( - private readonly message: string, - private readonly actions: SamCliValidationNotificationAction[] - ) {} - - public async show(): Promise { - const userResponse: string | undefined = await vscode.window.showErrorMessage( - this.message, - ...this.actions.map((action) => action.label()) - ) - - if (userResponse) { - const responseActions: Promise[] = this.actions - .filter((action) => action.label() === userResponse) - .map(async (action) => action.invoke()) - - await Promise.all(responseActions) - } - } -} - -export async function notifySamCliValidation(samCliValidationError: InvalidSamCliError): Promise { - if (!samCliValidationError) { - return - } - - const notification = getInvalidSamMsg(samCliValidationError) - await notification.show() -} - -export function getInvalidSamMsg( - samCliValidationError: InvalidSamCliError, - onCreateNotification: ( - message: string, - actions: SamCliValidationNotificationAction[] - ) => SamCliValidationNotification = (message, actions): SamCliValidationNotification => - new DefaultSamCliValidationNotification(message, actions) -): SamCliValidationNotification { - if (samCliValidationError instanceof SamCliNotFoundError) { - return onCreateNotification( - localize( - 'AWS.samcli.notification.not.found', - 'Cannot find SAM CLI. It is required in order to work with Serverless Applications locally.' - ), - [actionGoToSamCli] - ) - } else if (samCliValidationError instanceof InvalidSamCliVersionError) { - return onCreateNotification( - getInvalidVersionMsg(samCliValidationError.versionValidation), - getActions(samCliValidationError.versionValidation.validation) - ) - } else { - return onCreateNotification( - localize( - 'AWS.samcli.notification.unexpected.validation.issue', - 'Unexpected error while validating SAM CLI: {0}', - samCliValidationError.message - ), - [] - ) - } -} - -function getInvalidVersionMsg(validationResult: SamCliVersionValidatorResult): string { - const win185msg = localize( - 'AWS.sam.updateSamWindows', - 'SAM CLI 1.85-1.86 has [known issues](https://github.com/aws/aws-sam-cli/issues/5243) on Windows. Update SAM CLI.' - ) - let recommendation: string - - switch (validationResult.validation) { - case SamCliVersionValidation.VersionTooHigh: - recommendation = localize('AWS.sam.updateToolkit', 'Update {0} Toolkit.', getIdeProperties().company) - break - case SamCliVersionValidation.Version185Win: - return win185msg - case SamCliVersionValidation.VersionNotParseable: { - if (process.platform === 'win32') { - return win185msg - } - return localize('AWS.sam.installSam', 'SAM CLI failed to run.') - } - default: - recommendation = localize('AWS.sam.updateSam', 'Update SAM CLI.') - break - } - - return localize( - 'AWS.sam.invalid', - 'SAM CLI {0} is not in required range ({1} ≤ version < {2}). {3}', - validationResult.version, - minSamCliVersion, - maxSamCliVersionExclusive, - recommendation - ) -} - -function getActions(validation: SamCliVersionValidation): SamCliValidationNotificationAction[] { - const actions: SamCliValidationNotificationAction[] = [] - - if (validation === SamCliVersionValidation.VersionTooHigh) { - actions.push(actionGoToVsCodeMarketplace) - } else { - actions.push(actionGoToSamCli) - } - - return actions -} diff --git a/packages/core/src/shared/sam/cli/samCliValidationUtils.ts b/packages/core/src/shared/sam/cli/samCliValidationUtils.ts deleted file mode 100644 index abfbd0f730..0000000000 --- a/packages/core/src/shared/sam/cli/samCliValidationUtils.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { notifySamCliValidation } from './samCliValidationNotification' -import { - InvalidSamCliError, - InvalidSamCliVersionError, - SamCliNotFoundError, - SamCliValidatorResult, - SamCliVersionValidation, -} from './samCliValidator' - -export function throwIfInvalid(validationResult: SamCliValidatorResult): void { - if (!validationResult.samCliFound) { - throw new SamCliNotFoundError() - } - - if (!validationResult.versionValidation) { - // This should never happen - throw new Error('SAM CLI detected but version validation is missing') - } - - if (validationResult.versionValidation.validation === SamCliVersionValidation.Valid) { - // valid state - return - } - - // Invalid version - throw new InvalidSamCliVersionError(validationResult.versionValidation) -} - -export function throwAndNotifyIfInvalid(validationResult: SamCliValidatorResult): void { - try { - throwIfInvalid(validationResult) - } catch (err) { - if (err instanceof InvalidSamCliError) { - // SAM not found. - // Calling code does not wait for the notification to complete - void notifySamCliValidation(err) - } - - // SAM found but version is invalid or failed to parse. - // code: 'InvalidSamCliVersion' - throw err - } -} diff --git a/packages/core/src/shared/sam/cli/samCliValidator.ts b/packages/core/src/shared/sam/cli/samCliValidator.ts deleted file mode 100644 index 83caaa5494..0000000000 --- a/packages/core/src/shared/sam/cli/samCliValidator.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as semver from 'semver' -import { ClassToInterfaceType } from '../../utilities/tsUtils' -import { SamCliSettings } from './samCliSettings' -import { SamCliInfoInvocation, SamCliInfoResponse } from './samCliInfo' -import { ToolkitError } from '../../errors' - -export const minSamCliVersion = '0.47.0' -export const minSamCliVersionForImageSupport = '1.13.0' -export const maxSamCliVersionExclusive = '2.0.0' -export const minSamCliVersionForGoSupport = '1.18.1' -export const minSamCliVersionForArmSupport = '1.33.0' -export const minSamCliVersionForAppBuilderSupport = '1.98.0' - -// Errors -export class InvalidSamCliError extends ToolkitError {} - -export class SamCliNotFoundError extends InvalidSamCliError { - public constructor() { - super('SAM CLI was not found', { code: 'MissingSamCli' }) - } -} - -export class InvalidSamCliVersionError extends InvalidSamCliError { - public constructor(public versionValidation: SamCliVersionValidatorResult) { - super('SAM CLI has an invalid version', { code: 'InvalidSamCliVersion' }) - } -} - -// Validation -export enum SamCliVersionValidation { - Valid = 'Valid', - VersionTooLow = 'VersionTooLow', - VersionTooHigh = 'VersionTooHigh', - VersionNotParseable = 'VersionNotParseable', - Version185Win = 'Version185Win', -} - -export type SamCliVersionValidatorResult = - | { - readonly validation: Exclude - readonly version: string - } - | { - readonly validation: SamCliVersionValidation.VersionNotParseable - readonly version?: string | undefined - } - -export interface SamCliValidatorResult { - samCliFound: boolean - versionValidation?: SamCliVersionValidatorResult -} - -export interface SamCliValidator { - detectValidSamCli(): Promise - getVersionValidatorResult(): Promise -} - -export type SamCliValidatorContext = ClassToInterfaceType - -export class DefaultSamCliValidator implements SamCliValidator { - public constructor(private readonly context: SamCliValidatorContext) {} - - public async detectValidSamCli(): Promise { - const result: SamCliValidatorResult = { - samCliFound: false, - } - - const sam = await this.context.samCliLocation() - if (sam) { - result.samCliFound = true - result.versionValidation = await this.getVersionValidatorResult() - } - - return result - } - - public async getVersionValidatorResult(): Promise { - const r = await this.context.getSamCliInfo() - return { - version: r.version, - validation: DefaultSamCliValidator.validateSamCliVersion(r.version), - } - } - - public static validateSamCliVersion(version?: string): SamCliVersionValidation { - if (!version) { - return SamCliVersionValidation.VersionNotParseable - } - - if (!semver.valid(version)) { - return SamCliVersionValidation.VersionNotParseable - } - - if (semver.lt(version, minSamCliVersion)) { - return SamCliVersionValidation.VersionTooLow - } - - if (semver.gte(version, maxSamCliVersionExclusive)) { - return SamCliVersionValidation.VersionTooHigh - } - - if (process.platform === 'win32' && semver.gte(version, '1.85.0') && semver.lte(version, '1.86.0')) { - return SamCliVersionValidation.Version185Win - } - - return SamCliVersionValidation.Valid - } -} - -export class DefaultSamCliValidatorContext implements SamCliValidatorContext { - public constructor(private readonly config: SamCliSettings) {} - - public async samCliLocation(): Promise { - return (await this.config.getOrDetectSamCli()).path - } - - public async getSamCliInfo(): Promise { - const samPath = await this.samCliLocation() - if (!samPath) { - throw new Error('Unable to get SAM CLI info without an executable path') - } - const samCliInfo = new SamCliInfoInvocation(samPath) - - return await samCliInfo.execute() - } -} diff --git a/packages/core/src/shared/sam/config.ts b/packages/core/src/shared/sam/config.ts deleted file mode 100644 index 4a961956fd..0000000000 --- a/packages/core/src/shared/sam/config.ts +++ /dev/null @@ -1,236 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import fs from '../fs/fs' -import { JsonMap, parse, stringify } from '@iarna/toml' -import { cast, Optional } from '../utilities/typeConstructors' -import { getLogger } from '../logger/logger' -import { ToolkitError } from '../errors' - -export interface Config { - readonly environments: Record> -} - -export interface Environment { - readonly name: string - readonly commands: Record | undefined> -} - -interface Command { - readonly name: string - readonly parameters?: Record -} - -enum DeployType { - Deploy, - Sync, - Build, -} - -// This only handles a subset of possible user configs -// `@iarna/toml@2.25` supports 0.5.0 of the spec while SAM CLI 1.67.0 supports 1.0.0 -// In practive this is likely good enough for the vast majority of users -export async function parseConfig(contents: string): Promise { - const data = await parse.async(contents) - const objs = Object.entries(data).filter(([_, v]) => typeof v === 'object') as [string, Record][] - const environments = {} as Config['environments'] - for (const [k, v] of objs) { - environments[k] = parseEnvironment(v) - } - - return { environments } -} - -function parseEnvironment(section: Record): Omit { - const objs = Object.entries(section).filter(([_, v]) => typeof v === 'object') as [ - string, - Record, - ][] - const commands = {} as Environment['commands'] - for (const [k, v] of objs) { - commands[k] = parseCommand(v) - } - - return { commands } -} - -function parseCommand(section: Record): Omit { - return { parameters: cast(section['parameters'], Optional(Object)) } -} - -export enum SamConfigErrorCode { - samNoConfigFound = 'samNoConfigFound', - samConfigParseError = 'samConfigParseError', - samNoProjectRootFound = 'samNoProjectRootFound', -} - -export class SamConfig { - public constructor( - public readonly location: vscode.Uri, - private readonly config: Config = { environments: {} } - ) {} - - public getCommand(command: string, targetEnv = 'default') { - const env = this.config.environments[targetEnv] - return env?.commands[command] - } - - public getCommandParam(command: string, key: string, targetEnv = 'default'): unknown { - const primarySection = this.getCommand(command, targetEnv) - const globalSection = this.getCommand('global', targetEnv) - return primarySection?.parameters?.[key] ?? globalSection?.parameters?.[key] - } - - /** - * @description Finds samconfig.toml file under the provided project folder - * @param projectRoot The root folder of the application project - * @returns The SamConfig object - */ - public static async fromProjectRoot(projectRoot: vscode.Uri) { - if (!projectRoot) { - throw new ToolkitError('No project folder found', { code: SamConfigErrorCode.samNoProjectRootFound }) - } - const uri = await getConfigFileUri(projectRoot) - return this.fromConfigFileUri(uri) - } - - /** - * @description Parse Samconfig content in samconfig.toml - * @param uri The vscode uri of samconfig.toml - * @returns The SamConfig object - */ - public static async fromConfigFileUri(uri: vscode.Uri) { - try { - const contents = await fs.readFileText(uri) - const config = await parseConfig(contents) - return new this(uri, config) - } catch (error) { - throw new ToolkitError(`Error parsing samconfig.toml: ${error}`, { - code: SamConfigErrorCode.samConfigParseError, - }) - } - } - - public listEnvironments(): Environment[] { - const envs = this.config.environments - - return Object.entries(envs).map(([name, data]) => ({ name, ...data })) - } -} - -function generateConfigFileName(projectRoot: vscode.Uri) { - return vscode.Uri.joinPath(projectRoot, 'samconfig.toml') -} - -/** - * @description Finds the samconfig.toml file under the provided project folder - * @param projectRoot The root folder of the application project - * @returns The URI of the samconfig.toml file - */ -export async function getConfigFileUri(projectRoot: vscode.Uri) { - const path = generateConfigFileName(projectRoot) - if (!(await fs.exists(path.fsPath))) { - getLogger().warn('No samconfig.toml found') - throw new ToolkitError(`No samconfig.toml file found in ${projectRoot.fsPath}`, { - code: SamConfigErrorCode.samNoConfigFound, - }) - } - return path -} - -/** - * @description Overwrite stack name and region information to samconfig.toml file. - * If samconfig.toml file doesn't exist, create a new one. - * @param projectRoot The root folder of the application project - * @param stackName The name of the stack - * @param region The region of the stack - */ -export async function writeSamconfigGlobal(projectRoot: vscode.Uri, stackName: string, region: string) { - const path = vscode.Uri.joinPath(projectRoot, 'samconfig.toml') - if (!(await fs.exists(path))) { - getLogger().warn('No samconfig.toml found, creating...') - const data = { default: { global: { parameters: { stack_name: stackName, region: region } } } } - return createNewConfigFile(projectRoot, data) - } - const contents = await fs.readFileText(path) - const data = await parse.async(contents) - if (data.default) { - const defaultEnv = data.default as { global?: { parameters?: Record } } - if (defaultEnv.global) { - if (defaultEnv.global.parameters) { - defaultEnv.global.parameters.stack_name = stackName - defaultEnv.global.parameters.region = region - } - } else { - defaultEnv.global = { parameters: { stack_name: stackName, region: region } } - } - } - const obj = stringify(data) - await fs.writeFile(path, obj) -} - -/** - * @description Create a new samconfig.toml file under the provided project folder. - * This will overwrite the existing file. - - * @param projectRoot The root folder of the application project - * @param data The data to be written to the new samconfig.toml file - */ -export async function createNewConfigFile(projectRoot: vscode.Uri, data: JsonMap) { - const path = generateConfigFileName(projectRoot) - await fs.writeFile(path, 'version = 0.1\n\n') - await fs.appendFile(path, stringify(data)) -} - -async function validateAppBuilderSamConfig( - projectRoot: vscode.Uri | undefined, - configType: DeployType -): Promise { - if (!projectRoot) { - return false - } - - let content - try { - content = await SamConfig.fromProjectRoot(projectRoot) - } catch (error) { - return false - } - - const globalRegion = content.getCommandParam('global', 'region') - const globalStackName = content.getCommandParam('global', 'stack_name') - const buildCommandConfig = content.getCommand('build') - const deployCommandConfig = content.getCommand('deploy') - const syncCommandConfig = content.getCommand('sync') - - const hasRequiredGlobalParams: boolean = !!globalRegion && !!globalStackName - const hasRequiredDeployParameters: boolean = !!hasRequiredGlobalParams && !!deployCommandConfig - const hasRequiredSyncParameters: boolean = !!hasRequiredGlobalParams && !!syncCommandConfig - - switch (configType) { - case DeployType.Deploy: - return hasRequiredGlobalParams && hasRequiredDeployParameters - case DeployType.Sync: - return hasRequiredGlobalParams && hasRequiredSyncParameters - case DeployType.Build: - return !!buildCommandConfig - default: - getLogger().error(`Unsupported config type: ${configType}`) - return false - } -} - -export async function validateSamDeployConfig(uri: vscode.Uri | undefined) { - return await validateAppBuilderSamConfig(uri, DeployType.Deploy) -} - -export async function validateSamSyncConfig(uri: vscode.Uri | undefined) { - return await validateAppBuilderSamConfig(uri, DeployType.Sync) -} - -export async function validateSamBuildConfig(uri: vscode.Uri | undefined) { - return await validateAppBuilderSamConfig(uri, DeployType.Build) -} diff --git a/packages/core/src/shared/sam/constants.ts b/packages/core/src/shared/sam/constants.ts deleted file mode 100644 index ac0d66f75b..0000000000 --- a/packages/core/src/shared/sam/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const buildProcessMementoRootKey = 'samcli.build.processes' -export const globalIdentifier = 'global' -export const buildMementoRootKey = 'samcli.build.params' -export const deployMementoRootKey = 'samcli.deploy.params' -export const syncMementoRootKey = 'samcli.sync.params' diff --git a/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.gen.ts b/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.gen.ts deleted file mode 100644 index b211ad48c9..0000000000 --- a/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.gen.ts +++ /dev/null @@ -1,236 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/** - * This file is not generated from the core library. Please regenerate from - * 'packages/toolkit', e.g. `npm run generateConfigurationAttributes -w packages/toolkit` - */ - -/** - * This file was automatically generated by json-schema-to-typescript. - * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, - * and run json-schema-to-typescript to regenerate this file. - */ - -import * as vscode from "vscode"; - -export interface AwsSamDebuggerConfiguration extends vscode.DebugConfiguration { - aws?: AWSConnection; - invokeTarget: TemplateTargetProperties | CodeTargetProperties | APITargetProperties; - lambda?: LambdaProperties; - sam?: SAMCLIProperties; - api?: APIGatewayProperties; -} -/** - * AWS connection details - */ -export interface AWSConnection { - /** - * The AWS credentials provider and name to use during the invocation. Example: credential profile "default" would be entered as `profile:default`. - */ - credentials?: string; - /** - * AWS region to use during the invocation. - */ - region?: string; -} -/** - * Configures the application to launch - */ -export interface TemplateTargetProperties { - /** - * Path to the CFN/SAM template. - */ - templatePath: string; - /** - * Resource name of an AWS::Lambda::Function or AWS::Serverless::Function to invoke. - */ - logicalId: string; - /** - * The type of invocation to launch. Possible values: -* * `template` uses a CFN/SAM Template as an entrypoint -* * `code` invokes Lambda code directly. -* * `api` uses the CFN/SAM Template to emulate API Gateway - */ - target: "template"; -} -/** - * Configures the application to launch - */ -export interface CodeTargetProperties { - /** - * Lambda Function handler to invoke. - */ - lambdaHandler: string; - /** - * The root of the project, used to determine where in the file-system to locate the lambdaHandler. - */ - projectRoot: string; - /** - * The type of invocation to launch. Possible values: -* * `template` uses a CFN/SAM Template as an entrypoint -* * `code` invokes Lambda code directly. -* * `api` uses the CFN/SAM Template to emulate API Gateway - */ - target: "code"; - /** - * Architecture used for local SAM Lambda emulation - */ - architecture?: "x86_64" | "arm64"; -} -/** - * Configures the application to launch - */ -export interface APITargetProperties { - /** - * Path to the CFN/SAM template. - */ - templatePath: string; - /** - * Resource name of an AWS::Lambda::Function or AWS::Serverless::Function to invoke. - */ - logicalId: string; - /** - * The type of invocation to launch. Possible values: -* * `template` uses a CFN/SAM Template as an entrypoint -* * `code` invokes Lambda code directly. -* * `api` uses the CFN/SAM Template to emulate API Gateway - */ - target: "api"; -} -/** - * Lambda specific details of the invocation - */ -export interface LambdaProperties { - /** - * Environment variables to pass to the function invocation (replaces template variables). - */ - environmentVariables?: { - [k: string]: string; - }; - /** - * Event payload to pass to the Lambda invocation. -* Must specify one of 'json' or 'path'. - */ - payload?: { - /** - * JSON definition to use as the event payload - */ - json?: { - [k: string]: unknown; - }; - /** - * Path to a file to use as the event payload - */ - path?: string; - }; - /** - * The amount of memory (in Mb) the Lambda function has access to. - */ - memoryMb?: number; - /** - * The Lambda Function's runtime - */ - runtime?: string; - /** - * The amount of time (in seconds) that Lambda allows a function to run before stopping it. - */ - timeoutSec?: number; - pathMappings?: PathMapping[]; -} -export interface PathMapping { - localRoot: string; - remoteRoot: string; -} -/** - * SAM CLI specific configurations - */ -export interface SAMCLIProperties { - /** - * Additional arguments to pass to the `sam build` command. - */ - buildArguments?: string[]; - /** - * Base directory to build and run the application. -* A temporary directory will be created if not specified. - */ - buildDir?: string; - /** - * Whether to build inside a container (default: false). - */ - containerBuild?: boolean; - /** - * Specifies the name or id of an existing Docker network that Lambda Docker containers should connect to. - */ - dockerNetwork?: string; - /** - * Additional arguments to pass to the `sam local` command. - */ - localArguments?: string[]; - /** - * Specifies whether the command should skip pulling down the latest Docker image for Lambda runtime (default: false). - */ - skipNewImageCheck?: boolean; - /** - * Values to override in the template - */ - template?: { - /** - * Key:value mappings for SAM template parameter overrides. More information can be found here: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stackinstances-override.html#stackinstances-override-cli - */ - parameters?: { - [k: string]: string | number; - }; - }; -} -/** - * API Gateway configuration - */ -export interface APIGatewayProperties { - /** - * The path to the api (must start with /) - */ - path: string; - /** - * The HTTP message method that will be used - */ - httpMethod: "delete" | "get" | "head" | "options" | "patch" | "post" | "put"; - /** - * Event payload to pass to the Lambda invocation. -* Must specify one of 'json' or 'path'. - */ - payload?: { - /** - * JSON definition to use as the event payload - */ - json?: { - [k: string]: unknown; - }; - /** - * Path to a file to use as the event payload - */ - path?: string; - }; - /** - * Additional HTTP headers - */ - headers?: { - [k: string]: string; - }; - /** - * URL query string (e.g. key=foo&value=bar) - */ - querystring?: string; - /** - * key-value map of API Gateway stage variables - */ - stageVariables?: { - [k: string]: string; - }; - /** - * The API Gateway client certificate ID - */ - clientCertificateId?: string; -} diff --git a/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.ts b/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.ts deleted file mode 100644 index f11be86d00..0000000000 --- a/packages/core/src/shared/sam/debugger/awsSamDebugConfiguration.ts +++ /dev/null @@ -1,251 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import { Runtime } from '@aws-sdk/client-lambda' -import { getNormalizedRelativePath } from '../../utilities/pathUtils' -import { - APIGatewayProperties, - AwsSamDebuggerConfiguration, - CodeTargetProperties, - TemplateTargetProperties, -} from './awsSamDebugConfiguration.gen' -import { getLogger } from '../../logger/logger' - -export * from './awsSamDebugConfiguration.gen' - -export const AWS_SAM_DEBUG_TYPE = 'aws-sam' // eslint-disable-line @typescript-eslint/naming-convention -export const DIRECT_INVOKE_TYPE = 'direct-invoke' // eslint-disable-line @typescript-eslint/naming-convention -export const TEMPLATE_TARGET_TYPE = 'template' as const // eslint-disable-line @typescript-eslint/naming-convention -export const CODE_TARGET_TYPE = 'code' as const // eslint-disable-line @typescript-eslint/naming-convention -export const API_TARGET_TYPE = 'api' as const // eslint-disable-line @typescript-eslint/naming-convention -export const awsSamDebugRequestTypes = [DIRECT_INVOKE_TYPE] -export const awsSamDebugTargetTypes = [TEMPLATE_TARGET_TYPE, CODE_TARGET_TYPE, API_TARGET_TYPE] - -export type AwsSamTargetType = 'api' | 'code' | 'template' - -export type TargetProperties = AwsSamDebuggerConfiguration['invokeTarget'] - -export interface ReadonlyJsonObject { - readonly [key: string]: string | number | boolean -} - -export function isAwsSamDebugConfiguration(config: vscode.DebugConfiguration): config is AwsSamDebuggerConfiguration { - return config.type === AWS_SAM_DEBUG_TYPE -} - -export function isTemplateTargetProperties(props: TargetProperties): props is TemplateTargetProperties { - return props.target === TEMPLATE_TARGET_TYPE || props.target === API_TARGET_TYPE -} - -export function isCodeTargetProperties(props: TargetProperties): props is CodeTargetProperties { - return props.target === CODE_TARGET_TYPE -} - -/** - * Ensures that the `projectRoot` or `templatePath` relative properties on - * the given `config` are relative (not absolute) paths. - * - * @param folder Workspace folder, or empty to use the workspace associated with `projectRoot` or `templatePath`. - * @param config - */ -export function ensureRelativePaths( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration -): void { - if (!config?.invokeTarget?.target) { - // User has an invalid type=aws-sam launch-config. - return - } - const filepath = - config.invokeTarget.target === CODE_TARGET_TYPE - ? config.invokeTarget.projectRoot - : config.invokeTarget.templatePath - if (!path.isAbsolute(filepath)) { - return - } - const uri = vscode.Uri.file(filepath) - folder = folder ? folder : vscode.workspace.getWorkspaceFolder(uri) - if (!folder) { - getLogger().warn(`ensureRelativePaths: no workspace for path: "${filepath}"`) - return - } - const relPath = getNormalizedRelativePath(folder!.uri.fsPath, filepath) - if (config.invokeTarget.target === CODE_TARGET_TYPE) { - config.invokeTarget.projectRoot = relPath - } else { - config.invokeTarget.templatePath = relPath - } -} - -/** - * Creates a description for a SAM debugconfig entry (in launch.json). - * - * Example: `makeName('foo', '/bar/baz', 'zub')` => `"baz:foo (zub)" - * - * @param primaryName - * @param parentDir Optional directory name (used as a prefix) - * @param suffix Optional info used to differentiate the name - */ -function makeName(primaryName: string, parentDir: string | undefined, suffix: string | undefined) { - const withPrefix = parentDir ? `${parentDir}:${primaryName}` : primaryName - return suffix ? `${withPrefix} (${suffix})` : withPrefix -} - -/** - * Creates a description for a SAM debugconfig entry (in launch.json), preprending API - * - * see: makeName for the format - */ -function makeNameApi(primaryName: string, parentDir: string | undefined, suffix: string | undefined) { - return `API ${makeName(primaryName, parentDir, suffix)}` -} - -/** - * - * @param folder - * @param runtimeName Optional runtime name used to enhance the config name - * @addRuntimeToConfig - * @param resourceName - * @param templatePath - * @param preloadedConfig - */ -export function createTemplateAwsSamDebugConfig( - folder: vscode.WorkspaceFolder | undefined, - runtimeName: string | undefined, - addRuntimeToConfig: boolean, - resourceName: string, - templatePath: string, - preloadedConfig?: { - eventJson?: ReadonlyJsonObject - environmentVariables?: { [key: string]: string } - dockerNetwork?: string - useContainer?: boolean - } -): AwsSamDebuggerConfiguration { - const workspaceRelativePath = makeWorkspaceRelativePath(folder, templatePath) - const templateParentDir = path.basename(path.dirname(templatePath)) - - const response: AwsSamDebuggerConfiguration = { - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - name: makeName(resourceName, templateParentDir, runtimeName), - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: workspaceRelativePath, - logicalId: resourceName, - }, - lambda: { - payload: {}, - environmentVariables: {}, - }, - } - - if (addRuntimeToConfig) { - response.lambda!.runtime = runtimeName - } - - if (preloadedConfig) { - return { - ...response, - lambda: - preloadedConfig.environmentVariables || preloadedConfig.eventJson - ? { - payload: preloadedConfig.eventJson ? { json: preloadedConfig.eventJson } : {}, - environmentVariables: preloadedConfig.environmentVariables, - } - : { - payload: {}, - environmentVariables: {}, - }, - sam: - preloadedConfig.dockerNetwork || preloadedConfig.useContainer - ? { - dockerNetwork: preloadedConfig.dockerNetwork, - containerBuild: preloadedConfig.useContainer, - } - : undefined, - } - } - - return response -} - -export function createCodeAwsSamDebugConfig( - folder: vscode.WorkspaceFolder | undefined, - lambdaHandler: string, - projectRoot: string, - runtime: Runtime -): AwsSamDebuggerConfiguration { - const workspaceRelativePath = makeWorkspaceRelativePath(folder, projectRoot) - const parentDir = path.basename(projectRoot) - - return { - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - name: makeName(lambdaHandler, parentDir, runtime), - invokeTarget: { - target: CODE_TARGET_TYPE, - projectRoot: workspaceRelativePath, - lambdaHandler: lambdaHandler, - }, - lambda: { - runtime, - payload: {}, - environmentVariables: {}, - }, - } -} - -export function createApiAwsSamDebugConfig( - folder: vscode.WorkspaceFolder | undefined, - runtimeName: string | undefined, - resourceName: string, - templatePath: string, - preloadedConfig?: { - path?: string - httpMethod?: string - payload?: APIGatewayProperties['payload'] - } -): AwsSamDebuggerConfiguration { - const workspaceRelativePath = makeWorkspaceRelativePath(folder, templatePath) - const templateParentDir = path.basename(path.dirname(templatePath)) - - const withRuntime = runtimeName - ? { - lambda: { - runtime: runtimeName, - }, - } - : undefined - - return { - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - name: makeNameApi(resourceName, templateParentDir, runtimeName), - invokeTarget: { - target: API_TARGET_TYPE, - templatePath: workspaceRelativePath, - logicalId: resourceName, - }, - api: { - path: preloadedConfig?.path ?? '/', - // coerce it into the correct type. The types do not entirely overlap (there is an any) - // so in that case let the user decide - httpMethod: (preloadedConfig?.httpMethod as APIGatewayProperties['httpMethod']) ?? 'get', - payload: preloadedConfig?.payload ?? { json: {} }, - }, - ...withRuntime, - } -} - -function makeWorkspaceRelativePath(folder: vscode.WorkspaceFolder | undefined, target: string): string { - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length <= 1) { - return folder ? `\${workspaceFolder}/${getNormalizedRelativePath(folder.uri.fsPath, target)}` : target - } - - return target -} diff --git a/packages/core/src/shared/sam/debugger/awsSamDebugConfigurationValidator.ts b/packages/core/src/shared/sam/debugger/awsSamDebugConfigurationValidator.ts deleted file mode 100644 index b400746d7e..0000000000 --- a/packages/core/src/shared/sam/debugger/awsSamDebugConfigurationValidator.ts +++ /dev/null @@ -1,348 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as fs from 'fs' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import { samImageLambdaRuntimes, samZipLambdaRuntimes } from '../../../lambda/models/samLambdaRuntime' -import * as CloudFormation from '../../cloudformation/cloudformation' -import { localize, replaceVscodeVars } from '../../utilities/vsCodeUtils' -import { - awsSamDebugRequestTypes, - awsSamDebugTargetTypes, - AwsSamDebuggerConfiguration, - CODE_TARGET_TYPE, - TemplateTargetProperties, - TEMPLATE_TARGET_TYPE, - API_TARGET_TYPE, -} from './awsSamDebugConfiguration' -import { tryGetAbsolutePath } from '../../utilities/workspaceUtils' -import { CloudFormationTemplateRegistry } from '../../fs/templateRegistry' -import { Runtime } from '@aws-sdk/client-lambda' - -export interface ValidationResult { - isValid: boolean - message?: string -} - -export interface AwsSamDebugConfigurationValidator { - validate(config: AwsSamDebuggerConfiguration, registry: CloudFormationTemplateRegistry): Promise -} - -export class DefaultAwsSamDebugConfigurationValidator implements AwsSamDebugConfigurationValidator { - public constructor(private readonly workspaceFolder: vscode.WorkspaceFolder | undefined) {} - - /** - * Validates debug configuration properties. - */ - public async validate( - config: AwsSamDebuggerConfiguration, - registry: CloudFormationTemplateRegistry, - resolveVars?: boolean - ): Promise { - let rv: ValidationResult = { isValid: false, message: undefined } - if (resolveVars) { - config = doTraverseAndReplace(config, this.workspaceFolder?.uri.fsPath ?? '') - } - if (!config.request) { - rv.message = localize( - 'AWS.sam.debugger.missingField', - 'Missing required field "{0}" in debug config', - 'request' - ) - } else if (!awsSamDebugRequestTypes.includes(config.request)) { - rv.message = localize( - 'AWS.sam.debugger.invalidRequest', - 'Debug Configuration has an unsupported request type. Supported types: {0}', - awsSamDebugRequestTypes.join(', ') - ) - } else if (!awsSamDebugTargetTypes.includes(config.invokeTarget.target)) { - rv.message = localize( - 'AWS.sam.debugger.invalidTarget', - 'Debug Configuration has an unsupported target type. Supported types: {0}', - awsSamDebugTargetTypes.join(', ') - ) - } else if ( - config.invokeTarget.target === TEMPLATE_TARGET_TYPE || - config.invokeTarget.target === API_TARGET_TYPE - ) { - let cfnTemplate: CloudFormation.Template | undefined - if (config.invokeTarget.templatePath) { - // TODO: why wasn't ${workspaceFolder} resolved before now? - const resolvedPath = replaceVscodeVars( - config.invokeTarget.templatePath, - this.workspaceFolder?.uri.fsPath - ) - // Normalize to absolute path for use in the runner. - const fullpath = tryGetAbsolutePath(this.workspaceFolder, resolvedPath) - config.invokeTarget.templatePath = fullpath - // Forcefully add to the registry in case the registry scan somehow missed the file. #2614 - // If the user (launch config) gave an explicit path we should always "find" it. - const uri = vscode.Uri.file(fullpath) - cfnTemplate = (await registry.addItem(uri, true))?.item - } - rv = this.validateTemplateConfig(config, config.invokeTarget.templatePath, cfnTemplate) - } else if (config.invokeTarget.target === CODE_TARGET_TYPE) { - rv = this.validateCodeConfig(config) - } - - // Validate additional properties of API target type - if (rv.isValid && config.invokeTarget.target === API_TARGET_TYPE) { - rv = this.validateApiConfig(config) - } - - if (rv.isValid) { - rv = this.validateLambda(config) - } - - if (!rv.isValid && !rv.message) { - // Missing message, should never happen. - throw Error(`invalid debug-config: ${rv.message}`) - } - - return rv - } - - private validateTemplateConfig( - config: AwsSamDebuggerConfiguration, - cfnTemplatePath: string | undefined, - cfnTemplate: CloudFormation.Template | undefined - ): ValidationResult { - const templateTarget = config.invokeTarget as TemplateTargetProperties - - if (!cfnTemplatePath) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingField', - 'Missing required field "{0}" in debug config', - 'templatePath' - ), - } - } - - if (!cfnTemplate) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingTemplate', - 'Invalid (or missing) template file (path must be workspace-relative, or absolute): {0}', - templateTarget.templatePath - ), - } - } - - const resources = cfnTemplate.Resources - if (!templateTarget.logicalId) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingField', - 'Missing required field "{0}" in debug config', - 'logicalId' - ), - } - } - - const resource = resources?.[templateTarget.logicalId] - - if (!resource) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingResource', - 'Cannot find the template resource "{0}" in template file: {1}', - templateTarget.logicalId, - templateTarget.templatePath - ), - } - } - - if (![CloudFormation.SERVERLESS_FUNCTION_TYPE, CloudFormation.LAMBDA_FUNCTION_TYPE].includes(resource.Type)) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.resourceNotAFunction', - 'Template Resource {0} in Template file {1} must be of type {2} or {3}', - templateTarget.logicalId, - templateTarget.templatePath, - CloudFormation.SERVERLESS_FUNCTION_TYPE, - CloudFormation.LAMBDA_FUNCTION_TYPE - ), - } - } - - if (CloudFormation.isImageLambdaResource(resource?.Properties)) { - // SAM also checks that Metadata.DockerContext and Metadata.Dockerfile exists - if (!resource?.Metadata?.DockerContext || !resource?.Metadata?.Dockerfile) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingSamMetadata', - 'The Metadata section for Template Resource {0} in Template file {1} is not defined', - templateTarget.logicalId, - templateTarget.templatePath - ), - } - } - // can't infer the runtime for image-based lambdas - if (!config.lambda?.runtime || !samImageLambdaRuntimes().has(config.lambda.runtime as Runtime)) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingRuntimeForImage', - 'Run configurations for Image-based Lambdas require a valid Lambda runtime value, expected one of [{0}]', - Array.from(samImageLambdaRuntimes()).join(', ') - ), - } - } - } else { - // TODO: Decide what to do with this re: refs. - // As of now, this has to be directly declared without a ref, despite the fact that SAM will handle a ref. - // Should we just pass validation off to SAM and ignore validation at this point, or should we directly process the value (like the handler)? - const runtime = CloudFormation.getStringForProperty(resource?.Properties, 'Runtime', cfnTemplate) as Runtime - if (!runtime || !samZipLambdaRuntimes.has(runtime)) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.unsupportedRuntime', - 'Runtime for Template Resource {0} in Template file {1} is either undefined or unsupported.', - templateTarget.logicalId, - templateTarget.templatePath - ), - } - } - } - - const templateEnv = resource?.Properties?.Environment - if (templateEnv?.Variables) { - const templateEnvVars = Object.keys(templateEnv.Variables) - const missingVars: string[] = [] - if (config.lambda && config.lambda.environmentVariables) { - for (const key of Object.keys(config.lambda.environmentVariables)) { - if (!templateEnvVars.includes(key)) { - missingVars.push(key) - } - } - } - if (missingVars.length > 0) { - // this check doesn't affect template validity. - return { - isValid: true, - message: localize( - 'AWS.sam.debugger.extraEnvVars', - 'The following environment variables are not found in the targeted template and will not be overridden: {0}', - missingVars.join(', ') - ), - } - } - } - - return { isValid: true } - } - - private validateApiConfig(debugConfiguration: AwsSamDebuggerConfiguration): ValidationResult { - if (!debugConfiguration.api) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingField', - 'Missing required field "{0}" in debug config', - 'api' - ), - } - } else if (!debugConfiguration.api.path.startsWith('/')) { - return { - isValid: false, - message: localize('AWS.sam.debugger.missingSlash', "Path must start with a '/'"), - } - } - return { isValid: true } - } - - private validateCodeConfig(debugConfiguration: AwsSamDebuggerConfiguration): ValidationResult { - if ( - !debugConfiguration.lambda?.runtime || - !samZipLambdaRuntimes.has(debugConfiguration.lambda.runtime as Runtime) - ) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingRuntime', - 'Debug Configurations with an invoke target of "{0}" require a valid Lambda runtime value, expected one of [{1}]', - CODE_TARGET_TYPE, - Array.from(samZipLambdaRuntimes).join(', ') - ), - } - } - - return { isValid: true } - } - - private validateLambda(config: AwsSamDebuggerConfiguration): ValidationResult { - if (config.lambda?.payload?.path) { - const fullpath = tryGetAbsolutePath(this.workspaceFolder, config.lambda?.payload?.path) - if (!fs.existsSync(fullpath)) { - return { - isValid: false, - message: localize( - 'AWS.sam.debugger.missingRuntime', - 'Payload file not found: "{0}"', - config.lambda?.payload?.path - ), - } - } - } - - return { isValid: true } - } -} - -/** - * Resolves the `${workspaceFolder}` variable with the workspace folder. - * Unsure if any of the other variables are valuable for `aws-sam` configs at this time. - * @param folder - * @param config - * @returns resolved config - */ -export function resolveWorkspaceFolderVariable( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration -): AwsSamDebuggerConfiguration { - return doTraverseAndReplace(config, folder?.uri.fsPath) -} - -function doTraverseAndReplace(object: { [key: string]: any }, fspath: string | undefined): any { - const wsfRegex = /^(.*)(\$\{workspaceFolder\})(.*)$/g - if (!vscode.workspace.workspaceFolders && !fspath) { - throw new Error('No workspace folders available; cannot resolve workspaceFolder variable.') - } - const keys = Object.keys(object) - const final = JSON.parse(JSON.stringify(object)) - for (const key of keys) { - const val = object[key] - if (typeof val === 'string') { - const result = wsfRegex.exec(val) - if (result) { - if (!fspath) { - for (const wsf of vscode.workspace.workspaceFolders!) { - if (fs.existsSync(path.join(result[1], wsf.uri.fsPath, result[3]))) { - fspath = wsf.uri.fsPath - break - } - } - } - if (!fspath) { - throw new Error(`No compatible workspace folders for path: ${val}`) - } - final[key] = path.join(result[1], fspath, result[3]) - } - } else if (typeof val === 'object') { - final[key] = doTraverseAndReplace(val, fspath) - } - } - - return final -} diff --git a/packages/core/src/shared/sam/debugger/awsSamDebugger.ts b/packages/core/src/shared/sam/debugger/awsSamDebugger.ts deleted file mode 100644 index 217ce451dc..0000000000 --- a/packages/core/src/shared/sam/debugger/awsSamDebugger.ts +++ /dev/null @@ -1,728 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as semver from 'semver' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { - getCodeRoot, - getHandlerName, - getTemplateResource, - NodejsDebugConfiguration, - PythonDebugConfiguration, - GoDebugConfiguration, - getTemplate, - getArchitecture, - isImageLambdaConfig, -} from '../../../lambda/local/debugConfiguration' -import { - Architecture, - getDefaultRuntime, - getFamily, - getRuntimeFamily, - goRuntimes, - RuntimeFamily, -} from '../../../lambda/models/samLambdaRuntime' -import { Timeout } from '../../utilities/timeoutUtils' -import * as csharpDebug from './csharpSamDebug' -import * as javaDebug from './javaSamDebug' -import * as pythonDebug from './pythonSamDebug' -import * as tsDebug from './typescriptSamDebug' -import * as goDebug from './goSamDebug' -import { ExtContext } from '../../extensions' -import { isInDirectory, makeTemporaryToolkitFolder } from '../../filesystemUtilities' -import { getLogger } from '../../logger/logger' -import { getStartPort } from '../../utilities/debuggerUtils' -import * as pathutil from '../../utilities/pathUtils' -import { tryGetAbsolutePath } from '../../utilities/workspaceUtils' -import { - AwsSamDebuggerConfiguration, - AWS_SAM_DEBUG_TYPE, - createApiAwsSamDebugConfig, - createTemplateAwsSamDebugConfig, -} from './awsSamDebugConfiguration' -import { TemplateTargetProperties } from './awsSamDebugConfiguration.gen' -import { - AwsSamDebugConfigurationValidator, - DefaultAwsSamDebugConfigurationValidator, -} from './awsSamDebugConfigurationValidator' -import { getInputTemplatePath, makeInputTemplate, makeJsonFiles } from '../localLambdaRunner' -import { SamLocalInvokeCommand } from '../cli/samCliLocalInvoke' -import { getCredentialsFromStore } from '../../../auth/credentials/store' -import { fromString } from '../../../auth/providers/credentials' -import { Credentials } from '@aws-sdk/types' -import * as CloudFormation from '../../cloudformation/cloudformation' -import { getSamCliContext, getSamCliVersion } from '../cli/samCliContext' -import { minSamCliVersionForImageSupport, minSamCliVersionForGoSupport } from '../cli/samCliValidator' -import { getIdeProperties } from '../../extensionUtilities' -import { resolve } from 'path' -import globals from '../../extensionGlobals' -import { telemetry, Runtime as TelemetryRuntime } from '../../telemetry/telemetry' -import { Runtime } from '@aws-sdk/client-lambda' -import { ErrorInformation, isUserCancelledError, ToolkitError } from '../../errors' -import { openLaunchJsonFile } from './commands/addSamDebugConfiguration' -import { Logging } from '../../logger/commands' -import { credentialHelpUrl, samTroubleshootingUrl } from '../../constants' -import { Auth } from '../../../auth/auth' -import { openUrl } from '../../utilities/vsCodeUtils' -import fs from '../../fs/fs' - -const localize = nls.loadMessageBundle() - -interface NotificationButton { - readonly label: string - readonly onClick: () => Promise | T -} - -class SamLaunchRequestError extends ToolkitError.named('SamLaunchRequestError') { - private readonly buttons: NotificationButton[] - - public constructor(message: string, info?: ErrorInformation & { readonly extraButtons?: NotificationButton[] }) { - super(message, info) - this.buttons = info?.extraButtons ?? [ - { - label: localize('AWS.generic.message.troubleshooting', 'Troubleshooting'), - onClick: () => openUrl(samTroubleshootingUrl), - }, - { - label: localize('AWS.generic.message.openConfig', 'Open Launch Config'), - onClick: openLaunchJsonFile, - }, - ] - } - - public async showNotification(): Promise { - if (isUserCancelledError(this)) { - getLogger().verbose(`SAM run/debug: user cancelled`) - return - } - - const logId = getLogger().error(this.trace) - - const viewLogsButton = { - label: localize('AWS.generic.message.viewLogs', 'View Logs...'), - onClick: () => Logging.instance.viewLogsAtMessage.execute(logId), - } - - const buttonsWithLogs = [viewLogsButton, ...this.buttons] - - await vscode.window.showErrorMessage(this.message, ...buttonsWithLogs.map((b) => b.label)).then((resp) => { - return buttonsWithLogs.find(({ label }) => label === resp)?.onClick() - }) - } -} - -/** - * SAM-specific launch attributes (which are not part of the DAP). - * - * Schema for these attributes lives in package.json - * ("configurationAttributes"). - * - * @see AwsSamDebuggerConfiguration - * @see AwsSamDebugConfigurationProvider.resolveDebugConfiguration - */ -export interface SamLaunchRequestArgs extends AwsSamDebuggerConfiguration { - // readonly type: 'node' | 'python' | 'coreclr' | 'aws-sam' - readonly request: 'attach' | 'launch' | 'direct-invoke' - - /** Runtime id-name passed to vscode to select a debugger/launcher. */ - runtime: Runtime - runtimeFamily: RuntimeFamily - /** Resolved (potentinally generated) handler name. This field is mutable and should adjust to whatever handler name is currently generated*/ - handlerName: string - workspaceFolder: vscode.WorkspaceFolder - - /** - * Absolute path to the SAM project root, calculated from any of: - * - `codeUri` in `template.yaml` - * - `projectRoot` for the case of `target=code` - * - provider-specific heuristic (last resort) - */ - codeRoot: string - - /** Path to (generated) directory used as a working/staging area for SAM. */ - baseBuildDir?: string - - /** sam cli "--mount-with" option. */ - mountWith?: 'read' | 'write' - - /** - * URI of the current editor document. - * Used as a last resort for deciding `codeRoot` (when there is no `launch.json` nor `template.yaml`) - */ - documentUri: vscode.Uri - - /** - * SAM/CFN template absolute path used for SAM CLI invoke. - * - For `target=code` this is the _generated_ template path. - * - For `target=template` this is the _generated_ template path (TODO: in - * the future we may change this to be the template found in the workspace. - */ - templatePath: string - - /** - * Path to the (generated) `event.json` file placed in `baseBuildDir` for SAM to discover. - * - * The file contains the event payload JSON to be consumed by SAM. - */ - eventPayloadFile?: string - - /** - * Path to the (generated) `env-vars.json` file placed in `baseBuildDir` for SAM to discover. - * - * The file contains a JSON map of environment variables to be consumed by - * SAM, resolved from `template.yaml` and/or `lambda.environmentVariables`. - */ - envFile?: string - - // - // Debug properties (when user runs with debugging enabled). - // - /** vscode implicit field, set if user invokes "Run (Start Without Debugging)". */ - noDebug?: boolean - // Local (host) directory given to "sam foo --debugger-path …" - debuggerPath?: string - debugArgs?: string[] - /** Passed to SAM CLI --container-env-vars. For Toolkit use, not exposed to the user. */ - containerEnvVars?: { [k: string]: string } - /** - * Path to `container-env-vars.json` (generated from `containerEnvVars`). - * For Toolkit use, not exposed to the user. - */ - containerEnvFile?: string - debugPort?: number - /** Local API webserver port. */ - apiPort?: number - - /** - * Credentials to add as env vars if available - */ - awsCredentials?: Credentials - - /** - * parameter overrides specified in the `sam.template.parameters` field - */ - parameterOverrides?: string[] - - // - // Invocation properties (for "execute" phase, after "config" phase). - // Non-serializable... - // - samLocalInvokeCommand?: SamLocalInvokeCommand - onWillAttachDebugger?(debugPort: number, timeout: Timeout): Promise - - /** - * Specifies container architecture. Necessary for C#, to either swap debugger download or to force into no-debug mode - */ - architecture?: Architecture -} - -/** - * `DebugConfigurationProvider` dynamically defines these aspects of a VSCode debugger: - * - Initial debug configurations (for newly-created launch.json) - * - To resolve a launch configuration before it is used to start a new - * debug session. - * Two "resolve" methods exist: - * - resolveDebugConfiguration: called before variables are substituted in - * the launch configuration. - * - resolveDebugConfigurationWithSubstitutedVariables: called after all - * variables have been substituted. - * - * https://code.visualstudio.com/api/extension-guides/debugger-extension#using-a-debugconfigurationprovider - */ -export class SamDebugConfigProvider implements vscode.DebugConfigurationProvider { - public constructor(readonly ctx: ExtContext) {} - - /** - * @param folder Workspace folder - * @param token Cancellation token - */ - public async provideDebugConfigurations( - folder: vscode.WorkspaceFolder | undefined, - token?: vscode.CancellationToken - ): Promise { - if (token?.isCancellationRequested) { - return undefined - } - - const configs: AwsSamDebuggerConfiguration[] = [] - if (folder) { - const folderPath = folder.uri.fsPath - const templates = (await globals.templateRegistry).items - - for (const templateDatum of templates) { - if (isInDirectory(folderPath, templateDatum.path)) { - if (!templateDatum.item.Resources) { - getLogger().error(`provideDebugConfigurations: invalid template: ${templateDatum.path}`) - continue - } - for (const resourceKey of Object.keys(templateDatum.item.Resources)) { - const resource = templateDatum.item.Resources[resourceKey] - if (resource) { - // we do not know enough to populate the runtime field for Image-based Lambdas - const runtimeName = CloudFormation.isZipLambdaResource(resource?.Properties) - ? (CloudFormation.getStringForProperty( - resource?.Properties, - 'Runtime', - templateDatum.item - ) ?? '') - : '' - configs.push( - createTemplateAwsSamDebugConfig( - folder, - runtimeName, - false, - resourceKey, - templateDatum.path - ) - ) - const events = resource?.Properties?.Events - if (events) { - // Check for api resources to add - for (const key in events) { - const value = events[key] - if (value.Type === 'Api') { - const properties = value.Properties as CloudFormation.ApiEventProperties - configs.push( - createApiAwsSamDebugConfig( - folder, - runtimeName, - resourceKey, - templateDatum.path, - { - path: properties?.Path, - httpMethod: properties?.Method, - } - ) - ) - } - } - } - } - } - } - } - getLogger().verbose(`provideDebugConfigurations: debugconfigs: %O`, configs) - } - - return configs - } - - /** - * Necessary to get the debug configuration over to the resolveDebugConfigurationWithSubstitutedVariables function below - * - * @param folder Workspace folder - * @param config User-provided config (from launch.json) - * @param token Cancellation token - */ - public async resolveDebugConfiguration( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration, - token?: vscode.CancellationToken, - source?: string - ): Promise { - return config - } - - /** - * Generates a full run-config from a user-provided config, then - * runs/debugs it (essentially `sam build` + `sam local invoke`). - * - * If `launch.json` is missing, attempts to generate a config dynamically. - * - * @param folder Workspace folder - * @param config User-provided config (from launch.json) - * @param token Cancellation token - */ - // eslint-disable-next-line id-length - public async resolveDebugConfigurationWithSubstitutedVariables( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration, - token?: vscode.CancellationToken, - source?: string - ): Promise { - await this.makeAndInvokeConfig(folder, config, token, source) - // TODO: return config here, and remove use of `startDebugging()` in `localLambdaRunner.ts`. - return undefined - } - - private async makeAndInvokeConfig( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration, - token?: vscode.CancellationToken, - source?: string - ): Promise { - try { - if (config.invokeTarget.target === 'api') { - await telemetry.apigateway_invokeLocal.run(async (span) => { - const resolved = await this.makeConfig(folder, config, token) - span.record({ httpMethod: resolved.api?.httpMethod }) - - return this.invokeConfig(resolved) - }) - } else { - await telemetry.lambda_invokeLocal.run(async () => { - telemetry.record({ source: source }) - const resolved = await this.makeConfig(folder, config, token) - - return this.invokeConfig(resolved) - }) - } - } catch (err) { - if (err instanceof SamLaunchRequestError) { - void err.showNotification() - } else if (err instanceof ToolkitError) { - void new SamLaunchRequestError(err.message, { ...err }).showNotification() - } else { - void SamLaunchRequestError.chain(err, 'Failed to run launch configuration').showNotification() - } - } - } - - /** - * Performs the CONFIG phase of SAM run/debug: - * - gathers info from `launch.json`, project workspace, OS - * - creates runtime-specific files - * - creates `input-template.yaml`, `env-vars.json`, `event.json` files - * - creates a config object to handoff to VSCode - * - * @returns Config to handoff to VSCode or nodejs/python/dotnet plugin (can - * also be used in `vscode.debug.startDebugging`) - */ - public async makeConfig( - folder: vscode.WorkspaceFolder | undefined, - config: AwsSamDebuggerConfiguration, - token?: vscode.CancellationToken - ): Promise { - if (token?.isCancellationRequested) { - throw new ToolkitError('Cancellation requested', { cancelled: true }) - } - - folder = - folder ?? (vscode.workspace.workspaceFolders?.length ? vscode.workspace.workspaceFolders[0] : undefined) - if (!folder) { - const message = localize( - 'AWS.sam.debugger.noWorkspace', - 'Choose a workspace, then try again', - getIdeProperties().company - ) - - throw new SamLaunchRequestError(message, { code: 'NoWorkspaceFolder', extraButtons: [] }) - } - - // If "request" field is missing this means launch.json does not exist. - // User/vscode expects us to dynamically decide defaults if possible. - const hasLaunchJson = !!config.request - const configValidator: AwsSamDebugConfigurationValidator = new DefaultAwsSamDebugConfigurationValidator(folder) - - if (!hasLaunchJson) { - const message = localize( - 'AWS.sam.debugger.noLaunchJson', - 'To debug a Lambda locally, create a launch.json from the Run panel, then select a configuration.' - ) - - throw new SamLaunchRequestError(message, { - code: 'NoLaunchConfig', - extraButtons: [ - { - label: localize('AWS.gotoRunPanel', 'Run panel'), - onClick: () => vscode.commands.executeCommand('workbench.view.debug'), - }, - ], - }) - } else { - const registry = await globals.templateRegistry - const rv = await configValidator.validate(config, registry) - if (!rv.isValid) { - throw new ToolkitError(`Invalid launch configuration: ${rv.message}`, { code: 'BadLaunchConfig' }) - } else if (rv.message) { - void vscode.window.showInformationMessage(rv.message) - } - getLogger().verbose(`SAM debug: config %s:`, config.name) - } - - const editor = vscode.window.activeTextEditor - const templateInvoke = config.invokeTarget as TemplateTargetProperties - const template = await getTemplate(folder, config) - const templateResource = await getTemplateResource(folder, config) - const codeRoot = await getCodeRoot(folder, config) - const architecture = getArchitecture(template, templateResource, config.invokeTarget) - // Handler is the only field that we need to parse refs for. - // This is necessary for Python debugging since we have to create the temporary entry file - // Other refs can fail; SAM will handle them. - const handlerName = await getHandlerName(folder, config) - - config.baseBuildDir = resolve(folder.uri.fsPath, config.sam?.buildDir ?? (await makeTemporaryToolkitFolder())) - await fs.mkdir(config.baseBuildDir) - - if (templateInvoke?.templatePath) { - // Normalize to absolute path. - // TODO: If path is relative, it is relative to launch.json (i.e. .vscode directory). - templateInvoke.templatePath = pathutil.normalize(tryGetAbsolutePath(folder, templateInvoke.templatePath)) - } else if (config.invokeTarget.target === 'code') { - const codeConfig = config as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } } - // 'projectRoot' may be a relative path - // Older code left this property as relative, but there's no benefit in doing that since it's relative to the workspace - codeConfig.invokeTarget.projectRoot = pathutil.normalize( - resolve(folder.uri.fsPath, config.invokeTarget.projectRoot) - ) - templateInvoke.templatePath = getInputTemplatePath(codeConfig) - } - - const isZip = CloudFormation.isZipLambdaResource(templateResource?.Properties) - const runtime: Runtime | undefined = - (config.lambda?.runtime as Runtime) ?? - (template && isZip - ? CloudFormation.getStringForProperty(templateResource?.Properties, 'Runtime', template) - : undefined) ?? - getDefaultRuntime(getRuntimeFamily(editor?.document?.languageId ?? 'unknown')) - - const lambdaMemory = - (template - ? CloudFormation.getNumberForProperty(templateResource?.Properties, 'MemorySize', template) - : undefined) ?? config.lambda?.memoryMb - const lambdaTimeout = - (template - ? CloudFormation.getNumberForProperty(templateResource?.Properties, 'Timeout', template) - : undefined) ?? config.lambda?.timeoutSec - - // TODO: Remove this when min sam version is > 1.13.0 - if (!isZip) { - const samCliVersion = await getSamCliVersion(this.ctx.samCliContext()) - if (semver.lt(samCliVersion, minSamCliVersionForImageSupport)) { - const message = localize( - 'AWS.output.sam.no.image.support', - 'Support for Image-based Lambdas requires a minimum SAM CLI version of 1.13.0.' - ) - - throw new SamLaunchRequestError(message, { code: 'UnsupportedSamVersion', details: { samCliVersion } }) - } - } - - if (!runtime) { - const message = localize( - 'AWS.sam.debugger.failedLaunch.missingRuntime', - 'Toolkit could not infer a runtime for config: {0}. Add a "lambda.runtime" field to your launch configuration.', - config.name - ) - - throw new SamLaunchRequestError(message, { code: 'MissingRuntime' }) - } - - // SAM CLI versions before 1.18.1 do not work correctly for Go debugging. - // TODO: remove this when min sam version is >= 1.18.1 - if (goRuntimes.includes(runtime) && !config.noDebug) { - const samCliVersion = await getSamCliVersion(this.ctx.samCliContext()) - if (semver.lt(samCliVersion, minSamCliVersionForGoSupport)) { - void vscode.window.showWarningMessage( - localize( - 'AWS.output.sam.local.no.go.support', - 'Debugging go1.x lambdas requires a minimum SAM CLI version of {0}. Function will run locally without debug.', - minSamCliVersionForGoSupport - ) - ) - config.noDebug = true - } - } - - const runtimeFamily = getFamily(runtime) - // use region in debug config first, if not found, fall back to toolkit default region. - const region = config.aws?.region ?? this.ctx.awsContext.getCredentialDefaultRegion() - const documentUri = - vscode.window.activeTextEditor?.document.uri ?? - // XXX: don't know what URI to choose... - vscode.Uri.parse(templateInvoke.templatePath!) - - let awsCredentials = await this.ctx.awsContext.getCredentials() - if (!awsCredentials && !config.aws?.credentials) { - getLogger().warn('SAM debug: missing AWS credentials (Toolkit is not connected)') - } else if (config.aws?.credentials) { - // "aws.credentials" defined in the launch-config takes precedence - // over Toolkit's current active credentials. - let fromStore: Credentials | undefined - try { - const credentialsId = fromString(config.aws.credentials) - fromStore = await getCredentialsFromStore(credentialsId, this.ctx.credentialsStore) - } catch { - getLogger().error(`SAM debug: fromString('${config.aws.credentials}') failed`) - } - if (fromStore) { - awsCredentials = fromStore - } else { - const credentialsId = config.aws.credentials - const getHelp = localize('AWS.generic.message.getHelp', 'Get Help...') - - throw new SamLaunchRequestError(`Invalid credentials found in launch configuration: ${credentialsId}`, { - code: 'InvalidCredentials', - extraButtons: [ - { - label: getHelp, - onClick: () => openUrl(vscode.Uri.parse(credentialHelpUrl)), - }, - ], - }) - } - } - - if (config.api) { - config.api.headers = { - 'content-type': 'application/json', - ...(config.api.headers ? config.api.headers : {}), - } - } - - let parameterOverrideArr: string[] | undefined - const params = config.sam?.template?.parameters - if (params) { - parameterOverrideArr = [] - for (const key of Object.keys(params)) { - parameterOverrideArr.push(`${key}=${params[key].toString()}`) - } - } - - // TODO: Let the OS (or SAM CLI) assign the port, then we need to - // scrape SAM CLI to find the port that was actually used? - const apiPort = config.invokeTarget.target === 'api' ? await getStartPort() : undefined - const debugPort = config.noDebug ? undefined : await getStartPort(apiPort ? apiPort + 1 : undefined) - let launchConfig: SamLaunchRequestArgs = { - ...config, - request: 'attach', - codeRoot: codeRoot ?? '', - workspaceFolder: folder, - runtime: runtime as Runtime, - runtimeFamily: runtimeFamily, - handlerName: handlerName, - documentUri: documentUri, - templatePath: pathutil.normalize(templateInvoke?.templatePath), - eventPayloadFile: '', // Populated by makeConfig(). - envFile: '', // Populated by makeConfig(). - apiPort: apiPort, - debugPort: debugPort, - invokeTarget: { - ...config.invokeTarget, - }, - lambda: { - ...config.lambda, - memoryMb: lambdaMemory, - timeoutSec: lambdaTimeout, - environmentVariables: { ...config.lambda?.environmentVariables }, - }, - region: region, - awsCredentials: awsCredentials, - parameterOverrides: parameterOverrideArr, - architecture: architecture, - } - - // - // Configure and launch. - // - // 1. prepare a bunch of arguments - // 2. do `sam build` - // 3. do `sam local invoke` - // - switch (launchConfig.runtimeFamily) { - case RuntimeFamily.NodeJS: { - // Make a NodeJS launch-config from the generic config. - launchConfig = await tsDebug.makeTypescriptConfig(launchConfig) - break - } - case RuntimeFamily.Python: { - // Make a Python launch-config from the generic config. - launchConfig = await pythonDebug.makePythonDebugConfig(launchConfig) - break - } - case RuntimeFamily.DotNet: { - // Make a DotNet launch-config from the generic config. - launchConfig = await csharpDebug.makeCsharpConfig(launchConfig) - break - } - case RuntimeFamily.Go: { - launchConfig = await goDebug.makeGoConfig(launchConfig) - break - } - case RuntimeFamily.Java: { - // Make a Java launch-config from the generic config. - launchConfig = await javaDebug.makeJavaConfig(launchConfig) - break - } - default: { - const message = localize( - 'AWS.sam.debugger.invalidRuntime', - 'Unknown or unsupported runtime: {0}', - runtime - ) - - throw new ToolkitError(message, { code: 'UnsupportedRuntime' }) - } - } - - // generate template for target=code - if (launchConfig.invokeTarget.target === 'code') { - const codeConfig = launchConfig as SamLaunchRequestArgs & { invokeTarget: { target: 'code' } } - await makeInputTemplate(codeConfig) - } - - await makeJsonFiles(launchConfig) - - // Set the type, then vscode will pass the config to SamDebugSession.attachRequest(). - // (Registered in sam/activation.ts which calls registerDebugAdapterDescriptorFactory()). - // By this point launchConfig.request is now set to "attach" (not "direct-invoke"). - launchConfig.type = AWS_SAM_DEBUG_TYPE - - if (launchConfig.request !== 'attach' && launchConfig.request !== 'launch') { - // The "request" field must be updated so that it routes to the - // DebugAdapter (SamDebugSession.attachRequest()), else this will - // just cycle back (and it indicates a bug in the config logic). - throw Error( - `resolveDebugConfiguration: launchConfig was not correctly resolved before return: ${JSON.stringify( - launchConfig - )}` - ) - } - - return launchConfig - } - - /** - * Performs the EXECUTE phase of SAM run/debug. - */ - public async invokeConfig(config: SamLaunchRequestArgs): Promise { - telemetry.record({ - debug: !config.noDebug, - runtime: config.runtime as TelemetryRuntime, - lambdaArchitecture: config.architecture, - lambdaPackageType: (await isImageLambdaConfig(config)) ? 'Image' : 'Zip', - version: await getSamCliVersion(getSamCliContext()), - }) - - await Auth.instance.tryAutoConnect() - switch (config.runtimeFamily) { - case RuntimeFamily.NodeJS: { - config.type = 'node' - const c = await tsDebug.invokeTypescriptLambda(this.ctx, config as NodejsDebugConfiguration) - return c - } - case RuntimeFamily.Python: { - config.type = 'python' - return await pythonDebug.invokePythonLambda(this.ctx, config as PythonDebugConfiguration) - } - case RuntimeFamily.DotNet: { - config.type = 'coreclr' - return await csharpDebug.invokeCsharpLambda(this.ctx, config) - } - case RuntimeFamily.Go: { - config.type = 'go' - return await goDebug.invokeGoLambda(this.ctx, config as GoDebugConfiguration) - } - case RuntimeFamily.Java: { - config.type = 'java' - return await javaDebug.invokeJavaLambda(this.ctx, config) - } - default: { - throw new Error(`unknown runtimeFamily: ${config.runtimeFamily}`) - } - } - } -} diff --git a/packages/core/src/shared/sam/debugger/commands/addSamDebugConfiguration.ts b/packages/core/src/shared/sam/debugger/commands/addSamDebugConfiguration.ts deleted file mode 100644 index 9cf89a8cb2..0000000000 --- a/packages/core/src/shared/sam/debugger/commands/addSamDebugConfiguration.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { getExistingConfiguration } from '../../../../lambda/config/templates' -import { createRuntimeQuickPick, getDefaultRuntime, RuntimeFamily } from '../../../../lambda/models/samLambdaRuntime' -import { localize } from '../../../utilities/vsCodeUtils' -import { - API_TARGET_TYPE, - AwsSamDebuggerConfiguration, - CODE_TARGET_TYPE, - createApiAwsSamDebugConfig, - createCodeAwsSamDebugConfig, - createTemplateAwsSamDebugConfig, - TEMPLATE_TARGET_TYPE, -} from '../awsSamDebugConfiguration' -import * as CloudFormation from '../../../cloudformation/cloudformation' -import { LaunchConfiguration } from '../../../debug/launchConfiguration' -import { getIdeProperties } from '../../../extensionUtilities' -import { isValidResponse } from '../../../wizards/wizard' -import globals from '../../../extensionGlobals' - -/** - * Holds information required to create a launch config - * @field resourceName: Resource being used. For templates, this is the resource name in the CFN stack. For code, this is the handler's name - * @field rootUri: The code root. For templates, this is the CodeUri value. For code, this is the manifest's URI. - */ -export interface AddSamDebugConfigurationInput { - resourceName: string - rootUri: vscode.Uri - apiEvent?: { name: string; event: CloudFormation.Event } - runtimeFamily?: RuntimeFamily -} - -/** - * Adds a new debug configuration for the given sam function resource and template. - */ -export async function addSamDebugConfiguration( - { resourceName, rootUri, apiEvent, runtimeFamily }: AddSamDebugConfigurationInput, - type: typeof CODE_TARGET_TYPE | typeof TEMPLATE_TARGET_TYPE | typeof API_TARGET_TYPE, - openWebview: boolean, - step?: { step: number; totalSteps: number } -): Promise { - // emit without waiting - void emitCommandTelemetry() - - let samDebugConfig: AwsSamDebuggerConfiguration - const workspaceFolder = vscode.workspace.getWorkspaceFolder(rootUri) - let runtimeName = runtimeFamily ? getDefaultRuntime(runtimeFamily) : undefined - let addRuntimeNameToConfig = false - - // both of these config types use templates - if (type === TEMPLATE_TARGET_TYPE || type === API_TARGET_TYPE) { - let preloadedConfig = undefined - - if (workspaceFolder) { - const templateDatum = (await globals.templateRegistry).getItem(rootUri) - if (templateDatum) { - const resource = templateDatum.item.Resources![resourceName] - if (!resource) { - return - } - - if (CloudFormation.isZipLambdaResource(resource.Properties)) { - if (type === TEMPLATE_TARGET_TYPE) { - const handler = CloudFormation.getStringForProperty( - resource.Properties, - 'Handler', - templateDatum.item - ) - const existingConfig = await getExistingConfiguration(workspaceFolder, handler ?? '', rootUri) - if (existingConfig) { - const responseMigrate: string = localize( - 'AWS.sam.debugger.useExistingConfig.migrate', - 'Create based on the legacy config' - ) - const responseNew: string = localize( - 'AWS.sam.debugger.useExistingConfig.doNotMigrate', - 'Create new config only' - ) - const prompt = await vscode.window.showInformationMessage( - localize( - 'AWS.sam.debugger.useExistingConfig', - '{0} Toolkit detected an existing legacy configuration for this function. Create the debug config based on the legacy config?', - getIdeProperties().company - ), - { modal: true }, - responseMigrate, - responseNew - ) - if (!prompt) { - // User selected "Cancel". Abandon config creation - return - } else if (prompt === responseMigrate) { - preloadedConfig = existingConfig - } - } - } - } else if (CloudFormation.isImageLambdaResource(resource.Properties)) { - const quickPick = createRuntimeQuickPick({ - showImageRuntimes: false, - runtimeFamily, - }) - - const choices = await quickPick.prompt() - - if (!isValidResponse(choices)) { - return - } - - runtimeName = choices.runtime - addRuntimeNameToConfig = true - } - } - } - - if (type === TEMPLATE_TARGET_TYPE) { - samDebugConfig = createTemplateAwsSamDebugConfig( - workspaceFolder, - runtimeName, - addRuntimeNameToConfig, - resourceName, - rootUri.fsPath, - preloadedConfig - ) - } else { - // If the event has no properties, the default will be used - const apiConfig = { - path: apiEvent?.event.Properties?.Path, - httpMethod: apiEvent?.event.Properties?.Method, - payload: apiEvent?.event.Properties?.Payload, - } - - samDebugConfig = createApiAwsSamDebugConfig( - workspaceFolder, - runtimeName, - resourceName, - rootUri.fsPath, - apiConfig - ) - } - } else if (type === CODE_TARGET_TYPE) { - const quickPick = createRuntimeQuickPick({ - showImageRuntimes: false, - runtimeFamily, - step: step?.step, - totalSteps: step?.totalSteps, - }) - - const choices = await quickPick.prompt() - - if (!isValidResponse(choices)) { - return - } - - // strip the manifest's URI to the manifest's dir here. More reliable to do this here than converting back and forth between URI/string up the chain. - samDebugConfig = createCodeAwsSamDebugConfig( - workspaceFolder, - resourceName, - path.dirname(rootUri.fsPath), - choices.runtime - ) - } else { - throw new Error('Unrecognized debug target type') - } - - if (openWebview) { - await vscode.commands.executeCommand('aws.launchConfigForm', samDebugConfig) - } else { - const launchConfig = new LaunchConfiguration(rootUri) - await launchConfig.addDebugConfiguration(samDebugConfig) - - await openLaunchJsonFile() - } -} - -export async function openLaunchJsonFile(): Promise { - await vscode.commands.executeCommand( - vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length <= 1 - ? 'workbench.action.debug.configure' - : 'workbench.action.openWorkspaceSettingsFile' - ) -} - -async function emitCommandTelemetry(): Promise { - // TODO add new metric for when command is executed -} diff --git a/packages/core/src/shared/sam/debugger/csharpSamDebug.ts b/packages/core/src/shared/sam/debugger/csharpSamDebug.ts deleted file mode 100644 index 01aef679cd..0000000000 --- a/packages/core/src/shared/sam/debugger/csharpSamDebug.ts +++ /dev/null @@ -1,286 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as os from 'os' -import * as path from 'path' -import { - DotNetDebugConfiguration, - dotnetDebuggerPath, - getCodeRoot, - isImageLambdaConfig, -} from '../../../lambda/local/debugConfiguration' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import * as pathutil from '../../../shared/utilities/pathUtils' -import { ExtContext } from '../../extensions' -import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' -import { runLambdaFunction, waitForPort } from '../localLambdaRunner' -import { SamLaunchRequestArgs } from './awsSamDebugger' -import { ChildProcess } from '../../utilities/processUtils' -import { HttpResourceFetcher } from '../../resourcefetcher/httpResourceFetcher' -import { getLogger } from '../../logger/logger' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import globals from '../../extensionGlobals' -import fs from '../../fs/fs' -const localize = nls.loadMessageBundle() - -/** - * Gathers and sets launch-config info by inspecting the workspace and creating - * temp files/directories as needed. - * - * Does NOT execute/invoke SAM, docker, etc. - */ -export async function makeCsharpConfig(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - config.codeRoot = (await getCodeRoot(config.workspaceFolder, config))! - // TODO: avoid the reassignment - // TODO: walk the tree to find .sln, .csproj ? - const originalCodeRoot = config.codeRoot - config.codeRoot = getSamProjectDirPathForFile(config.templatePath) - - config = { - ...config, - // https://github.com/dotnet/vscode-csharp/blob/a270be4f2f1236d9bc1b31b6214f1672096788b7/src/lsptoolshost/debugger.ts#L45C1-L54 - // The "ms-dotnettools.csharp" extension declares: - // - "coreclr" as the debugger type for ".NET 5+ and .NET Core" - // - "dotnet" as the debugger type for "C#" - // but haven't found the actual circumstances when "dotnet" works while "coreclr" doesn't. - // type: config.runtime == 'dotnet5.0' ? 'coreclr' : 'dotnet', - type: 'coreclr', - request: config.noDebug ? 'launch' : 'attach', - runtimeFamily: RuntimeFamily.DotNet, - } - - if (config.sam?.containerBuild) { - config.mountWith = 'write' - } - - if (!config.noDebug) { - config = await makeDotnetDebugConfiguration(config, originalCodeRoot) - } - - return config -} - -/** - * Launches and attaches debugger to a SAM dotnet (csharp) project. - * - * We spin up a C# Lambda Docker container, download and build the debugger for - * Linux, then mount it with the SAM app on run. User's C# workspace dir will - * have a `.vsdbg` dir after the first run. - */ -export async function invokeCsharpLambda(ctx: ExtContext, config: SamLaunchRequestArgs): Promise { - config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.DOTNET]) - // eslint-disable-next-line @typescript-eslint/unbound-method - config.onWillAttachDebugger = waitForPort - const platformArchitecture = os.arch() - - if (!config.noDebug) { - if ([config.architecture, platformArchitecture].includes('arm64')) { - void vscode.window.showWarningMessage( - localize( - 'AWS.sam.noArm.dotnet.debug', - 'The vsdbg debugger does not currently support the arm64 architecture. Function will run locally without debug.' - ) - ) - getLogger().warn('SAM Invoke: Attempting to debug dotnet on ARM - removing debug flag.') - config.noDebug = true - } - } - return await runLambdaFunction(ctx, config, async () => { - if (!config.noDebug) { - await _installDebugger({ - debuggerPath: config.debuggerPath!, - }) - } - }) -} - -interface InstallDebuggerArgs { - debuggerPath: string -} - -function getDebuggerPath(parentFolder: string): string { - return path.resolve(parentFolder, '.vsdbg') -} - -async function _installDebugger({ debuggerPath }: InstallDebuggerArgs): Promise { - await fs.mkdir(debuggerPath) - - try { - getLogger().info( - localize( - 'AWS.samcli.local.invoke.debugger.install', - 'Installing .NET Core Debugger to {0}...', - debuggerPath - ) - ) - - const vsDbgVersion = 'latest' - // TODO: If vsdbg works with qemu, have this detect Architecture and swap to `linux-arm64` if ARM. - // See https://github.com/OmniSharp/omnisharp-vscode/issues/3277 ; - // qemu appears to set PrivateTmp=true : https://github.com/qemu/qemu/blob/326ff8dd09556fc2e257196c49f35009700794ac/contrib/systemd/qemu-pr-helper.service#L8 ? - const vsDbgRuntime = 'linux-x64' - - const installScriptPath = await downloadInstallScript(debuggerPath) - - let installCommand: string - let installArgs: string[] - if (os.platform() === 'win32') { - const windir = process.env['WINDIR'] - if (!windir) { - throw new Error('Environment variable `WINDIR` not defined') - } - - installCommand = `${windir}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` - installArgs = [ - '-NonInteractive', - '-NoProfile', - '-WindowStyle', - 'Hidden', - '-ExecutionPolicy', - 'RemoteSigned', - '-File', - installScriptPath, - '-Version', - vsDbgVersion, - '-RuntimeID', - vsDbgRuntime, - '-InstallPath', - debuggerPath, - ] - } else { - installCommand = installScriptPath - installArgs = ['-v', vsDbgVersion, '-r', vsDbgRuntime, '-l', debuggerPath] - } - - const childProcess = new ChildProcess(installCommand, installArgs) - - const install = await childProcess.run({ - onStdout: (text: string) => { - globals.outputChannel.append(text) - }, - onStderr: (text: string) => { - globals.outputChannel.append(text) - }, - }) - - if (install.exitCode) { - throw new Error(`command failed (exit code: ${install.exitCode}): ${installCommand}`) - } - } catch (err) { - getLogger().info( - localize( - 'AWS.samcli.local.invoke.debugger.install.failed', - 'Error installing .NET Core Debugger: {0}', - err instanceof Error ? (err as Error).message : String(err) - ) - ) - - throw err - } -} - -async function downloadInstallScript(debuggerPath: string): Promise { - let installScriptUrl: string - let installScriptPath: string - if (os.platform() === 'win32') { - installScriptUrl = 'https://aka.ms/getvsdbgps1' - installScriptPath = path.join(debuggerPath, 'installVsdbgScript.ps1') - } else { - installScriptUrl = 'https://aka.ms/getvsdbgsh' - installScriptPath = path.join(debuggerPath, 'installVsdbgScript.sh') - } - - const installScriptFetcher = await new HttpResourceFetcher(installScriptUrl, { showUrl: true }).get() - const installScript = await installScriptFetcher?.text() - if (!installScript) { - throw Error(`Failed to download ${installScriptUrl}`) - } - - await fs.writeFile(installScriptPath, installScript, 'utf8') - await fs.chmod(installScriptPath, 0o700) - - return installScriptPath -} - -function getSamProjectDirPathForFile(filepath: string): string { - return pathutil.normalize(path.dirname(filepath)) -} - -/** - * Creates a .NET launch-config composed with the given `config`. - */ -export async function makeDotnetDebugConfiguration( - config: SamLaunchRequestArgs, - codeUri: string -): Promise { - if (config.noDebug) { - throw Error(`SAM debug: invalid config: ${config.name}`) - } - const pipeArgs = ['-c', `docker exec -i $(docker ps -q -f publish=${config.debugPort}) \${debuggerCommand}`] - config.debuggerPath = pathutil.normalize(getDebuggerPath(codeUri)) - await fs.mkdir(config.debuggerPath) - - const isImageLambda = await isImageLambdaConfig(config) - - if (isImageLambda && !config.noDebug) { - config.containerEnvVars = { - _AWS_LAMBDA_DOTNET_DEBUGGING: '1', - } - } - - if (os.platform() === 'win32') { - // Coerce drive letter to uppercase. While Windows is case-insensitive, sourceFileMap is case-sensitive. - codeUri = codeUri.replace(pathutil.driveLetterRegex, (match) => match.toUpperCase()) - } - - if (isImageLambda) { - // default build path, in dotnet image-based templates - // Not needed for ZIP lambdas, because SAM prevents dotnet from being - // built in-container thus PDBs already point to the user workspace. - if (!config.sourceFileMap) { - config.sourceFileMap = {} - } - config.sourceFileMap['/build'] = codeUri - } - - if (config.lambda?.pathMappings !== undefined) { - if (!config.sourceFileMap) { - config.sourceFileMap = {} - } - // we could safely leave this entry in, but might as well give the user full control if they're specifying mappings - delete config.sourceFileMap['/build'] - for (const mapping of config.lambda.pathMappings) { - // this looks weird because we're mapping the PDB path to the local workspace - config.sourceFileMap[mapping.remoteRoot] = mapping.localRoot - } - } - - return { - ...config, - runtimeFamily: RuntimeFamily.DotNet, - request: 'attach', - // Since SAM CLI 1.0 we cannot assume PID=1. So use processName=dotnet - // instead of processId=1. - processName: 'dotnet', - pipeTransport: { - pipeProgram: 'sh', - pipeArgs, - debuggerPath: dotnetDebuggerPath, - pipeCwd: codeUri, - }, - windows: { - pipeTransport: { - pipeProgram: 'powershell', - pipeArgs, - debuggerPath: dotnetDebuggerPath, - pipeCwd: codeUri, - }, - }, - } -} diff --git a/packages/core/src/shared/sam/debugger/goSamDebug.ts b/packages/core/src/shared/sam/debugger/goSamDebug.ts deleted file mode 100644 index d0d3ee2ed5..0000000000 --- a/packages/core/src/shared/sam/debugger/goSamDebug.ts +++ /dev/null @@ -1,275 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as os from 'os' -import * as path from 'path' -import * as vscode from 'vscode' -import { GoDebugConfiguration, goDebuggerPath, isImageLambdaConfig } from '../../../lambda/local/debugConfiguration' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import * as pathutil from '../../../shared/utilities/pathUtils' -import { ExtContext } from '../../extensions' -import { findParentProjectFile } from '../../utilities/workspaceUtils' -import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' -import { runLambdaFunction } from '../localLambdaRunner' -import { SamLaunchRequestArgs } from './awsSamDebugger' -import { getLogger } from '../../logger/logger' -import fs from '../../fs/fs' -import { ChildProcess } from '../../utilities/processUtils' -import { Timeout } from '../../utilities/timeoutUtils' -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports -import * as nls from 'vscode-nls' -import { sleep } from '../../utilities/timeoutUtils' -import globals from '../../extensionGlobals' -import { ToolkitError } from '../../errors' -const localize = nls.loadMessageBundle() - -/** - * Launches and attaches debugger to a SAM Go project. A general overview of the program flow: - * 1. We cross-compile a Delve binary for the container using Delve's source, either from the user's - * GOPATH or using 'go get'. The binary is placed in the extension's global storage path with - * path 'debuggers/delve/dlv' - * 3. SAM CLI will then mount the 'delve' directory to '/tmp/lambci_debug'. We use --debugger-path - * to specify which directory to mount. SAM CLI will always look for 'dlv', so the binary must - * be compiled with that name. - * 4. The generate configuration options are passed along to SAM build + SAM invoke which will - * compile the handler using the path specified by 'codeRoot'. The handler entry point must be - * a direct child of 'codeRoot', otherwise it will fail to build. Builds are done by a sub-module - * of SAM CLI [1]. SAM invoke then mounts the build-artifact directory as '/var/task/[HandlerName]' - * 5. Something called the 'Runtime Interface Emulator' is started in the container, which then - * starts an intermediate program [2] that starts the Delve debugger in 'headless exec' mode. - * The handler is finally executed by Delve. We can now communicate with Delve using the Go - * extension's debug adapter client. - * 6. All paths communicated by Delve will be absolute from the moment it was compiled. So any - * kind of path mappings are unncessary as long as the binary is compiled directly from source. - * In other words, don't build the binary from a copy of the source. - * - * References: - * [1] https://github.com/aws/aws-lambda-builders/blob/b663326079c871e50f1545f36f9695f6958cfaa2/aws_lambda_builders/workflows/go_modules/builder.py - * [2] https://github.com/lambci/docker-lambda/blob/f6b4765a9b659ceb949c34b19390026820ddd462/go1.x/run/aws-lambda-mock.go - */ -export async function invokeGoLambda(ctx: ExtContext, config: GoDebugConfiguration): Promise { - config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.GO_DELVE]) - // eslint-disable-next-line @typescript-eslint/unbound-method - config.onWillAttachDebugger = waitForDelve - - if (!config.noDebug && !(await installDebugger(config.debuggerPath!))) { - throw new ToolkitError( - localize('AWS.sam.debugger.godelve.failed', 'Failed to install Delve for the lambda container.'), - { - code: 'NoDelveInstallation', - } - ) - } - - const c = (await runLambdaFunction(ctx, config, async () => {})) as GoDebugConfiguration - return c -} - -/** - * Triggered before the debugger attachment process begins. We should verify that the debugger is ready to go - * before returning. The Delve DAP will only accept a single client, so checking if the ports are in use will - * cause Delve to terminate. - * - * @param debugPort Port to check for activity - * @param timeout Cancellation token to prevent stalling - */ -async function waitForDelve(debugPort: number, timeout: Timeout) { - await sleep(1000) -} - -export async function getSamProjectDirPathForFile(filepath: string): Promise { - const modulePath = await findParentProjectFile(vscode.Uri.parse(filepath), /^go\.mod$/) - if (!modulePath) { - throw new Error(`Cannot find go.mod for: ${filepath}`) - } - - return path.dirname(modulePath.fsPath) -} - -/** - * Gathers and sets launch-config info by inspecting the workspace and creating - * temp files/directories as needed. - * - * Does NOT execute/invoke SAM, docker, etc. - */ -export async function makeGoConfig(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - - config.codeRoot = - config.codeRoot ?? - pathutil.normalize(await getSamProjectDirPathForFile(config?.templatePath ?? config.documentUri!.fsPath)) - - if (!config.codeRoot) { - throw Error('missing launch.json, template.yaml, and failed to discover project root') - } - - const port: number = config.debugPort ?? -1 - - config.codeRoot = pathutil.normalize(config.codeRoot) - - // We want to persist the binary we build since it takes a non-trivial amount of time to build - config.debuggerPath = path.join(globals.context.globalStorageUri.fsPath, 'debuggers', 'delve') - - const isImageLambda = await isImageLambdaConfig(config) - - // Reference: https://github.com/aws/aws-sam-cli/blob/4543732bf3c0da3b57fe1e5aa43ce3f41d2bd0ba/samcli/local/docker/lambda_debug_settings.py#L94-L103 - // These are the default settings. For some reason SAM CLI is not setting them even if the container - // environment file does not exist. SAM CLI will skip the debugging step if these are not set! - if (isImageLambda && !config.noDebug) { - config.containerEnvVars = { - _AWS_LAMBDA_GO_DEBUGGING: '1', - _AWS_LAMBDA_GO_DELVE_API_VERSION: '2', - _AWS_LAMBDA_GO_DELVE_PATH: path.posix.join(goDebuggerPath, 'dlv'), - _AWS_LAMBDA_GO_DELVE_LISTEN_PORT: port.toString(), - } - } - - // Make a go launch-config from the generic config. - const goLaunchConfig: GoDebugConfiguration = { - ...config, // Compose. - type: 'go', - processName: 'godelve', - request: 'attach', - mode: config.noDebug ? undefined : 'remote', - runtimeFamily: RuntimeFamily.Go, - preLaunchTask: undefined, - host: 'localhost', - port: port, - skipFiles: [], - debugArgs: isImageLambda || config.noDebug ? undefined : ['-delveAPI=2'], - debugAdapter: 'legacy', // Just in case the Go extension decides to make Delve DAP the default - } - - return goLaunchConfig -} - -interface InstallScript { - path: string - options?: SpawnOptions -} - -/** - * We want to cross-compile Delve using the version currently installed by the user. Git is used to check for the current - * version by locating the package directory in the user's GOPATH. We append this version to an install script, so we know - * in the future if we already built it. - * - * @param debuggerPath Installation path for the debugger - * @param isWindows Flag for making a windows script - * @returns Path for the debugger install script, undefined if we already built the binary - */ -async function makeInstallScript(debuggerPath: string, isWindows: boolean): Promise { - let script: string = isWindows ? '' : '#!/bin/sh\n' - const delveRepo: string = 'github.com/go-delve/delve' - const scriptExt: string = isWindows ? 'cmd' : 'sh' - const delvePath: string = path.posix.join(debuggerPath, 'dlv') - const installOptions: SpawnOptions = { env: { ...process.env } } - let delveVersion: string = '' - - // Since Go1.16, GO111MODULE is on by default. This causes Go to require the - // existence of a go.mod file or else it gives up. GO111MODULE=off prevents - // Go from trying to find the manifest file and uses GOPATH provided below. - installOptions.env!['GO111MODULE'] = 'off' - - async function getDelveVersion(repo: string, silent: boolean): Promise { - try { - return ( - await ChildProcess.run('git', ['-C', repo, 'describe', '--tags', '--abbrev=0'], { collect: true }) - ).stdout.trim() - } catch (e) { - if (!silent) { - throw e - } - return '' - } - } - - // It's fine if we can't get the latest Delve version, the Toolkit will use the last built one instead - try { - const result = await ChildProcess.run('go', ['env', '-json'], { collect: true }) - const goPath: string = JSON.parse(result.stdout).GOPATH - let repoPath: string = path.join(goPath, 'src', delveRepo) - - if (!getDelveVersion(repoPath, true)) { - getLogger().info( - localize( - 'AWS.sam.debugger.godelve.download', - 'The Delve repo was not found in your GOPATH. Downloading in a temporary directory...' - ) - ) - installOptions.env!['GOPATH'] = debuggerPath - repoPath = path.join(debuggerPath, 'src', delveRepo) - const args = ['get', '-d', `${delveRepo}/cmd/dlv`] - const out = await ChildProcess.run('go', args, { ...(installOptions as any), collect: true }) - getLogger().debug('"go %O": %s', args, out) - } - - delveVersion = await getDelveVersion(repoPath, false) - } catch (e) { - getLogger().debug('Failed to get latest Delve version: %O', e as Error) - } - - delveVersion = delveVersion.replace('v', '-') - const installScriptPath: string = path.join(debuggerPath, `install${delveVersion}.${scriptExt}`) - const alreadyInstalled = await fs.exists(installScriptPath) - - if (alreadyInstalled && delveVersion !== '') { - return undefined - } - - installOptions.env!['GOARCH'] = 'amd64' - installOptions.env!['GOOS'] = 'linux' - - script += `go build -o "${delvePath}" "${delveRepo}/cmd/dlv"\n` - - await fs.writeFile(installScriptPath, script, 'utf8') - await fs.chmod(installScriptPath, 0o755) - - return { path: installScriptPath, options: installOptions } -} - -/** - * Downloads and builds the delve debugger for our container - * - * @param debuggerPath Installation path for the debugger - * @returns False when installation fails - */ -async function installDebugger(debuggerPath: string): Promise { - await fs.mkdir(debuggerPath) - const isWindows: boolean = os.platform() === 'win32' - let installScript: InstallScript | undefined - - try { - installScript = await makeInstallScript(debuggerPath, isWindows) - - if (!installScript) { - return true - } - - const childProcess = new ChildProcess(installScript.path, [], { spawnOptions: installScript.options }) - const install = await childProcess.run({ - onStdout: (text: string) => getLogger().info(`[Delve install script] -> ${text}`), - onStderr: (text: string) => getLogger().error(`[Delve install script] -> ${text}`), - }) - - const code = install.exitCode - if (!(await fs.exists(path.join(debuggerPath, 'dlv')))) { - throw new Error(`Install script did not generate the Delve binary: exit code ${code}`) - } else if (code) { - getLogger().warn(`Install script did not sucessfully run, using old Delve binary...`) - } else { - getLogger().info(`Installed Delve debugger in ${debuggerPath}`) - } - } catch (e) { - if (installScript && (await fs.exists(installScript.path))) { - await fs.delete(installScript.path) // Removes the install script since it failed - } - getLogger().error('Failed to cross-compile Delve debugger: %O', e as Error) - return false - } - - return true -} diff --git a/packages/core/src/shared/sam/debugger/javaSamDebug.ts b/packages/core/src/shared/sam/debugger/javaSamDebug.ts deleted file mode 100644 index 9078c9e2a0..0000000000 --- a/packages/core/src/shared/sam/debugger/javaSamDebug.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Runtime } from '@aws-sdk/client-lambda' -import { getCodeRoot, isImageLambdaConfig } from '../../../lambda/local/debugConfiguration' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import { ExtContext } from '../../extensions' -import { sleep } from '../../utilities/timeoutUtils' -import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' -import { runLambdaFunction, waitForPort } from '../localLambdaRunner' -import { SamLaunchRequestArgs } from './awsSamDebugger' - -export async function makeJavaConfig(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - - config = { - ...config, - request: config.noDebug ? 'launch' : 'attach', - } - - config.codeRoot = (await getCodeRoot(config.workspaceFolder, config))! - - config.type = 'java' - config.runtimeFamily = RuntimeFamily.Java - - if (!config.noDebug) { - config.hostName = '127.0.0.1' - config.port = config.debugPort - if (await isImageLambdaConfig(config)) { - config.containerEnvVars = { - _JAVA_OPTIONS: getJavaOptionsEnvVar(config), - } - } - } - - return config -} - -export async function invokeJavaLambda(ctx: ExtContext, config: SamLaunchRequestArgs): Promise { - config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.JAVA]) - // eslint-disable-next-line @typescript-eslint/unbound-method - config.onWillAttachDebugger = async (port, timeout) => { - await waitForPort(port, timeout, true) - await sleep(1000) - } - return await runLambdaFunction(ctx, config, async () => {}) -} - -function getJavaOptionsEnvVar(config: SamLaunchRequestArgs): string { - switch (config.runtime) { - case 'java11': - // https://github.com/aws/aws-sam-cli/blob/86f88cbd7df365960f7015c5d086b0db7aedd9d5/samcli/local/docker/lambda_debug_settings.py#L53 - return `-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:${config.debugPort} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true` - case 'java17': - case 'java21': - case 'java25' as Runtime: - // https://github.com/aws/aws-sam-cli/blob/90aa5cf11e1c5cbfbe66aea2e2de10d478d48231/samcli/local/docker/lambda_debug_settings.py#L96 - return `-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:${config.debugPort} -XX:MaxHeapSize=2834432k -XX:+UseSerialGC -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Djava.net.preferIPv4Stack=true` - default: - return `-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=${config.debugPort} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true -Xshare:off` - } -} diff --git a/packages/core/src/shared/sam/debugger/pythonSamDebug.ts b/packages/core/src/shared/sam/debugger/pythonSamDebug.ts deleted file mode 100644 index 12f25d7981..0000000000 --- a/packages/core/src/shared/sam/debugger/pythonSamDebug.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Runtime } from '@aws-sdk/client-lambda' -import * as os from 'os' -import * as path from 'path' -import { - isImageLambdaConfig, - PythonDebugConfiguration, - PythonPathMapping, -} from '../../../lambda/local/debugConfiguration' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import globals from '../../extensionGlobals' -import { ExtContext } from '../../extensions' -import { fileExists, readFileAsString } from '../../filesystemUtilities' -import { getLogger } from '../../logger/logger' -import * as pathutil from '../../utilities/pathUtils' -import { getLocalRootVariants } from '../../utilities/pathUtils' -import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' -import { runLambdaFunction } from '../localLambdaRunner' -import { SamLaunchRequestArgs } from './awsSamDebugger' -import fs from '../../fs/fs' - -/** SAM will mount the --debugger-path to /tmp/lambci_debug_files */ -const debugpyWrapperPath = '/tmp/lambci_debug_files/py_debug_wrapper.py' - -// TODO: Fix this! Implement a more robust/flexible solution. This is just a basic minimal proof of concept. -export async function getSamProjectDirPathForFile(filepath: string): Promise { - return path.dirname(filepath) -} - -// Add create debugging manifest/requirements.txt containing debugpy -async function makePythonDebugManifest(params: { - isImageLambda: boolean - samProjectCodeRoot: string - outputDir: string -}): Promise { - let manifestText = '' - const manfestPath = path.join(params.samProjectCodeRoot, 'requirements.txt') - // TODO: figure out how to get debugpy in the container without hacking the user's requirements - const debugManifestPath = params.isImageLambda ? manfestPath : path.join(params.outputDir, 'debug-requirements.txt') - if (await fileExists(manfestPath)) { - manifestText = await readFileAsString(manfestPath) - } - getLogger().debug(`pythonCodeLensProvider.makePythonDebugManifest params: %O`, params) - - // TODO: If another module name includes the string "debugpy", this will be skipped... - if (!manifestText.includes('debugpy')) { - manifestText += `${os.EOL}debugpy>=1.0,<2` - await fs.writeFile(debugManifestPath, manifestText) - - return debugManifestPath - } - // else we don't need to override the manifest. nothing to return -} - -/** - * Gathers and sets launch-config info by inspecting the workspace and creating - * temp files/directories as needed. - * - * Does NOT execute/invoke SAM, docker, etc. - */ -export async function makePythonDebugConfig(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - if (!config.codeRoot) { - // Last-resort attempt to discover the project root (when there is no - // `launch.json` nor `template.yaml`). - config.codeRoot = await getSamProjectDirPathForFile(config?.templatePath ?? config.documentUri!.fsPath) - if (!config.codeRoot) { - // TODO: return error and show it at the caller. - throw Error('missing launch.json, template.yaml, and failed to discover project root') - } - } - config.codeRoot = pathutil.normalize(config.codeRoot) - - let manifestPath: string | undefined - if (!config.noDebug) { - const isImageLambda = await isImageLambdaConfig(config) - - // Mounted in the Docker container as: /tmp/lambci_debug_files - config.debuggerPath = globals.context.asAbsolutePath(path.join('resources', 'debugger')) - // NOTE: SAM CLI splits on each *single* space in `--debug-args`! - // Extra spaces will be passed as spurious "empty" arguments :( - const debugArgs = `${debugpyWrapperPath} --listen 0.0.0.0:${config.debugPort} --wait-for-client --log-to-stderr` - if (isImageLambda) { - const params = getPythonExeAndBootstrap(config.runtime) - config.debugArgs = [`${params.python} ${debugArgs} ${params.bootstrap}`] - } else { - config.debugArgs = [debugArgs] - } - - manifestPath = await makePythonDebugManifest({ - isImageLambda: isImageLambda, - samProjectCodeRoot: config.codeRoot, - outputDir: config.baseBuildDir, - }) - } - - let pathMappings: PythonPathMapping[] - if (config.lambda?.pathMappings !== undefined) { - pathMappings = config.lambda.pathMappings - } else { - pathMappings = getLocalRootVariants(config.codeRoot).map((variant) => { - return { - localRoot: variant, - remoteRoot: '/var/task', - } - }) - } - - // Make debugpy output log information if our loglevel is at 'debug' - if (!config.noDebug && getLogger().logLevelEnabled('debug')) { - config.debugArgs![0] += ' --debug' - } - - return { - ...config, - type: 'python', - request: config.noDebug ? 'launch' : 'attach', - runtimeFamily: RuntimeFamily.Python, - - // - // Python-specific fields. - // - manifestPath: manifestPath, - port: config.debugPort ?? -1, - host: 'localhost', - pathMappings, - // Disable redirectOutput, we collect child process stdout/stderr and - // explicitly write to Debug Console. - redirectOutput: false, - } -} - -/** - * Launches and attaches debugger to a SAM Python project. - */ -export async function invokePythonLambda( - ctx: ExtContext, - config: PythonDebugConfiguration -): Promise { - config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.PYTHON]) - - config.onWillAttachDebugger = undefined - const c = (await runLambdaFunction(ctx, config, async () => {})) as PythonDebugConfiguration - return c -} - -function getPythonExeAndBootstrap(runtime: Runtime) { - // unfortunately new 'Image'-base images did not standardize the paths - // https://github.com/aws/aws-sam-cli/blob/7d5101a8edeb575b6925f9adecf28f47793c403c/samcli/local/docker/lambda_debug_settings.py - switch (runtime) { - case 'python3.9': - case 'python3.10': - case 'python3.11': - case 'python3.12': - case 'python3.13' as Runtime: - case 'python3.14' as Runtime: - return { python: `/var/lang/bin/${runtime}`, bootstrap: '/var/runtime/bootstrap.py' } - default: - throw new Error(`Python SAM debug logic ran for invalid Python runtime: ${runtime}`) - } -} diff --git a/packages/core/src/shared/sam/debugger/typescriptSamDebug.ts b/packages/core/src/shared/sam/debugger/typescriptSamDebug.ts deleted file mode 100644 index e7ebece92e..0000000000 --- a/packages/core/src/shared/sam/debugger/typescriptSamDebug.ts +++ /dev/null @@ -1,224 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { existsSync, PathLike, readFileSync } from 'fs' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import * as vscode from 'vscode' -import { isImageLambdaConfig, NodejsDebugConfiguration } from '../../../lambda/local/debugConfiguration' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import { ChildProcess } from '../../utilities/processUtils' -import * as pathutil from '../../../shared/utilities/pathUtils' -import { ExtContext } from '../../extensions' -import { getLogger } from '../../logger/logger' -import { findParentProjectFile } from '../../utilities/workspaceUtils' -import { DefaultSamLocalInvokeCommand, waitForDebuggerMessages } from '../cli/samCliLocalInvoke' -import { runLambdaFunction, waitForPort } from '../localLambdaRunner' -import { SamLaunchRequestArgs } from './awsSamDebugger' -import { findTypescriptCompiler } from '../../utilities/pathFind' -import fs from '../../fs/fs' - -const tsConfigFile = 'aws-toolkit-tsconfig.json' - -// use project tsconfig.json as initial base - if unable to parse existing config -const tsConfigInitialBaseFile = 'tsconfig.json' - -/** - * Launches and attaches debugger to a SAM Node project. - */ -export async function invokeTypescriptLambda( - ctx: ExtContext, - config: NodejsDebugConfiguration -): Promise { - config.samLocalInvokeCommand = new DefaultSamLocalInvokeCommand([waitForDebuggerMessages.NODEJS]) - // eslint-disable-next-line @typescript-eslint/unbound-method - config.onWillAttachDebugger = waitForPort - - const c = (await runLambdaFunction(ctx, config, async () => {})) as NodejsDebugConfiguration - return c -} - -export async function getSamProjectDirPathForFile(filepath: string): Promise { - const packageJsonPath = await findParentProjectFile(vscode.Uri.parse(filepath), /^package\.json$/) - if (!packageJsonPath) { - throw new Error(`Cannot find package.json for: ${filepath}`) - } - - return path.dirname(packageJsonPath.fsPath) -} - -/** - * Gathers and sets launch-config info by inspecting the workspace and creating - * temp files/directories as needed. - * - * Does NOT execute/invoke SAM, docker, etc. - */ -export async function makeTypescriptConfig(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - if (!config.codeRoot) { - // Last-resort attempt to discover the project root (when there is no - // `launch.json` nor `template.yaml`). - config.codeRoot = pathutil.normalize( - await getSamProjectDirPathForFile(config?.templatePath ?? config.documentUri!.fsPath) - ) - if (!config.codeRoot) { - // TODO: return error and show it at the caller. - throw Error('missing launch.json, template.yaml, and failed to discover project root') - } - } - let localRoot: string | undefined - let remoteRoot: string | undefined - config.codeRoot = pathutil.normalize(config.codeRoot) - - // compile typescript code and convert lambda handler if necessary - await compileTypeScript(config) - - const isImageLambda = await isImageLambdaConfig(config) - - if (isImageLambda && !config.noDebug) { - // Need --inspect to enable debugging. SAM CLI doesn't send env vars for "Image" packagetype. - config.containerEnvVars = { - NODE_OPTIONS: `--inspect=0.0.0.0:${config.debugPort} --max-http-header-size 81920`, - } - } - - // if provided, use the user's mapping instead - if (config.lambda?.pathMappings !== undefined && config.lambda.pathMappings.length > 0) { - const mappings = config.lambda.pathMappings - if (mappings.length !== 1) { - getLogger().warn( - 'This language only supports a single path mapping entry. Taking the first entry in the list.' - ) - } - localRoot = mappings[0].localRoot - remoteRoot = mappings[0].remoteRoot - } - - // Make a node launch-config from the generic config. - const nodejsLaunchConfig: NodejsDebugConfiguration = { - ...config, // Compose. - type: 'node', - request: config.noDebug ? 'launch' : 'attach', - runtimeFamily: RuntimeFamily.NodeJS, - preLaunchTask: undefined, - address: 'localhost', - port: config.debugPort ?? -1, - // in theory, roots should never be undefined for node - localRoot: localRoot ?? config.codeRoot, - remoteRoot: remoteRoot ?? '/var/task', - protocol: 'inspector', - // Stop at first user breakpoint, not the runtime bootstrap file. - stopOnEntry: config.stopOnEntry ?? false, - // See `continueOnAttach` at: https://code.visualstudio.com/docs/nodejs/nodejs-debugging - // Workaround SAM CLI's outdated settings: https://github.com/aws/aws-sam-cli/blob/1adc080b82476288804c41c553c5e2ad86f28298/samcli/local/docker/lambda_debug_settings.py#L165 - // Not needed (but harmless) for "Image" packagetype: we set "NODE_OPTIONS:--inspect=…" above. - continueOnAttach: config.continueOnAttach ?? true, - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - } - - return nodejsLaunchConfig -} - -/** - * Compiles non-template (target=code) debug configs, using a temporary default - * tsconfig.json file. - * - * Assumes that `sam build` was not already performed. - */ -async function compileTypeScript(config: SamLaunchRequestArgs): Promise { - if (!config.baseBuildDir) { - throw Error('invalid state: config.baseBuildDir was not set') - } - if (config.invokeTarget.target !== 'code') { - return - } - - async function findTsOrTsConfig(dir: string, child: boolean): Promise { - const glob = (child ? '*/' : '') + '{*.ts,tsconfig.json}' - const found = await vscode.workspace.findFiles(new vscode.RelativePattern(dir, glob), '**/node_modules/**', 1) - if (found.length === 0) { - return undefined - } - return found[0].fsPath - } - - // Require tsconfig.json or *.ts in the top-level of the source app, to - // indicate a typescript Lambda. #2086 - // Note: we use this tsconfig.json as a base for compiling the target=code - // Lambda app below. If it does not exist, we generate a minimal one. - const isTsApp = (await findTsOrTsConfig(config.codeRoot, false)) !== undefined - if (!isTsApp) { - return - } - - const loadBaseConfig = (tsConfigPath: PathLike) => { - if (!existsSync(tsConfigPath)) { - return undefined - } - - try { - const tsConfig = JSON.parse(readFileSync(tsConfigPath).toString()) - getLogger().info(`Using base TypeScript config: ${tsConfigPath}`) - return tsConfig - } catch (err) { - getLogger().error(`Unable to use TypeScript base: ${tsConfigPath}`) - } - - return undefined - } - - const tsConfigPath = path.join(config.codeRoot, tsConfigFile) - - const tsConfig = - loadBaseConfig(tsConfigPath) ?? loadBaseConfig(path.join(config.codeRoot, tsConfigInitialBaseFile)) ?? {} - - if (tsConfig.compilerOptions === undefined) { - getLogger().info('Creating TypeScript config') - tsConfig.compilerOptions = { - target: 'es6', - module: 'commonjs', - inlineSourceMap: true, - } - } - - const compilerOptions = tsConfig.compilerOptions - - // determine build directory - const tsBuildDir = path.resolve(config.codeRoot, config.sam?.buildDir ?? compilerOptions.outDir ?? '.') - compilerOptions.outDir = tsBuildDir - - // overwrite rootDir, sourceRoot - compilerOptions.rootDir = '.' - compilerOptions.sourceRoot = config.codeRoot - - const typeRoots: string[] = Array.isArray(compilerOptions.typeRoots) ? compilerOptions.typeRoots : [] - typeRoots.push('node_modules/@types') - compilerOptions.typeRoots = [...new Set(typeRoots)] - - const types: string[] = Array.isArray(compilerOptions.types) ? compilerOptions.types : [] - types.push('node') - compilerOptions.types = [...new Set(types)] - - await fs.writeFile(tsConfigPath, JSON.stringify(tsConfig, undefined, 4)) - - // resolve ts lambda handler to point into build directory relative to codeRoot - const tsLambdaHandler = path.relative(config.codeRoot, path.join(tsBuildDir, config.invokeTarget.lambdaHandler)) - config.invokeTarget.lambdaHandler = pathutil.normalizeSeparator(tsLambdaHandler) - getLogger().info(`Resolved compiled lambda handler to ${tsLambdaHandler}`) - - const tsc = await findTypescriptCompiler() - if (!tsc) { - throw new Error('TypeScript compiler "tsc" not found in node_modules/ or the system.') - } - - try { - getLogger().info(`Compiling TypeScript app with: "${tsc}"`) - await new ChildProcess(tsc, ['--project', tsConfigPath]).run() - } catch (error) { - getLogger().error(`TypeScript compile error: ${error}`) - throw Error('Failed to compile TypeScript app') - } -} diff --git a/packages/core/src/shared/sam/deploy.ts b/packages/core/src/shared/sam/deploy.ts deleted file mode 100644 index 387417e82e..0000000000 --- a/packages/core/src/shared/sam/deploy.ts +++ /dev/null @@ -1,260 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { AWSTreeNodeBase } from '../treeview/nodes/awsTreeNodeBase' -import { TreeNode, isTreeNode } from '../treeview/resourceTreeDataProvider' -import globals from '../../shared/extensionGlobals' -import { ToolkitError } from '../../shared/errors' -import { CloudFormationClient } from '../clients/cloudFormation' -import { S3Client } from '../clients/s3' -import { samDeployUrl } from '../constants' -import { getSpawnEnv } from '../env/resolveEnv' -import { CloudFormationTemplateRegistry } from '../fs/templateRegistry' -import { telemetry } from '../telemetry/telemetry' -import { createExitPrompter } from '../ui/common/exitPrompter' -import { createRegionPrompter } from '../ui/common/region' -import { ChildProcess } from '../utilities/processUtils' -import { CancellationError } from '../utilities/timeoutUtils' -import { addTelemetryEnvVar } from './cli/samCliInvokerUtils' -import { validateSamDeployConfig, SamConfig, writeSamconfigGlobal } from './config' -import { BucketSource, createBucketSourcePrompter, createBucketNamePrompter } from '../ui/sam/bucketPrompter' -import { createStackPrompter } from '../ui/sam/stackPrompter' -import { TemplateItem, createTemplatePrompter } from '../ui/sam/templatePrompter' -import { createDeployParamsSourcePrompter, ParamsSource } from '../ui/sam/paramsSourcePrompter' -import { getErrorCode, getProjectRoot, getSamCliPathAndVersion, getSource, updateRecentResponse } from './utils' -import { runInTerminal } from './processTerminal' -import { deployMementoRootKey } from './constants' -import { - TemplateParametersForm, - TemplateParametersWizard, -} from '../../awsService/appBuilder/wizards/templateParametersWizard' -import { getParameters } from '../../lambda/config/parameterUtils' -import { CompositeWizard } from '../wizards/compositeWizard' - -export interface DeployParams { - readonly paramsSource: ParamsSource - readonly template: TemplateItem - readonly templateParameters: any - readonly region: string - readonly stackName: string - readonly bucketSource: BucketSource - readonly bucketName: string - readonly projectRoot: vscode.Uri - - [key: string]: any -} - -export enum SamDeployEntryPoints { - SamTemplateFile, - RegionNodeContextMenu, - AppBuilderNodeButton, - CommandPalette, -} - -function getDeployEntryPoint(arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined) { - if (arg instanceof vscode.Uri) { - return SamDeployEntryPoints.SamTemplateFile - } else if (arg instanceof AWSTreeNodeBase) { - return SamDeployEntryPoints.RegionNodeContextMenu - } else if (isTreeNode(arg)) { - return SamDeployEntryPoints.AppBuilderNodeButton - } else { - return SamDeployEntryPoints.CommandPalette - } -} - -type DeployResult = { - isSuccess: boolean -} - -export class DeployWizard extends CompositeWizard { - registry: CloudFormationTemplateRegistry - state: Partial - arg: any - public constructor( - state: Partial, - registry: CloudFormationTemplateRegistry, - arg?: any, - shouldPromptExit: boolean = true - ) { - super({ initState: state, exitPrompterProvider: shouldPromptExit ? createExitPrompter : undefined }) - this.registry = registry - this.state = state - this.arg = arg - } - - public override async init(): Promise { - this.form.template.bindPrompter(() => createTemplatePrompter(this.registry, deployMementoRootKey, samDeployUrl)) - - this.form.templateParameters.bindPrompter( - async ({ template }) => - this.createWizardPrompter( - TemplateParametersWizard, - template!.uri, - samDeployUrl, - deployMementoRootKey - ), - { - showWhen: async ({ template }) => { - const samTemplateParameters = await getParameters(template!.uri) - return !!samTemplateParameters && samTemplateParameters.size > 0 - }, - } - ) - - this.form.projectRoot.setDefault(({ template }) => getProjectRoot(template)) - - this.form.paramsSource.bindPrompter(async ({ projectRoot }) => - createDeployParamsSourcePrompter(await validateSamDeployConfig(projectRoot)) - ) - this.form.region.bindPrompter(() => createRegionPrompter().transform((r) => r.id), { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - }) - this.form.stackName.bindPrompter( - ({ region }) => createStackPrompter(new CloudFormationClient(region!), deployMementoRootKey, samDeployUrl), - { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - } - ) - this.form.bucketSource.bindPrompter(() => createBucketSourcePrompter(samDeployUrl), { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - }) - this.form.bucketName.bindPrompter( - ({ region }) => createBucketNamePrompter(new S3Client(region!), deployMementoRootKey, samDeployUrl), - { - showWhen: ({ bucketSource }) => bucketSource === BucketSource.UserProvided, - } - ) - - return this - } -} - -export async function getDeployWizard(arg?: any, shouldPromptExit?: boolean): Promise { - let initState: Partial - let templateUri: vscode.Uri - const entryPoint = getDeployEntryPoint(arg) - - switch (entryPoint) { - case SamDeployEntryPoints.SamTemplateFile: - initState = { template: { uri: arg as vscode.Uri, data: {} } as TemplateItem } - break - case SamDeployEntryPoints.RegionNodeContextMenu: - initState = { region: arg.regionCode } - break - case SamDeployEntryPoints.AppBuilderNodeButton: - templateUri = arg.getTreeItem().resourceUri as vscode.Uri - initState = { template: { uri: templateUri, data: {} } as TemplateItem } - break - case SamDeployEntryPoints.CommandPalette: - default: - initState = {} - break - } - - const wizard = new DeployWizard(initState, await globals.templateRegistry, arg, shouldPromptExit) - return wizard -} - -export async function runDeploy(arg: any, wizardParams?: DeployParams): Promise { - return await telemetry.sam_deploy.run(async () => { - const source = getSource(arg) - telemetry.record({ source: source }) - const deployEnv = await getSpawnEnv(process.env, { promptForInvalidCredential: true }) - const params = wizardParams ?? (await (await getDeployWizard(arg)).run()) - if (params === undefined) { - throw new CancellationError('user') - } - - const deployFlags: string[] = ['--no-confirm-changeset'] - const buildFlags: string[] = ['--cached'] - - if (params.paramsSource === ParamsSource.SamConfig) { - const samconfig = await SamConfig.fromProjectRoot(params.projectRoot) - const samconfigRegionInfo = `${await samconfig.getCommandParam('global', 'region')}` - - // When entry point is RegionNode, the params.region should take precedence - deployFlags.push('--region', `${params.region || samconfigRegionInfo}`) - deployFlags.push('--config-file', `${samconfig.location.fsPath}`) - } else { - deployFlags.push('--region', `${params.region}`) - deployFlags.push('--stack-name', `${params.stackName}`) - params.bucketName - ? deployFlags.push('--s3-bucket', `${params.bucketName}`) - : deployFlags.push('--resolve-s3') - deployFlags.push('--capabilities', 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM') - } - - if (params.paramsSource === ParamsSource.SpecifyAndSave) { - deployFlags.push('--save-params') - } - - if (!!params.templateParameters && Object.entries(params.templateParameters).length > 0) { - const templateParameters = new Map(Object.entries(params.templateParameters)) - const paramsToSet: string[] = [] - for (const [key, value] of templateParameters.entries()) { - if (value) { - await updateRecentResponse(deployMementoRootKey, params.template.uri.fsPath, key, value) - paramsToSet.push(`ParameterKey=${key},ParameterValue=${value}`) - } - } - paramsToSet.length > 0 && deployFlags.push('--parameter-overrides', paramsToSet.join(' ')) - } - - await updateRecentResponse(deployMementoRootKey, 'global', 'templatePath', params.template.uri.fsPath) - - try { - const { path: samCliPath } = await getSamCliPathAndVersion() - - // Create a child process to run the SAM build command - const buildProcess = new ChildProcess(samCliPath, ['build', ...buildFlags], { - spawnOptions: await addTelemetryEnvVar({ - cwd: params.projectRoot.fsPath, - env: deployEnv, - }), - }) - - try { - // Run SAM build in Terminal - await runInTerminal(buildProcess, 'build') - } catch (error) { - throw ToolkitError.chain(error, 'Failed to build SAM template', { details: { ...buildFlags } }) - } - - // Create a child process to run the SAM deploy command - const deployProcess = new ChildProcess(samCliPath, ['deploy', ...deployFlags], { - spawnOptions: await addTelemetryEnvVar({ - cwd: params.projectRoot.fsPath, - env: deployEnv, - }), - }) - - // Run SAM deploy in Terminal - const { paramsSource, stackName, region, projectRoot } = params - const shouldWriteDeploySamconfigGlobal = paramsSource !== ParamsSource.SamConfig && !!stackName && !!region - try { - await runInTerminal(deployProcess, 'deploy') - shouldWriteDeploySamconfigGlobal && (await writeSamconfigGlobal(projectRoot, stackName, region)) - } catch (error: any) { - if (error.code === 'NoUpdateExitCode') { - shouldWriteDeploySamconfigGlobal && (await writeSamconfigGlobal(projectRoot, stackName, region)) - } - throw error - } - } catch (error) { - throw ToolkitError.chain(error, 'Failed to deploy SAM template', { - details: { ...deployFlags }, - code: getErrorCode(error), - }) - } - return { - isSuccess: true, - } - }) -} diff --git a/packages/core/src/shared/sam/localLambdaRunner.ts b/packages/core/src/shared/sam/localLambdaRunner.ts deleted file mode 100644 index 447fad81f2..0000000000 --- a/packages/core/src/shared/sam/localLambdaRunner.ts +++ /dev/null @@ -1,610 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as tcpPortUsed from 'tcp-port-used' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import * as pathutil from '../utilities/pathUtils' -import got, { OptionsOfTextResponseBody, RequestError } from 'got' -import { isImageLambdaConfig } from '../../lambda/local/debugConfiguration' -import { getFamily, RuntimeFamily } from '../../lambda/models/samLambdaRuntime' -import { ExtContext } from '../extensions' -import { getLogger } from '../logger/logger' -import { SamTemplateGenerator } from '../templates/sam/samTemplateGenerator' -import { Timeout } from '../utilities/timeoutUtils' -import { tryGetAbsolutePath } from '../utilities/workspaceUtils' -import { SamCliBuildInvocation, SamCliBuildInvocationArguments } from './cli/samCliBuild' -import { SamCliLocalInvokeInvocation, SamCliLocalInvokeInvocationArguments } from './cli/samCliLocalInvoke' -import { SamLaunchRequestArgs } from './debugger/awsSamDebugger' -import { buildSamCliStartApiArguments } from './cli/samCliStartApi' -import { DefaultSamCliProcessInvoker } from './cli/samCliInvoker' -import { APIGatewayProperties } from './debugger/awsSamDebugConfiguration.gen' -import { ChildProcess } from '../utilities/processUtils' -import { SamCliSettings } from './cli/samCliSettings' -import * as CloudFormation from '../cloudformation/cloudformation' -import { sleep } from '../utilities/timeoutUtils' -import { showMessageWithCancel } from '../utilities/messages' -import { ToolkitError, UnknownError } from '../errors' -import { SamCliError } from './cli/samCliInvokerUtils' -import fs from '../fs/fs' -import { getSpawnEnv } from '../env/resolveEnv' -import { asEnvironmentVariables } from '../../auth/credentials/utils' -import { Runtime } from '@aws-sdk/client-lambda' - -const localize = nls.loadMessageBundle() - -// TODO: remove this and all related code. -export interface LambdaLocalInvokeParams { - /** URI of the current editor document. */ - uri: vscode.Uri - handlerName: string - isDebug: boolean - workspaceFolder: vscode.WorkspaceFolder - samTemplate: vscode.Uri - samTemplateResourceName: string | undefined -} - -export interface SAMTemplateEnvironmentVariables { - [resource: string]: { - [k: string]: string | number | boolean - } -} - -function getEnvironmentVariables( - resourceName: string, - env?: { [k: string]: string | number | boolean } -): SAMTemplateEnvironmentVariables { - return env ? { [resourceName]: env } : {} -} - -/** - * Decides the resource name for the generated template.yaml. - */ -function makeResourceName(config: SamLaunchRequestArgs): string { - if (config.invokeTarget.target === 'code') { - // CodeUri may be ".", we need a name. #1685 - const fullPath = tryGetAbsolutePath(config.workspaceFolder, config.invokeTarget.projectRoot) - const logicalId = CloudFormation.makeResourceId(path.parse(fullPath).name) - return logicalId === '' - ? 'resource1' // projectRoot has only non-alphanumeric chars :( - : logicalId - } else { - return config.invokeTarget.logicalId - } -} - -const samLocalPortCheckRetryIntervalMillis: number = 125 -const attachDebuggerRetryDelayMillis: number = 1000 - -export function getInputTemplatePath(config: SamLaunchRequestArgs & { invokeTarget: { target: 'code' } }): string { - const inputTemplatePath: string = path.join(config.baseBuildDir!, 'app___vsctk___template.yaml') - - return pathutil.normalize(inputTemplatePath) -} - -export async function makeInputTemplate( - config: SamLaunchRequestArgs & { invokeTarget: { target: 'code' } } -): Promise { - let newTemplate: SamTemplateGenerator - const inputTemplatePath: string = config.templatePath - const resourceName = makeResourceName(config) - - // code type - generate ephemeral SAM template - newTemplate = new SamTemplateGenerator() - .withFunctionHandler(config.invokeTarget.lambdaHandler) - .withResourceName(resourceName) - .withRuntime(config.lambda!.runtime!) - .withCodeUri(pathutil.normalize(config.invokeTarget.projectRoot)) - - if (config.lambda?.environmentVariables && Object.entries(config.lambda?.environmentVariables).length) { - newTemplate = newTemplate.withEnvironment({ - Variables: config.lambda?.environmentVariables, - }) - } - - // additional overrides - if (config.lambda?.memoryMb) { - newTemplate = newTemplate.withMemorySize(config.lambda?.memoryMb) - } - if (config.lambda?.timeoutSec) { - newTemplate = newTemplate.withTimeout(config.lambda?.timeoutSec) - } - if (config.invokeTarget.architecture) { - newTemplate = newTemplate.withArchitectures([config.invokeTarget.architecture]) - } - - await newTemplate.generate(inputTemplatePath) -} - -async function buildLambdaHandler( - timer: Timeout, - env: NodeJS.ProcessEnv, - config: SamLaunchRequestArgs, - settings: SamCliSettings -): Promise { - const processInvoker = new DefaultSamCliProcessInvoker(settings) - - getLogger().info(localize('AWS.output.building.sam.application', 'Building SAM application...')) - const samBuildOutputFolder = path.join(config.baseBuildDir!, 'output') - - const samCliArgs: SamCliBuildInvocationArguments = { - buildDir: samBuildOutputFolder, - // undefined triggers SAM to use the template's dir as the code root - baseDir: config.invokeTarget.target === 'code' ? config.codeRoot : undefined, - templatePath: config.templatePath!, - invoker: processInvoker, - manifestPath: config.manifestPath, - environmentVariables: env, - useContainer: config.sam?.containerBuild || false, - extraArgs: config.sam?.buildArguments, - skipPullImage: config.sam?.skipNewImageCheck, - parameterOverrides: config.parameterOverrides, - } - if (!config.noDebug) { - // SAM_BUILD_MODE: https://github.com/aws/aws-sam-cli/blame/846dfc3e0a8ed12627d554a4f712790a0ddc8b47/designs/build_debug_artifacts.md#L58 - // Needed at least for dotnet case because omnisharp ignores anything 'optimized' - // and thinks everything is library code; harmless for others. - // TODO: why doesn't this affect JB Toolkit/Rider? - samCliArgs.environmentVariables = { - ...samCliArgs.environmentVariables, - SAM_BUILD_MODE: 'debug', - } - } - - const samBuild = new SamCliBuildInvocation(samCliArgs) - const err = await samBuild - .execute(timer) - .then(() => {}) - .catch((e) => UnknownError.cast(e)) - const failure = err ?? samBuild.failure() - if (failure) { - // TODO(sijaden): ask SAM CLI for a way to map exit codes to error codes - if (typeof failure === 'string') { - throw new ToolkitError(failure, { code: 'BuildFailure' }) - } else { - const msg = `SAM build failed${err instanceof SamCliError ? ': ' + err.message : ''}` - throw ToolkitError.chain(err, msg, { code: 'BuildFailure' }) - } - } - getLogger().info(localize('AWS.output.building.sam.application.complete', 'Build complete.')) - - return path.join(samBuildOutputFolder, 'template.yaml') -} - -async function invokeLambdaHandler( - timer: Timeout, - env: NodeJS.ProcessEnv, - config: SamLaunchRequestArgs, - settings: SamCliSettings -): Promise { - getLogger().info(localize('AWS.output.starting.sam.app.locally', 'Starting SAM application locally')) - getLogger().debug(`localLambdaRunner.invokeLambdaFunction: ${config.name}`) - - const debugPort = !config.noDebug ? config.debugPort?.toString() : undefined - - if (config.invokeTarget.target === 'api') { - // sam local start-api ... - const sam = await settings.getOrDetectSamCli() - if (!sam.path) { - getLogger().warn('SAM CLI not found and not configured') - } else if (sam.autoDetected) { - getLogger().info('SAM CLI not configured, using SAM found at: %O', sam.path) - } - const samCommand = sam.path ? sam.path : 'sam' - const samArgs = await buildSamCliStartApiArguments({ - templatePath: config.templatePath, - dockerNetwork: config.sam?.dockerNetwork, - environmentVariablePath: config.envFile, - environmentVariables: env, - port: config.apiPort?.toString(), - debugPort: debugPort, - debuggerPath: config.debuggerPath, - debugArgs: config.debugArgs, - skipPullImage: config.sam?.skipNewImageCheck, - parameterOverrides: config.parameterOverrides, - containerEnvFile: config.containerEnvFile, - extraArgs: config.sam?.localArguments, - name: config.name, - region: config.region, - }) - - return config - .samLocalInvokeCommand!.invoke({ - options: { - env: env, - }, - command: samCommand, - args: samArgs, - // "sam local start-api" produces "attach" messages similar to "sam local invoke". - waitForCues: true, - timeout: timer, - name: config.name, - }) - .catch((err) => { - const msg = `SAM local start-api failed${err instanceof SamCliError ? ': ' + err.message : ''}` - - throw ToolkitError.chain(err, msg) - }) - } else { - // 'target=code' or 'target=template' - const localInvokeArgs: SamCliLocalInvokeInvocationArguments = { - templateResourceName: makeResourceName(config), - templatePath: config.templatePath, - eventPath: config.eventPayloadFile, - environmentVariablePath: config.envFile, - environmentVariables: env, - invoker: config.samLocalInvokeCommand!, - dockerNetwork: config.sam?.dockerNetwork, - debugPort: debugPort, - debuggerPath: config.debuggerPath, - debugArgs: config.debugArgs, - containerEnvFile: config.containerEnvFile, - extraArgs: config.sam?.localArguments, - skipPullImage: - config.sam?.skipNewImageCheck ?? ((await isImageLambdaConfig(config)) || config.sam?.containerBuild), - parameterOverrides: config.parameterOverrides, - name: config.name, - region: config.region, - runtime: config.lambda?.runtime as Runtime, - } - - // sam local invoke ... - const command = new SamCliLocalInvokeInvocation(localInvokeArgs) - - try { - return await command.execute(timer) - } catch (err) { - const msg = `SAM local invoke failed${err instanceof SamCliError ? ': ' + err.message : ''}` - throw ToolkitError.chain(err, msg) - } finally { - if (config.sam?.buildDir === undefined) { - await fs.delete(config.templatePath) - } - } - } -} - -/** - * Prepares and invokes a lambda function via `sam local (invoke/api)`. - * - * @param ctx - * @param config - * @param onAfterBuild Called after `SamCliBuildInvocation.execute()` - */ -export async function runLambdaFunction( - ctx: ExtContext, - config: SamLaunchRequestArgs, - onAfterBuild: () => Promise -): Promise { - // Switch over to the output channel so the user has feedback that we're getting things ready - ctx.outputChannel.show(true) - if (!config.noDebug) { - const msg = - (config.invokeTarget.target === 'api' ? `API "${config.api?.path}", ` : '') + - `Lambda "${config.handlerName}"` - getLogger().info(localize('AWS.output.sam.local.startDebug', 'Preparing to debug locally: {0}', msg)) - } else { - getLogger().info(localize('AWS.output.sam.local.startRun', 'Preparing to run locally: {0}', config.handlerName)) - } - - const envVars = await getSpawnEnv( - { - ...process.env, - ...(config.awsCredentials ? asEnvironmentVariables(config.awsCredentials) : {}), - ...(config.aws?.region ? { AWS_DEFAULT_REGION: config.aws.region } : {}), - }, - { - // only inject toolkit credential if config credential is not set - injectCredential: config.awsCredentials ? false : true, - } - ) - - const settings = SamCliSettings.instance - const timer = new Timeout(settings.getLocalInvokeTimeout()) - - // TODO: refactor things to not mutate the config - config.templatePath = await buildLambdaHandler(timer, envVars, config, settings) - - await onAfterBuild() - timer.refresh() - - let apiRequest: Promise | undefined - if (config.invokeTarget.target === 'api') { - const payload = - config.eventPayloadFile === undefined - ? undefined - : JSON.parse(await fs.readFileText(config.eventPayloadFile)) - - apiRequest = requestLocalApi(timer, config.api!, config.apiPort!, payload) - } - - // SAM CLI and any API requests are executed in parallel - // A failure from either is a failure for the whole invocation - const [processInvoker] = await Promise.all([ - invokeLambdaHandler(timer, envVars, config, settings), - apiRequest, - ]).catch((err) => { - timer.cancel() - throw err - }) - - if (config.noDebug) { - return config - } - - const terminationListener = vscode.debug.onDidTerminateDebugSession((session) => { - const config = session.configuration as SamLaunchRequestArgs - if (config.invokeTarget?.target === 'api') { - stopApi(processInvoker, config) - } - }) - - async function attach() { - if (config.onWillAttachDebugger) { - getLogger().info(localize('AWS.output.sam.local.waiting', 'Waiting for SAM application to start...')) - await config.onWillAttachDebugger(config.debugPort!, timer) - } - // HACK: remove non-serializable properties before attaching. - // TODO: revisit this :) - // eslint-disable-next-line @typescript-eslint/unbound-method - config.onWillAttachDebugger = undefined - config.samLocalInvokeCommand = undefined - - await attachDebugger({ - debugConfig: config, - retryDelayMillis: attachDebuggerRetryDelayMillis, - }) - } - - try { - await attach() - await showOutputChannel(ctx) - } finally { - vscode.Disposable.from(timer, terminationListener).dispose() - } - - return config -} - -function stopApi(samStartApiProc: ChildProcess, config: vscode.DebugConfiguration) { - if (!samStartApiProc) { - getLogger().error('SAM: unknown debug session: %s', config.name) - return - } - - try { - getLogger().verbose('SAM: sending SIGHUP to sam process: pid %d', samStartApiProc.pid()) - samStartApiProc.stop(true, 'SIGHUP') - } catch (e) { - getLogger().warn('SAM: failed to stop sam process: pid %d: %O', samStartApiProc.pid(), e as Error) - } -} - -/** - * Sends an HTTP request to the local API webserver, which invokes the backing - * Lambda, which will then enter debugging. - */ -async function requestLocalApi( - timeout: Timeout, - api: APIGatewayProperties, - apiPort: number, - payload: any -): Promise { - const retryLimit = 10 // Number of attempts before showing a 'cancel' notification - const retryDelay = 200 - const reqMethod = api?.httpMethod || 'GET' - const qs = (api?.querystring?.startsWith('?') ? '' : '?') + (api?.querystring ?? '') - const uri = `http://127.0.0.1:${apiPort}${api?.path}${qs}` - - let notificationShown = false - - const reqOpts: OptionsOfTextResponseBody = { - json: payload, - timeout: { socket: 1000 }, - headers: api?.headers, - method: reqMethod, - retry: { - // note: `calculateDelay` overrides the default function, so any functionality normally specified in the - // retry options needs to be implemented yourself - calculateDelay: (obj) => { - if (obj.error.response !== undefined) { - getLogger().debug('Local API response: %s : %O', uri, obj.error.response.statusMessage) - } - if (obj.error.code === 'ETIMEDOUT' || obj.error.response?.statusCode === 403 || timeout.completed) { - return 0 - } else if (!notificationShown && obj.attemptCount >= retryLimit) { - notificationShown = true - getLogger().debug('Local API: showing cancel notification') - void showMessageWithCancel('Waiting for local API to start...', timeout) - } - - getLogger().debug(`Local API: retry (${obj.attemptCount}): ${uri}: ${obj.error.message}`) - return retryDelay - }, - }, - // TODO: api?.stageVariables, - } - - getLogger().info(localize('AWS.sam.localApi.request', 'Sending request to local API: {0}', uri)) - - await got(uri, reqOpts).catch((err: RequestError) => { - if (err.code === 'ETIMEDOUT') { - // HACK: request timeout (as opposed to ECONNREFUSED) - // is a hint that debugger is attached, so we can stop requesting now. - getLogger().info('Local API is alive: %s', uri) - return - } - const msg = - err.response?.statusCode === 403 - ? `Local API failed to respond to path: ${api?.path}` - : `Local API failed to respond (${err.code}) at path: ${api?.path}, error: ${err.message}` - - throw ToolkitError.chain(err, msg) - }) -} - -export interface AttachDebuggerContext { - debugConfig: SamLaunchRequestArgs - retryDelayMillis?: number - onStartDebugging?: typeof vscode.debug.startDebugging - onWillRetry?(): Promise -} - -export async function attachDebugger({ - retryDelayMillis = attachDebuggerRetryDelayMillis, - onStartDebugging = vscode.debug.startDebugging, - onWillRetry = async (): Promise => { - getLogger().debug('attachDebugger: retrying...') - await sleep(retryDelayMillis) - }, - ...params -}: AttachDebuggerContext): Promise { - const obj = { - name: params.debugConfig.name, - invokeTarget: params.debugConfig.invokeTarget, - } - getLogger().debug(`localLambdaRunner.attachDebugger: startDebugging with config: %O`, obj) - - getLogger().info(localize('AWS.output.sam.local.attaching', 'Attaching debugger to SAM application...')) - - // The Python extension will silently fail, so it's ok for us to automatically retry - // Users still will not be able to stop debugging without clicking stop a bunch, but - // at least it's not modal popups. - // TODO: figure out why the Python debug client fails to attach on the first try - function maxRetries() { - return params.debugConfig.runtimeFamily === RuntimeFamily.Python ? 8 : 1 - } - - let retries = 0 - while (true) { - const isDebuggerAttached = await onStartDebugging(undefined, params.debugConfig) - if (isDebuggerAttached) { - break - } else if (retries < maxRetries()) { - if (onWillRetry) { - await onWillRetry() - } - retries += 1 - } else { - throw new ToolkitError(localize('AWS.output.sam.local.attach.failed', 'Failed to attach debugger'), { - code: 'DebuggerRetryLimit', - }) - } - } - - getLogger().info(localize('AWS.output.sam.local.attach.success', 'Debugger attached')) - getLogger().verbose( - `SAM: debug session: "${vscode.debug.activeDebugSession?.name}" / ${vscode.debug.activeDebugSession?.id}` - ) -} - -/** - * Waits for a port to be in use. - * - * @param port Port number to wait for - * @param timeout Time to wait - * @param isDebugPort Is this a debugger port or a `sam local start-api` HTTP port? - */ -export async function waitForPort(port: number, timeout: Timeout, isDebugPort: boolean = true): Promise { - const time = timeout.remainingTime - try { - // this function always attempts once no matter the timeoutDuration - await tcpPortUsed.waitUntilUsed(port, samLocalPortCheckRetryIntervalMillis, time) - } catch (err) { - getLogger().warn(`Timeout after ${time} ms: port was not used: ${port}`) - if (isDebugPort) { - getLogger().warn( - localize('AWS.samcli.local.invoke.portUnavailable', 'Failed to use debugger port: {0}', port.toString()) - ) - } else { - getLogger().warn(localize('AWS.apig.portUnavailable', 'Failed to use API port: {0}', port.toString())) - } - } -} - -export function shouldAppendRelativePathToFuncHandler(runtime: Runtime): boolean { - // getFamily will throw an error if the runtime doesn't exist - switch (getFamily(runtime)) { - case RuntimeFamily.NodeJS: - case RuntimeFamily.Python: - return true - case RuntimeFamily.DotNet: - return false - // if the runtime exists but for some reason we forgot to cover it here, throw anyway so we remember to cover it - default: - throw new Error('localLambdaRunner can not determine if runtime requires a relative path.') - } -} - -/** - * Brings the Output Channel in focus. - */ -async function showOutputChannel(ctx: ExtContext): Promise { - try { - ctx.outputChannel.show(true) - } catch (err) { - // in case the vs code command changes or misbehaves, swallow error - getLogger().verbose('Unable to focus output channel: %O', err as Error) - } -} - -// TODO: rework this function. The fact that it is mutating the config file AND writing to disk is bizarre. -/** - * Common logic shared by `makeCsharpConfig`, `makeTypescriptConfig`, `makePythonDebugConfig`. - * - * Rules for environment variables: - * - SAM implicitly ignores envvars present in `env-vars.json` but absent in `input-template.yaml`. - * - Do NOT merge envvars from template.yaml and `lambda.environmentVariables`. - * - For `target=template`: - * 1. Pass envvars from `template.yaml` to the temporary `input-template.yaml` (see `makeInputTemplate()`). - * 2. Pass envvars from `lambda.environmentVariables` to `env-vars.json` (consumed by SAM). - * - For `target=code`: - * 1. Pass envvars from `lambda.environmentVariables` to `input-template.yaml` (see `makeInputTemplate()`). - * 2. Does not use `env-vars.json`. - * - * @param config - */ -export async function makeJsonFiles(config: SamLaunchRequestArgs): Promise { - config.eventPayloadFile = undefined - config.envFile = undefined - - // env-vars.json (NB: effectively ignored for the `target=code` case). - const configEnv = config.lambda?.environmentVariables ?? {} - if (Object.keys(configEnv).length !== 0) { - const env = JSON.stringify(getEnvironmentVariables(makeResourceName(config), configEnv)) - config.envFile = path.join(config.baseBuildDir!, 'env-vars.json') - await fs.writeFile(config.envFile, env) - } - - // container-env-vars.json - if (config.containerEnvVars) { - config.containerEnvFile = path.join(config.baseBuildDir!, 'container-env-vars.json') - const containerEnv = JSON.stringify(config.containerEnvVars) - await fs.writeFile(config.containerEnvFile, containerEnv) - } - - // event.json - const payloadObj = config.lambda?.payload?.json ?? config.api?.payload?.json - const payloadPath = config.lambda?.payload?.path ?? config.api?.payload?.path - - if (Object.keys(payloadObj ?? {}).length === 0 && payloadPath === undefined) { - return - } - - config.eventPayloadFile = path.join(config.baseBuildDir!, 'event.json') - - if (payloadPath) { - const fullpath = tryGetAbsolutePath(config.workspaceFolder, payloadPath) - try { - JSON.parse(await fs.readFileText(fullpath)) - } catch (e) { - throw Error(`Invalid JSON in payload file: ${payloadPath}`) - } - await fs.copy(fullpath, config.eventPayloadFile) - } else { - await fs.writeFile(config.eventPayloadFile, JSON.stringify(payloadObj || {})) - } -} diff --git a/packages/core/src/shared/sam/processTerminal.ts b/packages/core/src/shared/sam/processTerminal.ts deleted file mode 100644 index 977fed9b02..0000000000 --- a/packages/core/src/shared/sam/processTerminal.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' - -import { ToolkitError, UnknownError } from '../errors' -import { ChildProcess, ChildProcessResult } from '../utilities/processUtils' -import { CancellationError } from '../utilities/timeoutUtils' -import { getLogger } from '../logger/logger' -import { isAutomation } from '../vscode/env' -import { throwIfErrorMatches } from './utils' - -let oldTerminal: ProcessTerminal | undefined -export async function runInTerminal(proc: ChildProcess, cmd: string) { - const handleResult = (result?: ChildProcessResult, terminal?: vscode.Terminal) => { - if (result && result.exitCode !== 0) { - throwIfErrorMatches(result, terminal) - - // If no specific error matched, throw the default non-zero exit code error. - const defaultMessage = `sam ${cmd} exited with a non-zero exit code: ${result.exitCode}` - throw ToolkitError.chain(result.error, defaultMessage, { - code: 'NonZeroExitCode', - details: { terminal: terminal }, - }) - } - } - - // The most recent terminal won't get garbage collected until the next run - if (oldTerminal?.stopped === true) { - oldTerminal.close() - } - const pty = (oldTerminal = new ProcessTerminal(proc)) - const terminal = vscode.window.createTerminal({ pty, name: `SAM ${cmd}` }) - terminal.sendText('\n') - terminal.show() - - const result = await new Promise((resolve) => pty.onDidExit(resolve)) - if (pty.cancelled) { - throw result.error !== undefined - ? ToolkitError.chain(result.error, 'SAM CLI was cancelled before exiting', { cancelled: true }) - : new CancellationError('user') - } else { - return handleResult(result, terminal) - } -} - -// This is a decent improvement over using the output channel but it isn't a tty/pty -// SAM CLI uses `click` which has reduced functionality if `os.isatty` returns false -// Historically, Windows lack of a pty-equivalent is why it's not available in libuv -// Maybe it's doable now with the ConPTY API? https://github.com/libuv/libuv/issues/2640 -class ProcessTerminal implements vscode.Pseudoterminal { - private readonly onDidCloseEmitter = new vscode.EventEmitter() - private readonly onDidWriteEmitter = new vscode.EventEmitter() - private readonly onDidExitEmitter = new vscode.EventEmitter() - public readonly onDidWrite = this.onDidWriteEmitter.event - public readonly onDidClose = this.onDidCloseEmitter.event - public readonly onDidExit = this.onDidExitEmitter.event - - public constructor(private readonly process: ChildProcess) { - // Used in integration tests - if (isAutomation()) { - // Disable because it is a test. - // eslint-disable-next-line aws-toolkits/no-console-log - this.onDidWrite((text) => console.log(text.trim())) - } - } - - #cancelled = false - public get cancelled() { - return this.#cancelled - } - - public get stopped() { - return this.process.stopped - } - - public open(initialDimensions: vscode.TerminalDimensions | undefined): void { - this.process - .run({ - onStdout: (text) => this.mapStdio(text), - onStderr: (text) => this.mapStdio(text), - }) - .then((result) => this.onDidExitEmitter.fire(result)) - .catch((err) => - this.onDidExitEmitter.fire({ error: UnknownError.cast(err), exitCode: -1, stderr: '', stdout: '' }) - ) - .finally(() => this.onDidWriteEmitter.fire('\r\nPress any key to close this terminal')) - } - - public close(): void { - this.process.stop() - this.onDidCloseEmitter.fire() - } - - public handleInput(data: string) { - // ETX - if (data === '\u0003' || this.process.stopped) { - this.#cancelled ||= data === '\u0003' - return this.close() - } - - // enter - if (data === '\u000D') { - this.process.send('\n').then(undefined, (e) => { - getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message) - }) - this.onDidWriteEmitter.fire('\r\n') - } else { - this.process.send(data).then(undefined, (e) => { - getLogger().error('ProcessTerminal: process.send() failed: %s', (e as Error).message) - }) - this.onDidWriteEmitter.fire(data) - } - } - - private mapStdio(text: string): void { - const lines = text.split('\n') - const first = lines.shift() - - if (first) { - this.onDidWriteEmitter.fire(first) - } - - for (const line of lines) { - this.onDidWriteEmitter.fire('\r\n') - this.onDidWriteEmitter.fire(line) - } - } -} diff --git a/packages/core/src/shared/sam/sync.ts b/packages/core/src/shared/sam/sync.ts deleted file mode 100644 index 4e3187aec0..0000000000 --- a/packages/core/src/shared/sam/sync.ts +++ /dev/null @@ -1,636 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../extensionGlobals' - -import * as vscode from 'vscode' -import * as path from 'path' -import * as localizedText from '../localizedText' -import { S3Client } from '../clients/s3' -import { DataQuickPickItem, createMultiPick, createQuickPick } from '../ui/pickerPrompter' -import { CloudFormationClient } from '../clients/cloudFormation' -import * as CloudFormation from '../cloudformation/cloudformation' -import { DefaultEcrClient } from '../clients/ecrClient' -import { createRegionPrompter } from '../ui/common/region' -import { CancellationError } from '../utilities/timeoutUtils' -import { ChildProcess } from '../utilities/processUtils' -import { keys, selectFrom } from '../utilities/tsUtils' -import { AWSTreeNodeBase } from '../treeview/nodes/awsTreeNodeBase' -import { ToolkitError } from '../errors' -import { telemetry } from '../telemetry/telemetry' -import { createCommonButtons } from '../ui/buttons' -import { ToolkitPromptSettings } from '../settings' -import { getLogger } from '../logger/logger' -import { createExitPrompter } from '../ui/common/exitPrompter' -import { getConfigFileUri, SamConfig, validateSamSyncConfig, writeSamconfigGlobal } from './config' -import { cast, Optional } from '../utilities/typeConstructors' -import { pushIf, toRecord } from '../utilities/collectionUtils' -import { getParameters } from '../../lambda/config/parameterUtils' -import { addTelemetryEnvVar } from './cli/samCliInvokerUtils' -import { samSyncParamUrl, samSyncUrl, samUpgradeUrl } from '../constants' -import { openUrl } from '../utilities/vsCodeUtils' -import { showOnce } from '../utilities/messages' -import { IamConnection } from '../../auth/connection' -import { CloudFormationTemplateRegistry } from '../fs/templateRegistry' -import { isTreeNode, TreeNode } from '../treeview/resourceTreeDataProvider' -import { getSpawnEnv } from '../env/resolveEnv' -import { - getProjectRoot, - getProjectRootUri, - getRecentResponse, - getSamCliPathAndVersion, - getSource, - getErrorCode, - updateRecentResponse, -} from './utils' -import { TemplateItem, createTemplatePrompter } from '../ui/sam/templatePrompter' -import { createStackPrompter } from '../ui/sam/stackPrompter' -import { ParamsSource, createSyncParamsSourcePrompter } from '../ui/sam/paramsSourcePrompter' -import { createEcrPrompter } from '../ui/sam/ecrPrompter' -import { BucketSource, createBucketNamePrompter, createBucketSourcePrompter } from '../ui/sam/bucketPrompter' -import { runInTerminal } from './processTerminal' -import { syncMementoRootKey } from './constants' -import { - TemplateParametersForm, - TemplateParametersWizard, -} from '../../awsService/appBuilder/wizards/templateParametersWizard' -import { CompositeWizard } from '../wizards/compositeWizard' - -export interface SyncParams { - readonly paramsSource: ParamsSource - readonly region: string - readonly deployType: 'infra' | 'code' - readonly projectRoot: vscode.Uri - readonly template: TemplateItem - readonly templateParameters: any - readonly stackName: string - readonly bucketSource: BucketSource - readonly bucketName: string - readonly ecrRepoUri?: string - readonly connection: IamConnection - readonly skipDependencyLayer?: boolean - readonly syncFlags?: string -} - -// TODO: hook this up so it prompts the user when more than 1 environment is present in `samconfig.toml` -export function createEnvironmentPrompter(config: SamConfig, environments = config.listEnvironments()) { - const recentEnvironmentName = getRecentResponse(syncMementoRootKey, config.location.fsPath, 'environmentName') - const items = environments.map((env) => ({ - label: env.name, - data: env, - recentlyUsed: env.name === recentEnvironmentName, - })) - - return createQuickPick(items, { - title: 'Select an Environment to Use', - placeholder: 'Select an environment', - buttons: createCommonButtons(samSyncUrl), - }) -} - -function hasImageBasedResources(template: CloudFormation.Template) { - const resources = template.Resources - - return resources === undefined - ? false - : Object.keys(resources) - .filter((key) => resources[key]?.Type === 'AWS::Serverless::Function') - .map((key) => resources[key]?.Properties?.PackageType) - .includes('Image') -} - -export const syncFlagItems: DataQuickPickItem[] = [ - { - label: 'Build in source', - data: '--build-in-source', - description: 'Opts in to build project in the source folder. Only for node apps', - }, - { - label: 'Code', - data: '--code', - description: 'Sync only code resources (Lambda Functions, API Gateway, Step Functions)', - }, - { - label: 'Dependency layer', - data: '--dependency-layer', - description: 'Separate dependencies of individual function into Lambda layers', - }, - { - label: 'Skip deploy sync', - data: '--skip-deploy-sync', - description: "This will skip the initial infrastructure deployment if it's not required", - }, - { - label: 'Use container', - data: '--use-container', - description: 'Build functions with an AWS Lambda-like container', - }, - { - label: 'Watch', - data: '--watch', - description: 'Watch local files and automatically sync with cloud', - picked: true, - }, - { - label: 'Save parameters', - data: '--save-params', - description: 'Save to samconfig.toml as default parameters', - picked: true, - }, - { - label: 'Beta features', - data: '--beta-features', - description: 'Enable beta features', - }, - { - label: 'Debug', - data: '--debug', - description: 'Turn on debug logging to print debug messages and display timestamps', - }, -] - -export enum SamSyncEntryPoints { - SamTemplateFile, - SamConfigFile, - RegionNodeContextMenu, - AppBuilderNodeButton, - CommandPalette, -} - -function getSyncEntryPoint(arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined) { - if (arg instanceof vscode.Uri) { - if (arg.path.endsWith('samconfig.toml')) { - return SamSyncEntryPoints.SamConfigFile - } - return SamSyncEntryPoints.SamTemplateFile - } else if (arg instanceof AWSTreeNodeBase) { - return SamSyncEntryPoints.RegionNodeContextMenu - } else if (isTreeNode(arg)) { - return SamSyncEntryPoints.AppBuilderNodeButton - } else { - return SamSyncEntryPoints.CommandPalette - } -} - -export class SyncWizard extends CompositeWizard { - registry: CloudFormationTemplateRegistry - public constructor( - state: Pick & Partial, - registry: CloudFormationTemplateRegistry, - shouldPromptExit: boolean = true - ) { - super({ initState: state, exitPrompterProvider: shouldPromptExit ? createExitPrompter : undefined }) - this.registry = registry - } - - public override async init(): Promise { - this.form.template.bindPrompter(() => createTemplatePrompter(this.registry, syncMementoRootKey, samSyncUrl)) - this.form.templateParameters.bindPrompter( - async ({ template }) => - this.createWizardPrompter( - TemplateParametersWizard, - template!.uri, - samSyncUrl, - syncMementoRootKey - ), - { - showWhen: async ({ template }) => { - const samTemplateParameters = await getParameters(template!.uri) - return !!samTemplateParameters && samTemplateParameters.size > 0 - }, - } - ) - - this.form.projectRoot.setDefault(({ template }) => getProjectRoot(template)) - - this.form.paramsSource.bindPrompter(async ({ projectRoot }) => { - const existValidSamConfig: boolean | undefined = await validateSamSyncConfig(projectRoot) - return createSyncParamsSourcePrompter(existValidSamConfig) - }) - - this.form.region.bindPrompter(() => createRegionPrompter().transform((r) => r.id), { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - }) - - this.form.stackName.bindPrompter( - ({ region }) => createStackPrompter(new CloudFormationClient(region!), syncMementoRootKey, samSyncUrl), - { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - } - ) - this.form.bucketSource.bindPrompter(() => createBucketSourcePrompter(samSyncUrl), { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - }) - - this.form.bucketName.bindPrompter( - ({ region }) => createBucketNamePrompter(new S3Client(region!), syncMementoRootKey, samSyncUrl), - { - showWhen: ({ bucketSource }) => bucketSource === BucketSource.UserProvided, - } - ) - - this.form.ecrRepoUri.bindPrompter( - ({ region }) => createEcrPrompter(new DefaultEcrClient(region!), syncMementoRootKey), - { - showWhen: ({ template, paramsSource }) => - !!template && - hasImageBasedResources(template.data) && - (paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave), - } - ) - - // todo wrap with localize - this.form.syncFlags.bindPrompter( - () => - createMultiPick(syncFlagItems, { - title: 'Specify parameters for sync', - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samSyncParamUrl), - }), - { - showWhen: ({ paramsSource }) => - paramsSource === ParamsSource.Specify || paramsSource === ParamsSource.SpecifyAndSave, - } - ) - return this - } -} - -type BindableData = Record -export function bindDataToParams(data: T, bindings: { [P in keyof T]-?: string }): string[] { - const params = [] as string[] - - for (const [k, v] of Object.entries(data)) { - if (v === true) { - params.push(bindings[k]) - } else if (typeof v === 'string') { - params.push(bindings[k], v) - } - } - - return params -} - -export async function ensureBucket(resp: Pick) { - const newBucketName = resp.bucketName.match(/^newbucket:(.*)/)?.[1] - if (newBucketName === undefined) { - return resp.bucketName - } - - try { - await new S3Client(resp.region).createBucket({ bucketName: newBucketName }) - - return newBucketName - } catch (err) { - throw ToolkitError.chain(err, `Failed to create new bucket "${newBucketName}"`) - } -} - -export async function ensureRepo(resp: Pick) { - const newRepoName = resp.ecrRepoUri?.match(/^newrepo:(.*)/)?.[1] - if (newRepoName === undefined) { - return resp.ecrRepoUri - } - - try { - const repo = await new DefaultEcrClient(resp.region).createRepository(newRepoName) - - return repo.repository?.repositoryUri - } catch (err) { - throw ToolkitError.chain(err, `Failed to create new ECR repository "${newRepoName}"`) - } -} - -export async function saveAndBindArgs(args: SyncParams): Promise<{ readonly boundArgs: string[] }> { - const data = { - codeOnly: args.deployType === 'code', - templatePath: args.template?.uri?.fsPath, - bucketName: args.bucketName && (await ensureBucket(args)), - ecrRepoUri: args.ecrRepoUri && (await ensureRepo(args)), - ...selectFrom(args, 'stackName', 'region', 'skipDependencyLayer'), - } - - await Promise.all([ - updateSyncRecentResponse(args.region, 'stackName', data.stackName), - updateSyncRecentResponse(args.region, 'bucketName', data.bucketName), - updateSyncRecentResponse(args.region, 'ecrRepoUri', data.ecrRepoUri), - updateSyncRecentResponse('global', 'templatePath', data.templatePath), - ]) - - const boundArgs = bindDataToParams(data, { - region: '--region', - codeOnly: '--code', - templatePath: '--template', - stackName: '--stack-name', - bucketName: '--s3-bucket', - ecrRepoUri: '--image-repository', - skipDependencyLayer: '--no-dependency-layer', - }) - - if (args.paramsSource === ParamsSource.SamConfig) { - const samConfigFile = await getConfigFileUri(args.projectRoot) - boundArgs.push('--config-file', `${samConfigFile.fsPath}`) - } - - if (args.paramsSource === ParamsSource.SpecifyAndSave) { - boundArgs.push('--save-params') - } - - return { boundArgs } -} - -export async function runSamSync(args: SyncParams) { - telemetry.record({ lambdaPackageType: args.ecrRepoUri !== undefined ? 'Image' : 'Zip' }) - - const { path: samCliPath, parsedVersion } = await getSamCliPathAndVersion() - const { boundArgs } = await saveAndBindArgs(args) - - if (!!args.templateParameters && Object.entries(args.templateParameters).length > 0) { - const templateParameters = new Map(Object.entries(args.templateParameters)) - const paramsToSet: string[] = [] - for (const [key, value] of templateParameters.entries()) { - if (value) { - await updateRecentResponse(syncMementoRootKey, args.template.uri.fsPath, key, value) - paramsToSet.push(`ParameterKey=${key},ParameterValue=${value}`) - } - } - paramsToSet.length > 0 && boundArgs.push('--parameter-overrides', paramsToSet.join(' ')) - } - - // '--no-watch' was not added until https://github.com/aws/aws-sam-cli/releases/tag/v1.77.0 - // Forcing every user to upgrade will be a headache for what is otherwise a minor problem - if ((parsedVersion?.compare('1.77.0') ?? -1) >= 0) { - boundArgs.push('--no-watch') - } - - if ((parsedVersion?.compare('1.98.0') ?? 1) < 0) { - await showOnce('sam.sync.updateMessage', async () => { - const message = `Your current version of SAM CLI (${parsedVersion?.version}) does not include the latest improvements for "sam sync". Some parameters may not be available. Update to the latest version to get all new parameters/options.` - const learnMoreUrl = vscode.Uri.parse( - 'https://aws.amazon.com/about-aws/whats-new/2023/03/aws-toolkits-jetbrains-vs-code-sam-accelerate/' - ) - const openDocsItem = 'Open Upgrade Documentation' - const resp = await vscode.window.showInformationMessage(message, localizedText.learnMore, openDocsItem) - if (resp === openDocsItem) { - await openUrl(samUpgradeUrl) - } else if (resp === localizedText.learnMore) { - await openUrl(learnMoreUrl) - } - }) - } - - const syncFlags: string[] = args.syncFlags ? JSON.parse(args.syncFlags) : [] - boundArgs.push(...syncFlags) - - const sam = new ChildProcess(samCliPath, ['sync', ...resolveSyncArgConflict(boundArgs)], { - spawnOptions: await addTelemetryEnvVar({ - cwd: args.projectRoot.fsPath, - env: await getSpawnEnv(process.env, { promptForInvalidCredential: true }), - }), - }) - - // with '--watch' selected, the sync process will run in the background until the user manually kills it - // we need to save the stack and region to the samconfig file now, otherwise the user would not see latest deployed resoure during this sync process - const { paramsSource, stackName, region, projectRoot } = args - const shouldWriteSyncSamconfigGlobal = paramsSource !== ParamsSource.SamConfig && !!stackName && !!region - if (boundArgs.includes('--watch')) { - shouldWriteSyncSamconfigGlobal && (await writeSamconfigGlobal(projectRoot, stackName, region)) - } - - await runInTerminal(sam, 'sync') - shouldWriteSyncSamconfigGlobal && (await writeSamconfigGlobal(projectRoot, stackName, region)) -} - -export async function getSyncWizard( - deployType: SyncParams['deployType'], - arg: any, - validate?: boolean, - shouldPromptExit?: boolean -): Promise { - const registry = await globals.templateRegistry - const wizard = new SyncWizard( - { deployType, ...(await prepareSyncParams(arg, validate)) }, - registry, - shouldPromptExit - ) - return wizard -} - -const getStringParam = (config: SamConfig, key: string) => { - try { - return cast(config.getCommandParam('sync', key), Optional(String)) - } catch (err) { - throw ToolkitError.chain(err, `Unable to read "${key}" in config file`, { - details: { location: config.location.path }, - }) - } -} - -const configKeyMapping: Record = { - region: 'region', - stackName: 'stack_name', - bucketName: 's3_bucket', - ecrRepoUri: 'image_repository', - templatePath: ['template', 'template_file'], -} - -export function getSyncParamsFromConfig(config: SamConfig) { - const samConfigParams: string[] = [] - const params = toRecord(keys(configKeyMapping), (k) => { - const key = configKeyMapping[k] - if (typeof key === 'string') { - const param = getStringParam(config, key) - pushIf(samConfigParams, param !== undefined, key) - - return param - } else { - for (const alt of key) { - const param = getStringParam(config, alt) - if (param !== undefined) { - samConfigParams.push(alt) - - return param - } - } - } - }) - - telemetry.record({ samConfigParams: samConfigParams.join(',') } as any) - - return params -} - -export async function prepareSyncParams( - arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined, - validate?: boolean -): Promise> { - // Skip creating dependency layers by default for backwards compat - const baseParams: Partial = { skipDependencyLayer: true } - const entryPoint = getSyncEntryPoint(arg) - - switch (entryPoint) { - case SamSyncEntryPoints.SamTemplateFile: { - const entryPointArg = arg as vscode.Uri - const template = { - uri: entryPointArg, - data: await CloudFormation.load(entryPointArg.fsPath, validate), - } - - return { - ...baseParams, - template: template, - projectRoot: getProjectRootUri(template.uri), - } - } - case SamSyncEntryPoints.SamConfigFile: { - const config = await SamConfig.fromConfigFileUri(arg as vscode.Uri) - const params = getSyncParamsFromConfig(config) - const projectRoot = vscode.Uri.joinPath(config.location, '..') - const templateUri = params.templatePath - ? vscode.Uri.file(path.resolve(projectRoot.fsPath, params.templatePath)) - : undefined - const samConfigFileTemplate = templateUri - ? { - uri: templateUri, - data: await CloudFormation.load(templateUri.fsPath), - } - : undefined - // Always use the dependency layer if the user specified to do so - const skipDependencyLayer = !config.getCommandParam('sync', 'dependency_layer') - - return { - ...baseParams, - ...params, - template: samConfigFileTemplate, - projectRoot, - skipDependencyLayer, - } as SyncParams - } - case SamSyncEntryPoints.RegionNodeContextMenu: { - const entryPointArg = arg as AWSTreeNodeBase - return { ...baseParams, region: entryPointArg.regionCode } - } - case SamSyncEntryPoints.AppBuilderNodeButton: { - const entryPointArg = arg as TreeNode - const templateUri = (entryPointArg.getTreeItem() as vscode.TreeItem).resourceUri - if (templateUri) { - const template = { - uri: templateUri, - data: await CloudFormation.load(templateUri.fsPath, validate), - } - return { - ...baseParams, - template, - projectRoot: getProjectRootUri(templateUri), - } - } - return baseParams - } - case SamSyncEntryPoints.CommandPalette: - default: - return baseParams - } -} - -export type SamSyncResult = { - isSuccess: boolean -} - -export async function runSync( - deployType: SyncParams['deployType'], - arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined, - validate?: boolean, - syncParam?: SyncParams -): Promise { - return await telemetry.sam_sync.run(async () => { - const source = getSource(arg) - telemetry.record({ syncedResources: deployType === 'infra' ? 'AllResources' : 'CodeOnly', source: source }) - - await confirmDevStack() - const params = syncParam ?? (await (await getSyncWizard(deployType, arg, validate)).run()) - getLogger().info('%O', params) - - if (params === undefined) { - throw new CancellationError('user') - } - - try { - await runSamSync({ ...params }) - return { - isSuccess: true, - } - } catch (err) { - throw ToolkitError.chain(err, 'Failed to sync SAM application', { - details: { ...params }, - code: getErrorCode(err), - }) - } - }) -} - -async function updateSyncRecentResponse(region: string, key: string, value: string | undefined) { - return await updateRecentResponse(syncMementoRootKey, region, key, value) -} - -export async function confirmDevStack() { - const canPrompt = ToolkitPromptSettings.instance.isPromptEnabled('samcliConfirmDevStack') - if (!canPrompt) { - return - } - - const message = ` -The SAM CLI will use the AWS Lambda, Amazon API Gateway, and AWS StepFunctions APIs to upload your code without -performing a CloudFormation deployment. This will cause drift in your CloudFormation stack. -**The sync command should only be used against a development stack**. - -Confirm that you are synchronizing a development stack. -`.trim() - - const okDontShow = "OK, and don't show this again" - const resp = await vscode.window.showInformationMessage(message, { modal: true }, localizedText.ok, okDontShow) - if (resp !== localizedText.ok && resp !== okDontShow) { - throw new CancellationError('user') - } - - if (resp === okDontShow) { - await ToolkitPromptSettings.instance.disablePrompt('samcliConfirmDevStack') - } -} - -function resolveSyncArgConflict(boundArgs: string[]): string[] { - const boundArgsSet = new Set(boundArgs) - if (boundArgsSet.has('--watch')) { - boundArgsSet.delete('--no-watch') - } - if (boundArgsSet.has('--dependency-layer')) { - boundArgsSet.delete('--no--dependency-layer') - } - if (boundArgsSet.has('--build-in-source')) { - boundArgsSet.delete('--no-build-in-source') - } - if (boundArgsSet.has('--use-container') || boundArgsSet.has('-u')) { - boundArgsSet.delete('--build-in-source') - } - - // TODO phase 2: add anti param - // // apply anti param if param is not set - // if (!boundArgsSet.has('--cached')) { - // boundArgsSet.add('--no-cached') - // } - // if (!boundArgsSet.has('--build-in-source')) { - // boundArgsSet.add('--no-build-in-source') - // } - // if (!boundArgsSet.has('--dependency-layer')) { - // boundArgsSet.add('--no-dependency-layer') - // } - // if (!boundArgsSet.has('--skip-deploy-sync')) { - // boundArgsSet.add('--no-skip-deploy-sync') - // } - - return Array.from(boundArgsSet) -} diff --git a/packages/core/src/shared/sam/utils.ts b/packages/core/src/shared/sam/utils.ts deleted file mode 100644 index 51e93e8064..0000000000 --- a/packages/core/src/shared/sam/utils.ts +++ /dev/null @@ -1,150 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import { AWSTreeNodeBase } from '../treeview/nodes/awsTreeNodeBase' -import { TreeNode, isTreeNode } from '../treeview/resourceTreeDataProvider' -import * as CloudFormation from '../cloudformation/cloudformation' -import { TemplateItem } from '../ui/sam/templatePrompter' -import { RuntimeFamily, getFamily } from '../../lambda/models/samLambdaRuntime' -import { SamCliSettings } from './cli/samCliSettings' -import { ToolkitError } from '../errors' -import { SamCliInfoInvocation } from './cli/samCliInfo' -import { parse } from 'semver' -import { telemetry } from '../telemetry/telemetry' -import globals from '../extensionGlobals' -import { getLogger } from '../logger/logger' -import { ChildProcessResult } from '../utilities/processUtils' -import { Runtime } from '@aws-sdk/client-lambda' - -/** - * @description determines the root directory of the project given Template Item - * @param templateItem Template item object. - * @returns The URI of the root project folder (may differ from workspace) - * */ -export const getProjectRoot = (template: TemplateItem | undefined) => - template ? getProjectRootUri(template.uri) : undefined - -/** - * @description determines the root directory of the project given uri of the template file - * @param template The template.yaml uri - * @returns The URI of the root project folder (may differ from workspace) - * */ -export const getProjectRootUri = (templateUri: vscode.Uri) => vscode.Uri.file(path.dirname(templateUri.path)) - -export function getSource(arg: vscode.Uri | AWSTreeNodeBase | TreeNode | undefined): string | undefined { - if (arg instanceof vscode.Uri) { - return 'template' - } else if (arg instanceof AWSTreeNodeBase) { - return 'regionNode' - } else if (isTreeNode(arg)) { - return 'appBuilderDeploy' - } else { - return undefined - } -} -/** - * Returns if a corresponding SAM template (received as a URI or string content) has - * any function using .NET runtime - * @param path A uri with the absolute path to the template file - * @param contents The contents of the template file, directly as a string - */ -export async function isDotnetRuntime(templateUri: vscode.Uri, contents?: string): Promise { - const samTemplate = await CloudFormation.tryLoad(templateUri, contents) - - if (!samTemplate.template?.Resources) { - return false - } - for (const resource of Object.values(samTemplate.template.Resources)) { - if (resource?.Type === 'AWS::Serverless::Function') { - if (resource.Properties?.Runtime) { - if (getFamily(resource.Properties?.Runtime) === RuntimeFamily.DotNet) { - return true - } - } - } - } - const globalRuntime = samTemplate.template.Globals?.Function?.Runtime as Runtime - return globalRuntime ? getFamily(globalRuntime) === RuntimeFamily.DotNet : false -} - -export async function getSamCliPathAndVersion() { - const { path: samCliPath } = await SamCliSettings.instance.getOrDetectSamCli() - if (samCliPath === undefined) { - throw new ToolkitError('SAM CLI could not be found', { code: 'MissingExecutable' }) - } - - const info = await new SamCliInfoInvocation(samCliPath).execute() - const parsedVersion = parse(info.version) - telemetry.record({ version: info.version }) - - if (parsedVersion?.compare('1.53.0') === -1) { - throw new ToolkitError('SAM CLI version 1.53.0 or higher is required', { code: 'VersionTooLow' }) - } - - return { path: samCliPath, parsedVersion } -} - -export function getRecentResponse(mementoRootKey: string, identifier: string, key: string): string | undefined { - const root = globals.context.workspaceState.get(mementoRootKey, {} as Record>) - return root[identifier]?.[key] -} - -export async function updateRecentResponse( - mementoRootKey: string, - identifier: string, - key: string, - value: string | undefined -) { - try { - const root = globals.context.workspaceState.get(mementoRootKey, {} as Record>) - await globals.context.workspaceState.update(mementoRootKey, { - ...root, - [identifier]: { ...root[identifier], [key]: value }, - }) - } catch (err) { - getLogger().warn(`sam: unable to save response at key "${key}": %s`, err) - } -} - -export function getSamCliErrorMessage(stderr: string): string { - // Split the stderr string by newline, filter out empty lines, and get the last line - const lines = stderr - .trim() - .split('\n') - .filter((line) => line.trim() !== '') - return lines[lines.length - 1] -} - -export function getErrorCode(error: unknown): string | undefined { - return error instanceof ToolkitError ? error.code : undefined -} - -export function throwIfErrorMatches(result: ChildProcessResult, terminal?: vscode.Terminal) { - const errorMessage = getSamCliErrorMessage(result.stderr) - for (const errorType in SamCliErrorTypes) { - if (errorMessage.includes(SamCliErrorTypes[errorType as keyof typeof SamCliErrorTypes])) { - throw ToolkitError.chain(result.error, errorMessage, { - code: errorType, - details: { terminal: terminal }, - }) - } - } -} - -export function getTerminalFromError(error: any): vscode.Terminal { - return error.details?.['terminal'] as unknown as vscode.Terminal - // return vscode.window.activeTerminal as vscode.Terminal -} - -export enum SamCliErrorTypes { - DockerUnreachable = 'Docker is unreachable.', - ResolveS3AndS3Set = 'Cannot use both --resolve-s3 and --s3-bucket parameters in non-guided deployments.', - DeployStackStatusMissing = 'Was not able to find a stack with the name:', - DeployStackOutPutFailed = 'Failed to get outputs from stack', - DeployBucketRequired = 'Templates with a size greater than 51,200 bytes must be deployed via an S3 Bucket.', - ChangeSetEmpty = 'is up to date', -} diff --git a/packages/core/src/shared/settings.ts b/packages/core/src/shared/settings.ts index 20bce5f21e..50c64b5727 100644 --- a/packages/core/src/shared/settings.ts +++ b/packages/core/src/shared/settings.ts @@ -4,7 +4,6 @@ */ import * as vscode from 'vscode' -import * as codecatalyst from './clients/codecatalystClient' import { getLogger } from './logger/logger' import { cast, @@ -774,7 +773,6 @@ const devSettings = { renderDebugDetails: Boolean, devenvTimeoutMs: Number, endpoints: Record(String, String), - codecatalystService: Record(String, String), codewhispererService: Record(String, String), amazonqLsp: Record(String, String), amazonqWorkspaceLsp: Record(String, String), @@ -790,7 +788,6 @@ type AwsDevSetting = keyof ResolvedDevSettings type ServiceClients = keyof ServiceTypeMap interface ServiceTypeMap { - codecatalystService: codecatalyst.CodeCatalystConfig amazonqLsp: object // type is provided inside of amazon q amazonqWorkspaceLsp: object // type is provided inside of amazon q codewhispererService: CodeWhispererConfig diff --git a/packages/core/src/shared/templates/baseTemplates.ts b/packages/core/src/shared/templates/baseTemplates.ts deleted file mode 100644 index ce4aa1c266..0000000000 --- a/packages/core/src/shared/templates/baseTemplates.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export class BaseTemplates { - public static readonly simpleHtml = ` - - - - - - - - <%= content %> - - ` -} diff --git a/packages/core/src/shared/templates/sam/samTemplateGenerator.ts b/packages/core/src/shared/templates/sam/samTemplateGenerator.ts deleted file mode 100644 index 9c98bd8cea..0000000000 --- a/packages/core/src/shared/templates/sam/samTemplateGenerator.ts +++ /dev/null @@ -1,135 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as yaml from 'js-yaml' -import * as path from 'path' -import { Architecture } from '../../../lambda/models/samLambdaRuntime' -import * as filesystemUtilities from '../../../shared/filesystemUtilities' -import * as CloudFormation from '../../cloudformation/cloudformation' -import ZipResourceProperties = CloudFormation.ZipResourceProperties -import fs from '../../fs/fs' - -export class SamTemplateGenerator { - private resourceName?: string - private templateResources?: CloudFormation.TemplateResources - private readonly properties: Partial = {} - private globals: CloudFormation.TemplateGlobals | undefined - private parameters: - | { - [key: string]: CloudFormation.Parameter | undefined - } - | undefined - - public constructor(private readonly originalTemplate?: CloudFormation.Template) {} - - public withResourceName(resourceName: string): SamTemplateGenerator { - this.resourceName = resourceName - - return this - } - - public withTemplateResources(templateResources: CloudFormation.TemplateResources) { - this.templateResources = templateResources - - return this - } - - public withFunctionHandler(handlerName: string | CloudFormation.Ref): SamTemplateGenerator { - ;(this.properties as ZipResourceProperties).Handler = handlerName - - return this - } - - public withCodeUri(codeUri: string | CloudFormation.Ref): SamTemplateGenerator { - ;(this.properties as ZipResourceProperties).CodeUri = codeUri - - return this - } - - public withRuntime(runtime: string | CloudFormation.Ref): SamTemplateGenerator { - ;(this.properties as ZipResourceProperties).Runtime = runtime - - return this - } - - public withMemorySize(memorySize: number | CloudFormation.Ref): SamTemplateGenerator { - this.properties.MemorySize = memorySize - - return this - } - - public withTimeout(timeout: number | CloudFormation.Ref): SamTemplateGenerator { - this.properties.Timeout = timeout - - return this - } - - public withEnvironment(env: CloudFormation.Environment): SamTemplateGenerator { - this.properties.Environment = env - - return this - } - - public withGlobals(globals: CloudFormation.TemplateGlobals): SamTemplateGenerator { - this.globals = globals - - return this - } - - public withParameters(parameters: { [key: string]: CloudFormation.Parameter | undefined }): SamTemplateGenerator { - this.parameters = parameters - - return this - } - - public withArchitectures(architectures: Architecture[]): SamTemplateGenerator { - this.properties.Architectures = architectures - - return this - } - - public async generate(filename: string): Promise { - if (!this.resourceName && !this.templateResources) { - throw new Error('Missing value: at least one of ResourceName or TemplateResources') - } - - const templateAdditions: CloudFormation.Template = { - Resources: { - ...this.templateResources, - }, - } - - if (this.resourceName) { - templateAdditions.Resources = { - ...templateAdditions.Resources, - [this.resourceName]: { - Type: CloudFormation.SERVERLESS_FUNCTION_TYPE, - Properties: CloudFormation.validateZipLambdaProperties(this.properties as ZipResourceProperties), - }, - } - } - - if (this.globals) { - templateAdditions.Globals = this.globals - } - - if (this.parameters) { - templateAdditions.Parameters = this.parameters - } - - const template: CloudFormation.Template = { - ...this.originalTemplate, - ...templateAdditions, - } - - const templateAsYaml: string = yaml.dump(template, { skipInvalid: true }) - - const parentDirectory: string = path.dirname(filename) - if (!(await filesystemUtilities.fileExists(parentDirectory))) { - await fs.mkdir(parentDirectory) - } - await fs.writeFile(filename, templateAsYaml, 'utf8') - } -} diff --git a/packages/core/src/shared/ui/pickerPrompter.ts b/packages/core/src/shared/ui/pickerPrompter.ts index eaf4a4e6b4..daa65246fe 100644 --- a/packages/core/src/shared/ui/pickerPrompter.ts +++ b/packages/core/src/shared/ui/pickerPrompter.ts @@ -12,8 +12,6 @@ import { Prompter, PromptResult, Transform } from './prompter' import { assign, isAsyncIterable } from '../utilities/collectionUtils' import { recentlyUsed } from '../localizedText' import { getLogger } from '../logger/logger' -import { openUrl } from '../utilities/vsCodeUtils' -import { MetadataManager } from '../../awsService/appBuilder/serverlessLand/metadataManager' const localize = nls.loadMessageBundle() @@ -145,22 +143,6 @@ export function createQuickPick( assign(mergedOptions, picker) picker.buttons = mergedOptions.buttons ?? [] - picker.onDidTriggerItemButton(async (event) => { - const metadataManager = MetadataManager.getInstance() - if (event.button.tooltip !== 'Open in Serverless Land') { - return - } - const selectedPattern = event.item - if (!selectedPattern) { - return - } - const patternUrl = metadataManager.getUrl(selectedPattern.label) - if (!patternUrl) { - return - } - await openUrl(vscode.Uri.parse(patternUrl)) - }) - const prompter = mergedOptions.filterBoxInputSettings !== undefined ? new FilterBoxQuickPickPrompter(picker, mergedOptions) diff --git a/packages/core/src/shared/ui/sam/bucketPrompter.ts b/packages/core/src/shared/ui/sam/bucketPrompter.ts deleted file mode 100644 index 047452ab5b..0000000000 --- a/packages/core/src/shared/ui/sam/bucketPrompter.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { S3Client } from '../../clients/s3' -import { createCommonButtons } from '../buttons' -import { createQuickPick, DataQuickPickItem } from '../pickerPrompter' -import type { SyncParams } from '../../sam/sync' -import * as nls from 'vscode-nls' -import * as vscode from 'vscode' -import { getRecentResponse } from '../../sam/utils' - -const localize = nls.loadMessageBundle() -export const prefixNewBucketName = (name: string) => `newbucket:${name}` - -export enum BucketSource { - SamCliManaged, - UserProvided, -} - -/** - * Creates a quick pick prompter for configuring S3 bucket used for sync or deploy application artifact - * Provides two options: - * 1. Create a SAM CLI managed bucket - * 2. Specify an existing bucket - * @param samCommandUrl URL to the SAM CLI command documentation - * @returns A QuickPick prompter configured with bucket source options - */ -export function createBucketSourcePrompter(samCommandUrl: vscode.Uri) { - const items: DataQuickPickItem[] = [ - { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }, - { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }, - ] - - return createQuickPick(items, { - title: 'Specify S3 bucket for deployment artifacts', - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samCommandUrl), - }) -} - -/** - * Creates a quick pick prompter for configuring S3 bucket name used for sync or deploy application artifact - * The prompter supports choosing from existing s3 bucket name or creating a new one - * @param client S3 client - * @param mementoRootKey Memento key to store recent bucket name (e.g 'samcli.deploy.params') - * @param samCommandUrl URL to the SAM CLI command documentation - * @returns A quick pick prompter configured with bucket name options - */ -export function createBucketNamePrompter(client: S3Client, mementoRootKey: string, samCommandUrl: vscode.Uri) { - const recentBucket = getRecentResponse(mementoRootKey, client.regionCode, 'bucketName') - const items = client.listBucketsIterable().map((b) => [ - { - label: b.Name, - data: b.Name as SyncParams['bucketName'], - recentlyUsed: b.Name === recentBucket, - }, - ]) - - return createQuickPick(items, { - title: 'Select an S3 Bucket', - placeholder: 'Select a bucket (or enter a name to create one)', - buttons: createCommonButtons(samCommandUrl), - filterBoxInputSettings: { - label: 'Create a New Bucket', - // This is basically a hack. I need to refactor `createQuickPick` a bit. - transform: (v) => prefixNewBucketName(v), - }, - noItemsFoundItem: { - label: localize( - 'aws.cfn.noStacks', - 'No S3 buckets for region "{0}". Enter a name to create a new one.', - client.regionCode - ), - data: undefined, - onClick: undefined, - }, - }) -} diff --git a/packages/core/src/shared/ui/sam/ecrPrompter.ts b/packages/core/src/shared/ui/sam/ecrPrompter.ts deleted file mode 100644 index 3c8a642919..0000000000 --- a/packages/core/src/shared/ui/sam/ecrPrompter.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { getAwsConsoleUrl } from '../../awsConsole' -import { DefaultEcrClient } from '../../clients/ecrClient' -import { samSyncUrl } from '../../constants' -import { createCommonButtons } from '../buttons' -import { createQuickPick } from '../pickerPrompter' - -import * as nls from 'vscode-nls' -import { getRecentResponse } from '../../sam/utils' - -export const localize = nls.loadMessageBundle() -export const prefixNewRepoName = (name: string) => `newrepo:${name}` - -/** - * Creates a quick pick prompter for ECR repositories - * The prompter supports choosing from existing option and new repositories by entering a name - * - * @param client ECR client used to list repositories - * @param mementoRootKey Key used to store/retrieve recently used repository (e.g 'samcli.deploy.params') - * @returns A quick pick prompter configured for ECR repository - */ -export function createEcrPrompter(client: DefaultEcrClient, mementoRootKey: string) { - const recentEcrRepo = getRecentResponse(mementoRootKey, client.regionCode, 'ecrRepoUri') - const consoleUrl = getAwsConsoleUrl('ecr', client.regionCode) - const items = client.listAllRepositories().map((list) => - list.map((repo) => ({ - label: repo.repositoryName, - data: repo.repositoryUri, - detail: repo.repositoryArn, - recentlyUsed: repo.repositoryUri === recentEcrRepo, - })) - ) - - return createQuickPick(items, { - title: 'Select an ECR Repository', - placeholder: 'Select a repository (or enter a name to create one)', - buttons: createCommonButtons(samSyncUrl, consoleUrl), - filterBoxInputSettings: { - label: 'Create a New Repository', - transform: (v) => prefixNewRepoName(v), - }, - noItemsFoundItem: { - label: localize( - 'aws.ecr.noRepos', - 'No ECR repositories in region "{0}". Enter a name to create a new one.', - client.regionCode - ), - data: undefined, - onClick: undefined, - }, - }) -} diff --git a/packages/core/src/shared/ui/sam/paramsSourcePrompter.ts b/packages/core/src/shared/ui/sam/paramsSourcePrompter.ts deleted file mode 100644 index 332cdc650f..0000000000 --- a/packages/core/src/shared/ui/sam/paramsSourcePrompter.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { samDeployUrl, samSyncUrl } from '../../constants' -import { createCommonButtons } from '../buttons' -import { DataQuickPickItem, createQuickPick } from '../pickerPrompter' - -export enum ParamsSource { - SpecifyAndSave, - Specify, - SamConfig, -} - -function loadParamsSourcePrompterItems(existValidSamconfig: boolean | undefined) { - const items: DataQuickPickItem[] = [ - { - label: 'Specify required parameters and save as defaults', - data: ParamsSource.SpecifyAndSave, - }, - { - label: 'Specify required parameters', - data: ParamsSource.Specify, - }, - ] - - if (existValidSamconfig) { - items.push({ - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - }) - } - - return items -} - -/** - * Creates a quick pick prompter for SAM deploy parameter source selection - * - * @param existValidSamconfig Whether a valid samconfig.toml file exist and contain necessary flag for SAM sync operation - * @returns A quick pick prompter - */ -export function createDeployParamsSourcePrompter(existValidSamconfig: boolean | undefined) { - const items = loadParamsSourcePrompterItems(existValidSamconfig) - - return createQuickPick(items, { - title: 'Specify parameter source for deploy', - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samDeployUrl), - }) -} - -/** - * Creates a quick pick prompter for SAM sync parameter source selection - * - * @param existValidSamconfig Whether a valid samconfig.toml file exist and contain necessary flag for SAM sync operation - * @returns A quick pick prompter - */ - -export function createSyncParamsSourcePrompter(existValidSamconfig: boolean | undefined) { - const items = loadParamsSourcePrompterItems(existValidSamconfig) - - return createQuickPick(items, { - title: 'Specify parameter source for sync', - placeholder: 'Press enter to proceed with highlighted option', - buttons: createCommonButtons(samSyncUrl), - }) -} diff --git a/packages/core/src/shared/ui/sam/stackPrompter.ts b/packages/core/src/shared/ui/sam/stackPrompter.ts deleted file mode 100644 index 3e86dc0885..0000000000 --- a/packages/core/src/shared/ui/sam/stackPrompter.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { StackSummary } from '@aws-sdk/client-cloudformation' -import { getAwsConsoleUrl } from '../../awsConsole' -import { CloudFormationClient } from '../../clients/cloudFormation' -import * as vscode from 'vscode' -import { createCommonButtons } from '../buttons' -import { createQuickPick } from '../pickerPrompter' -import * as nls from 'vscode-nls' -import { getRecentResponse } from '../../sam/utils' - -export const localize = nls.loadMessageBundle() - -const canPickStack = (s: StackSummary) => s.StackStatus?.endsWith('_COMPLETE') -const canShowStack = (s: StackSummary) => - (s.StackStatus?.endsWith('_COMPLETE') || s.StackStatus?.endsWith('_IN_PROGRESS')) && - !s.StackStatus.includes('DELETE') - -/** - * Creates a quick pick prompter for choosing a CloudFormation stack - * The promper supports selecting from existing options or creating a new stack by entering a name - * - * @param client - CloudFormation client to use for listing stacks - * @param mementoRootKey - Key used to store/retrieve recently used stack (e.g 'samcli.deploy.params') - * @param samCommandUrl - URI for sam command wizard webpage - * @returns A quick pick prompter configured for stack selection - * - */ -export function createStackPrompter(client: CloudFormationClient, mementoRootKey: string, samCommandUrl: vscode.Uri) { - const recentStack = getRecentResponse(mementoRootKey, client.regionCode, 'stackName') - const consoleUrl = getAwsConsoleUrl('cloudformation', client.regionCode) - const items = client.listAllStacks().map((stacks) => - stacks.filter(canShowStack).map((s) => ({ - label: s.StackName, - data: s.StackName, - invalidSelection: !canPickStack(s), - recentlyUsed: s.StackName === recentStack, - description: !canPickStack(s) ? 'stack create/update already in progress' : undefined, - })) - ) - - return createQuickPick(items, { - title: 'Select a CloudFormation Stack', - placeholder: 'Select a stack (or enter a name to create one)', - filterBoxInputSettings: { - label: 'Create a New Stack', - transform: (v) => v, - }, - buttons: createCommonButtons(samCommandUrl, consoleUrl), - noItemsFoundItem: { - label: localize( - 'aws.cfn.noStacks', - 'No stacks in region "{0}". Enter a name to create a new one.', - client.regionCode - ), - data: undefined, - onClick: undefined, - }, - }) -} diff --git a/packages/core/src/shared/ui/sam/templatePrompter.ts b/packages/core/src/shared/ui/sam/templatePrompter.ts deleted file mode 100644 index f51d1feaaf..0000000000 --- a/packages/core/src/shared/ui/sam/templatePrompter.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { getSamInitDocUrl } from '../../extensionUtilities' -import * as CloudFormation from '../../cloudformation/cloudformation' -import { CloudFormationTemplateRegistry } from '../../fs/templateRegistry' -import { createCommonButtons } from '../buttons' -import { createQuickPick } from '../pickerPrompter' -import { openUrl } from '../../utilities/vsCodeUtils' -import * as nls from 'vscode-nls' -import { getRecentResponse } from '../../sam/utils' - -export const localize = nls.loadMessageBundle() - -export interface TemplateItem { - readonly uri: vscode.Uri - readonly data: CloudFormation.Template -} - -/** - * Creates a quick pick prompter for choosing SAM/CloudFormation templates - * - * @param registry - Registry containing CloudFormation templates - * @param mementoRootKey - Root key for storing recent template selections (e.g 'samcli.deploy.params') - * @param samCommandUrl URL to the SAM CLI command documentation - * @param projectRoot - Optional URI of the project root to filter templates - * @returns A QuickPick prompter configured for template selection - * - * The prompter displays a list of SAM/CloudFormation templates found in the workspace. - * Templates are shown with relative paths when possible, and workspace folder names when multiple folders exist. - * Recently used templates are marked. If no templates are found, provides a help link. - */ -export function createTemplatePrompter( - registry: CloudFormationTemplateRegistry, - mementoRootKey: string, - samCommandUrl: vscode.Uri, - projectRoot?: vscode.Uri -) { - const folders = new Set() - const recentTemplatePath = getRecentResponse(mementoRootKey, 'global', 'templatePath') - const filterTemplates = projectRoot - ? registry.items.filter(({ path: filePath }) => !path.relative(projectRoot.fsPath, filePath).startsWith('..')) - : registry.items - - const items = filterTemplates.map(({ item, path: filePath }) => { - const uri = vscode.Uri.file(filePath) - const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri) - const label = workspaceFolder ? path.relative(workspaceFolder.uri.fsPath, uri.fsPath) : uri.fsPath - folders.add(workspaceFolder?.name ?? '') - - return { - label, - data: { uri, data: item }, - description: workspaceFolder?.name, - recentlyUsed: recentTemplatePath === uri.fsPath, - } - }) - - const trimmedItems = folders.size === 1 ? items.map((item) => ({ ...item, description: undefined })) : items - return createQuickPick(trimmedItems, { - title: 'Select a SAM/CloudFormation Template', - placeholder: 'Select a SAM/CloudFormation Template', - buttons: createCommonButtons(samCommandUrl), - noItemsFoundItem: { - label: localize('aws.sam.noWorkspace', 'No SAM template.yaml file(s) found. Select for help'), - data: undefined, - onClick: () => openUrl(getSamInitDocUrl()), - }, - }) -} diff --git a/packages/core/src/shared/utilities/treeNodeUtils.ts b/packages/core/src/shared/utilities/treeNodeUtils.ts deleted file mode 100644 index 0e9525e5eb..0000000000 --- a/packages/core/src/shared/utilities/treeNodeUtils.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DeployedResource } from '../../awsService/appBuilder/explorer/nodes/deployedNode' -import { AWSResourceNode } from '../treeview/nodes/awsResourceNode' -import { isTreeNode, TreeNode } from '../treeview/resourceTreeDataProvider' - -export function getSourceNode(sourceNode: TreeNode | AWSResourceNode): T { - if (isTreeNode(sourceNode)) { - const resource = sourceNode.resource as DeployedResource - return resource.explorerNode as T - } else { - return sourceNode as T - } -} diff --git a/packages/core/src/ssmDocument/activation.ts b/packages/core/src/ssmDocument/activation.ts deleted file mode 100644 index 185051c2c4..0000000000 --- a/packages/core/src/ssmDocument/activation.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { activate as activateSSMLanguageServer } from './ssm/ssmClient' -import { AwsContext } from '../shared/awsContext' - -import { createSsmDocumentFromTemplate } from './commands/createDocumentFromTemplate' -import { publishSSMDocument } from './commands/publishDocument' -import { RegionProvider } from '../shared/regions/regionProvider' -import { openDocumentItem, openDocumentItemJson, openDocumentItemYaml } from './commands/openDocumentItem' -import { DocumentItemNode } from './explorer/documentItemNode' -import { deleteDocument } from './commands/deleteDocument' -import { DocumentItemNodeWriteable } from './explorer/documentItemNodeWriteable' -import { updateDocumentVersion } from './commands/updateDocumentVersion' -import { Commands } from '../shared/vscode/commands2' -import * as constants from '../shared/constants' -import { PerfLog } from '../shared/logger/perfLogger' - -// Activate SSM Document related functionality for the extension. -export async function activate( - extensionContext: vscode.ExtensionContext, - awsContext: AwsContext, - regionProvider: RegionProvider, - outputChannel: vscode.OutputChannel -): Promise { - await registerSsmDocumentCommands(extensionContext, awsContext, regionProvider, outputChannel) - - let onDidOpenSsmDoc: vscode.Disposable - // PERFORMANCE: Start the LSP client/server _only_ when the first SSM document is opened. - // eslint-disable-next-line prefer-const - onDidOpenSsmDoc = vscode.window.onDidChangeActiveTextEditor(async (e) => { - if (e?.document.languageId === constants.ssmJson || e?.document.languageId === constants.ssmYaml) { - const perflog = new PerfLog('ssmDocument: start LSP client/server') - await activateSSMLanguageServer(extensionContext) - perflog.done() - onDidOpenSsmDoc?.dispose() // Handler should only run once. - } - }) - extensionContext.subscriptions.push(onDidOpenSsmDoc) -} - -async function registerSsmDocumentCommands( - extensionContext: vscode.ExtensionContext, - awsContext: AwsContext, - regionProvider: RegionProvider, - outputChannel: vscode.OutputChannel -): Promise { - extensionContext.subscriptions.push( - Commands.register('aws.ssmDocument.createLocalDocument', async () => { - await createSsmDocumentFromTemplate(extensionContext) - }), - Commands.register('aws.ssmDocument.deleteDocument', async (node: DocumentItemNodeWriteable) => { - await deleteDocument(node) - }), - Commands.register('aws.ssmDocument.openLocalDocument', async (node: DocumentItemNode) => { - await openDocumentItem(node, awsContext) - }), - Commands.register('aws.ssmDocument.openLocalDocumentJson', async (node: DocumentItemNode) => { - await openDocumentItemJson(node, awsContext) - }), - Commands.register('aws.ssmDocument.openLocalDocumentYaml', async (node: DocumentItemNode) => { - await openDocumentItemYaml(node, awsContext) - }), - Commands.register('aws.ssmDocument.publishDocument', async () => { - await publishSSMDocument() - }), - Commands.register('aws.ssmDocument.updateDocumentVersion', async (node: DocumentItemNodeWriteable) => { - await updateDocumentVersion(node, awsContext) - }) - ) -} diff --git a/packages/core/src/ssmDocument/commands/createDocumentFromTemplate.ts b/packages/core/src/ssmDocument/commands/createDocumentFromTemplate.ts deleted file mode 100644 index 418e5069c1..0000000000 --- a/packages/core/src/ssmDocument/commands/createDocumentFromTemplate.ts +++ /dev/null @@ -1,146 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as path from 'path' -import * as vscode from 'vscode' -import * as yaml from 'js-yaml' -import { getLogger, Logger } from '../../shared/logger/logger' -import * as picker from '../../shared/ui/picker' -import { promptUserForDocumentFormat } from '../util/util' -import { readFileAsString } from '../../shared/filesystemUtilities' -import { Result, DocumentFormat } from '../../shared/telemetry/telemetry' -import { telemetry } from '../../shared/telemetry/telemetry' - -export interface SsmDocumentTemplateQuickPickItem { - label: string - description: string - filename: string - docType: string -} - -const ssmdocumentTemplates: SsmDocumentTemplateQuickPickItem[] = [ - { - label: localize('AWS.ssmDocument.template.automationHelloWorldPython.label', 'Hello world using Python'), - description: localize( - 'AWS.ssmDocument.template.automationHelloWorldPython.description', - 'An example of an Automation document using "`aws:executeScript`" with a Python script' - ), - filename: 'HelloWorldPython.ssm.yaml', - docType: 'automation', - }, - { - label: localize( - 'AWS.ssmDocument.template.automationHelloWorldPowershell.label', - 'Hello world using Powershell' - ), - description: localize( - 'AWS.ssmDocument.template.automationHelloWorldPowershell.description', - 'An example of an Automation document using "`aws:executeScript`" with a Powershell script' - ), - filename: 'HelloWorldPowershell.ssm.yaml', - docType: 'automation', - }, -] - -export async function createSsmDocumentFromTemplate(extensionContext: vscode.ExtensionContext): Promise { - let result: Result = 'Succeeded' - let format: DocumentFormat | undefined = undefined - let templateName: string | undefined = undefined - const logger: Logger = getLogger() - - const quickPick = picker.createQuickPick({ - options: { - ignoreFocusOut: true, - title: localize('AWS.message.prompt.selectSsmDocumentTemplate.placeholder', 'Select a document template'), - step: 1, - totalSteps: 2, - }, - buttons: [vscode.QuickInputButtons.Back], - items: ssmdocumentTemplates, - }) - - const choices = await picker.promptUser({ - picker: quickPick, - onDidTriggerButton: (_, resolve) => { - resolve(undefined) - }, - }) - - const selection = picker.verifySinglePickerOutput(choices) - - try { - // User pressed escape and didn't select a template - if (selection === undefined) { - result = 'Cancelled' - } else { - logger.debug(`User selected template: ${selection.label}`) - templateName = selection.label - const selectedDocumentFormat = await promptUserForDocumentFormat(['YAML', 'JSON'], { - step: 2, - totalSteps: 2, - }) - format = selectedDocumentFormat ? (selectedDocumentFormat as DocumentFormat) : format - const textDocument: vscode.TextDocument | undefined = await openTextDocumentFromSelection( - selection, - extensionContext.extensionPath, - selectedDocumentFormat - ) - if (textDocument !== undefined) { - await vscode.window.showTextDocument(textDocument) - } else { - result = 'Cancelled' - } - } - } catch (err) { - result = 'Failed' - logger.error(err as Error) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.error.ssmDocument.openTextDocumentFromSelection', - 'There was an error creating the SSM Document from the template, check log for details.' - ) - ) - } finally { - telemetry.ssm_createDocument.emit({ - result: result, - documentFormat: format, - starterTemplate: templateName, - }) - } -} - -function yamlToJson(yamlStr: string): string { - const jsonObject = yaml.load(yamlStr, { schema: yaml.JSON_SCHEMA }) - return JSON.stringify(jsonObject, undefined, 4) -} - -async function openTextDocumentFromSelection( - item: SsmDocumentTemplateQuickPickItem, - extensionPath: string, - selectedDocumentFormat: string | undefined -): Promise { - // By default the template content is YAML, so when the format is not Yaml, we convert to JSON. - // We only support JSON and YAML for ssm documents - const templateYamlContent = await readFileAsString(path.join(extensionPath, 'templates', item.filename)) - - // user pressed escape and didn't select a format - if (selectedDocumentFormat === undefined) { - return - } - - let content: string - let languageId: string - if (selectedDocumentFormat === 'YAML') { - content = templateYamlContent - languageId = 'ssm-yaml' - } else { - content = yamlToJson(templateYamlContent) - languageId = 'ssm-json' - } - return await vscode.workspace.openTextDocument({ content: content, language: languageId }) -} diff --git a/packages/core/src/ssmDocument/commands/deleteDocument.ts b/packages/core/src/ssmDocument/commands/deleteDocument.ts deleted file mode 100644 index 9ff836c485..0000000000 --- a/packages/core/src/ssmDocument/commands/deleteDocument.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { getLogger, Logger } from '../../shared/logger/logger' -import { DocumentItemNodeWriteable } from '../explorer/documentItemNodeWriteable' -import { RegistryItemNode } from '../explorer/registryItemNode' -import { showConfirmationMessage } from '../util/util' -import * as localizedText from '../../shared/localizedText' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Result } from '../../shared/telemetry/telemetry' - -export async function deleteDocument(node: DocumentItemNodeWriteable) { - const logger: Logger = getLogger() - - let result: Result = 'Succeeded' - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.ssmDocument.deleteDocument.prompt', - 'Are you sure you want to delete document {0}?', - node.documentName - ), - confirm: localizedText.localizedDelete, - cancel: localizedText.cancel, - }) - if (!isConfirmed) { - logger.info('DeleteDocument cancelled') - telemetry.ssm_deleteDocument.emit({ result: 'Cancelled' }) - return - } - - try { - const deleteDocumentResult = await node.deleteDocument() - if (!deleteDocumentResult) { - result = 'Failed' - logger.error(`Delete document failed: Empty document name`) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.info.ssmDocument.deleteDocument.failed.empty_document_name', - 'Delete document failed: Empty document name' - ) - ) - } else { - logger.info(`Deleted Systems Manager Document: ${node.documentName}`) - void vscode.window.showInformationMessage( - localize( - 'AWS.message.info.ssmDocument.deleteDocument.delete_success', - 'Deleted document: {0}', - node.documentName - ) - ) - await refreshNode(node.parent) - } - } catch (err) { - result = 'Failed' - const error = err as Error - logger.error('Error on deleting document: %O', error) - void showViewLogsMessage( - localize( - 'AWS.message.error.ssmDocument.deleteDocument.could_not_delete', - 'Could not delete document {0}.', - error.message - ) - ) - } finally { - telemetry.ssm_deleteDocument.emit({ result: result }) - } -} - -async function refreshNode(node: RegistryItemNode): Promise { - return vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) -} diff --git a/packages/core/src/ssmDocument/commands/openDocumentItem.ts b/packages/core/src/ssmDocument/commands/openDocumentItem.ts deleted file mode 100644 index 07832d0b56..0000000000 --- a/packages/core/src/ssmDocument/commands/openDocumentItem.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { DocumentFormat, DocumentVersionInfo } from '@aws-sdk/client-ssm' -import * as vscode from 'vscode' -import { DocumentItemNode } from '../explorer/documentItemNode' -import { AwsContext } from '../../shared/awsContext' -import { getLogger, Logger } from '../../shared/logger/logger' -import * as picker from '../../shared/ui/picker' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Result } from '../../shared/telemetry/telemetry' - -export async function openDocumentItem(node: DocumentItemNode, awsContext: AwsContext, format?: DocumentFormat) { - const logger: Logger = getLogger() - - let result: Result = 'Succeeded' - - let documentVersion: string | undefined = undefined - - if (node.documentOwner === awsContext.getCredentialAccountId()) { - const versions = await node.listSchemaVersion() - if (versions.length > 1) { - documentVersion = await promptUserforDocumentVersion(versions) - if (documentVersion === undefined) { - // user pressed escape and didn't select a version - return - } - } - } - - try { - const rawContent = await node.getDocumentContent(documentVersion, format) - const textDocument = await vscode.workspace.openTextDocument({ - content: rawContent.Content, - language: `ssm-${rawContent.DocumentFormat!.toLowerCase()}`, - }) - await vscode.window.showTextDocument(textDocument) - } catch (err) { - result = 'Failed' - const error = err as Error - logger.error('Error on opening document: %O', error) - void showViewLogsMessage( - localize( - 'AWS.message.error.ssmDocument.openDocument.could_not_open', - 'Could not fetch document: {0}', - node.documentName - ) - ) - } finally { - telemetry.ssm_openDocument.emit({ result: result }) - } -} - -export async function openDocumentItemJson(node: DocumentItemNode, awsContext: AwsContext) { - await openDocumentItem(node, awsContext, 'JSON') -} - -export async function openDocumentItemYaml(node: DocumentItemNode, awsContext: AwsContext) { - await openDocumentItem(node, awsContext, 'YAML') -} - -async function promptUserforDocumentVersion(versions: DocumentVersionInfo[]): Promise { - // Prompt user to pick document version - const quickPickItems: vscode.QuickPickItem[] = [] - for (const version of versions) { - if (version.DocumentVersion) { - quickPickItems.push({ - label: version.DocumentVersion, - description: `${version.IsDefaultVersion ? 'Default' : ''}`, - }) - } - } - - if (quickPickItems.length > 1) { - const versionPick = picker.createQuickPick({ - options: { - ignoreFocusOut: true, - title: localize( - 'AWS.message.prompt.selectSsmDocumentVersion.placeholder', - 'Select a document version to download' - ), - }, - items: quickPickItems, - }) - - const versionChoices = await picker.promptUser({ - picker: versionPick, - }) - - const versionSelection = picker.verifySinglePickerOutput(versionChoices) - - // User pressed escape and didn't select a template - return versionSelection?.label - } -} diff --git a/packages/core/src/ssmDocument/commands/publishDocument.ts b/packages/core/src/ssmDocument/commands/publishDocument.ts deleted file mode 100644 index 36a3145b95..0000000000 --- a/packages/core/src/ssmDocument/commands/publishDocument.ts +++ /dev/null @@ -1,164 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CreateDocumentRequest, UpdateDocumentRequest } from '@aws-sdk/client-ssm' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() -import { DefaultSsmDocumentClient, SsmDocumentClient } from '../../shared/clients/ssmDocumentClient' -import { ssmJson, ssmYaml } from '../../shared/constants' - -import * as localizedText from '../../shared/localizedText' -import { getLogger, Logger } from '../../shared/logger/logger' -import { - PublishSSMDocumentAction, - PublishSSMDocumentWizard, - PublishSSMDocumentWizardResponse, -} from '../wizards/publishDocumentWizard' -import { showConfirmationMessage } from '../util/util' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Result, SsmOperation } from '../../shared/telemetry/telemetry' - -export async function publishSSMDocument(): Promise { - const logger: Logger = getLogger() - - const textDocument = vscode.window.activeTextEditor?.document - if (!textDocument) { - const errorMsg = 'Could not get active text editor for local Systems Manager Document definition' - logger.error(errorMsg) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.error.ssmDocument.publishDocument.could_not_open', - 'Could not get active text editor for local Systems Manager Document definition' - ) - ) - return - } - - if (textDocument.languageId !== ssmJson && textDocument.languageId !== ssmYaml) { - const supportedFormats = [ssmJson, ssmYaml] - const errorMsg = 'Current editor language does not match the supported formats: ' + supportedFormats.join(', ') - logger.error(errorMsg) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.error.ssmDocument.publishDocument.invalid_format', - 'Current editor language does not match the supported formats: {0}', - supportedFormats.join(', ') - ) - ) - return - } - - try { - const response = await new PublishSSMDocumentWizard().run() - if (response?.action === PublishSSMDocumentAction.QuickCreate) { - await createDocument(response, textDocument) - } else if (response?.action === PublishSSMDocumentAction.QuickUpdate) { - await updateDocument(response, textDocument) - } - } catch (err) { - logger.error(err as Error) - } -} - -export async function createDocument( - wizardResponse: PublishSSMDocumentWizardResponse, - textDocument: vscode.TextDocument, - client: SsmDocumentClient = new DefaultSsmDocumentClient(wizardResponse.region) -) { - let result: Result = 'Succeeded' - const ssmOperation = wizardResponse.action as SsmOperation - - const logger: Logger = getLogger() - logger.info(`Creating Systems Manager Document '${wizardResponse.name}'`) - - try { - const request: CreateDocumentRequest = { - Content: textDocument.getText(), - Name: wizardResponse.name, - DocumentType: wizardResponse.documentType, - DocumentFormat: textDocument.languageId === ssmYaml ? 'YAML' : 'JSON', - } - - const createResult = await client.createDocument(request) - logger.info(`Created Systems Manager Document: %O`, createResult.DocumentDescription) - void vscode.window.showInformationMessage(`Created Systems Manager Document: ${wizardResponse.name}`) - } catch (err) { - const error = err as Error - logger.error(`Failed to create Systems Manager Document "${wizardResponse.name}": %s`, error.message) - result = 'Failed' - void vscode.window.showErrorMessage( - `Failed to create Systems Manager Document '${wizardResponse.name}'. \n${error.message}` - ) - } finally { - telemetry.ssm_publishDocument.emit({ result, ssmOperation }) - } -} - -export async function updateDocument( - wizardResponse: PublishSSMDocumentWizardResponse, - textDocument: vscode.TextDocument, - client: SsmDocumentClient = new DefaultSsmDocumentClient(wizardResponse.region) -) { - let result: Result = 'Succeeded' - const ssmOperation = wizardResponse.action as SsmOperation - - const logger: Logger = getLogger() - logger.info(`Updating Systems Manager Document '${wizardResponse.name}'`) - - try { - const request: UpdateDocumentRequest = { - Content: textDocument.getText(), - Name: wizardResponse.name, - DocumentVersion: '$LATEST', - DocumentFormat: textDocument.languageId === ssmYaml ? 'YAML' : 'JSON', - } - - const updateResult = await client.updateDocument(request) - - logger.info(`Updated Systems Manager Document: %O`, updateResult.DocumentDescription) - void vscode.window.showInformationMessage(`Updated Systems Manager Document: ${wizardResponse.name}`) - - const isConfirmed = await showConfirmationMessage({ - prompt: localize( - 'AWS.ssmDocument.publishDocument.updateVersion.prompt', - 'Would you like to make this the default version for {0}?', - wizardResponse.name - ), - confirm: localizedText.yes, - cancel: localizedText.no, - }) - - if (!isConfirmed) { - logger.info('Declined update default version on update document success.') - } else { - try { - const documentVersion: string | undefined = updateResult.DocumentDescription?.DocumentVersion - if (documentVersion !== undefined) { - await client.updateDocumentVersion(wizardResponse.name, documentVersion) - logger.info('Updated Systems Manager Document default version') - void vscode.window.showInformationMessage('Updated Systems Manager Document default version') - } - } catch (err) { - logger.error( - `Failed to update Systems Manager Document default version for "${wizardResponse.name}": %s`, - (err as Error).message - ) - void vscode.window.showErrorMessage( - `Failed to update Systems Manager Document default version for: ${wizardResponse.name}` - ) - } - } - } catch (err) { - const error = err as Error - logger.error(`Failed to update Systems Manager Document '${wizardResponse.name}'. %0`, error) - result = 'Failed' - void vscode.window.showErrorMessage( - `Failed to update Systems Manager Document '${wizardResponse.name}'. \n${error.message}` - ) - } finally { - telemetry.ssm_publishDocument.emit({ result, ssmOperation }) - } -} diff --git a/packages/core/src/ssmDocument/commands/updateDocumentVersion.ts b/packages/core/src/ssmDocument/commands/updateDocumentVersion.ts deleted file mode 100644 index 2574ed2f70..0000000000 --- a/packages/core/src/ssmDocument/commands/updateDocumentVersion.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { DocumentVersionInfo } from '@aws-sdk/client-ssm' -import * as vscode from 'vscode' -import { AwsContext } from '../../shared/awsContext' -import { getLogger, Logger } from '../../shared/logger/logger' -import * as picker from '../../shared/ui/picker' -import { DocumentItemNodeWriteable } from '../explorer/documentItemNodeWriteable' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { telemetry } from '../../shared/telemetry/telemetry' -import { Result } from '../../shared/telemetry/telemetry' - -export async function updateDocumentVersion(node: DocumentItemNodeWriteable, awsContext: AwsContext) { - const logger: Logger = getLogger() - - let result: Result = 'Succeeded' - - try { - if (node.documentOwner === awsContext.getCredentialAccountId()) { - const versions = await node.listSchemaVersion() - const documentVersion: string | undefined = await promptUserforDocumentVersion(versions) - if (!documentVersion) { - result = 'Cancelled' - } else { - const updateDocumentResult = await node.updateDocumentVersion(documentVersion) - if (!updateDocumentResult) { - result = 'Failed' - logger.error(`Update document version failed: empty document version`) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.info.ssmDocument.updateDocumentVersion.failed.emptyversion', - 'ould not update document {0} default version. An empty version was provided.', - node.documentName - ) - ) - } else { - void vscode.window.showInformationMessage( - localize( - 'AWS.message.info.ssmDocument.updateDocumentVersion.success', - 'Updated document {0} default version to {1}', - node.documentName, - documentVersion - ) - ) - } - } - } else { - result = 'Failed' - void vscode.window.showErrorMessage( - localize( - 'AWS.message.error.ssmDocument.updateDocumentVersion.does_not_own', - 'Could not update document {0} default version. The current account does not have ownership of this document.', - node.documentName - ) - ) - } - } catch (err) { - result = 'Failed' - const error = err as Error - void showViewLogsMessage( - localize( - 'AWS.message.error.ssmDocument.updateDocumentVersion.could_not_update_version', - 'Could not update default version for: {0}', - node.documentName - ) - ) - logger.error('Error on updating document version: %O', error) - } finally { - telemetry.ssm_updateDocumentVersion.emit({ result: result }) - } -} - -async function promptUserforDocumentVersion(versions: DocumentVersionInfo[]): Promise { - // Prompt user to pick document version - const quickPickItems: vscode.QuickPickItem[] = [] - for (const version of versions) { - if (version.DocumentVersion) { - quickPickItems.push({ - label: version.DocumentVersion, - description: `${version.IsDefaultVersion ? 'Default' : ''}`, - }) - } - } - - if (quickPickItems.length > 1) { - const versionPick = picker.createQuickPick({ - options: { - ignoreFocusOut: true, - title: localize( - 'AWS.message.prompt.selectSsmDocumentVersion.placeholder', - 'Select a document version to download' - ), - }, - items: quickPickItems, - }) - - const versionChoices = await picker.promptUser({ - picker: versionPick, - }) - - const versionSelection = picker.verifySinglePickerOutput(versionChoices) - - // User pressed escape and didn't select a template - return versionSelection?.label - } else { - void vscode.window.showInformationMessage( - localize( - 'AWS.message.info.ssmDocument.updateDocumentVersion.no_other_versions', - 'Selected document has only one version. Unable to change default version.' - ) - ) - return undefined - } -} diff --git a/packages/core/src/ssmDocument/explorer/documentItemNode.ts b/packages/core/src/ssmDocument/explorer/documentItemNode.ts deleted file mode 100644 index ee17e53d41..0000000000 --- a/packages/core/src/ssmDocument/explorer/documentItemNode.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DocumentFormat, DocumentIdentifier, DocumentVersionInfo, GetDocumentResult } from '@aws-sdk/client-ssm' -import { SsmDocumentClient } from '../../shared/clients/ssmDocumentClient' -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' - -import { toArrayAsync } from '../../shared/utilities/collectionUtils' -import { getIcon } from '../../shared/icons' - -export class DocumentItemNode extends AWSTreeNodeBase { - public constructor( - private documentItem: DocumentIdentifier, - public readonly client: SsmDocumentClient, - public override readonly regionCode: string - ) { - super('') - this.update(documentItem) - this.contextValue = 'awsDocumentItemNode' - this.iconPath = getIcon('vscode-file') - } - - public update(documentItem: DocumentIdentifier): void { - this.documentItem = documentItem - this.label = this.documentName - } - - public get documentName(): string { - return this.documentItem.Name || '' - } - - public get documentOwner(): string { - return this.documentItem.Owner || '' - } - - public async getDocumentContent( - documentVersion?: string, - documentFormat?: DocumentFormat - ): Promise { - if (!this.documentName || !this.documentName.length) { - return Promise.resolve({}) - } - - let resolvedDocumentFormat: DocumentFormat | undefined - - if (documentFormat === undefined) { - // retrieves the document format from the service - const documentDescription = await this.client.describeDocument(this.documentName, documentVersion) - resolvedDocumentFormat = documentDescription.Document?.DocumentFormat - } else { - resolvedDocumentFormat = documentFormat - } - - return await this.client.getDocument( - this.documentName, - documentVersion || this.documentItem.DocumentVersion, - resolvedDocumentFormat - ) - } - - public async listSchemaVersion(): Promise { - return await toArrayAsync(this.client.listDocumentVersions(this.documentName)) - } -} diff --git a/packages/core/src/ssmDocument/explorer/documentItemNodeWriteable.ts b/packages/core/src/ssmDocument/explorer/documentItemNodeWriteable.ts deleted file mode 100644 index 4c2ffd6e81..0000000000 --- a/packages/core/src/ssmDocument/explorer/documentItemNodeWriteable.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DeleteDocumentResult, DocumentIdentifier, UpdateDocumentDefaultVersionResult } from '@aws-sdk/client-ssm' -import { RegistryItemNode } from './registryItemNode' -import { SsmDocumentClient } from '../../shared/clients/ssmDocumentClient' -import { DocumentItemNode } from './documentItemNode' - -export class DocumentItemNodeWriteable extends DocumentItemNode { - public constructor( - documentItem: DocumentIdentifier, - public override readonly client: SsmDocumentClient, - public override readonly regionCode: string, - public readonly parent: RegistryItemNode - ) { - super(documentItem, client, regionCode) - this.contextValue = 'awsDocumentItemNodeWriteable' - this.parent = parent - } - - public async deleteDocument(): Promise { - if (!this.documentName || !this.documentName.length) { - return Promise.resolve({}) - } - - return await this.client.deleteDocument(this.documentName) - } - - public async updateDocumentVersion(documentVersion?: string): Promise { - if (!documentVersion || !documentVersion.length) { - return Promise.resolve({}) - } - return await this.client.updateDocumentVersion(this.documentName, documentVersion) - } -} diff --git a/packages/core/src/ssmDocument/explorer/documentTypeNode.ts b/packages/core/src/ssmDocument/explorer/documentTypeNode.ts deleted file mode 100644 index 811af28756..0000000000 --- a/packages/core/src/ssmDocument/explorer/documentTypeNode.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' - -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../shared/treeview/utils' -import { updateInPlace } from '../../shared/utilities/collectionUtils' -import { amazonRegistryName, RegistryItemNode, sharedRegistryName, userRegistryName } from './registryItemNode' -import { DefaultSsmDocumentClient } from '../../shared/clients/ssmDocumentClient' - -export class DocumentTypeNode extends AWSTreeNodeBase { - private readonly registryNodes: Map - private readonly childRegistryNames = [amazonRegistryName, userRegistryName, sharedRegistryName] - - public constructor( - public override readonly regionCode: string, - public documentType: string, - private readonly client = new DefaultSsmDocumentClient(regionCode) - ) { - super('', vscode.TreeItemCollapsibleState.Collapsed) - - this.setLabel() - - this.registryNodes = new Map() - } - - private setLabel() { - this.label = `${this.documentType}` + ' Documents' - } - - public update(documentType: string): void { - this.documentType = documentType - this.setLabel() - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.registryNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.ssmDocument.noRegistry', '[No Systems Manager Document Registries]') - ), - sort: (nodeA, nodeB) => nodeA.registryName.localeCompare(nodeB.registryName), - }) - } - - public async updateChildren(): Promise { - updateInPlace( - this.registryNodes, - this.childRegistryNames, - (key) => this.registryNodes.get(key)!.update(key), - (key) => new RegistryItemNode(this.regionCode, key, this.documentType, this.client) - ) - } -} diff --git a/packages/core/src/ssmDocument/explorer/registryItemNode.ts b/packages/core/src/ssmDocument/explorer/registryItemNode.ts deleted file mode 100644 index dae0771f67..0000000000 --- a/packages/core/src/ssmDocument/explorer/registryItemNode.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { DocumentIdentifier, ListDocumentsRequest } from '@aws-sdk/client-ssm' -import * as vscode from 'vscode' - -import { DefaultSsmDocumentClient, SsmDocumentClient } from '../../shared/clients/ssmDocumentClient' - -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../shared/treeview/utils' -import { toArrayAsync, updateInPlace } from '../../shared/utilities/collectionUtils' -import { DocumentItemNode } from './documentItemNode' -import { DocumentItemNodeWriteable } from './documentItemNodeWriteable' - -export const amazonRegistryName = localize('AWS.explorerNode.registry.name.amazon', 'Owned by Amazon') -export const userRegistryName = localize('AWS.explorerNode.registry.name.self', 'Owned by me') -export const sharedRegistryName = localize('AWS.explorerNode.registry.name.shared', 'Shared with me') - -export class RegistryItemNode extends AWSTreeNodeBase { - private readonly documentNodes: Map - - public constructor( - public override readonly regionCode: string, - public registryName: string, - readonly documentType: string, - private readonly client = new DefaultSsmDocumentClient(regionCode) - ) { - super('', vscode.TreeItemCollapsibleState.Collapsed) - - this.setLabel() - - this.documentNodes = new Map() - } - - private setLabel() { - this.label = this.registryName - this.iconPath = '' - } - - public update(registryName: string): void { - this.registryName = registryName - this.setLabel() - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.documentNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.documentType.noSsmDocument', `[No documents found]`) - ), - sort: (nodeA, nodeB) => nodeA.documentName.localeCompare(nodeB.documentName), - }) - } - - private async getDocumentByOwner(client: SsmDocumentClient): Promise { - const request: ListDocumentsRequest = { - Filters: [ - { - Key: 'DocumentType', - Values: [this.documentType], - }, - ], - } - - if (this.registryName === userRegistryName) { - request.Filters?.push({ - Key: 'Owner', - Values: ['Self'], - }) - } else if (this.registryName === sharedRegistryName) { - request.Filters?.push({ - Key: 'Owner', - Values: ['Private'], - }) - } else if (this.registryName === amazonRegistryName) { - request.Filters?.push({ - Key: 'Owner', - Values: ['Amazon'], - }) - } - - return toArrayAsync(client.listDocuments(request)) - } - - public async updateChildren(): Promise { - const documents = new Map() - - const docs = await this.getDocumentByOwner(this.client) - for (const doc of docs) { - documents.set(doc.Name!, doc) - } - - if (this.registryName === userRegistryName) { - updateInPlace( - this.documentNodes, - documents.keys(), - (key) => this.documentNodes.get(key)!.update(documents.get(key)!), - (key) => new DocumentItemNodeWriteable(documents.get(key)!, this.client, this.regionCode, this) - ) - } else { - updateInPlace( - this.documentNodes, - documents.keys(), - (key) => this.documentNodes.get(key)!.update(documents.get(key)!), - (key) => new DocumentItemNode(documents.get(key)!, this.client, this.regionCode) - ) - } - } -} diff --git a/packages/core/src/ssmDocument/explorer/ssmDocumentNode.ts b/packages/core/src/ssmDocument/explorer/ssmDocumentNode.ts deleted file mode 100644 index d0560630e5..0000000000 --- a/packages/core/src/ssmDocument/explorer/ssmDocumentNode.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' - -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../shared/treeview/utils' -import { updateInPlace } from '../../shared/utilities/collectionUtils' -import { DocumentTypeNode } from './documentTypeNode' -import { DefaultSsmDocumentClient } from '../../shared/clients/ssmDocumentClient' - -export class SsmDocumentNode extends AWSTreeNodeBase { - private readonly documentTypeNodes: Map - - public constructor( - public override readonly regionCode: string, - private readonly client = new DefaultSsmDocumentClient(regionCode) - ) { - super('Systems Manager', vscode.TreeItemCollapsibleState.Collapsed) - this.documentTypeNodes = new Map() - this.contextValue = 'awsSsmDocumentNode' - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.documentTypeNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.registry.noSsmDocument', `[No documentType found]`) - ), - sort: (nodeA, nodeB) => nodeA.documentType.localeCompare(nodeB.documentType), - }) - } - - public async updateChildren(): Promise { - const documentTypes = ['Automation'] - - updateInPlace( - this.documentTypeNodes, - documentTypes, - (key) => this.documentTypeNodes.get(key)!.update(key), - (key) => new DocumentTypeNode(this.regionCode, key, this.client) - ) - } -} diff --git a/packages/core/src/ssmDocument/ssm/ssmClient.ts b/packages/core/src/ssmDocument/ssm/ssmClient.ts deleted file mode 100644 index 1fce41b2ce..0000000000 --- a/packages/core/src/ssmDocument/ssm/ssmClient.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as nls from 'vscode-nls' - -import { getPortPromise } from 'portfinder' -import { EnvironmentVariables } from '../../shared/environmentVariables' - -const localize = nls.loadMessageBundle() - -import { ExtensionContext, LanguageConfiguration, languages, window, workspace } from 'vscode' - -import { DidChangeConfigurationNotification, LanguageClientOptions, NotificationType } from 'vscode-languageclient' -import { ServerOptions, TransportKind, LanguageClient } from 'vscode-languageclient/node' - -export const ResultLimitReached: NotificationType = new NotificationType('ssm/resultLimitReached') - -const jsonLanguageConfiguration: LanguageConfiguration = { - wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|[^\s{}\[\],:]+/, - indentationRules: { - increaseIndentPattern: /({+(?=([^"]*"[^"]*")*[^"}]*$))|(\[+(?=([^"]*"[^"]*")*[^"\]]*$))/, - decreaseIndentPattern: /^\s*[}\]],?\s*$/, - }, -} - -const yamlLanguageConfiguration: LanguageConfiguration = { - indentationRules: { - increaseIndentPattern: /^\\s*.*(:|-) ?(&\\w+)?(\\{[^}\"']*|\\\([^)\"']*)?$/, - decreaseIndentPattern: /^\\s+\\}$/, - }, -} - -const ssmdocumentLanguageserverDefaultport = 6010 - -async function getLanguageServerDebuggerPort(extensionContext: ExtensionContext): Promise { - // get the port from env variable or use 6010 as default if not set - const env = process.env as EnvironmentVariables - const port = env.SSMDOCUMENT_LANGUAGESERVER_PORT - ? parseInt(env.SSMDOCUMENT_LANGUAGESERVER_PORT as string) - : ssmdocumentLanguageserverDefaultport - - return getPortPromise({ port: port }) -} - -/** - * Starts the SSM Documents LSP client/server and creates related resources (vscode `OutputChannel`). - */ -export async function activate(extensionContext: ExtensionContext) { - const toDispose = extensionContext.subscriptions - - // The server is implemented in node - // This file is copied by webpack from "aws-ssm-document-language-service" dependency at build time - const serverModule = extensionContext.asAbsolutePath(path.join('dist/src/ssmDocument/ssm/', 'ssmServer.js')) - - // The debug options for the server - const debuggerPort = await getLanguageServerDebuggerPort(extensionContext) - // --inspect=${port}: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - const debugOptions = { execArgv: ['--nolazy', `--inspect=${debuggerPort}`] } - - // If the extension is launch in debug mode the debug server options are use - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }, - } - - const documentSelector = [ - { schema: 'file', language: 'ssm-json' }, - { schema: 'untitled', language: 'ssm-json' }, - { schema: 'file', language: 'ssm-yaml' }, - { schema: 'untitled', language: 'ssm-yaml' }, - ] - - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for json documents - documentSelector, - initializationOptions: { - handledSchemaProtocols: ['file', 'untitled'], // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client. - provideFormatter: false, - }, - synchronize: { - fileEvents: workspace.createFileSystemWatcher('**/*.ssm.{json,yml,yaml}'), - }, - middleware: { - workspace: { - didChangeConfiguration: () => - client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings() }), - }, - }, - } - - // Create the language client and start the client. - const client = new LanguageClient( - 'ssm', - localize('ssm.server.name', 'AWS: SSM Document Language Server'), - serverOptions, - clientOptions - ) - client.registerProposedFeatures() - - void client.start() - toDispose.push(client) - - languages.setLanguageConfiguration('ssm-json', jsonLanguageConfiguration) - languages.setLanguageConfiguration('ssm-yaml', yamlLanguageConfiguration) - - return client.start().then(() => { - client.onNotification(ResultLimitReached, (message: any) => { - void window.showInformationMessage( - `${message}\nUse setting 'aws.ssmDocument.ssm.maxItemsComputed' to configure the limit.` - ) - }) - }) -} - -function getSettings() { - const resultLimit: number = - Math.trunc(Math.max(0, Number(workspace.getConfiguration().get('aws.ssmDocument.ssm.maxItemsComputed')))) || - 5000 - - return { - ssm: { - resultLimit, - }, - } -} diff --git a/packages/core/src/ssmDocument/util/util.ts b/packages/core/src/ssmDocument/util/util.ts deleted file mode 100644 index 967cc902e2..0000000000 --- a/packages/core/src/ssmDocument/util/util.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import * as picker from '../../shared/ui/picker' - -export async function promptUserForDocumentFormat( - formats: string[], - params?: { step?: number; totalSteps?: number } -): Promise { - // Prompt user to pick document format - const quickPickItems: vscode.QuickPickItem[] = formats.map((format) => { - return { - label: format, - description: `Download document as ${format}`, - } - }) - - const formatPick = picker.createQuickPick({ - options: { - ignoreFocusOut: true, - title: localize('AWS.message.prompt.selectSsmDocumentFormat.placeholder', 'Select a document format'), - step: params?.step, - totalSteps: params?.totalSteps, - }, - items: quickPickItems, - }) - - const formatChoices = await picker.promptUser({ - picker: formatPick, - }) - - const formatSelection = picker.verifySinglePickerOutput(formatChoices) - - // User pressed escape and didn't select a template - if (formatSelection === undefined) { - return undefined - } - - return formatSelection.label -} - -export async function showConfirmationMessage({ - prompt, - confirm, - cancel, -}: { - prompt: string - confirm: string - cancel: string -}): Promise { - const confirmItem: vscode.MessageItem = { title: confirm } - const cancelItem: vscode.MessageItem = { title: cancel, isCloseAffordance: true } - - const selection = await vscode.window.showWarningMessage(prompt, { modal: true }, confirmItem, cancelItem) - return selection === confirmItem -} diff --git a/packages/core/src/ssmDocument/util/validateDocumentName.ts b/packages/core/src/ssmDocument/util/validateDocumentName.ts deleted file mode 100644 index 8c2d39748a..0000000000 --- a/packages/core/src/ssmDocument/util/validateDocumentName.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { localize } from '../../shared/utilities/vsCodeUtils' - -export function validateDocumentName(name: string): string | undefined { - { - if (!name) { - return localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentName.validation.empty', - 'Document name cannot be empty' - ) - } - - if (name.startsWith('AWS-') || name.startsWith('Amazon')) { - return localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentName.validation.reservedWord', - 'Document name cannot start with Amazon or AWS-' - ) - } - - const docNameRegex: RegExp = /^[\_a-zA-Z0-9\-.]+$/ - if (!docNameRegex.test(name)) { - return localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentName.validation.invalidCharacter', - 'Document name contains invalid characters, only a-z, A-Z, 0-9, and _, -, and . are allowed' - ) - } - - if (name.length < 3 || name.length > 128) { - return localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentName.validation.invalidLength', - 'Document name length must be between 3 and 128 characters' - ) - } - - return undefined - } -} diff --git a/packages/core/src/ssmDocument/wizards/publishDocumentWizard.ts b/packages/core/src/ssmDocument/wizards/publishDocumentWizard.ts deleted file mode 100644 index 763c999593..0000000000 --- a/packages/core/src/ssmDocument/wizards/publishDocumentWizard.ts +++ /dev/null @@ -1,143 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { DocumentKeyValuesFilter } from '@aws-sdk/client-ssm' -import { createCommonButtons } from '../../shared/ui/buttons' -import { createRegionPrompter } from '../../shared/ui/common/region' -import { createInputBox } from '../../shared/ui/inputPrompter' -import { createQuickPick } from '../../shared/ui/pickerPrompter' -import { Wizard, WIZARD_BACK } from '../../shared/wizards/wizard' -import { validateDocumentName } from '../util/validateDocumentName' -import { DefaultSsmDocumentClient } from '../../shared/clients/ssmDocumentClient' - -export interface PublishSSMDocumentWizardResponse { - readonly action: PublishSSMDocumentAction - readonly name: string - readonly documentType: 'Automation' - readonly region: string -} - -export enum PublishSSMDocumentAction { - QuickCreate = 'Create', - QuickUpdate = 'Update', -} - -async function* loadDocuments(region: string, documentType?: string) { - const filters: DocumentKeyValuesFilter[] = [ - { - Key: 'Owner', - Values: ['Self'], - }, - ] - if (documentType !== undefined) { - filters.push({ - Key: 'DocumentType', - Values: [documentType], - }) - } - const client = new DefaultSsmDocumentClient(region) - - for await (const document of client.listDocuments({ Filters: filters })) { - yield [ - { - label: document.Name!, - data: document.Name!, - description: `DocumentType: ${document.DocumentType}, DocumentVersion: ${document.DocumentVersion}`, - }, - ] - } -} - -function createNamePrompter() { - return createInputBox({ - title: localize('AWS.ssmDocument.publishWizard.ssmDocumentName.title', 'Name your document'), - buttons: createCommonButtons(), - validateInput: validateDocumentName, - }) -} - -function createUpdateDocumentPrompter(region: string, documentType?: string) { - return createQuickPick(loadDocuments(region, documentType), { - title: localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentToUpdate.title', - 'Select a document to update ({0})', - region - ), - noItemsFoundItem: { - label: localize( - 'AWS.ssmDocument.publishWizard.ssmDocumentToUpdate.noDocument', - 'No self-owned documents could be found. Please create and upload a Systems Manager Document before updating.' - ), - data: WIZARD_BACK, - }, - buttons: createCommonButtons(), - }) -} - -function createPublishPrompter(region: string) { - const publishItems = [ - { - label: localize('AWS.ssmDocument.publishWizard.publishAction.quickCreate.label', 'Quick Create'), - detail: localize( - 'AWS.ssmDocument.publishWizard.publishAction.quickCreate.detail', - 'Create a Systems Manager Document' - ), - data: PublishSSMDocumentAction.QuickCreate, - }, - { - label: localize('AWS.ssmDocument.publishWizard.publishAction.quickUpdate.label', 'Quick Update'), - detail: localize( - 'AWS.ssmDocument.publishWizard.publishAction.quickUpdate.detail', - 'Update an existing Systems Manager Document' - ), - data: PublishSSMDocumentAction.QuickUpdate, - }, - ] - - return createQuickPick(publishItems, { - title: localize( - 'AWS.ssmDocument.publishWizard.publishAction.title', - 'Publish to AWS Systems Manager Document ({0})', - region - ), - buttons: createCommonButtons(), - }) -} - -export class PublishSSMDocumentWizard extends Wizard { - public constructor(region?: string) { - super({ initState: { region } }) - const form = this.form - - // TODO: add prompt when more types are supported - form.documentType.setDefault('Automation') - - form.region.bindPrompter(() => - createRegionPrompter(undefined, { - title: localize( - 'AWS.message.prompt.ssmDocument.publishDocument.region', - 'Which AWS Region would you like to publish to?' - ), - serviceFilter: 'ssm', - }).transform((r) => r.id) - ) - - form.action.bindPrompter((state) => createPublishPrompter(state.region!)) - - form.name.bindPrompter((state) => { - switch (state.action!) { - case PublishSSMDocumentAction.QuickCreate: - return createNamePrompter() - case PublishSSMDocumentAction.QuickUpdate: - return createUpdateDocumentPrompter(state.region!, state.documentType) - default: - throw new Error(`Invalid publish action: ${state.action}`) - } - }) - } -} diff --git a/packages/core/src/stepFunctions/activation.ts b/packages/core/src/stepFunctions/activation.ts deleted file mode 100644 index ab37cb7a09..0000000000 --- a/packages/core/src/stepFunctions/activation.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import globals from '../shared/extensionGlobals' - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { AwsContext } from '../shared/awsContext' -import { createStateMachineFromTemplate } from './commands/createStateMachineFromTemplate' -import { publishStateMachine } from './commands/publishStateMachine' -import { Commands } from '../shared/vscode/commands2' - -import { ASL_FORMATS, YAML_ASL, JSON_ASL } from './constants/aslFormats' -import { renderCdkStateMachineGraph } from './commands/visualizeStateMachine/renderStateMachineGraphCDK' -import { ToolkitError } from '../shared/errors' -import { telemetry } from '../shared/telemetry/telemetry' -import { PerfLog } from '../shared/logger/perfLogger' -import { ASLLanguageClient } from './asl/client' -import { WorkflowStudioEditorProvider } from './workflowStudio/workflowStudioEditorProvider' -import { StateMachineNode } from './explorer/stepFunctionsNodes' -import { downloadStateMachineDefinition } from './commands/downloadStateMachineDefinition' - -/** - * Activate Step Functions related functionality for the extension. - */ -export async function activate( - extensionContext: vscode.ExtensionContext, - awsContext: AwsContext, - outputChannel: vscode.OutputChannel -): Promise { - await registerStepFunctionCommands(extensionContext, awsContext, outputChannel) - initializeCodeLens(extensionContext) - - let onDidOpenAslDoc: vscode.Disposable // eslint-disable-line prefer-const - // PERFORMANCE: Start the LSP client/server _only_ when the first ASL document is opened. - // eslint-disable-next-line prefer-const - onDidOpenAslDoc = vscode.window.onDidChangeActiveTextEditor(async (e) => { - if (e?.document && ASL_FORMATS.includes(e.document.languageId)) { - onDidOpenAslDoc?.dispose() // Handler should only run once. - await startLspServer(extensionContext) - } - }, undefined) - extensionContext.subscriptions.push(onDidOpenAslDoc) - - if (isASLFileOpen()) { - onDidOpenAslDoc?.dispose() - await startLspServer(extensionContext) - } -} - -export const previewStateMachineCommand = Commands.declare( - 'aws.previewStateMachine', - () => async (arg?: vscode.TextEditor | vscode.Uri | StateMachineNode) => { - await telemetry.run('stepfunctions_previewstatemachine', async () => { - if (arg instanceof StateMachineNode) { - return downloadStateMachineDefinition({ - stateMachineNode: arg, - outputChannel: globals.outputChannel, - isPreviewAndRender: true, - }) - } - - arg ??= vscode.window.activeTextEditor - const input = arg instanceof vscode.Uri ? arg : arg?.document.uri - - if (!input) { - throw new ToolkitError('No active text editor or document found') - } - - await WorkflowStudioEditorProvider.openWithWorkflowStudio(input, { - preserveFocus: true, - viewColumn: vscode.ViewColumn.Beside, - }) - }) - } -) - -async function registerStepFunctionCommands( - extensionContext: vscode.ExtensionContext, - awsContext: AwsContext, - outputChannel: vscode.OutputChannel -): Promise { - extensionContext.subscriptions.push( - previewStateMachineCommand.register(), - renderCdkStateMachineGraph.register(), - Commands.register('aws.stepfunctions.createStateMachineFromTemplate', async () => { - try { - await createStateMachineFromTemplate(extensionContext) - } finally { - telemetry.stepfunctions_createStateMachineFromTemplate.emit() - } - }), - Commands.register('aws.stepfunctions.publishStateMachine', async (node?: any) => { - const region: string | undefined = node?.regionCode - await publishStateMachine({ awsContext: awsContext, outputChannel: outputChannel, region: region }) - }) - ) -} - -async function startLspServer(extensionContext: vscode.ExtensionContext) { - const perflog = new PerfLog('stepFunctions: start LSP client/server') - await ASLLanguageClient.create(extensionContext) - perflog.done() -} - -function isASLFileOpen() { - return vscode.window.visibleTextEditors.some( - (textEditor) => textEditor?.document && ASL_FORMATS.includes(textEditor.document.languageId) - ) -} - -function initializeCodeLens(context: vscode.ExtensionContext) { - class StepFunctionsCodeLensProvider implements vscode.CodeLensProvider { - public async provideCodeLenses(document: vscode.TextDocument): Promise { - const topOfDocument = new vscode.Range(0, 0, 0, 0) - - const openCustomEditor = previewStateMachineCommand.build(document.uri).asCodeLens(topOfDocument, { - title: localize('AWS.command.stepFunctions.openWithWorkflowStudio', 'Open with Workflow Studio'), - }) - - if (ASL_FORMATS.includes(document.languageId)) { - const publishCommand: vscode.Command = { - command: 'aws.stepfunctions.publishStateMachine', - title: localize('AWS.stepFunctions.publish', 'Publish to Step Functions'), - } - const publishCodeLens = new vscode.CodeLens(topOfDocument, publishCommand) - - return [publishCodeLens, openCustomEditor] - } else { - return [openCustomEditor] - } - } - } - - const docSelector = [{ language: JSON_ASL }, { language: YAML_ASL }] - - const codeLensProviderDisposable = vscode.languages.registerCodeLensProvider( - docSelector, - new StepFunctionsCodeLensProvider() - ) - - context.subscriptions.push(codeLensProviderDisposable) -} diff --git a/packages/core/src/stepFunctions/asl/aslServer.ts b/packages/core/src/stepFunctions/asl/aslServer.ts deleted file mode 100644 index 7a55cfa5ed..0000000000 --- a/packages/core/src/stepFunctions/asl/aslServer.ts +++ /dev/null @@ -1,549 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/*! - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -// Disable because this is a language server. -/* eslint-disable aws-toolkits/no-console-log */ - -import { - ClientCapabilities as aslClientCapabilities, - DocumentLanguageSettings, - getLanguageService as getAslJsonLanguageService, - getYamlLanguageService as getAslYamlLanguageService, - JSONDocument, - LanguageService, - TextDocument, -} from 'amazon-states-language-service' -import { - createConnection, - Diagnostic, - Disposable, - DocumentRangeFormattingRequest, - Connection, - InitializeParams, - InitializeResult, - NotificationType, - RequestType, - ServerCapabilities, - TextDocuments, - TextDocumentSyncKind, - DidChangeWatchedFilesParams, -} from 'vscode-languageserver' - -import { posix } from 'path' -import * as URL from 'url' -import { getLanguageModelCache } from '../../shared/lsp/languageModelCache' -import { formatError, runSafe, runSafeAsync } from '../../shared/lsp/utils/runner' -import { YAML_ASL, JSON_ASL } from '../constants/aslFormats' - -export const ResultLimitReached: NotificationType = new NotificationType('asl/resultLimitReached') - -export const ForceValidateRequest: RequestType = new RequestType('asl/validate') - -// Create a connection for the server -const connection: Connection = (createConnection as any)() - -process.on('unhandledRejection', (e: any) => { - console.error(formatError('Unhandled exception', e)) -}) -process.on('uncaughtException', (e: any) => { - console.error(formatError('Unhandled exception', e)) -}) - -console.log = connection.console.log.bind(connection.console) -console.error = connection.console.error.bind(connection.console) - -const workspaceContext = { - resolveRelativePath: (relativePath: string, resource: string) => { - return URL.resolve(resource, relativePath) - }, -} - -// create the JSON language service -let aslJsonLanguageService = getAslJsonLanguageService({ - workspaceContext, - contributions: [], - clientCapabilities: aslClientCapabilities.LATEST, -}) -// create the YAML language service -let aslYamlLanguageService = getAslYamlLanguageService({ - workspaceContext, - contributions: [], - clientCapabilities: aslClientCapabilities.LATEST, -}) - -// Create a text document manager. -const documents = new TextDocuments(TextDocument) - -// Make the text document manager listen on the connection -// for open, change and close text document events -documents.listen(connection) - -let clientSnippetSupport = false -let dynamicFormatterRegistration = false -let hierarchicalDocumentSymbolSupport = false - -let foldingRangeLimitDefault = Number.MAX_VALUE -let foldingRangeLimit = Number.MAX_VALUE -let resultLimit = Number.MAX_VALUE - -// After the server has started the client sends an initialize request. The server receives -// in the passed params the rootPath of the workspace plus the client capabilities. -connection.onInitialize((params: InitializeParams): InitializeResult => { - aslJsonLanguageService = getAslJsonLanguageService({ - workspaceContext, - contributions: [], - clientCapabilities: params.capabilities, - }) - - aslYamlLanguageService = getAslYamlLanguageService({ - workspaceContext, - contributions: [], - clientCapabilities: params.capabilities, - }) - - function getClientCapability(name: string, def: T) { - const keys = name.split('.') - let c: any = params.capabilities - for (let i = 0; c && i < keys.length; i++) { - if (!Object.prototype.hasOwnProperty.call(c, keys[i])) { - return def - } - c = c[keys[i]] - } - - return c - } - - clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false) - dynamicFormatterRegistration = - getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && - typeof params.initializationOptions.provideFormatter !== 'boolean' - foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE) - hierarchicalDocumentSymbolSupport = getClientCapability( - 'textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', - false - ) - const capabilities: ServerCapabilities = { - textDocumentSync: TextDocumentSyncKind.Incremental, - completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['"'] } : undefined, - hoverProvider: true, - documentSymbolProvider: true, - documentRangeFormattingProvider: params.initializationOptions.provideFormatter === true, - colorProvider: {}, - foldingRangeProvider: true, - selectionRangeProvider: true, - } - - return { capabilities } -}) - -// The settings interface describes the server relevant settings part -interface Settings { - aws?: { - stepfunctions?: { - asl?: { - format?: { enable: boolean } - resultLimit?: number - } - } - } -} - -class LimitExceededWarnings { - static pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: NodeJS.Timeout } } = {} - - public static cancel(uri: string) { - const warning = LimitExceededWarnings.pendingWarnings[uri] - if (warning && warning.timeout) { - clearTimeout(warning.timeout) - delete LimitExceededWarnings.pendingWarnings[uri] - } - } - - public static onResultLimitExceeded(uri: string, maxResults: number, name: string) { - return () => { - let warning = LimitExceededWarnings.pendingWarnings[uri] - if (warning) { - if (!warning.timeout) { - // already shown - return - } - warning.features[name] = name - warning.timeout.refresh() - } else { - warning = { features: { [name]: name } } - warning.timeout = setTimeout(() => { - void connection.sendNotification( - ResultLimitReached, - `${posix.basename(uri)}: For performance reasons, ${Object.keys(warning.features).join( - ' and ' - )} have been limited to ${maxResults} items.` - ) - warning.timeout = undefined - }, 2000) - LimitExceededWarnings.pendingWarnings[uri] = warning - } - } - } -} - -let formatterRegistration: Thenable | undefined - -connection.onDidChangeConfiguration((change: any) => { - const settings = change.settings - - foldingRangeLimit = Math.trunc( - Math.max(settings?.aws?.stepfunctions?.asl?.resultLimit || foldingRangeLimitDefault, 0) - ) - resultLimit = Math.trunc(Math.max(settings?.aws?.stepfunctions?.asl?.resultLimit || Number.MAX_VALUE, 0)) - - // dynamically enable & disable the formatter - if (dynamicFormatterRegistration) { - const enableFormatter = settings?.aws?.stepfunctions?.asl?.format?.enable - if (enableFormatter) { - if (!formatterRegistration) { - formatterRegistration = connection.client.register(DocumentRangeFormattingRequest.type, { - documentSelector: [{ language: JSON_ASL }, { language: YAML_ASL }], - }) - } - } else if (formatterRegistration) { - formatterRegistration.then( - (r) => r.dispose(), - (e) => { - console.error('formatterRegistration failed: %s', (e as Error).message) - } - ) - formatterRegistration = undefined - } - } -}) - -// Retry schema validation on all open documents -connection.onRequest(ForceValidateRequest, async (uri: any) => { - return new Promise((resolve) => { - const document = documents.get(uri) - if (document) { - // updateConfiguration() - validateTextDocument(document, (diagnostics) => { - resolve(diagnostics) - }) - } else { - resolve([]) - } - }) -}) - -// The content of a text document has changed. This event is emitted -// when the text document first opened or when its content has changed. -documents.onDidChangeContent((change: any) => { - LimitExceededWarnings.cancel(change.document.uri) - triggerValidation(change.document) -}) - -// a document has closed: clear all diagnostics -documents.onDidClose((event) => { - LimitExceededWarnings.cancel(event.document.uri) - cleanPendingValidation(event.document) - void connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }) -}) - -const pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {} -const validationDelayMs = 500 - -function cleanPendingValidation(textDocument: TextDocument): void { - const request = pendingValidationRequests[textDocument.uri] - if (request) { - clearTimeout(request) - delete pendingValidationRequests[textDocument.uri] - } -} - -function triggerValidation(textDocument: TextDocument): void { - cleanPendingValidation(textDocument) - pendingValidationRequests[textDocument.uri] = setTimeout(() => { - delete pendingValidationRequests[textDocument.uri] - validateTextDocument(textDocument) - }, validationDelayMs) -} - -// sets language service depending on document language -function getLanguageService(langId: string): LanguageService { - if (langId === YAML_ASL) { - return aslYamlLanguageService - } else { - return aslJsonLanguageService - } -} - -function validateTextDocument(textDocument: TextDocument, callback?: (diagnostics: Diagnostic[]) => void): void { - const respond = (diagnostics: Diagnostic[]) => { - void connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }) - if (callback) { - callback(diagnostics) - } - } - if (textDocument.getText().length === 0) { - respond([]) - - return - } - const jsonDocument = getJSONDocument(textDocument) - const version = textDocument.version - - const documentSettings: DocumentLanguageSettings = { comments: 'error', trailingCommas: 'error' } - getLanguageService(textDocument.languageId) - .doValidation(textDocument, jsonDocument, documentSettings) - .then( - (diagnostics) => { - setTimeout(() => { - const currDocument = documents.get(textDocument.uri) - if (currDocument && currDocument.version === version) { - respond(diagnostics) // Send the computed diagnostics to VSCode. - } - }, 100) - }, - (error) => { - connection.console.error(formatError(`Error while validating ${textDocument.uri}`, error)) - } - ) -} - -connection.onDidChangeWatchedFiles((change: DidChangeWatchedFilesParams) => { - // Monitored files have changed in VSCode - let hasChanges = false - for (const c of change.changes) { - if (getLanguageService('asl').resetSchema(c.uri)) { - hasChanges = true - } - } - if (hasChanges) { - // eslint-disable-next-line unicorn/no-array-for-each - documents.all().forEach(triggerValidation) - } -}) - -const jsonDocuments = getLanguageModelCache(10, 60, (document) => - getLanguageService('asl').parseJSONDocument(document) -) -documents.onDidClose((e) => { - jsonDocuments.onDocumentRemoved(e.document) -}) -connection.onShutdown(() => { - jsonDocuments.dispose() -}) - -function getJSONDocument(document: TextDocument): JSONDocument { - return jsonDocuments.get(document) -} - -connection.onCompletion((textDocumentPosition, token) => { - return runSafeAsync( - async () => { - const document = documents.get(textDocumentPosition.textDocument.uri) - if (document) { - const jsonDocument = getJSONDocument(document) - const completions = await getLanguageService(document.languageId).doComplete( - document, - textDocumentPosition.position, - jsonDocument - ) - return completions - } - - return undefined - }, - undefined, - `Error while computing completions for ${textDocumentPosition.textDocument.uri}`, - token - ) -}) - -connection.onCompletionResolve((completionItem, token) => { - return runSafeAsync( - () => { - // the asl-yaml-languageservice uses doResolve from the asl service - return getLanguageService('asl').doResolve(completionItem) - }, - completionItem, - 'Error while resolving completion proposal', - token - ) -}) - -connection.onHover((textDocumentPositionParams, token) => { - return runSafeAsync( - async () => { - const document = documents.get(textDocumentPositionParams.textDocument.uri) - if (document) { - const jsonDocument = getJSONDocument(document) - return getLanguageService(document.languageId).doHover( - document, - textDocumentPositionParams.position, - jsonDocument - ) - } - - return undefined - }, - undefined, - `Error while computing hover for ${textDocumentPositionParams.textDocument.uri}`, - token - ) -}) - -connection.onDocumentSymbol((documentSymbolParams, token) => { - return runSafe( - () => { - const document = documents.get(documentSymbolParams.textDocument.uri) - if (document) { - const jsonDocument = getJSONDocument(document) - const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded( - document.uri, - resultLimit, - 'document symbols' - ) - if (hierarchicalDocumentSymbolSupport) { - return getLanguageService(document.languageId).findDocumentSymbols2(document, jsonDocument, { - resultLimit, - onResultLimitExceeded, - }) - } else { - return getLanguageService(document.languageId).findDocumentSymbols(document, jsonDocument, { - resultLimit, - onResultLimitExceeded, - }) - } - } - - return [] - }, - [], - `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`, - token - ) -}) - -connection.onDocumentRangeFormatting((formatParams, token) => { - return runSafe( - () => { - const document = documents.get(formatParams.textDocument.uri) - if (document) { - return getLanguageService(document.languageId).format( - document, - formatParams.range, - formatParams.options - ) - } - - return [] - }, - [], - `Error while formatting range for ${formatParams.textDocument.uri}`, - token - ) -}) - -connection.onDocumentColor((params, token) => { - return runSafeAsync( - async () => { - const document = documents.get(params.textDocument.uri) - if (document) { - const onResultLimitExceeded = LimitExceededWarnings.onResultLimitExceeded( - document.uri, - resultLimit, - 'document colors' - ) - const jsonDocument = getJSONDocument(document) - - return getLanguageService(document.languageId).findDocumentColors(document, jsonDocument, { - resultLimit, - onResultLimitExceeded, - }) - } - - return [] - }, - [], - `Error while computing document colors for ${params.textDocument.uri}`, - token - ) -}) - -connection.onColorPresentation((params, token) => { - return runSafe( - () => { - const document = documents.get(params.textDocument.uri) - if (document) { - const jsonDocument = getJSONDocument(document) - - return getLanguageService(document.languageId).getColorPresentations( - document, - jsonDocument, - params.color, - params.range - ) - } - - return [] - }, - [], - `Error while computing color presentations for ${params.textDocument.uri}`, - token - ) -}) - -connection.onFoldingRanges((params, token) => { - return runSafe( - () => { - const document = documents.get(params.textDocument.uri) - if (document) { - const onRangeLimitExceeded = LimitExceededWarnings.onResultLimitExceeded( - document.uri, - foldingRangeLimit, - 'folding ranges' - ) - return getLanguageService(document.languageId).getFoldingRanges(document, { - rangeLimit: foldingRangeLimit, - onRangeLimitExceeded, - }) - } - - return undefined - }, - undefined, - `Error while computing folding ranges for ${params.textDocument.uri}`, - token - ) -}) - -connection.onSelectionRanges((params, token) => { - return runSafe( - () => { - const document = documents.get(params.textDocument.uri) - if (document) { - const jsonDocument = getJSONDocument(document) - - return getLanguageService(document.languageId).getSelectionRanges( - document, - params.positions, - jsonDocument - ) - } - - return [] - }, - [], - `Error while computing selection ranges for ${params.textDocument.uri}`, - token - ) -}) - -// Listen on the connection -connection.listen() diff --git a/packages/core/src/stepFunctions/asl/client.ts b/packages/core/src/stepFunctions/asl/client.ts deleted file mode 100644 index fff743252a..0000000000 --- a/packages/core/src/stepFunctions/asl/client.ts +++ /dev/null @@ -1,195 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/*! - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -import * as path from 'path' -import * as nls from 'vscode-nls' - -const localize = nls.loadMessageBundle() - -import { - CancellationToken, - Disposable, - ExtensionContext, - FormattingOptions, - LanguageConfiguration, - languages, - ProviderResult, - Range, - TextDocument, - TextEdit, - window, - workspace, -} from 'vscode' - -import { - DidChangeConfigurationNotification, - DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, - LanguageClientOptions, - NotificationType, -} from 'vscode-languageclient' -import { ServerOptions, TransportKind, LanguageClient } from 'vscode-languageclient/node' - -import { YAML_ASL, JSON_ASL, ASL_FORMATS } from '../constants/aslFormats' -import { StepFunctionsSettings } from '../utils' - -export const ResultLimitReached: NotificationType = new NotificationType('asl/resultLimitReached') - -interface Settings { - asl?: { - format?: { enable: boolean } - resultLimit?: number - } -} - -export class ASLLanguageClient { - static isReady = false - - /** - * Starts the ASL LSP client/server and creates related resources (vscode `OutputChannel`, - * `registerDocumentRangeFormattingEditProvider`, …). - */ - static async create(extensionContext: ExtensionContext) { - const config = new StepFunctionsSettings() - const toDispose = extensionContext.subscriptions - - let rangeFormatting: Disposable | undefined - - // The server is implemented in node - const serverModule = extensionContext.asAbsolutePath(path.join('dist/src/stepFunctions/asl/', 'aslServer.js')) - // The debug options for the server - // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging - const debugOptions = { execArgv: ['--nolazy', '--inspect=6009', '--preserve-symlinks'] } - - // If the extension is launch in debug mode the debug server options are use - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }, - } - - const documentSelector = [ - { schema: 'file', language: JSON_ASL }, - { schema: 'untitled', language: JSON_ASL }, - { schema: 'file', language: YAML_ASL }, - { schema: 'untitled', language: YAML_ASL }, - ] - - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for json documents - documentSelector, - initializationOptions: { - handledSchemaProtocols: ['file', 'untitled'], // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client. - provideFormatter: false, // tell the server to not provide formatting capability and ignore the `aws.stepfunctions.asl.format.enable` setting. - }, - synchronize: { - // Synchronize the setting section 'json' to the server - configurationSection: ASL_FORMATS, - fileEvents: workspace.createFileSystemWatcher('**/*.{asl,asl.json,asl.yml,asl.yaml}'), - }, - middleware: { - workspace: { - didChangeConfiguration: () => - client.sendNotification(DidChangeConfigurationNotification.type, { - settings: getSettings(config), - }), - }, - }, - } - - // Create the language client and start the client. - const client = new LanguageClient( - 'asl', - localize('asl.server.name', 'AWS: Amazon States Language Server'), - serverOptions, - clientOptions - ) - client.registerProposedFeatures() - - void client.start() - toDispose.push(client) - - const languageConfiguration: LanguageConfiguration = { - wordPattern: /("(?:[^\\\"]*(?:\\.)?)*"?)|[^\s{}\[\],:]+/, - indentationRules: { - increaseIndentPattern: /({+(?=([^"]*"[^"]*")*[^"}]*$))|(\[+(?=([^"]*"[^"]*")*[^"\]]*$))/, - decreaseIndentPattern: /^\s*[}\]],?\s*$/, - }, - } - languages.setLanguageConfiguration('asl', languageConfiguration) - - function updateFormatterRegistration() { - const formatEnabled = config.get('format.enable', false) - if (!formatEnabled && rangeFormatting) { - rangeFormatting.dispose() - rangeFormatting = undefined - } else if (formatEnabled && !rangeFormatting) { - rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { - provideDocumentRangeFormattingEdits( - document: TextDocument, - range: Range, - options: FormattingOptions, - token: CancellationToken - ): ProviderResult { - const params: DocumentRangeFormattingParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - range: client.code2ProtocolConverter.asRange(range), - options: (client.code2ProtocolConverter as any).asFormattingOptions(options, {}), - } - - return (client as any) - .sendRequest(DocumentRangeFormattingRequest.method, params, undefined) - .then( - (response: any) => client.protocol2CodeConverter.asTextEdits(response), - async () => { - return Promise.resolve([]) - } - ) - }, - }) - } - } - - return client.start().then(() => { - updateFormatterRegistration() - const disposableFunc = { dispose: () => rangeFormatting?.dispose() as void } - toDispose.push(disposableFunc) - toDispose.push( - config.onDidChange(({ key }) => { - if (key === 'format.enable') { - updateFormatterRegistration() - } - }) - ) - - client.onNotification(ResultLimitReached, (message: any) => { - void window.showInformationMessage( - `${message}\nUse setting 'aws.stepfunctions.asl.maxItemsComputed' to configure the limit.` - ) - }) - this.isReady = true - }) - } -} - -export async function deactivate(): Promise { - return Promise.resolve(undefined) -} - -function getSettings(config: StepFunctionsSettings): Settings { - const resultLimit = config.get('maxItemsComputed', 5000) - - return { - asl: { - resultLimit, - }, - } -} diff --git a/packages/core/src/stepFunctions/commands/createStateMachineFromTemplate.ts b/packages/core/src/stepFunctions/commands/createStateMachineFromTemplate.ts deleted file mode 100644 index a5e8187e47..0000000000 --- a/packages/core/src/stepFunctions/commands/createStateMachineFromTemplate.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() -import { dump, load } from 'js-yaml' - -import * as path from 'path' -import * as vscode from 'vscode' -import { readFileAsString } from '../../shared/filesystemUtilities' -import { getLogger } from '../../shared/logger/logger' -import { CreateStateMachineWizard, TemplateFormats } from '../wizards/createStateMachineWizard' - -import { YAML_ASL, JSON_ASL } from '../constants/aslFormats' - -export async function createStateMachineFromTemplate(context: vscode.ExtensionContext) { - const response = await new CreateStateMachineWizard().run() - if (!response) { - return - } - - try { - const textDocumentFromSelection = await getTextDocumentForSelectedItem( - response.templateFile, - context.extensionPath, - response.templateFormat - ) - - await vscode.window.showTextDocument(textDocumentFromSelection) - } catch (err) { - getLogger().error(err as Error) - void vscode.window.showErrorMessage( - localize( - 'AWS.message.error.stepfunctions.getTextDocumentForSelectedItem', - 'There was an error creating the State Machine Template, check log for details.' - ) - ) - } -} - -async function getTextDocumentForSelectedItem( - fileName: string, - extensionPath: string, - format: TemplateFormats -): Promise { - let content = await readFileAsString(path.join(extensionPath, 'templates', fileName)) - - if (format === TemplateFormats.YAML) { - // Convert JSON string to YAML string - content = dump(load(content)) - } - - const options = { - content, - language: format === TemplateFormats.YAML ? YAML_ASL : JSON_ASL, - } - - return await vscode.workspace.openTextDocument(options) -} diff --git a/packages/core/src/stepFunctions/commands/downloadStateMachineDefinition.ts b/packages/core/src/stepFunctions/commands/downloadStateMachineDefinition.ts deleted file mode 100644 index e89dad1ffc..0000000000 --- a/packages/core/src/stepFunctions/commands/downloadStateMachineDefinition.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import * as os from 'os' -const localize = nls.loadMessageBundle() - -import * as StepFunctions from '@aws-sdk/client-sfn' -import * as path from 'path' -import * as vscode from 'vscode' -import { StepFunctionsClient } from '../../shared/clients/stepFunctions' - -import { getLogger, Logger } from '../../shared/logger/logger' -import { Result } from '../../shared/telemetry/telemetry' -import { StateMachineNode } from '../explorer/stepFunctionsNodes' -import { telemetry } from '../../shared/telemetry/telemetry' -import { fs } from '../../shared/fs/fs' -import { WorkflowStudioEditorProvider } from '../workflowStudio/workflowStudioEditorProvider' - -export async function downloadStateMachineDefinition(params: { - outputChannel: vscode.OutputChannel - stateMachineNode: StateMachineNode - isPreviewAndRender?: boolean -}) { - const logger: Logger = getLogger() - let downloadResult: Result = 'Succeeded' - const stateMachineName = params.stateMachineNode.details.name - try { - const client: StepFunctionsClient = new StepFunctionsClient(params.stateMachineNode.regionCode) - const stateMachineDetails: StepFunctions.DescribeStateMachineCommandOutput = - await client.getStateMachineDetails({ - stateMachineArn: params.stateMachineNode.details.stateMachineArn, - }) - - if (params.isPreviewAndRender) { - const doc = await vscode.workspace.openTextDocument({ - language: 'asl', - content: stateMachineDetails.definition, - }) - - const textEditor = await vscode.window.showTextDocument(doc) - await WorkflowStudioEditorProvider.openWithWorkflowStudio(textEditor.document.uri, { - preserveFocus: true, - viewColumn: vscode.ViewColumn.Beside, - }) - } else { - const wsPath = vscode.workspace.workspaceFolders - ? vscode.workspace.workspaceFolders[0].uri.fsPath - : os.homedir() - const defaultFilePath = path.join(wsPath, params.stateMachineNode.details.name + '.asl.json') - const fileInfo = await vscode.window.showSaveDialog({ defaultUri: vscode.Uri.file(defaultFilePath) }) - - if (fileInfo) { - const filePath = fileInfo.fsPath - await fs.writeFile(filePath, stateMachineDetails.definition || '', 'utf8') - const openPath = vscode.Uri.file(filePath) - const doc = await vscode.workspace.openTextDocument(openPath) - await vscode.window.showTextDocument(doc) - } - } - } catch (err) { - const error = err as Error - logger.error(error) - downloadResult = 'Failed' - params.outputChannel.show(true) - params.outputChannel.appendLine( - localize( - 'AWS.message.error.stepfunctions.downloadStateMachineDefinition', - "Unable to download state machine '{0}', check logs for details.", - stateMachineName - ) - ) - params.outputChannel.appendLine(error.message) - params.outputChannel.appendLine('') - } finally { - telemetry.stepfunctions_downloadStateMachineDefinition.emit({ result: downloadResult }) - } -} diff --git a/packages/core/src/stepFunctions/commands/publishStateMachine.ts b/packages/core/src/stepFunctions/commands/publishStateMachine.ts deleted file mode 100644 index 799d5b2a72..0000000000 --- a/packages/core/src/stepFunctions/commands/publishStateMachine.ts +++ /dev/null @@ -1,168 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { load } from 'js-yaml' -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import { AwsContext } from '../../shared/awsContext' -import { StepFunctionsClient } from '../../shared/clients/stepFunctions' -import { getLogger, Logger } from '../../shared/logger/logger' -import { showViewLogsMessage } from '../../shared/utilities/messages' -import { VALID_SFN_PUBLISH_FORMATS, YAML_FORMATS } from '../constants/aslFormats' -import { refreshStepFunctionsTree } from '../explorer/stepFunctionsNodes' -import { PublishStateMachineWizard, PublishStateMachineWizardState } from '../wizards/publishStateMachineWizard' -const localize = nls.loadMessageBundle() - -interface publishStateMachineParams { - awsContext: AwsContext - outputChannel: vscode.OutputChannel - region?: string - text?: vscode.TextDocument -} -export async function publishStateMachine(params: publishStateMachineParams) { - const logger: Logger = getLogger() - let textDocument: vscode.TextDocument | undefined - - if (params.text) { - textDocument = params.text - } else { - textDocument = vscode.window.activeTextEditor?.document - } - - if (!textDocument) { - logger.error('Could not get active text editor for state machine definition') - throw new Error('Could not get active text editor for state machine definition') - } - - let text = textDocument.getText() - - if (!VALID_SFN_PUBLISH_FORMATS.includes(textDocument.languageId)) { - const errorMessage = `Cannot publish state machine from "${textDocument.languageId}" file` - logger.error(errorMessage) - throw new Error(errorMessage) - } - - if (YAML_FORMATS.includes(textDocument.languageId)) { - try { - text = JSON.stringify(load(text), undefined, ' ') - } catch (error) { - const localizedMsg = localize( - 'AWS.stepFunctions.publishStateMachine.error.invalidYAML', - 'Cannot publish invalid YAML file' - ) - - logger.error(error as Error) - void showViewLogsMessage(localizedMsg) - return - } - } - - try { - const response = await new PublishStateMachineWizard(params.region).run() - if (!response) { - return - } - const client = new StepFunctionsClient(response.region) - - if (response?.createResponse) { - await createStateMachine(response.createResponse, text, params.outputChannel, response.region, client) - refreshStepFunctionsTree(response.region) - } else if (response?.updateResponse) { - await updateStateMachine(response.updateResponse, text, params.outputChannel, response.region, client) - } - } catch (err) { - logger.error(err as Error) - } -} - -async function createStateMachine( - wizardResponse: NonNullable, - definition: string, - outputChannel: vscode.OutputChannel, - region: string, - client: StepFunctionsClient -) { - const logger: Logger = getLogger() - logger.info(`Creating state machine '${wizardResponse.name}'`) - outputChannel.show() - outputChannel.appendLine('') - outputChannel.appendLine( - localize( - 'AWS.stepFunctions.publishStateMachine.info.creating', - "Creating state machine '{0}' in {1}...", - wizardResponse.name, - region - ) - ) - try { - const result = await client.createStateMachine({ - definition, - name: wizardResponse.name, - roleArn: wizardResponse.roleArn, - }) - outputChannel.appendLine( - localize( - 'AWS.stepFunctions.publishStateMachine.info.createSuccess', - "Created state machine '{0}'", - wizardResponse.name - ) - ) - outputChannel.appendLine(result.stateMachineArn || '') - logger.info(`Created "${result.stateMachineArn}"`) - } catch (err) { - const msg = localize( - 'AWS.stepFunctions.publishStateMachine.error.createFailure', - 'Failed to create state machine: {0}', - wizardResponse.name - ) - void showViewLogsMessage(msg) - outputChannel.appendLine(msg) - logger.error(`Failed to create state machine '${wizardResponse.name}': %O`, err as Error) - } -} - -async function updateStateMachine( - wizardResponse: NonNullable, - definition: string, - outputChannel: vscode.OutputChannel, - region: string, - client: StepFunctionsClient -) { - const logger: Logger = getLogger() - logger.info(`Updating state machine ${wizardResponse.stateMachineArn}`) - outputChannel.show() - outputChannel.appendLine('') - outputChannel.appendLine( - localize( - 'AWS.stepFunctions.publishStateMachine.info.updating', - "Updating state machine '{0}' in {1}...", - wizardResponse.stateMachineArn, - region - ) - ) - try { - await client.updateStateMachine({ - definition, - stateMachineArn: wizardResponse.stateMachineArn, - }) - outputChannel.appendLine( - localize( - 'AWS.stepFunctions.publishStateMachine.info.updateSuccess', - 'Updated state machine: {0}', - wizardResponse.stateMachineArn - ) - ) - logger.info(`Updated ${wizardResponse.stateMachineArn}`) - } catch (err) { - const msg = localize( - 'AWS.stepFunctions.publishStateMachine.error.updateFailure', - 'Failed to update state machine: {0}', - wizardResponse.stateMachineArn - ) - void showViewLogsMessage(msg) - outputChannel.appendLine(msg) - logger.error(`Failed to update state machine '${wizardResponse.stateMachineArn}': %O`, err as Error) - } -} diff --git a/packages/core/src/stepFunctions/commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate.ts b/packages/core/src/stepFunctions/commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate.ts deleted file mode 100644 index d21a8cee52..0000000000 --- a/packages/core/src/stepFunctions/commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as fs from 'fs' // eslint-disable-line no-restricted-imports -import { getLogger, Logger } from '../../../shared/logger/logger' - -/** - * @param {string} uniqueIdentifier - unique identifier of state machine - * @param {string} templatePath - path for the template.json file - * - * @returns the escaped ASL Json definition string of the state machine construct - */ -export function getStateMachineDefinitionFromCfnTemplate(uniqueIdentifier: string, templatePath: string): string { - const logger: Logger = getLogger() - try { - const data = fs.readFileSync(templatePath, 'utf8') - const jsonObj = JSON.parse(data) - const resources = jsonObj?.Resources - if (!resources) { - return '' - } - let matchingKey: string - - const matchingKeyList: string[] = [] - for (const key of Object.keys(resources)) { - // the resources list always contains 'CDKMetadata' - if (key === 'CDKMetadata') { - continue - } - - if (key.substring(0, uniqueIdentifier.length) === uniqueIdentifier) { - matchingKeyList.push(key) - } - } - if (matchingKeyList.length === 0) { - return '' - } else { - // return minimum length key in matchingKeyList - matchingKey = matchingKeyList.reduce((a, b) => (a.length <= b.length ? a : b)) - } - - const definitionString = jsonObj.Resources[matchingKey].Properties.DefinitionString - - return !definitionString ? '' : definitionString - } catch (err) { - logger.debug('Unable to extract state machine definition string from template.json file.') - logger.error(err as Error) - return '' - } -} - -/** - * @param {string} escapedAslJsonStr - json state machine construct definition - * @returns unescaped json state machine construct definition in asl.json - */ -export function toUnescapedAslJsonString( - escapedAslJsonStr: - | string - | { - 'Fn::Join': any[] // eslint-disable-line @typescript-eslint/naming-convention - } -): string { - if (typeof escapedAslJsonStr === 'string') { - return escapedAslJsonStr - } - - const definitionStringWithPlaceholders: any[] = escapedAslJsonStr['Fn::Join'][1] - const definitionStringSegments: string[] = definitionStringWithPlaceholders.filter( - (segment) => typeof segment === 'string' - ) - return definitionStringSegments.join('') -} diff --git a/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts b/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts deleted file mode 100644 index 0a473e8020..0000000000 --- a/packages/core/src/stepFunctions/commands/visualizeStateMachine/renderStateMachineGraphCDK.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' - -import { getLogger } from '../../../shared/logger/logger' -import { telemetry } from '../../../shared/telemetry/telemetry' -import { isTreeNode } from '../../../shared/treeview/resourceTreeDataProvider' -import { unboxTreeNode } from '../../../shared/treeview/utils' -import { Commands } from '../../../shared/vscode/commands2' -import { PreviewStateMachineCDKWizard } from '../../wizards/previewStateMachineCDKWizard' -import { WorkflowMode } from '../../workflowStudio/types' -import { WorkflowStudioEditorProvider } from '../../workflowStudio/workflowStudioEditorProvider' -import { getStateMachineDefinitionFromCfnTemplate } from './getStateMachineDefinitionFromCfnTemplate' - -const localize = nls.loadMessageBundle() - -function isLocationResource(obj: unknown): obj is { location: vscode.Uri } { - return !!obj && typeof obj === 'object' && (obj as any).location instanceof vscode.Uri -} - -/** - * Renders a state graph of the state machine. - * - * If given a {@link TreeNode}, it should contain a resource with a URI pointing to the `tree.json`. - * URIs should have a fragment with the resource path in order to locate it within the CFN template. - */ -export const renderCdkStateMachineGraph = Commands.declare( - 'aws.cdk.renderStateMachineGraph', - () => async (input?: unknown) => { - const resource = isTreeNode(input) ? unboxTreeNode(input, isLocationResource) : undefined - const resourceUri = resource?.location ?? (await new PreviewStateMachineCDKWizard().run())?.resource.location - - if (!resourceUri) { - return - } - const logger = getLogger('stepfunctions') - try { - telemetry.ui_click.emit({ - elementId: 'stepfunctions_renderCDKStateMachineGraph', - }) - const [appName, resourceName] = resourceUri.fragment.split('/') - const cdkOutPath = vscode.Uri.joinPath(resourceUri, '..') - const templateUri = vscode.Uri.joinPath(cdkOutPath, `${appName}.template.json`) - const definitionString = getStateMachineDefinitionFromCfnTemplate(resourceName, templateUri.fsPath) - - if (definitionString) { - // Append stateMachineName and Readonly WorkflowMode to templateUri - // to instruct WorkflowStudioEditorProvider to open in Readonly mode and get ASL definition from CloudFormation template - const query = `statemachineName=${encodeURIComponent(resourceName)}&workflowMode=${encodeURIComponent(WorkflowMode.Readonly)}` - const wfsUriWithTemplateInfo = templateUri.with({ query }) - await WorkflowStudioEditorProvider.openWithWorkflowStudio(wfsUriWithTemplateInfo) - } else { - void vscode.window.showErrorMessage( - localize( - 'AWS.stepfunctions.visualisation.errors.rendering', - 'There was an error rendering State Machine Graph, check logs for details.' - ) - ) - logger.error('Unable to extract state machine definition string from template.json file.') - } - } catch (err) { - void vscode.window.showErrorMessage( - localize( - 'AWS.stepfunctions.visualisation.errors.rendering', - 'There was an error rendering State Machine Graph, check logs for details.' - ) - ) - - logger.error(`Unexpected exception: %s`, err) - } - } -) diff --git a/packages/core/src/stepFunctions/constants/aslFormats.ts b/packages/core/src/stepFunctions/constants/aslFormats.ts deleted file mode 100644 index 37c1d67e81..0000000000 --- a/packages/core/src/stepFunctions/constants/aslFormats.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const JSON_ASL = 'asl' // eslint-disable-line @typescript-eslint/naming-convention -export const JSON_TYPE = 'json' // eslint-disable-line @typescript-eslint/naming-convention -export const YAML_ASL = 'asl-yaml' // eslint-disable-line @typescript-eslint/naming-convention -export const YAML_TYPE = 'yaml' // eslint-disable-line @typescript-eslint/naming-convention -export const YAML_FORMATS = [YAML_TYPE, YAML_ASL] // eslint-disable-line @typescript-eslint/naming-convention -export const JSON_FORMATS = [JSON_TYPE, JSON_ASL] // eslint-disable-line @typescript-eslint/naming-convention -export const VALID_SFN_PUBLISH_FORMATS = JSON_FORMATS.concat(YAML_FORMATS) // eslint-disable-line @typescript-eslint/naming-convention -export const ASL_FORMATS = [JSON_ASL, YAML_ASL] // eslint-disable-line @typescript-eslint/naming-convention diff --git a/packages/core/src/stepFunctions/explorer/stepFunctionsNodes.ts b/packages/core/src/stepFunctions/explorer/stepFunctionsNodes.ts deleted file mode 100644 index 8b693d3bb9..0000000000 --- a/packages/core/src/stepFunctions/explorer/stepFunctionsNodes.ts +++ /dev/null @@ -1,125 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as os from 'os' -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as StepFunctions from '@aws-sdk/client-sfn' -import * as vscode from 'vscode' -import { StepFunctionsClient } from '../../shared/clients/stepFunctions' - -import { AWSResourceNode } from '../../shared/treeview/nodes/awsResourceNode' -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../shared/treeview/nodes/placeholderNode' -import { makeChildrenNodes } from '../../shared/treeview/utils' -import { toArrayAsync, toMap, updateInPlace } from '../../shared/utilities/collectionUtils' -import { listStateMachines } from '../../stepFunctions/utils' -import { getIcon } from '../../shared/icons' - -export const contextValueStateMachine = 'awsStateMachineNode' - -const sfnNodeMap = new Map() - -export function refreshStepFunctionsTree(regionCode: string) { - const node = sfnNodeMap.get(regionCode) - - if (node) { - void vscode.commands.executeCommand('aws.refreshAwsExplorerNode', node) - } -} - -/** - * An AWS Explorer node representing the Step Functions Service. - * Contains State Machines for a specific region as child nodes. - */ -export class StepFunctionsNode extends AWSTreeNodeBase { - private readonly stateMachineNodes: Map - - public constructor( - public override readonly regionCode: string, - private readonly client = new StepFunctionsClient(regionCode) - ) { - super('Step Functions', vscode.TreeItemCollapsibleState.Collapsed) - this.stateMachineNodes = new Map() - this.contextValue = 'awsStepFunctionsNode' - sfnNodeMap.set(regionCode, this) - } - - public override async getChildren(): Promise { - return await makeChildrenNodes({ - getChildNodes: async () => { - await this.updateChildren() - - return [...this.stateMachineNodes.values()] - }, - getNoChildrenPlaceholderNode: async () => - new PlaceholderNode( - this, - localize('AWS.explorerNode.stepfunctions.noStateMachine', '[No State Machines found]') - ), - sort: (nodeA, nodeB) => nodeA.functionName.localeCompare(nodeB.functionName), - }) - } - - public async updateChildren(): Promise { - const functions: Map = toMap( - await toArrayAsync(listStateMachines(this.client)), - (details) => details.name - ) - - updateInPlace( - this.stateMachineNodes, - functions.keys(), - (key) => this.stateMachineNodes.get(key)!.update(functions.get(key)!), - (key) => makeStateMachineNode(this, this.regionCode, functions.get(key)!) - ) - } -} - -export class StateMachineNode extends AWSTreeNodeBase implements AWSResourceNode { - public constructor( - public readonly parent: AWSTreeNodeBase, - public override readonly regionCode: string, - public details: StepFunctions.StateMachineListItem - ) { - super('') - this.update(details) - this.iconPath = getIcon('aws-stepfunctions-preview') - } - - public update(details: StepFunctions.StateMachineListItem): void { - this.details = details - this.label = this.details.name || '' - this.tooltip = `${this.details.name}${os.EOL}${this.details.stateMachineArn}` - } - - public get functionName(): string { - return this.details.name || '' - } - - public get arn(): string { - return this.details.stateMachineArn || '' - } - - public get name(): string { - if (this.details.name === undefined) { - throw new Error('name expected but not found') - } - - return this.details.name - } -} - -function makeStateMachineNode( - parent: AWSTreeNodeBase, - regionCode: string, - details: StepFunctions.StateMachineListItem -): StateMachineNode { - const node = new StateMachineNode(parent, regionCode, details) - node.contextValue = contextValueStateMachine - - return node -} diff --git a/packages/core/src/stepFunctions/utils.ts b/packages/core/src/stepFunctions/utils.ts deleted file mode 100644 index f578d6cda8..0000000000 --- a/packages/core/src/stepFunctions/utils.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as StepFunctions from '@aws-sdk/client-sfn' -import * as yaml from 'js-yaml' -import * as vscode from 'vscode' -import { StepFunctionsClient } from '../shared/clients/stepFunctions' -import { - DiagnosticSeverity, - DocumentLanguageSettings, - getLanguageService, - TextDocument as ASLTextDocument, -} from 'amazon-states-language-service' -import { fromExtensionManifest } from '../shared/settings' -import { IamRole } from '../shared/clients/iam' - -const documentSettings: DocumentLanguageSettings = { comments: 'error', trailingCommas: 'error' } -const languageService = getLanguageService({}) - -export async function* listStateMachines( - client: StepFunctionsClient -): AsyncIterableIterator { - const status = vscode.window.setStatusBarMessage( - localize('AWS.message.statusBar.loading.statemachines', 'Loading State Machines...') - ) - - try { - yield* client.listStateMachines() - } finally { - if (status) { - status.dispose() - } - } -} - -/** - * Checks if the given IAM Role is assumable by AWS Step Functions. - * @param role The IAM role to check - */ -export function isStepFunctionsRole(role: IamRole): boolean { - const stepFunctionsSevicePrincipal: string = 'states.amazonaws.com' - const assumeRolePolicyDocument: string | undefined = role.AssumeRolePolicyDocument - - return !!assumeRolePolicyDocument?.includes(stepFunctionsSevicePrincipal) -} - -export async function isDocumentValid(text: string, textDocument?: vscode.TextDocument): Promise { - if (!textDocument || !text) { - return false - } - - const doc = ASLTextDocument.create(textDocument.uri.path, textDocument.languageId, textDocument.version, text) - const jsonDocument = languageService.parseJSONDocument(doc) - const diagnostics = await languageService.doValidation(doc, jsonDocument, documentSettings) - const isValid = !diagnostics.some((diagnostic) => diagnostic.severity === DiagnosticSeverity.Error) - - return isValid -} - -/** - * Checks if the JSON content in an ASL text document is invalid. - * Returns `true` for invalid JSON; `false` for valid JSON, empty content, or non-JSON files. - * - * @param textDocument - The text document to check. - * @returns `true` if invalid; `false` otherwise. - */ -export const isInvalidJsonFile = (textDocument: vscode.TextDocument): boolean => { - const documentContent = textDocument.getText().trim() - // An empty file or whitespace-only text is considered valid JSON for our use case - return textDocument.languageId === 'asl' && documentContent ? isInvalidJson(documentContent) : false -} - -/** - * Checks if the YAML content in an ASL text document is invalid. - * Returns `true` for invalid YAML; `false` for valid YAML, empty content, or non-YAML files. - * - * @param textDocument - The text document to check. - * @returns `true` if invalid; `false` otherwise. - */ -export const isInvalidYamlFile = (textDocument: vscode.TextDocument): boolean => { - try { - if (textDocument.languageId === 'asl-yaml') { - yaml.load(textDocument.getText()) - } - return false - } catch { - return true - } -} - -const isInvalidJson = (content: string): boolean => { - try { - JSON.parse(content) - return false - } catch { - return true - } -} - -const descriptor = { - maxItemsComputed: (v: unknown) => Math.trunc(Math.max(0, Number(v))), - ['format.enable']: Boolean, -} - -export class StepFunctionsSettings extends fromExtensionManifest('aws.stepfunctions.asl', descriptor) {} diff --git a/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.ts b/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.ts deleted file mode 100644 index b4e47bc65f..0000000000 --- a/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { StepFunctionsClient } from '../../../shared/clients/stepFunctions' - -import { getLogger } from '../../../shared/logger/logger' -import { Result } from '../../../shared/telemetry/telemetry' -import { StateMachineNode } from '../../explorer/stepFunctionsNodes' -import { ExtContext } from '../../../shared/extensions' -import { VueWebview } from '../../../webviews/main' -import * as vscode from 'vscode' -import { telemetry } from '../../../shared/telemetry/telemetry' - -interface StateMachine { - arn: string - name: string - region: string -} - -export class ExecuteStateMachineWebview extends VueWebview { - public static readonly sourcePath: string = 'src/stepFunctions/vue/executeStateMachine/index.js' - public readonly id = 'remoteInvoke' - - private readonly logger = getLogger() - - public constructor( - private readonly channel: vscode.OutputChannel, - private readonly stateMachine: StateMachine - ) { - super(ExecuteStateMachineWebview.sourcePath) - } - - public init() { - return this.stateMachine - } - - public async executeStateMachine(input: string) { - let executeResult: Result = 'Succeeded' - this.logger.info('Starting Step Functions State Machine execution') - - this.channel.show() - this.channel.appendLine('') - this.channel.appendLine( - localize( - 'AWS.stepFunctions.executeStateMachine.info.executing', - 'Starting execution of {0} in {1}...', - this.stateMachine.name, - this.stateMachine.region - ) - ) - this.channel.appendLine('') - - try { - const client = new StepFunctionsClient(this.stateMachine.region) - const startExecResponse = await client.executeStateMachine({ - stateMachineArn: this.stateMachine.arn, - input, - }) - this.logger.info('started execution for Step Functions State Machine') - this.channel.appendLine(localize('AWS.stepFunctions.executeStateMachine.info.started', 'Execution started')) - this.channel.appendLine(startExecResponse.executionArn || '') - } catch (e) { - executeResult = 'Failed' - const error = e as Error - this.logger.error('Error starting execution for Step Functions State Machine: %s', error) - this.channel.appendLine( - localize( - 'AWS.stepFunctions.executeStateMachine.error.failedToStart', - 'There was an error starting execution for {0}, check AWS Toolkit logs for more information.', - this.stateMachine.arn - ) - ) - } finally { - telemetry.stepfunctions_executeStateMachine.emit({ result: executeResult }) - } - } -} - -const Panel = VueWebview.compilePanel(ExecuteStateMachineWebview) - -export async function executeStateMachine(context: ExtContext, node: StateMachineNode): Promise { - const wv = new Panel(context.extensionContext, context.outputChannel, { - arn: node.details.stateMachineArn || '', - name: node.details.name || '', - region: node.regionCode, - }) - - await wv.show({ - title: localize('AWS.executeStateMachine.title', 'Start Execution'), - cssFiles: ['executeStateMachine.css'], - }) - telemetry.stepfunctions_executeStateMachineView.emit() -} diff --git a/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.vue b/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.vue deleted file mode 100644 index ce55ab7276..0000000000 --- a/packages/core/src/stepFunctions/vue/executeStateMachine/executeStateMachine.vue +++ /dev/null @@ -1,124 +0,0 @@ -/*! * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ - - - - diff --git a/packages/core/src/stepFunctions/vue/executeStateMachine/index.ts b/packages/core/src/stepFunctions/vue/executeStateMachine/index.ts deleted file mode 100644 index a28f50c17f..0000000000 --- a/packages/core/src/stepFunctions/vue/executeStateMachine/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createApp } from 'vue' -import component from './executeStateMachine.vue' - -const create = () => createApp(component) -const app = create() -app.mount('#vue-app') - -window.addEventListener('remount', () => { - app.unmount() - create().mount('#vue-app') -}) diff --git a/packages/core/src/stepFunctions/wizards/createStateMachineWizard.ts b/packages/core/src/stepFunctions/wizards/createStateMachineWizard.ts deleted file mode 100644 index 62f0f3ac87..0000000000 --- a/packages/core/src/stepFunctions/wizards/createStateMachineWizard.ts +++ /dev/null @@ -1,108 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import { Wizard } from '../../shared/wizards/wizard' -import { createCommonButtons } from '../../shared/ui/buttons' -import { createQuickPick, DataQuickPickItem } from '../../shared/ui/pickerPrompter' -import { sfnCreateStateMachineUrl } from '../../shared/constants' - -export const starterTemplates: DataQuickPickItem[] = [ - { - label: localize('AWS.stepfunctions.template.helloWorld.label', 'Hello world'), - description: localize( - 'AWS.stepfunctions.template.helloWorld.description', - 'A basic example using a Pass state.' - ), - data: 'HelloWorld.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.retryFailure.label', 'Retry failure'), - description: localize( - 'AWS.stepfunctions.template.retryFailure.description', - 'An example of a Task state using a retry policy to handle Lambda failures.' - ), - data: 'RetryFailure.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.waitState.label', 'Wait state'), - description: localize( - 'AWS.stepfunctions.template.waitState.description', - 'Delays the state machine from continuing for a specified time.' - ), - data: 'WaitState.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.parallel.label', 'Parallel'), - description: localize( - 'AWS.stepfunctions.template.parallel.description', - 'Used to create parallel branches of execution in your state machine.' - ), - data: 'Parallel.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.mapState.label', 'Map state'), - description: localize( - 'AWS.stepfunctions.template.mapState.description', - 'Use a Map state to dynamically process data in an array.' - ), - data: 'MapState.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.catchFailure.label', 'Catch failure'), - description: localize( - 'AWS.stepfunctions.template.catchFailure.description', - 'An example of a Task state using Catchers to handle Lambda failures.' - ), - data: 'CatchFailure.asl.json', - }, - { - label: localize('AWS.stepfunctions.template.choiceState.label', 'Choice state'), - description: localize( - 'AWS.stepfunctions.template.choiceState.description', - 'Adds branching logic to a state machine.' - ), - data: 'ChoiceState.asl.json', - }, -] - -export enum TemplateFormats { - YAML = 'YAML', - JSON = 'JSON', -} - -interface CreateStateMachineWizardResponse { - readonly templateFile: string - readonly templateFormat: TemplateFormats -} - -export class CreateStateMachineWizard extends Wizard { - public constructor() { - super() - - this.form.templateFile.bindPrompter(() => - createQuickPick(starterTemplates, { - title: localize( - 'AWS.message.prompt.selectStateMachineTemplate.placeholder', - 'Select a starter template' - ), - buttons: createCommonButtons(sfnCreateStateMachineUrl), - }) - ) - - const templateItems = Object.values(TemplateFormats).map((v) => ({ label: v, data: v })) - this.form.templateFormat.bindPrompter(() => - createQuickPick(templateItems, { - title: localize( - 'AWS.message.prompt.selectStateMachineTemplateFormat.placeholder', - 'Select template format' - ), - buttons: createCommonButtons(sfnCreateStateMachineUrl), - }) - ) - } -} diff --git a/packages/core/src/stepFunctions/wizards/previewStateMachineCDKWizard.ts b/packages/core/src/stepFunctions/wizards/previewStateMachineCDKWizard.ts deleted file mode 100644 index a868807238..0000000000 --- a/packages/core/src/stepFunctions/wizards/previewStateMachineCDKWizard.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import * as path from 'path' -import { CdkAppLocation, getApp } from '../../awsService/cdk/explorer/cdkProject' -import { ConstructNode, isStateMachine } from '../../awsService/cdk/explorer/nodes/constructNode' -import { detectCdkProjects } from '../../awsService/cdk/explorer/detectCdkProjects' -import { Wizard, WIZARD_BACK } from '../../shared/wizards/wizard' -import { createQuickPick } from '../../shared/ui/pickerPrompter' -import { createCommonButtons } from '../../shared/ui/buttons' -import { ConstructTreeEntity } from '../../awsService/cdk/explorer/tree/types' -import { getDisplayLabel } from '../../awsService/cdk/explorer/tree/treeInspector' - -function createLocationPrompter() { - const items = detectCdkProjects(vscode.workspace.workspaceFolders).then((locations) => { - return locations.map((l) => ({ - label: vscode.workspace.asRelativePath(l.cdkJsonUri), - data: l, - })) - }) - - return createQuickPick(items, { - title: localize('AWS.message.prompt.selectCDKWorkspace.placeholder', 'Select a CDK application'), - buttons: createCommonButtons(), - noItemsFoundItem: { - label: localize('Aws.cdk.app.noWorkspace', '[No applications found]'), - data: WIZARD_BACK, - }, - }) -} - -export function getStateMachines(construct: ConstructTreeEntity) { - const stateMachines: ConstructTreeEntity[] = [] - if (isStateMachine(construct)) { - stateMachines.push(construct) - } - - const children = Object.values(construct.children ?? {}) - for (const child of children) { - stateMachines.push(...getStateMachines(child)) - } - - return stateMachines -} - -function createResourcePrompter(location: CdkAppLocation) { - const items = getApp(location) - .then((app) => getStateMachines(app.constructTree.tree)) - .then((constructs) => - constructs.map((c) => ({ - label: getDisplayLabel(c), - description: path.dirname(c.path), - data: { construct: c, location: location.treeUri.with({ fragment: c.path }) }, - })) - ) - - return createQuickPick(items, { - buttons: createCommonButtons(), - title: localize('AWS.message.prompt.selectCDKStateMachine.placeholder', 'Select State Machine'), - noItemsFoundItem: { - label: localize( - 'Aws.cdk.explorerNode.app.noStateMachines', - "[No state machine(s) found in cdk application '{0}']", - vscode.workspace.asRelativePath(location.cdkJsonUri) - ), - data: WIZARD_BACK, - }, - }) -} - -interface State { - readonly location: CdkAppLocation - readonly resource: ConstructNode['resource'] -} - -export class PreviewStateMachineCDKWizard extends Wizard { - public constructor() { - super() - - this.form.location.bindPrompter(createLocationPrompter) - this.form.resource.bindPrompter(({ location }) => createResourcePrompter(location!)) - } -} diff --git a/packages/core/src/stepFunctions/wizards/publishStateMachineWizard.ts b/packages/core/src/stepFunctions/wizards/publishStateMachineWizard.ts deleted file mode 100644 index ed141ac189..0000000000 --- a/packages/core/src/stepFunctions/wizards/publishStateMachineWizard.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import * as vscode from 'vscode' -import { - sfnCreateIamRoleUrl, - sfnCreateStateMachineNameParamUrl, - sfnDeveloperGuideUrl, - sfnSupportedRegionsUrl, - sfnUpdateStateMachineUrl, -} from '../../shared/constants' -import { createCommonButtons } from '../../shared/ui/buttons' -import { createRegionPrompter } from '../../shared/ui/common/region' -import { createInputBox, InputBoxPrompter } from '../../shared/ui/inputPrompter' -import { createQuickPick, DataQuickPickItem, QuickPickPrompter } from '../../shared/ui/pickerPrompter' -import { Wizard, WIZARD_BACK } from '../../shared/wizards/wizard' -import { isStepFunctionsRole } from '../utils' -import { createRolePrompter } from '../../shared/ui/common/roles' -import { IamClient } from '../../shared/clients/iam' -import { StepFunctionsClient } from '../../shared/clients/stepFunctions' - -export enum PublishStateMachineAction { - QuickCreate, - QuickUpdate, -} - -function createPublishActionPrompter(region: string): QuickPickPrompter { - const publishItems: DataQuickPickItem[] = [ - { - label: localize('AWS.stepFunctions.publishWizard.publishAction.quickCreate.label', 'Quick Create'), - detail: localize( - 'AWS.stepFunctions.publishWizard.publishAction.quickCreate.detail', - 'Create a state machine from the ASL definition using default settings' - ), - data: PublishStateMachineAction.QuickCreate, - }, - { - label: localize('AWS.stepFunctions.publishWizard.publishAction.quickUpdate.label', 'Quick Update'), - detail: localize( - 'AWS.stepFunctions.publishWizard.publishAction.quickUpdate.detail', - 'Update an existing state machine with the ASL definition' - ), - data: PublishStateMachineAction.QuickUpdate, - }, - ] - - const prompter = createQuickPick(publishItems, { - title: localize( - 'AWS.stepFunctions.publishWizard.publishAction.title', - 'Publish to AWS Step Functions ({0})', - region - ), - buttons: createCommonButtons(sfnDeveloperGuideUrl), - }) - - return prompter -} - -function createNamePrompter(): InputBoxPrompter { - function validate(value: string): string | undefined { - if (!value) { - return localize( - 'AWS.stepFunctions.publishWizard.stateMachineName.validation.empty', - 'State machine name cannot be empty' - ) - } - - return undefined - } - - const prompter = createInputBox({ - title: localize('AWS.stepFunctions.publishWizard.stateMachineName.title', 'Name your state machine'), - validateInput: validate, - buttons: createCommonButtons(sfnCreateStateMachineNameParamUrl), - }) - - return prompter -} - -export interface PublishStateMachineWizardState { - readonly region: string - readonly publishAction: PublishStateMachineAction - readonly createResponse?: { - readonly name: string - readonly roleArn: string - } - readonly updateResponse?: { - readonly stateMachineArn: string - } -} - -function createStepFunctionsRolePrompter(region: string) { - const client = new IamClient(region) - - return createRolePrompter(client, { - helpUrl: vscode.Uri.parse(sfnCreateIamRoleUrl), - title: localize('AWS.stepFunctions.publishWizard.iamRole.title', 'Select execution role ({0})', region), - noRoleDetail: localize( - 'AWS.stepFunctions.publishWizard.iamRole.noRoles.detail', - 'Create an IAM role before proceeding. See documentation for details.' - ), - roleFilter: isStepFunctionsRole, - }) -} - -async function* listStateMachines(region: string) { - const client = new StepFunctionsClient(region) - - for await (const machine of client.listStateMachines()) { - yield [ - { - label: machine.name || '', - data: machine.stateMachineArn || '', - description: machine.stateMachineArn || '', - }, - ] - } -} - -function createUpdateStateMachinePrompter(region: string): QuickPickPrompter { - const prompter = createQuickPick(listStateMachines(region), { - title: localize( - 'AWS.stepFunctions.publishWizard.stateMachineNameToUpdate.title', - 'Select state machine to update ({0})', - region - ), - buttons: createCommonButtons(sfnUpdateStateMachineUrl), - noItemsFoundItem: { - label: localize( - 'AWS.stepFunctions.publishWizard.stateMachineNameToUpdate.noStateMachines.label', - 'No state machines could be found' - ), - alwaysShow: true, - data: WIZARD_BACK, - detail: localize( - 'AWS.stepFunctions.publishWizard.stateMachineNameToUpdate.noStateMachines.detail', - 'Create a state machine before proceeding. See documentation for details.' - ), - }, - }) - - return prompter -} - -export class PublishStateMachineWizard extends Wizard { - public constructor(region?: string) { - super({ initState: { region } }) - const form = this.form - - form.region.bindPrompter(() => - createRegionPrompter(undefined, { - serviceFilter: 'states', - helpUrl: sfnSupportedRegionsUrl, - }).transform((r) => r.id) - ) - - form.publishAction.bindPrompter(({ region }) => createPublishActionPrompter(region!)) - - form.createResponse.roleArn.bindPrompter( - ({ region }) => createStepFunctionsRolePrompter(region!).transform((r) => r.Arn), - { - showWhen: (form) => form.publishAction === PublishStateMachineAction.QuickCreate, - } - ) - - form.createResponse.name.bindPrompter(() => createNamePrompter(), { - showWhen: (form) => form.publishAction === PublishStateMachineAction.QuickCreate, - }) - - form.updateResponse.stateMachineArn.bindPrompter(({ region }) => createUpdateStateMachinePrompter(region!), { - showWhen: (form) => form.publishAction === PublishStateMachineAction.QuickUpdate, - }) - } -} diff --git a/packages/core/src/stepFunctions/workflowStudio/activation.ts b/packages/core/src/stepFunctions/workflowStudio/activation.ts deleted file mode 100644 index 74bcc24e65..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/activation.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { WorkflowStudioEditorProvider } from './workflowStudioEditorProvider' -import { Commands } from '../../shared/vscode/commands2' -import globals from '../../shared/extensionGlobals' - -/** - * Activates the extension and registers all necessary components. - */ -export async function activate(): Promise { - globals.context.subscriptions.push(WorkflowStudioEditorProvider.register()) - - // Open the file with Workflow Studio editor in a new tab, or focus on the tab with WFS if it is already open - globals.context.subscriptions.push( - Commands.register('aws.stepfunctions.openWithWorkflowStudio', async (uri: vscode.Uri) => { - await WorkflowStudioEditorProvider.openWithWorkflowStudio(uri) - }) - ) - - // Close the active editor and open the file with Workflow Studio (or close and switch to the existing relevant tab). - // This command is expected to always be called from the active tab in the default editor mode - globals.context.subscriptions.push( - Commands.register('aws.stepfunctions.switchToWorkflowStudio', async (uri: vscode.Uri) => { - await vscode.commands.executeCommand('workbench.action.closeActiveEditor') - await WorkflowStudioEditorProvider.openWithWorkflowStudio(uri) - }) - ) -} diff --git a/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts b/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts deleted file mode 100644 index 2b548ab957..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/handleMessage.ts +++ /dev/null @@ -1,278 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - Command, - Message, - MessageType, - SaveFileRequestMessage, - WebviewContext, - InitResponseMessage, - FileChangedMessage, - FileChangeEventTrigger, - SyncFileRequestMessage, - ApiCallRequestMessage, - UnsupportedMessage, - WorkflowMode, -} from './types' -import { submitFeedback } from '../../feedback/vue/submitFeedback' -import { placeholder } from '../../shared/vscode/commands2' -import * as nls from 'vscode-nls' -import vscode from 'vscode' -import { telemetry } from '../../shared/telemetry/telemetry' -import { ToolkitError } from '../../shared/errors' -import { WorkflowStudioApiHandler } from './workflowStudioApiHandler' -import globals from '../../shared/extensionGlobals' -import { getLogger } from '../../shared/logger/logger' -import { publishStateMachine } from '../commands/publishStateMachine' -import { - getStateMachineDefinitionFromCfnTemplate, - toUnescapedAslJsonString, -} from '../commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate' - -const localize = nls.loadMessageBundle() - -/** - * Handles messages received from the webview. Depending on the message type and command, - * calls the appropriate handler function - * @param message The message received from the webview - * @param context The context object containing information about the webview environment - */ -export async function handleMessage(message: Message, context: WebviewContext) { - const { command, messageType } = message - const isReadonlyMode = context.mode === WorkflowMode.Readonly - if (messageType === MessageType.REQUEST) { - switch (command) { - case Command.INIT: - void initMessageHandler(context) - break - case Command.SAVE_FILE: - !isReadonlyMode && void saveFileMessageHandler(message as SaveFileRequestMessage, context) - break - case Command.SAVE_FILE_AND_DEPLOY: - !isReadonlyMode && void saveFileAndDeployMessageHandler(message as SaveFileRequestMessage, context) - break - case Command.AUTO_SYNC_FILE: - !isReadonlyMode && void autoSyncFileMessageHandler(message as SyncFileRequestMessage, context) - break - case Command.CLOSE_WFS: - void closeCustomEditorMessageHandler(context) - break - case Command.OPEN_FEEDBACK: - !isReadonlyMode && void submitFeedback(placeholder, 'Workflow Studio') - break - case Command.API_CALL: - !isReadonlyMode && apiCallMessageHandler(message as ApiCallRequestMessage, context) - break - default: - void handleUnsupportedMessage(context, message) - break - } - } else if (messageType === MessageType.BROADCAST) { - switch (command) { - case Command.LOAD_STAGE: - void loadStageMessageHandler(context) - break - default: - void handleUnsupportedMessage(context, message) - break - } - } else { - void handleUnsupportedMessage(context, message) - } -} - -/** - * Method for extracting fileContents from the context object based on different WorkflowStudio modes. - * @param context The context object containing the necessary information for the webview. - */ -function getFileContents(context: WebviewContext): string { - const filePath = context.defaultTemplatePath - if (context.mode === WorkflowMode.Readonly) { - const definitionString = getStateMachineDefinitionFromCfnTemplate(context.stateMachineName, filePath) - return toUnescapedAslJsonString(definitionString || '') - } else { - return context.textDocument.getText().toString() - } -} - -/** - * Handler for when the webview is ready. - * This handler is used to initialize the webview with the contents of the asl file selected. - * @param context The context object containing the necessary information for the webview. - */ -async function initMessageHandler(context: WebviewContext) { - const filePath = context.defaultTemplatePath - - try { - const fileContents = getFileContents(context) - context.fileStates[filePath] = { fileContents } - - await broadcastFileChange(context, 'INITIAL_RENDER') - context.loaderNotification?.progress.report({ increment: 25 }) - } catch (e) { - await context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.INIT, - filePath, - isSuccess: false, - failureReason: (e as Error).message, - } as InitResponseMessage) - } -} - -/** - * Helper Function to broadcast the local file change to the Workflow Studio view - * @param context: The context of the webview - * @param trigger: The action that triggered the change (either initial render or user saving the file) - */ -export async function broadcastFileChange(context: WebviewContext, trigger: FileChangeEventTrigger) { - const fileContents = getFileContents(context) - await context.panel.webview.postMessage({ - messageType: MessageType.BROADCAST, - command: Command.FILE_CHANGED, - fileName: - context.mode === WorkflowMode.Readonly && context.stateMachineName - ? context.stateMachineName - : context.defaultTemplateName, - fileContents, - filePath: context.defaultTemplatePath, - trigger, - } as FileChangedMessage) -} - -/** - * Handler for managing webview stage load, which updates load notifications. - * @param message The message containing the load stage. - * @param context The context object containing the necessary information for the webview. - */ -async function loadStageMessageHandler(context: WebviewContext) { - context.loaderNotification?.progress.report({ increment: 25 }) - setTimeout(() => { - context.loaderNotification?.resolve() - }, 100) -} - -/** - * Handler for closing WFS custom editor. When called, disposes webview panel and opens default VSCode editor - * @param context The context object containing the necessary information for the webview. - */ -export async function closeCustomEditorMessageHandler(context: WebviewContext) { - await telemetry.stepfunctions_closeWorkflowStudio.run(async (span) => { - span.record({ - id: context.fileId, - }) - context.panel.dispose() - await vscode.commands.executeCommand('vscode.openWith', context.textDocument.uri, 'default') - }) -} - -/** - * Handler for saving a file from the webview which updates the workspace and saves the file. - * Triggered when the user explicitly applies save action in WFS - * @param request The request message containing the file contents. - * @param context The webview context containing the necessary information for saving the file. - */ -async function saveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) { - await telemetry.stepfunctions_saveFile.run(async (span) => { - span.record({ - id: context.fileId, - saveType: 'MANUAL_SAVE', - source: 'WORKFLOW_STUDIO', - isInvalidJson: request.isInvalidJson, - }) - - try { - await context.textDocument.save() - void vscode.window.showInformationMessage( - localize( - 'AWS.stepFunctions.workflowStudio.actions.saveSuccessMessage', - '{0} has been saved', - context.defaultTemplateName - ) - ) - } catch (err) { - throw ToolkitError.chain(err, 'Could not save asl file.', { - code: 'SaveFailed', - }) - } - }) -} - -/** - * Handler for saving a file and starting the state machine deployment flow while staying in WFS view. - * Triggered when the user triggers 'Save and Deploy' action in WFS - * @param request The request message containing the file contents. - * @param context The webview context containing the necessary information for saving the file. - */ -async function saveFileAndDeployMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) { - await saveFileMessageHandler(request, context) - await publishStateMachine({ - awsContext: globals.awsContext, - outputChannel: globals.outputChannel, - text: context.textDocument, - }) - - telemetry.ui_click.emit({ - elementId: 'stepfunctions_saveAndDeploy', - }) -} - -/** - * Handler for auto syncing a file from the webview which updates the workspace but does not save the file. - * Triggered on every code change from WFS, including invalid JSON. - * @param request The request message containing the file contents. - * @param context The webview context containing the necessary information for saving the file. - */ -async function autoSyncFileMessageHandler(request: SyncFileRequestMessage, context: WebviewContext) { - await telemetry.stepfunctions_saveFile.run(async (span) => { - span.record({ - id: context.fileId, - saveType: 'AUTO_SYNC', - source: 'WORKFLOW_STUDIO', - isInvalidJson: request.isInvalidJson, - }) - - try { - const edit = new vscode.WorkspaceEdit() - edit.replace( - context.textDocument.uri, - new vscode.Range(0, 0, context.textDocument.lineCount, 0), - request.fileContents - ) - await vscode.workspace.applyEdit(edit) - } catch (err) { - throw ToolkitError.chain(err, 'Could not autosave asl file.', { - code: 'AutoSaveFailed', - }) - } - }) -} - -/** - * Handler for making API calls from the webview and returning the response. - * @param request The request message containing the API to call and the parameters - * @param context The webview context used for returning the API response to the webview - */ -function apiCallMessageHandler(request: ApiCallRequestMessage, context: WebviewContext) { - const logger = getLogger('stepfunctions') - const apiHandler = new WorkflowStudioApiHandler(globals.awsContext.getCredentialDefaultRegion(), context) - apiHandler.performApiCall(request).catch((error) => logger.error('%s API call failed: %O', request.apiName, error)) -} - -/** - * Handles unsupported or unrecognized messages by sending a response to the webview. Ensures compatibility with future - * commands and message types, preventing issues if the user has an outdated extension version. - * @param context The context object containing information about the webview environment - * @param command The command received from the webview - * @param messageType The type of the message received - */ -async function handleUnsupportedMessage(context: WebviewContext, originalMessage: Message) { - await context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.UNSUPPORTED_COMMAND, - originalMessage, - } as UnsupportedMessage) -} diff --git a/packages/core/src/stepFunctions/workflowStudio/types.ts b/packages/core/src/stepFunctions/workflowStudio/types.ts deleted file mode 100644 index 22a93a8404..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/types.ts +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { ListRolesCommandInput } from '@aws-sdk/client-iam' -import { TestStateInput } from '@aws-sdk/client-sfn' -import * as vscode from 'vscode' - -export enum WorkflowMode { - Editable = 'toolkit', - Readonly = 'readonly', -} - -export type WebviewContext = { - stateMachineName: string - mode: WorkflowMode - panel: vscode.WebviewPanel - textDocument: vscode.TextDocument - disposables: vscode.Disposable[] - workSpacePath: string - defaultTemplatePath: string - defaultTemplateName: string - fileStates: Record - loaderNotification: undefined | LoaderNotification - fileId: string -} - -export type LoaderNotification = { - progress: vscode.Progress<{ - message?: string | undefined - increment?: number | undefined - }> - cancellationToken: vscode.CancellationToken - resolve: () => void -} - -export enum MessageType { - REQUEST = 'REQUEST', - RESPONSE = 'RESPONSE', - BROADCAST = 'BROADCAST', -} - -export enum Command { - INIT = 'INIT', - SAVE_FILE = 'SAVE_FILE', - SAVE_FILE_AND_DEPLOY = 'SAVE_FILE_AND_DEPLOY', - AUTO_SYNC_FILE = 'AUTO_SYNC_FILE', - FILE_CHANGED = 'FILE_CHANGED', - LOAD_STAGE = 'LOAD_STAGE', - OPEN_FEEDBACK = 'OPEN_FEEDBACK', - CLOSE_WFS = 'CLOSE_WFS', - API_CALL = 'API_CALL', - UNSUPPORTED_COMMAND = 'UNSUPPORTED_COMMAND', -} - -export type FileWatchInfo = { - fileContents: string -} - -export interface Message { - command: Command - messageType: MessageType -} - -export type FileChangeEventTrigger = 'INITIAL_RENDER' | 'MANUAL_SAVE' - -export interface FileChangedMessage extends Message { - fileName: string - fileContents: string - filePath: string - trigger: FileChangeEventTrigger -} - -export interface UnsupportedMessage extends Message { - originalMessage: Message -} - -export interface InitResponseMessage extends Omit { - isSuccess: boolean - failureReason?: string -} - -export interface SaveFileRequestMessage extends Message { - isInvalidJson: boolean -} - -export interface SyncFileRequestMessage extends SaveFileRequestMessage { - fileContents: string -} - -export enum ApiAction { - IAMListRoles = 'iam:ListRoles', - SFNTestState = 'sfn:TestState', -} - -type ApiCallRequestMapping = { - [ApiAction.IAMListRoles]: ListRolesCommandInput - [ApiAction.SFNTestState]: TestStateInput -} - -interface ApiCallRequestMessageBase extends Message { - requestId: string - apiName: ApiName - params: ApiCallRequestMapping[ApiName] -} - -/** - * The message from the webview describing what API and parameters to call. - */ -export type ApiCallRequestMessage = - | ApiCallRequestMessageBase - | ApiCallRequestMessageBase diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts b/packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts deleted file mode 100644 index 6c0fb850b9..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioApiHandler.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as StepFunctions from '@aws-sdk/client-sfn' -import { IamClient, IamRole } from '../../shared/clients/iam' -import { StepFunctionsClient } from '../../shared/clients/stepFunctions' -import { ApiAction, ApiCallRequestMessage, Command, MessageType, WebviewContext } from './types' -import { telemetry } from '../../shared/telemetry/telemetry' -import { ListRolesRequest } from '@aws-sdk/client-iam' - -export class WorkflowStudioApiHandler { - public constructor( - region: string, - private readonly context: WebviewContext, - private readonly clients = { - sfn: new StepFunctionsClient(region), - iam: new IamClient(region), - } - ) {} - - /** - * Performs the API call on behalf of the webview, and sends the sucesss or error response to the webview. - */ - public async performApiCall({ apiName, params, requestId }: ApiCallRequestMessage): Promise { - try { - let response - switch (apiName) { - case ApiAction.IAMListRoles: - response = await this.listRoles(params) - break - case ApiAction.SFNTestState: - response = await this.testState(params) - break - default: - throw new Error(`Unknown API: ${apiName}`) - } - - await this.context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.API_CALL, - apiName, - response, - requestId, - isSuccess: true, - }) - } catch (err) { - await this.context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.API_CALL, - apiName, - error: - err instanceof Error - ? { - message: err.message, - name: err.name, - stack: err.stack, - } - : { - message: String(err), - }, - requestId, - isSuccess: false, - }) - } - } - - public async testState(params: StepFunctions.TestStateInput): Promise { - telemetry.ui_click.emit({ - elementId: 'stepfunctions_testState', - }) - return this.clients.sfn.testState(params) - } - - public async listRoles(params: ListRolesRequest): Promise { - return this.clients.iam.resolveRoles(params) - } -} diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts deleted file mode 100644 index ba71985651..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditor.ts +++ /dev/null @@ -1,225 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as path from 'path' -import * as vscode from 'vscode' -import { telemetry } from '../../shared/telemetry/telemetry' -import { i18n } from '../../shared/i18n-helper' -import { broadcastFileChange } from './handleMessage' -import { FileWatchInfo, WebviewContext, WorkflowMode } from './types' -import { CancellationError } from '../../shared/utilities/timeoutUtils' -import { handleMessage } from './handleMessage' -import { isInvalidJsonFile } from '../utils' -import { setContext } from '../../shared/vscode/setContext' -import globals from '../../shared/extensionGlobals' - -/** - * The main class for Workflow Studio Editor. This class handles the creation and management - * of the webview panel for integration. It also handles the communication - * between the webview and the extension context. This class stores the state of the - * local file that is being edited in the webview panel, in the property 'fileStates'. - */ -export class WorkflowStudioEditor { - public readonly documentUri: vscode.Uri - public webviewPanel: vscode.WebviewPanel - protected readonly disposables: vscode.Disposable[] = [] - protected isPanelDisposed = false - private readonly onVisualizationDisposeEmitter = new vscode.EventEmitter() - private fileId: string - private readonly mode: WorkflowMode - private readonly stateMachineName: string - public workSpacePath: string - public defaultTemplatePath: string - public defaultTemplateName: string - // fileStates is used to store the state of the file being edited and achieve bi-direction sync - public fileStates: Record - private getWebviewContent: () => Promise - - public constructor( - textDocument: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - fileId: string, - getWebviewContent: () => Promise, - mode: WorkflowMode, - stateMachineName: string - ) { - this.mode = mode - this.stateMachineName = stateMachineName - this.getWebviewContent = getWebviewContent - this.documentUri = textDocument.uri - this.webviewPanel = webviewPanel - this.fileStates = {} - this.workSpacePath = path.dirname(textDocument.uri.fsPath) - this.defaultTemplatePath = textDocument.uri.fsPath - this.defaultTemplateName = path.basename(this.defaultTemplatePath) - this.fileId = fileId - - telemetry.stepfunctions_openWorkflowStudio.emit({ - id: this.fileId, - }) - - this.setupWebviewPanel(textDocument) - } - - public get onVisualizationDisposeEvent(): vscode.Event { - return this.onVisualizationDisposeEmitter.event - } - public getPanel(): vscode.WebviewPanel | undefined { - if (!this.isPanelDisposed) { - return this.webviewPanel - } - } - - public showPanel(): void { - this.getPanel()?.reveal() - } - - public async refreshPanel() { - if (!this.isPanelDisposed) { - this.webviewPanel.dispose() - const document = await vscode.workspace.openTextDocument(this.documentUri) - this.setupWebviewPanel(document) - } - } - - protected getText(textDocument: vscode.TextDocument): string { - return textDocument.getText() - } - - /** - * Sets up the webview panel for Workflow Studio Editor. This includes creating the - * panel, setting up the webview content, and handling the communication between the webview - * and the extension context. - * @param textDocument The text document to be displayed in the webview panel. - * @private - */ - private setupWebviewPanel(textDocument: vscode.TextDocument) { - const documentUri = textDocument.uri - - const contextObject: WebviewContext = { - panel: this.webviewPanel, - textDocument: textDocument, - disposables: this.disposables, - workSpacePath: this.workSpacePath, - defaultTemplatePath: this.defaultTemplatePath, - defaultTemplateName: this.defaultTemplateName, - fileStates: this.fileStates, - loaderNotification: undefined, - fileId: this.fileId, - mode: this.mode, - stateMachineName: this.stateMachineName, - } - - void vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: i18n('AWS.stepFunctions.workflowStudio.actions.progressMessage'), - cancellable: true, - }, - async (progress, token) => { - token.onCancellationRequested(async () => { - // Cancel opening in Worflow Studio and open regular code editor instead - contextObject.panel.dispose() - await vscode.commands.executeCommand('vscode.openWith', documentUri, 'default') - throw new CancellationError('user') - }) - - progress.report({ increment: 0 }) - - return new Promise(async (resolve) => { - contextObject.loaderNotification = { - progress: progress, - cancellationToken: token, - resolve, - } - - // Initialise webview panel for Workflow Studio and set up initial content - this.webviewPanel.webview.options = { - enableScripts: true, - localResourceRoots: [globals.context.extensionUri], - } - - // Set the initial html for the webpage - this.webviewPanel.webview.html = await this.getWebviewContent() - progress.report({ increment: 15 }) - - // The text document acts as our model, thus we send and event to the webview on file save to trigger update - contextObject.disposables.push( - vscode.workspace.onDidSaveTextDocument(async (savedDocument) => { - if (savedDocument.uri.toString() === this.documentUri.toString()) { - await telemetry.stepfunctions_saveFile.run(async (span) => { - span.record({ - id: contextObject.fileId, - saveType: 'MANUAL_SAVE', - source: 'VSCODE', - isInvalidJson: isInvalidJsonFile(contextObject.textDocument), - }) - await broadcastFileChange(contextObject, 'MANUAL_SAVE') - }) - } - }) - ) - - // When rendering StateMachine Graph from CDK applications, we are getting StateMachine ASL definition from the CloudFormation template produced by `cdk synth` - // Track file content update in the CloudFormation template and update the webview to render the updated StateMachine Graph - if (contextObject.mode === WorkflowMode.Readonly) { - const watcher = vscode.workspace.createFileSystemWatcher(contextObject.textDocument.uri.fsPath) - watcher.onDidChange(async () => { - await telemetry.stepfunctions_saveFile.run(async (span) => { - span.record({ - id: contextObject.fileId, - saveType: 'AUTO_SAVE', - source: 'CFN_TEMPLATE', - isInvalidJson: isInvalidJsonFile(contextObject.textDocument), - }) - await broadcastFileChange(contextObject, 'MANUAL_SAVE') - }) - }) - - contextObject.disposables.push(watcher) - } - - // Handle messages from the webview - this.disposables.push( - this.webviewPanel.webview.onDidReceiveMessage(async (message) => { - await handleMessage(message, contextObject) - }) - ) - - // Track webview focus to suppress VSCode's default undo, as WFS has its own - await setContext('aws.stepFunctions.isWorkflowStudioFocused', true) - this.disposables.push( - this.webviewPanel.onDidChangeViewState(async (event) => { - if (event.webviewPanel.active) { - await setContext('aws.stepFunctions.isWorkflowStudioFocused', true) - } else { - await setContext('aws.stepFunctions.isWorkflowStudioFocused', false) - } - }) - ) - - // When the panel is closed, dispose of any disposables/remove subscriptions - this.disposables.push( - this.webviewPanel.onDidDispose(async () => { - if (this.isPanelDisposed) { - return - } - - await setContext('aws.stepFunctions.isWorkflowStudioFocused', false) - this.isPanelDisposed = true - resolve() - this.onVisualizationDisposeEmitter.fire() - for (const disposable of this.disposables) { - disposable.dispose() - } - this.onVisualizationDisposeEmitter.dispose() - }) - ) - progress.report({ increment: 15 }) - }) - } - ) - } -} diff --git a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts b/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts deleted file mode 100644 index 797ac80fbe..0000000000 --- a/packages/core/src/stepFunctions/workflowStudio/workflowStudioEditorProvider.ts +++ /dev/null @@ -1,225 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { getLogger } from '../../shared/logger/logger' -import request from '../../shared/request' -import { getClientId } from '../../shared/telemetry/util' -import { telemetry } from '../../shared/telemetry/telemetry' -import globals from '../../shared/extensionGlobals' -import { getStringHash } from '../../shared/utilities/textUtilities' -import { ToolkitError } from '../../shared/errors' -import { getTabSizeSetting } from '../../shared/utilities/editorUtilities' -import { WorkflowStudioEditor } from './workflowStudioEditor' -import { i18n } from '../../shared/i18n-helper' -import { isInvalidJsonFile, isInvalidYamlFile } from '../utils' -import { WorkflowMode } from './types' - -const isLocalDev = false -const localhost = 'http://127.0.0.1:3002' -const cdn = 'https://d5t62uwepi9lu.cloudfront.net' -let clientId = '' - -/** - * Provider for Workflow Studio editors. - * - * Workflow Studio editor is used for `.asl.json`, `.asl.yaml` and `.asl.yml` files. - * To get started, run this extension and open any file with one of these extensions in VS Code. - */ -export class WorkflowStudioEditorProvider implements vscode.CustomTextEditorProvider { - public static readonly viewType = 'workflowStudio.asl' - - /** - * Opens a file in the Workflow Studio editor, after validating the document. - * @param uri The URI of the document to open in the Workflow Studio editor. - * @param params Optional parameters to customize the WebView panel. - */ - public static async openWithWorkflowStudio( - uri: vscode.Uri, - params?: Parameters[2] - ) { - const document = await vscode.workspace.openTextDocument(uri) - await telemetry.stepfunctions_openWorkflowStudio.run(async () => { - await this.validateAndThrowIfInvalid(document) - await vscode.commands.executeCommand('vscode.openWith', uri, WorkflowStudioEditorProvider.viewType, params) - }) - } - - /** - * Validates the document to ensure it is either valid JSON or YAML. - * If the document is invalid, a warning is shown (if specified) and an error is thrown to stop further execution. - * @param document The document to validate. - * @param alertOnError If true, shows a warning message when the document is invalid. Defaults to `false`. - */ - private static async validateAndThrowIfInvalid( - document: vscode.TextDocument, - alertOnError?: boolean - ): Promise { - const isInvalidJson = isInvalidJsonFile(document) - const isInvalidYaml = isInvalidYamlFile(document) - - if (isInvalidJson || isInvalidYaml) { - const language = isInvalidJson ? 'JSON' : 'YAML' - const errorKey = isInvalidJson ? 'InvalidJSONContent' : 'InvalidYAMLContent' - - if (alertOnError) { - void vscode.window.showErrorMessage(i18n(`AWS.stepFunctions.workflowStudio.actions.${errorKey}`)) - } - - throw ToolkitError.chain( - `Invalid ${language} file`, - `The Workflow Studio editor was not opened because the ${language} in the file is invalid`, - { code: errorKey } - ) - } - } - - /** - * Registers a new custom editor provider for asl files. - * @remarks This should only be called once per extension. - * @param context The extension context - */ - public static register(): vscode.Disposable { - const provider = new WorkflowStudioEditorProvider() - return vscode.window.registerCustomEditorProvider(WorkflowStudioEditorProvider.viewType, provider, { - webviewOptions: { - enableFindWidget: true, - retainContextWhenHidden: true, // Retain content when switching tabs - }, - }) - } - - protected webviewHtml: string - protected readonly managedVisualizations = new Map() - protected readonly logger = getLogger() - - constructor() { - this.webviewHtml = '' - } - - /** - * Fetches the webview HTML from the CDN or local server. - * @private - */ - private async fetchWebviewHtml() { - const source = isLocalDev ? localhost : cdn - const response = await request.fetch('GET', `${source}/index.html`).response - this.webviewHtml = await response.text() - - for (const visualization of this.managedVisualizations.values()) { - await visualization.refreshPanel() - } - } - - /** - * Gets the webview content for Workflow Studio. - * @private - */ - private getWebviewContent = async (mode: WorkflowMode) => { - const htmlFileSplit = this.webviewHtml.split('') - - // Set asset source to CDN - const source = isLocalDev ? localhost : cdn - const baseTag = `` - - // Set locale, dark mode - const locale = vscode.env.language - const localeTag = `` - const theme = vscode.window.activeColorTheme.kind - const isDarkMode = theme === vscode.ColorThemeKind.Dark || theme === vscode.ColorThemeKind.HighContrast - const tabSizeTag = `` - const darkModeTag = `` - - const modeTag = `` - return `${htmlFileSplit[0]} ${baseTag} ${localeTag} ${darkModeTag} ${tabSizeTag} ${modeTag} ${htmlFileSplit[1]}` - } - - /** - * Called when the custom editor is opened. - * @param document: The document to be displayed in the editor. - * @param webviewPanel: The webview panel that the editor should be displayed in. - * @param _token: A cancellation token that can be used to cancel the editor. - * @returns A promise that resolves when the editor is resolved. - */ - public async resolveCustomTextEditor( - document: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - _token: vscode.CancellationToken - ): Promise { - const uriSearchParams = new URLSearchParams(document.uri.query) - const stateMachineName = uriSearchParams.get('statemachineName') || '' - const workflowMode = (uriSearchParams.get('workflowMode') as WorkflowMode) || WorkflowMode.Editable - await telemetry.stepfunctions_openWorkflowStudio.run(async () => { - const reopenWithDefaultEditor = async () => { - await vscode.commands.executeCommand('vscode.openWith', document.uri, 'default') - webviewPanel.dispose() - } - - try { - await WorkflowStudioEditorProvider.validateAndThrowIfInvalid(document, true) - } catch (e) { - // If the document is invalid, reopen it with the default editor and re-throw the error - await reopenWithDefaultEditor() - throw e - } - - if (!this.webviewHtml) { - try { - await this.fetchWebviewHtml() - } catch (e) { - await reopenWithDefaultEditor() - - void vscode.window.showWarningMessage( - i18n('AWS.stepFunctions.workflowStudio.actions.webviewFetchFailed') - ) - throw ToolkitError.chain( - 'Failed to fetch editor content', - 'Could not retrieve content for the Workflow Studio editor', - { - code: 'webviewFetchFailed', - } - ) - } - } - - if (clientId === '') { - clientId = getClientId(globals.globalState) - } - - const existingVisualization = this.managedVisualizations.get(document.uri.fsPath) - if (existingVisualization) { - // Prevent opening multiple custom editors for a single file - await reopenWithDefaultEditor() - } else { - // Construct new visualization - try { - const fileId = getStringHash(`${document.uri.fsPath}${clientId}`) - const newVisualization = new WorkflowStudioEditor( - document, - webviewPanel, - fileId, - () => this.getWebviewContent(workflowMode), - workflowMode, - stateMachineName - ) - this.handleNewVisualization(document.uri.fsPath, newVisualization) - } catch (err) { - throw ToolkitError.chain(err, 'Could not open Workflow Studio editor', { - code: 'OpenWorkflowStudioFailed', - }) - } - } - }) - } - - protected handleNewVisualization(key: string, visualization: WorkflowStudioEditor): void { - this.managedVisualizations.set(key, visualization) - - const visualizationDisposable = visualization.onVisualizationDisposeEvent(() => { - this.managedVisualizations.delete(key) - }) - globals.context.subscriptions.push(visualizationDisposable) - } -} diff --git a/packages/core/src/templates/templates.json b/packages/core/src/templates/templates.json deleted file mode 100644 index 96a85cbec9..0000000000 --- a/packages/core/src/templates/templates.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "$id": "https://github.com/aws/amazon-q-vscode/tree/main/src/templates/templates.json", - "$schema": "http://json-schema.org/draft-07/schema", - "type": "object", - "properties": { - "templates": { - "type": "object", - "description": "Holds configurations for all SAM Templates within the Workspace Folder.", - "patternProperties": { - ".*": { - "type": "object", - "properties": { - "parameterOverrides": { - "type": "object", - "default": {}, - "description": "Configures the SAM Template's parameter values used when deploying the template.", - "patternProperties": { - ".*": { - "type": "string" - } - } - }, - "handlers": { - "type": "object", - "default": {}, - "description": "Holds configurations for each Lambda function contained in this SAM Template.", - "patternProperties": { - ".*": { - "type": "object", - "description": "Configuration for a Lambda handler within a SAM Template.", - "properties": { - "event": { - "type": "object", - "default": {}, - "description": "Event data passed to the Lambda function when it is invoked locally. If you don't specify this option, an empty payload is used as a default." - }, - "environmentVariables": { - "type": "object", - "default": {}, - "description": "Overrides the environment variable values defined in the SAM Template when running the Lambda function locally." - }, - "dockerNetwork": { - "type": "string", - "default": null, - "description": "If specified, adds a --docker-network parameter to the SAM CLI. If not specified, no parameter will be sent." - }, - "useContainer": { - "type": "boolean", - "default": false, - "description": "If specified, adds a --use-container parameter to the SAM CLI. If not specified, no parameter will be sent. If your functions depend on packages that have natively compiled dependencies use this flag to build your function inside a SAM Docker container." - } - } - } - } - } - } - } - } - } - } -} diff --git a/packages/core/src/test/applicationcomposer/messageHandlers/generateResourceHandler.test.ts b/packages/core/src/test/applicationcomposer/messageHandlers/generateResourceHandler.test.ts deleted file mode 100644 index 72a3a03e15..0000000000 --- a/packages/core/src/test/applicationcomposer/messageHandlers/generateResourceHandler.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { createTemplate, createWebviewContext } from '../utils' -import { generateResourceHandler } from '../../../applicationcomposer/messageHandlers/generateResourceHandler' -import { Command, MessageType } from '../../../applicationcomposer/types' -import { isMinVscode } from '../../../shared/vscode/env' - -describe('generateResourceHandler', function () { - it('amazon q is not installed', async function () { - if (isMinVscode({ throwWhen: '1.89.0' })) { - this.skip() - } - const panel = await createTemplate() - const postMessageSpy = sinon.spy(panel.webview, 'postMessage') - const context = await createWebviewContext({ - panel, - }) - await generateResourceHandler( - { - command: Command.GENERATE_RESOURCE, - messageType: MessageType.REQUEST, - cfnType: '', - prompt: '', - traceId: '0', - }, - context - ) - assert.ok(postMessageSpy.calledOnce) - assert.deepStrictEqual(postMessageSpy.getCall(0).args[0].isSuccess, false) - postMessageSpy.restore() - }) -}) diff --git a/packages/core/src/test/applicationcomposer/messageHandlers/initMessageHandler.test.ts b/packages/core/src/test/applicationcomposer/messageHandlers/initMessageHandler.test.ts deleted file mode 100644 index 8c2013a2da..0000000000 --- a/packages/core/src/test/applicationcomposer/messageHandlers/initMessageHandler.test.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { initMessageHandler } from '../../../applicationcomposer/messageHandlers/initMessageHandler' -import { createTemplate, createWebviewContext } from '../utils' -import { isMinVscode } from '../../../shared/vscode/env' - -describe('initMessageHandler', function () { - afterEach(function () { - sinon.restore() - }) - - it('not connected to codewhisperer', async function () { - if (isMinVscode({ throwWhen: '1.89.0' })) { - this.skip() - } - const panel = await createTemplate() - const postMessageSpy = sinon.spy(panel.webview, 'postMessage') - const context = await createWebviewContext({ - panel, - }) - await initMessageHandler(context) - assert.ok(postMessageSpy.calledOnce) - assert.deepStrictEqual(postMessageSpy.getCall(0).args[0].isConnectedToCodeWhisperer, false) - }) -}) diff --git a/packages/core/src/test/applicationcomposer/utils.ts b/packages/core/src/test/applicationcomposer/utils.ts deleted file mode 100644 index fdde28cd3d..0000000000 --- a/packages/core/src/test/applicationcomposer/utils.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ApplicationComposerManager } from '../../applicationcomposer/webviewManager' -import { globals } from '../../shared' -import { WebviewContext } from '../../applicationcomposer/types' -import { MockDocument } from '../fake/fakeDocument' - -export async function createTemplate() { - const manager = await ApplicationComposerManager.create(globals.context) - const panel = await manager.createTemplate() - assert.ok(panel) - return panel -} - -export async function createWebviewContext({ - defaultTemplateName, - defaultTemplatePath, - disposables, - panel, - fileWatches, - textDocument, - workSpacePath, -}: Partial): Promise { - return { - defaultTemplateName: defaultTemplateName ?? '', - defaultTemplatePath: defaultTemplatePath ?? '', - disposables: disposables ?? [], - panel: panel ?? (await createTemplate()), - fileWatches: fileWatches ?? {}, - textDocument: textDocument ?? new MockDocument('', 'foo', async () => true), - workSpacePath: workSpacePath ?? '', - } -} diff --git a/packages/core/src/test/awsExplorer/awsExplorer.test.ts b/packages/core/src/test/awsExplorer/awsExplorer.test.ts deleted file mode 100644 index 64e30470f4..0000000000 --- a/packages/core/src/test/awsExplorer/awsExplorer.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { AwsExplorer } from '../../awsexplorer/awsExplorer' -import { RegionNode } from '../../awsexplorer/regionNode' -import { RegionProvider } from '../../shared/regions/regionProvider' -import { FakeExtensionContext } from '../fakeExtensionContext' -import { - createTestRegionProvider, - DEFAULT_TEST_REGION_CODE, - DEFAULT_TEST_REGION_NAME, -} from '../shared/regions/testUtil' -import { makeFakeAwsContextWithPlaceholderIds } from '../utilities/fakeAwsContext' - -describe('AwsExplorer', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('displays region nodes with user-friendly region names', async function () { - // TODO: add test util to set-up `Auth` in a certain way - this.skip() - - const awsContext = makeFakeAwsContextWithPlaceholderIds({} as any as AWS.Credentials) - const regionProvider = createTestRegionProvider({ awsContext }) - await regionProvider.updateExplorerRegions([DEFAULT_TEST_REGION_CODE]) - - const fakeContext = await FakeExtensionContext.create() - const awsExplorer = new AwsExplorer(fakeContext, regionProvider) - - const treeNodes = await awsExplorer.getChildren() - assert.ok(treeNodes) - assert.strictEqual(treeNodes.length, 1, 'Expected Explorer to have one node') - - assert.ok(treeNodes[0] instanceof RegionNode, 'Expected Explorer node to be RegionNode') - const regionNode = treeNodes[0] as RegionNode - assert.strictEqual(regionNode.regionCode, DEFAULT_TEST_REGION_CODE) - assert.strictEqual(regionNode.regionName, DEFAULT_TEST_REGION_NAME) - }) - - it('refreshes when the Region Provider is updated', async function () { - const fakeContext = await FakeExtensionContext.create() - const endpoints = Promise.resolve({ partitions: [] }) - const regionProvider = RegionProvider.fromEndpointsProvider({ - local: () => endpoints, - remote: () => endpoints, - }) - const awsExplorer = new AwsExplorer(fakeContext, regionProvider) - const refreshStub = sandbox.stub(awsExplorer, 'refresh') - await endpoints - assert.ok(refreshStub.calledOnce, 'expected AWS Explorer to refresh itself') - }) -}) diff --git a/packages/core/src/test/awsExplorer/childNodeCache.test.ts b/packages/core/src/test/awsExplorer/childNodeCache.test.ts deleted file mode 100644 index ecd3849464..0000000000 --- a/packages/core/src/test/awsExplorer/childNodeCache.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ChildNodeCache } from '../../awsexplorer/childNodeCache' -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../shared/treeview/nodes/loadMoreNode' - -class FakeNode extends AWSTreeNodeBase implements LoadMoreNode { - public constructor(public name: string) { - super('FakeNode') - } - - public loadMoreChildren(): Promise { - return Promise.resolve() - } - - public isLoadingMoreChildren(): boolean { - return false - } - - public clearChildren(): void {} -} - -describe('ChildNodeCache', function () { - const continuationToken = 'continuationToken' - const fakeNode = new FakeNode('fakeNode') - const anotherFakeNode = new FakeNode('anotherFakeNode') - - it('starts empty, with no continuation token, and is pristine', function () { - const cache = new ChildNodeCache() - - assert.deepStrictEqual(cache.children, []) - assert.strictEqual(cache.continuationToken, undefined) - assert.strictEqual(cache.isPristine, true) - }) - - it('appends initial items and updates continuation token and pristine state', function () { - const cache = new ChildNodeCache() - cache.appendPage({ newChildren: [fakeNode, anotherFakeNode], newContinuationToken: continuationToken }) - - assert.deepStrictEqual(cache.children, [fakeNode, anotherFakeNode]) - assert.strictEqual(cache.continuationToken, continuationToken) - assert.strictEqual(cache.isPristine, false) - }) - - it('appends additional items', function () { - const newFakeNode = new FakeNode('newFakeNode') - - const cache = new ChildNodeCache() - cache.appendPage({ newChildren: [fakeNode, anotherFakeNode], newContinuationToken: continuationToken }) - cache.appendPage({ newChildren: [newFakeNode], newContinuationToken: undefined }) - assert.deepStrictEqual(cache.children, [fakeNode, anotherFakeNode, newFakeNode]) - assert.strictEqual(cache.continuationToken, undefined) - assert.strictEqual(cache.isPristine, false) - }) -}) diff --git a/packages/core/src/test/awsExplorer/childNodeLoader.test.ts b/packages/core/src/test/awsExplorer/childNodeLoader.test.ts deleted file mode 100644 index b2c4a81dd8..0000000000 --- a/packages/core/src/test/awsExplorer/childNodeLoader.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ChildNodeLoader } from '../../awsexplorer/childNodeLoader' -import { MoreResultsNode } from '../../awsexplorer/moreResultsNode' -import { AWSTreeNodeBase } from '../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../shared/treeview/nodes/loadMoreNode' - -class FakeNode extends AWSTreeNodeBase { - public constructor() { - super('FakeNode') - } -} - -class FakeLoadMore extends FakeNode implements LoadMoreNode { - public loadMoreChildren(): Promise { - return Promise.resolve() - } - - public isLoadingMoreChildren(): boolean { - return false - } - - public clearChildren(): void {} -} - -describe('ChildNodeLoader', function () { - const fakeNode = new FakeNode() - const fakeLoadMore = new FakeLoadMore() - - function childLoader(): ChildNodeLoader { - return continuedChildLoader() - } - - function continuedChildLoader(continuationToken?: string): ChildNodeLoader { - return new ChildNodeLoader(fakeLoadMore, (_token) => - Promise.resolve({ - newChildren: [fakeNode], - newContinuationToken: continuationToken, - }) - ) - } - - function delayedChildLoader(continuationToken: string): ChildNodeLoader { - return new ChildNodeLoader(fakeLoadMore, (_token) => { - return new Promise((resolve) => - setTimeout(() => - resolve({ - newChildren: [fakeNode], - newContinuationToken: continuationToken, - }) - ) - ) - }) - } - - describe('first call to getChildren', function () { - it('loads and returns initial children', async function () { - const loader = childLoader() - - const [firstNode, ...otherNodes] = await loader.getChildren() - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(otherNodes.length, 0) - }) - - it('loads and returns initial children with more results', async function () { - const loader = continuedChildLoader('token') - - const [firstNode, moreResultsNode, ...otherNodes] = await loader.getChildren() - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual((moreResultsNode as MoreResultsNode).parent, fakeLoadMore) - assert.strictEqual(otherNodes.length, 0) - }) - }) - - describe('subsequent calls to getChildren', function () { - it('returns existing children', async function () { - const loader = childLoader() - - await loader.getChildren() - const [firstNode, ...otherNodes] = await loader.getChildren() - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) - - describe('loadMoreChildren', function () { - it('loads and appends next page of children', async function () { - const loader = continuedChildLoader('token') - - await loader.getChildren() - await loader.loadMoreChildren() - const [firstNode, secondNode, moreResultsNode, ...otherNodes] = await loader.getChildren() - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(secondNode, fakeNode) - assert.strictEqual((moreResultsNode as MoreResultsNode).parent, fakeLoadMore) - assert.strictEqual(otherNodes.length, 0) - }) - - it('has no effect if all children already loaded', async function () { - const loader = childLoader() - - await loader.getChildren() - await loader.loadMoreChildren() - await loader.loadMoreChildren() - const [firstNode, ...otherNodes] = await loader.getChildren() - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(otherNodes.length, 0) - }) - - it('queues up concurrent calls', async function () { - const loader = delayedChildLoader('token') - - const firstCall = loader.getChildren() - const secondCall = loader.loadMoreChildren() - const thirdCall = loader.loadMoreChildren() - const fourthCall = loader.getChildren() - - const [, , , children] = await Promise.all([firstCall, secondCall, thirdCall, fourthCall]) - const [firstNode, secondNode, thirdNode, moreResultsNode, ...otherNodes] = children - - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(secondNode, fakeNode) - assert.strictEqual(thirdNode, fakeNode) - assert.strictEqual((moreResultsNode as MoreResultsNode).parent, fakeLoadMore) - assert.strictEqual(otherNodes.length, 0) - }) - }) - - describe('clearChildren', function () { - it('resets cache', async function () { - const loader = childLoader() - - await loader.getChildren() - await loader.loadMoreChildren() - await loader.loadMoreChildren() - loader.clearChildren() - - const [firstNode, ...otherNodes] = await loader.getChildren() - assert.strictEqual(firstNode, fakeNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsExplorer/commands/copyText.test.ts b/packages/core/src/test/awsExplorer/commands/copyText.test.ts deleted file mode 100644 index 801462d6ea..0000000000 --- a/packages/core/src/test/awsExplorer/commands/copyText.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { copyTextCommand } from '../../../awsexplorer/commands/copyText' -import { AWSResourceNode } from '../../../shared/treeview/nodes/awsResourceNode' -import { TreeShim } from '../../../shared/treeview/utils' -import { FakeClipboard } from '../../shared/vscode/fakeEnv' - -describe('copyTextCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - it('copies name to clipboard and shows status bar confirmation', async function () { - const node: AWSResourceNode = { - arn: 'arn', - name: 'name', - } - - await copyTextCommand(node, 'name') - - assert.strictEqual(await vscode.env.clipboard.readText(), 'name') - }) - - it('handles `TreeShim`', async function () { - const node = new TreeShim({ - id: 'shim', - resource: { name: 'resource', arn: 'arn' }, - getTreeItem: () => new vscode.TreeItem(''), - }) - - await copyTextCommand(node, 'name') - assert.strictEqual(await vscode.env.clipboard.readText(), 'resource') - }) - - it('copies arn to clipboard and shows status bar confirmation', async function () { - const node: AWSResourceNode = { - arn: 'arn', - name: 'name', - } - - await copyTextCommand(node, 'ARN') - - assert.strictEqual(await vscode.env.clipboard.readText(), 'arn') - }) - - it('copies id to clipboard and shows status bar confirmation', async function () { - const node: AWSResourceNode = { - arn: 'arn', - name: 'name', - id: 'id', - } - - await copyTextCommand(node, 'id') - - assert.strictEqual(await vscode.env.clipboard.readText(), 'id') - }) -}) diff --git a/packages/core/src/test/awsExplorer/commands/loadMoreChildren.test.ts b/packages/core/src/test/awsExplorer/commands/loadMoreChildren.test.ts deleted file mode 100644 index 18448a9cf1..0000000000 --- a/packages/core/src/test/awsExplorer/commands/loadMoreChildren.test.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { AwsExplorer } from '../../../awsexplorer/awsExplorer' -import { loadMoreChildren } from '../../../awsexplorer/commands/loadMoreChildren' -import { ToolkitError } from '../../../shared/errors' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../shared/treeview/nodes/loadMoreNode' -import { Stub, stub } from '../../utilities/stubber' -import sinon from 'sinon' - -describe('loadMoreChildren', function () { - let mockNode: AWSTreeNodeBase & LoadMoreNode - let mockAwsExplorer: Stub - - beforeEach(function () { - mockNode = {} as AWSTreeNodeBase & LoadMoreNode - mockAwsExplorer = stub(AwsExplorer, { - viewProviderId: '', - }) - mockAwsExplorer.refresh = sinon.stub() - }) - - it('loads more children and refreshes the node', async function () { - const isLoadingStub = sinon.stub().returns(false) - mockNode.isLoadingMoreChildren = isLoadingStub - const loadMoreStub = sinon.stub() - mockNode.loadMoreChildren = loadMoreStub - - await loadMoreChildren(mockAwsExplorer, mockNode) - - assert(isLoadingStub.calledOnce) - assert(loadMoreStub.calledOnce) - assert(mockAwsExplorer.refresh.calledOnceWith(mockNode)) - }) - - it('ignores invocation when load more is already in progress', async function () { - const isLoadingStub = sinon.stub().returns(true) - mockNode.isLoadingMoreChildren = isLoadingStub - const loadMoreStub = sinon.stub() - mockNode.loadMoreChildren = loadMoreStub - - await loadMoreChildren(mockAwsExplorer, mockNode) - - assert(isLoadingStub.calledOnce) - assert(loadMoreStub.notCalled) - assert(mockAwsExplorer.refresh.notCalled) - }) - - it('shows an error message and refreshes the node on failure', async function () { - const isLoadingStub = sinon.stub().returns(false) - mockNode.isLoadingMoreChildren = isLoadingStub - const loadMoreStub = sinon.stub().throws(new Error('Expected failure')) - mockNode.loadMoreChildren = loadMoreStub - - await assert.rejects(() => loadMoreChildren(mockAwsExplorer, mockNode), ToolkitError) - - assert(isLoadingStub.calledOnce) - assert(mockAwsExplorer.refresh.calledOnceWith(mockNode)) - }) -}) diff --git a/packages/core/src/test/awsExplorer/regionNode.test.ts b/packages/core/src/test/awsExplorer/regionNode.test.ts deleted file mode 100644 index fc3108aae0..0000000000 --- a/packages/core/src/test/awsExplorer/regionNode.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { RegionNode } from '../../awsexplorer/regionNode' -import { SchemasNode } from '../../eventSchemas/explorer/schemasNode' -import { - createTestRegionProvider, - DEFAULT_TEST_REGION_CODE, - DEFAULT_TEST_REGION_NAME, -} from '../shared/regions/testUtil' - -describe('RegionNode', function () { - let testNode: RegionNode - - beforeEach(function () { - const regionProvider = createTestRegionProvider() - testNode = new RegionNode({ id: regionCode, name: regionName }, regionProvider) - }) - - const regionCode = DEFAULT_TEST_REGION_CODE - const regionName = DEFAULT_TEST_REGION_NAME - - it('initializes name and tooltip', async function () { - assert.strictEqual(testNode.label, regionName) - assert.strictEqual(testNode.tooltip, `${regionName} [${regionCode}]`) - }) - - it('contains children', async function () { - const childNodes = await testNode.getChildren() - assert.ok(childNodes.length > 0, 'Expected region node to have child nodes') - }) - - it('does not have child nodes for services not available in a region', async function () { - const regionProvider = createTestRegionProvider() - const regionNode = new RegionNode({ id: regionCode, name: regionName }, regionProvider) - - const childNodes = await regionNode.getChildren() - assert.ok( - childNodes.filter((node) => node instanceof SchemasNode).length === 0, - 'Expected Schemas node to be absent from child nodes' - ) - }) -}) diff --git a/packages/core/src/test/awsService/accessanalyzer/iamPolicyChecks.test.ts b/packages/core/src/test/awsService/accessanalyzer/iamPolicyChecks.test.ts deleted file mode 100644 index 15f1c39850..0000000000 --- a/packages/core/src/test/awsService/accessanalyzer/iamPolicyChecks.test.ts +++ /dev/null @@ -1,770 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { - _readCustomChecksFile, - getResultCssColor, - IamPolicyChecksWebview, - PolicyChecksError, -} from '../../../awsService/accessanalyzer/vue/iamPolicyChecks' -import { globals } from '../../../shared' -import { AccessAnalyzerClient } from '@aws-sdk/client-accessanalyzer' -import * as s3Client from '../../../shared/clients/s3' -import { S3Client } from '../../../shared/clients/s3' -import * as iamPolicyChecks from '../../../awsService/accessanalyzer/vue/iamPolicyChecks' -import * as vscode from 'vscode' -import { IamPolicyChecksConstants } from '../../../awsService/accessanalyzer/vue/constants' -import { FileSystem } from '../../../shared/fs/fs' -import path from 'path' - -const defaultTerraformConfigPath = 'resources/policychecks-tf-default.yaml' -let sandbox: sinon.SinonSandbox -let fakePolicyChecksWebview: IamPolicyChecksWebview -const vscodeFs = FileSystem.instance - -describe('iamPolicyChecks', function () { - it('IamPolicyChecksWebview built .vue source file path exists', async function () { - assert.ok( - await vscodeFs.existsFile( - path.join( - path.dirname(globals.context.extensionPath), - `core/src/awsService/accessanalyzer/vue/iamPolicyChecks.vue` - ) - ) - ) - }) - - it('default Terraform config exists', async function () { - assert.ok(await vscodeFs.existsFile(globals.context.asAbsolutePath(defaultTerraformConfigPath))) - }) -}) - -describe('_readCustomChecksFile', () => { - let parseS3UriStub: sinon.SinonStub - let s3ClientStub: sinon.SinonStubbedInstance - sandbox = sinon.createSandbox() - - beforeEach(() => { - parseS3UriStub = sandbox.stub(s3Client, 'parseS3Uri') - s3ClientStub = sandbox.createStubInstance(S3Client) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should read file content if file exists', async () => { - const filePath = globals.context.asAbsolutePath(defaultTerraformConfigPath) - const fileContent = await vscodeFs.readFileText(filePath) - - const result = await _readCustomChecksFile(filePath) - - assert.strictEqual(result, fileContent) - }) - - it('should throw PolicyChecksError for invalid S3 URI', async () => { - const invalidS3Uri = 's3://invalid-uri' - - try { - await _readCustomChecksFile(invalidS3Uri) - assert.fail('Expected method to throw.') - } catch (error) { - assert(error instanceof PolicyChecksError) - } - }) - - it('should throw PolicyChecksError for other errors', async () => { - const s3Uri = 's3://bucket/key' - - parseS3UriStub.withArgs(s3Uri).returns(['region', 'bucket', 'key']) - s3ClientStub.getObject.rejects(new Error('Toolkit is not logged-in.')) - - try { - await _readCustomChecksFile(s3Uri) - assert.fail('Expected method to throw.') - } catch (error) { - assert(error instanceof PolicyChecksError) - } - }) -}) - -describe('validatePolicy', function () { - let onValidatePolicyResponseSpy: sinon.SinonSpy - let executeCommandStub: sinon.SinonStub - let pushValidatePolicyDiagnosticStub: sinon.SinonStub - let validateDiagnosticSetStub: sinon.SinonStub - const client = new AccessAnalyzerClient() - const validatePolicyMock = sinon.mock(client) - - beforeEach(function () { - sandbox = sinon.createSandbox() - const initialData = { - cfnParameterPath: '', - checkAccessNotGrantedActionsTextArea: '', - checkAccessNotGrantedFilePath: '', - checkAccessNotGrantedResourcesTextArea: '', - checkNoNewAccessFilePath: '', - checkNoNewAccessTextArea: '', - customChecksFileErrorMessage: '', - pythonToolsInstalled: false, - } - fakePolicyChecksWebview = new IamPolicyChecksWebview(initialData, client, 'us-east-1') - - pushValidatePolicyDiagnosticStub = sandbox.stub(fakePolicyChecksWebview, 'pushValidatePolicyDiagnostic') - executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand') - onValidatePolicyResponseSpy = sandbox.spy(fakePolicyChecksWebview.onValidatePolicyResponse, 'fire') - validateDiagnosticSetStub = sandbox.stub(iamPolicyChecks.validatePolicyDiagnosticCollection, 'set') - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should handle JSON Policy Language correctly', async function () { - const policyType = 'Identity' - const documentType = 'JSON Policy Language' - - sandbox.stub(fakePolicyChecksWebview, 'executeValidatePolicyCommand') - sandbox.stub(iamPolicyChecks, 'isJsonPolicyLanguage').returns(true) - IamPolicyChecksWebview.editedDocumentFileName = 'test.json' - await fakePolicyChecksWebview.validatePolicy(documentType, policyType) - validatePolicyMock.verify() - }) - - it('should handle Terraform Plan correctly', async function () { - const documentType = 'Terraform Plan' - - IamPolicyChecksWebview.editedDocumentFileName = 'test.json' - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - const executeValidatePolicySpy = sandbox.spy(fakePolicyChecksWebview, 'executeValidatePolicyCommand') - await fakePolicyChecksWebview.validatePolicy(documentType, 'Identity') - - assert(executeValidatePolicySpy.called) - assert.deepStrictEqual(executeValidatePolicySpy.getCalls()[0].args[0], { - command: 'tf-policy-validator', - args: [ - 'validate', - '--template-path', - IamPolicyChecksWebview.editedDocumentFileName, - '--region', - 'us-east-1', - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - 'undefined', - ], - cfnParameterPathExists: false, - documentType, - policyType: 'Identity', - }) - }) - - it('should handle CloudFormation correctly', async function () { - const documentType = 'CloudFormation' - const cfnParameterPath = 'path/to/parameters' - - IamPolicyChecksWebview.editedDocumentFileName = 'test.yaml' - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(true) - const executeValidatePolicySpy = sandbox.spy(fakePolicyChecksWebview, 'executeValidatePolicyCommand') - await fakePolicyChecksWebview.validatePolicy(documentType, 'Identity', cfnParameterPath) - - assert(executeValidatePolicySpy.called) - assert.deepStrictEqual(executeValidatePolicySpy.getCalls()[0].args[0], { - command: 'cfn-policy-validator', - args: [ - 'validate', - '--template-path', - IamPolicyChecksWebview.editedDocumentFileName, - '--region', - 'us-east-1', - '--profile', - 'undefined', - '--template-configuration-file', - cfnParameterPath, - ], - cfnParameterPathExists: true, - documentType, - policyType: 'Identity', - }) - }) - - it('handleValidatePolicyCliResponse no findings', function () { - const response = JSON.stringify({ - BlockingFindings: [], - NonBlockingFindings: [], - }) - - const findingsCount = fakePolicyChecksWebview.handleValidatePolicyCliResponse(response) - - assert.strictEqual(findingsCount, 0) - assert( - onValidatePolicyResponseSpy.calledOnceWith([ - IamPolicyChecksConstants.ValidatePolicySuccessNoFindings, - getResultCssColor('Success'), - ]) - ) - assert(executeCommandStub.notCalled) - }) - - it('handleValidatePolicyCliResponse should handle blocking and non-blocking findings correctly', function () { - const response = JSON.stringify({ - BlockingFindings: [ - { id: 1, message: 'Blocking finding 1' }, - { id: 2, message: 'Blocking finding 2' }, - ], - NonBlockingFindings: [{ id: 3, message: 'Non-blocking finding 1' }], - }) - - const findingsCount = fakePolicyChecksWebview.handleValidatePolicyCliResponse(response) - - assert.strictEqual(findingsCount, 3) - - assert(pushValidatePolicyDiagnosticStub.calledThrice) - assert(pushValidatePolicyDiagnosticStub.calledWith(sinon.match.any, sinon.match.object, true)) - assert(pushValidatePolicyDiagnosticStub.calledWith(sinon.match.any, sinon.match.object, false)) - - assert( - onValidatePolicyResponseSpy.calledOnceWith([ - IamPolicyChecksConstants.ValidatePolicySuccessWithFindings, - getResultCssColor('Warning'), - ]) - ) - assert(executeCommandStub.calledOnceWith('workbench.actions.view.problems')) - }) - - it('handleValidatePolicyCliResponse should handle JSON parse errors', function () { - const response = 'invalid json' - - assert.throws( - () => { - fakePolicyChecksWebview.handleValidatePolicyCliResponse(response) - }, - { - name: 'SyntaxError', - } - ) - - assert(onValidatePolicyResponseSpy.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('pushValidatePolicyDiagnostic should push a blocking diagnostic correctly', function () { - const diagnostics: vscode.Diagnostic[] = [] - const finding = { - findingType: 'ERROR', - code: 'E001', - details: { - findingDetails: 'Details about the finding', - learnMoreLink: 'http://example.com', - }, - resourceName: 'testResource', - policyName: 'testPolicy', - } - const isBlocking = true - pushValidatePolicyDiagnosticStub.restore() - fakePolicyChecksWebview.pushValidatePolicyDiagnostic(diagnostics, finding, isBlocking) - - assert.strictEqual(diagnostics.length, 1) - const diagnostic = diagnostics[0] - assert.deepStrictEqual(diagnostic.range, new vscode.Range(0, 0, 0, 0)) - assert.strictEqual( - diagnostic.message, - 'ERROR: E001 - Details about the finding Resource name: testResource, Policy name: testPolicy. Learn more: http://example.com' - ) - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Error) - assert(validateDiagnosticSetStub.calledOnce) - }) - - it('pushValidatePolicyDiagnostic should push a non-blocking diagnostic correctly', function () { - const diagnostics: vscode.Diagnostic[] = [] - const finding = { - findingType: 'WARNING', - code: 'W001', - details: { - findingDetails: 'Details about the warning', - learnMoreLink: 'http://example.com', - }, - resourceName: 'testResource', - policyName: 'testPolicy', - } - const isBlocking = false - pushValidatePolicyDiagnosticStub.restore() - fakePolicyChecksWebview.pushValidatePolicyDiagnostic(diagnostics, finding, isBlocking) - - assert.strictEqual(diagnostics.length, 1) - const diagnostic = diagnostics[0] - assert.deepStrictEqual(diagnostic.range, new vscode.Range(0, 0, 0, 0)) - assert.strictEqual( - diagnostic.message, - 'WARNING: W001 - Details about the warning Resource name: testResource, Policy name: testPolicy. Learn more: http://example.com' - ) - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Warning) - assert(validateDiagnosticSetStub.calledOnce) - }) -}) - -describe('customChecks', function () { - let executeCommandStub: sinon.SinonStub - let pushCustomPolicyCheckDiagnosticStub: sinon.SinonStub - let executeCustomPolicyChecksCommandStub: sinon.SinonStub - let customPolicyDiagnosticSetStub: sinon.SinonStub - let onCustomPolicyCheckResponseFireSpy: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - const client = AccessAnalyzerClient.prototype - const initialData = { - cfnParameterPath: '', - checkAccessNotGrantedActionsTextArea: '', - checkAccessNotGrantedFilePath: '', - checkAccessNotGrantedResourcesTextArea: '', - checkNoNewAccessFilePath: '', - checkNoNewAccessTextArea: '', - customChecksFileErrorMessage: '', - pythonToolsInstalled: false, - } - fakePolicyChecksWebview = new IamPolicyChecksWebview(initialData, client, 'us-east-1') - - pushCustomPolicyCheckDiagnosticStub = sandbox.stub(fakePolicyChecksWebview, 'pushCustomCheckDiagnostic') - executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand') - onCustomPolicyCheckResponseFireSpy = sandbox.spy(fakePolicyChecksWebview.onCustomPolicyCheckResponse, 'fire') - customPolicyDiagnosticSetStub = sandbox.stub(iamPolicyChecks.customPolicyCheckDiagnosticCollection, 'set') - }) - - afterEach(function () { - onCustomPolicyCheckResponseFireSpy.restore() - executeCommandStub.restore() - customPolicyDiagnosticSetStub.restore() - sandbox.restore() - }) - - it('checkNoNewAccess should handle Terraform Plan correctly', async function () { - const documentType = 'Terraform Plan' - const policyType = 'Identity' - const referenceDocument = '{"some": "reference"}' - const document = 'path/to/terraform.json' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoNewAccess(documentType, policyType, referenceDocument, cfnParameterPath) - - // We do not want to validate the path of the temporary folder, so we check every other field instead of the entire args - assert(executeCustomPolicyChecksCommandStub.called) - const actualCommand = executeCustomPolicyChecksCommandStub.getCalls()[0].args[0] - assert.deepStrictEqual(actualCommand.command, 'tf-policy-validator') - assert.deepStrictEqual(actualCommand.args[0], 'check-no-new-access') - assert.deepStrictEqual(actualCommand.args[2], document) - assert.deepStrictEqual(actualCommand.args[4], 'us-east-1') - assert.deepStrictEqual(actualCommand.args[6], `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`) - assert.deepStrictEqual(actualCommand.args[10], policyType) - assert.deepStrictEqual(actualCommand.cfnParameterPathExists, true) - assert.deepStrictEqual(actualCommand.documentType, documentType) - assert.deepStrictEqual(actualCommand.checkType, 'CheckNoNewAccess') - assert.deepStrictEqual(actualCommand.referencePolicyType, policyType) - - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - }) - - it('checkNoNewAccess should handle CloudFormation document type correctly', async function () { - const documentType = 'CloudFormation' - const policyType = 'Resource' - const referenceDocument = '{"some": "reference"}' - const document = 'path/to/cloudformation.yaml' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(true) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(false) - - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoNewAccess(documentType, policyType, referenceDocument, cfnParameterPath) - - assert(executeCustomPolicyChecksCommandStub.called) - const actualCommand = executeCustomPolicyChecksCommandStub.getCalls()[0].args[0] - assert.deepStrictEqual(actualCommand.command, 'cfn-policy-validator') - assert.deepStrictEqual(actualCommand.args[0], 'check-no-new-access') - assert.deepStrictEqual(actualCommand.args[2], document) - assert.deepStrictEqual(actualCommand.args[4], 'us-east-1') - assert.deepStrictEqual(actualCommand.args[8], policyType) - assert.deepStrictEqual(actualCommand.cfnParameterPathExists, true) - assert.deepStrictEqual(actualCommand.documentType, documentType) - assert.deepStrictEqual(actualCommand.checkType, 'CheckNoNewAccess') - assert.deepStrictEqual(actualCommand.referencePolicyType, policyType) - - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - }) - - it('checkNoNewAccess should handle missing reference document', async function () { - const documentType = 'Terraform Plan' - const policyType = 'Identity' - const referenceDocument = '' // Empty document - const document = 'path/to/terraform.json' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoNewAccess(documentType, policyType, referenceDocument) - - assert( - onCustomPolicyCheckResponseFireSpy.calledOnceWith([ - IamPolicyChecksConstants.MissingReferenceDocError, - getResultCssColor('Error'), - ]) - ) - assert(executeCommandStub.notCalled) - assert(executeCustomPolicyChecksCommandStub.notCalled) - }) - - it('checkAccessNotGranted should handle Terraform Plan document type correctly', async function () { - const documentType = 'Terraform Plan' - const actions = 'action1 action2' - const resources = 'resource1 resource2' - const document = 'path/to/terraform.json' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkAccessNotGranted(documentType, actions, resources, cfnParameterPath) - - assert( - executeCustomPolicyChecksCommandStub.calledOnceWith({ - command: 'tf-policy-validator', - args: [ - 'check-access-not-granted', - '--template-path', - document, - '--region', - 'us-east-1', - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - 'undefined', - '--actions', - 'action1action2', - '--resources', - 'resource1resource2', - ], - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckAccessNotGranted', - }) - ) - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('checkAccessNotGranted should handle CloudFormation document type correctly', async function () { - const documentType = 'CloudFormation' - const actions = 'action1 action2' - const resources = 'resource1 resource2' - const document = 'path/to/cloudformation.yaml' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(false) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(true) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkAccessNotGranted(documentType, actions, resources, cfnParameterPath) - - assert( - executeCustomPolicyChecksCommandStub.calledOnceWith({ - command: 'cfn-policy-validator', - args: [ - 'check-access-not-granted', - '--template-path', - document, - '--region', - 'us-east-1', - '--profile', - 'undefined', - '--actions', - 'action1action2', - '--resources', - 'resource1resource2', - '--template-configuration-file', - cfnParameterPath, - ], - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckAccessNotGranted', - }) - ) - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('checkAccessNotGranted should handle missing actions and resources', async function () { - const documentType = 'Terraform Plan' - const actions = '' - const resources = '' - const document = 'path/to/terraform.json' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkAccessNotGranted(documentType, actions, resources) - - assert( - onCustomPolicyCheckResponseFireSpy.calledOnceWith([ - IamPolicyChecksConstants.MissingActionsOrResourcesError, - getResultCssColor('Error'), - ]) - ) - assert(executeCustomPolicyChecksCommandStub.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('checkNoPublicAccess should handle Terraform Plan document type correctly', async function () { - const documentType = 'Terraform Plan' - const document = 'path/to/terraform.json' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(true) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoPublicAccess(documentType, cfnParameterPath) - - assert( - executeCustomPolicyChecksCommandStub.calledOnceWith({ - command: 'tf-policy-validator', - args: [ - 'check-no-public-access', - '--template-path', - document, - '--region', - 'us-east-1', - '--config', - `${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`, - '--profile', - 'undefined', - ], - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoPublicAccess', - }) - ) - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('checkNoPublicAccess should handle CloudFormation document type correctly', async function () { - const documentType = 'CloudFormation' - const document = 'path/to/cloudformation.yaml' - const cfnParameterPath = 'path/to/cfn-params' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(false) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(true) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoPublicAccess(documentType, cfnParameterPath) - - assert( - executeCustomPolicyChecksCommandStub.calledOnceWith({ - command: 'cfn-policy-validator', - args: [ - 'check-no-public-access', - '--template-path', - document, - '--region', - 'us-east-1', - '--profile', - 'undefined', - '--template-configuration-file', - cfnParameterPath, - ], - cfnParameterPathExists: !!cfnParameterPath, - documentType, - checkType: 'CheckNoPublicAccess', - }) - ) - assert(onCustomPolicyCheckResponseFireSpy.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('checkNoPublicAccess should handle incorrect Terraform Plan document', async function () { - const documentType = 'Terraform Plan' - const document = 'path/to/invalid.terraform' - - sandbox.stub(IamPolicyChecksWebview, 'editedDocumentFileName').value(document) - sandbox.stub(iamPolicyChecks, 'isTerraformPlan').returns(false) - sandbox.stub(iamPolicyChecks, 'isCloudFormationTemplate').returns(false) - executeCustomPolicyChecksCommandStub = sandbox.stub(fakePolicyChecksWebview, 'executeCustomPolicyChecksCommand') - - await fakePolicyChecksWebview.checkNoPublicAccess(documentType) - - assert( - onCustomPolicyCheckResponseFireSpy.calledOnceWith([ - IamPolicyChecksConstants.IncorrectFileExtension, - getResultCssColor('Error'), - ]) - ) - assert(executeCustomPolicyChecksCommandStub.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('handleCustomPolicyChecksCliResponse should handle response with no findings correctly', function () { - const response = JSON.stringify({ - BlockingFindings: [], - NonBlockingFindings: [], - }) - - const findingsCount = fakePolicyChecksWebview.handleCustomPolicyChecksCliResponse(response) - - assert.strictEqual(findingsCount, 0) - assert( - onCustomPolicyCheckResponseFireSpy.calledOnceWith([ - IamPolicyChecksConstants.CustomCheckSuccessNoFindings, - getResultCssColor('Success'), - ]) - ) - assert(pushCustomPolicyCheckDiagnosticStub.notCalled) - assert(executeCommandStub.notCalled) - }) - - it('handleCustomPolicyChecksCliResponse should handle response with blocking and non-blocking findings correctly', function () { - const response = JSON.stringify({ - BlockingFindings: [ - { - id: 1, - findingType: 'ERROR', - message: - 'The policy in existingPolicyDocument is invalid. Principal is a prohibited policy element.', - }, - ], - NonBlockingFindings: [{ id: 2 }], - }) - const errorMessage = - "ERROR: The policy in reference document is invalid. Principal is a prohibited policy element. Review the reference document's policy type and try again." - sandbox.stub(iamPolicyChecks, 'getCheckNoNewAccessErrorMessage').returns(errorMessage) - const findingsCount = fakePolicyChecksWebview.handleCustomPolicyChecksCliResponse(response) - - assert.strictEqual(findingsCount, 2) - assert(pushCustomPolicyCheckDiagnosticStub.calledTwice) - assert(pushCustomPolicyCheckDiagnosticStub.firstCall.calledWith(sinon.match.array, sinon.match.any, true)) - assert(pushCustomPolicyCheckDiagnosticStub.secondCall.calledWith(sinon.match.array, sinon.match.any, false)) - assert.deepStrictEqual(onCustomPolicyCheckResponseFireSpy.getCalls()[0].args[0], [ - errorMessage, - getResultCssColor('Error'), - ]) - assert(executeCommandStub.calledOnceWith('workbench.actions.view.problems')) - }) - - it('handleCustomPolicyChecksCliResponse should handle response with blocking findings and no error message correctly', function () { - const response = JSON.stringify({ - BlockingFindings: [{ id: 1 }], - NonBlockingFindings: [], - }) - sandbox.stub(iamPolicyChecks, 'getCheckNoNewAccessErrorMessage').returns(undefined) - - const findingsCount = fakePolicyChecksWebview.handleCustomPolicyChecksCliResponse(response) - - assert.strictEqual(findingsCount, 1) - assert(pushCustomPolicyCheckDiagnosticStub.calledOnce) - assert( - onCustomPolicyCheckResponseFireSpy.calledOnceWith([ - IamPolicyChecksConstants.CustomCheckSuccessWithFindings, - getResultCssColor('Warning'), - ]) - ) - assert(executeCommandStub.calledOnceWith('workbench.actions.view.problems')) - }) - - it('pushCustomCheckDiagnostic should push diagnostics with reasons correctly for blocking findings', function () { - const diagnostics: vscode.Diagnostic[] = [] - const finding = { - findingType: 'ERROR', - message: 'Test message with existingPolicyDocument', - resourceName: 'testResource', - policyName: 'testPolicy', - details: { - reasons: [{ description: 'Reason 1' }, { description: 'Reason 2' }], - }, - } - const isBlocking = true - pushCustomPolicyCheckDiagnosticStub.restore() - fakePolicyChecksWebview.pushCustomCheckDiagnostic(diagnostics, finding, isBlocking) - - assert.strictEqual(diagnostics.length, 2) // One diagnostic per reason - for (const [index, diagnostic] of diagnostics.entries()) { - assert.deepStrictEqual(diagnostic.range, new vscode.Range(0, 0, 0, 0)) - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Error) - assert.strictEqual( - diagnostic.message, - `${finding.findingType}: Test message with reference document - Resource name: ${finding.resourceName}, Policy name: ${finding.policyName} - Reason ${index + 1}` - ) - } - assert(customPolicyDiagnosticSetStub.calledOnce) - }) - - it('pushCustomCheckDiagnostic should push diagnostics with reasons correctly for non-blocking findings', function () { - const diagnostics: vscode.Diagnostic[] = [] - const finding = { - findingType: 'WARNING', - message: 'Another test message', - resourceName: 'testResource', - policyName: 'testPolicy', - details: { - reasons: [{ description: 'Reason A' }, { description: 'Reason B' }], - }, - } - const isBlocking = false - pushCustomPolicyCheckDiagnosticStub.restore() - fakePolicyChecksWebview.pushCustomCheckDiagnostic(diagnostics, finding, isBlocking) - - assert.strictEqual(diagnostics.length, 2) // One diagnostic per reason - for (const [index, diagnostic] of diagnostics.entries()) { - assert.deepStrictEqual(diagnostic.range, new vscode.Range(0, 0, 0, 0)) - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Warning) - assert.strictEqual( - diagnostic.message, - `WARNING: Another test message - Resource name: ${finding.resourceName}, Policy name: ${finding.policyName} - Reason ${index === 0 ? 'A' : 'B'}` - ) - } - assert(customPolicyDiagnosticSetStub.calledOnce) - }) - - it('should push a single diagnostic without reasons', function () { - const diagnostics: vscode.Diagnostic[] = [] - const finding = { - findingType: 'ERROR', - message: 'Message without reasons', - resourceName: 'testResource', - policyName: 'testPolicy', - details: {}, - } - const isBlocking = true - pushCustomPolicyCheckDiagnosticStub.restore() - fakePolicyChecksWebview.pushCustomCheckDiagnostic(diagnostics, finding, isBlocking) - - assert.strictEqual(diagnostics.length, 1) - const diagnostic = diagnostics[0] - assert.deepStrictEqual(diagnostic.range, new vscode.Range(0, 0, 0, 0)) - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Error) - assert.strictEqual( - diagnostic.message, - 'ERROR: Message without reasons - Resource name: testResource, Policy name: testPolicy' - ) - assert(customPolicyDiagnosticSetStub.calledOnce) - }) -}) diff --git a/packages/core/src/test/awsService/apigateway/commands/copyUrl.test.ts b/packages/core/src/test/awsService/apigateway/commands/copyUrl.test.ts deleted file mode 100644 index b6cf75d903..0000000000 --- a/packages/core/src/test/awsService/apigateway/commands/copyUrl.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { buildDefaultApiInvokeUrl } from '../../../../awsService/apigateway/commands/copyUrl' - -describe('buildDefaultApiInvokeUrl', function () { - it('builds a url', async function () { - const expected = 'https://1234567ab.execute-api.us-east-1.amazonaws.com/stagename' - - assert.deepStrictEqual( - buildDefaultApiInvokeUrl('1234567ab', 'us-east-1', 'amazonaws.com', 'stagename'), - expected - ) - }) -}) diff --git a/packages/core/src/test/awsService/apigateway/commands/invokeRemoteRestApi.test.ts b/packages/core/src/test/awsService/apigateway/commands/invokeRemoteRestApi.test.ts deleted file mode 100644 index 26c145dbb8..0000000000 --- a/packages/core/src/test/awsService/apigateway/commands/invokeRemoteRestApi.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { listValidMethods } from '../../../../awsService/apigateway/vue/invokeRemoteRestApi' -import { Resource } from '@aws-sdk/client-api-gateway' - -describe('listValidMethods', function () { - const allMethods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'] - it('returns all methods if "ANY" is a method', async function () { - const resource: Resource = { - resourceMethods: { - ANY: {}, - }, - } - - const actual = listValidMethods(resource) - - assert.deepStrictEqual(actual, allMethods) - }) - it('returns dedupe-d all methods if "ANY" declared with another method', async function () { - const resource: Resource = { - resourceMethods: { - ANY: {}, - POST: {}, - }, - } - - const actual = listValidMethods(resource) - - assert.deepStrictEqual(actual, allMethods) - }) - it('returns get if declares get', async function () { - const resource: Resource = { - resourceMethods: { - GET: {}, - }, - } - - const actual = listValidMethods(resource) - - assert.deepStrictEqual(actual, ['GET']) - }) - it('returns nothing if no methods', async function () { - const resource: Resource = {} - - const actual = listValidMethods(resource) - - assert.deepStrictEqual(actual, []) - }) -}) diff --git a/packages/core/src/test/awsService/apigateway/explorer/apiGatewayNodes.test.ts b/packages/core/src/test/awsService/apigateway/explorer/apiGatewayNodes.test.ts deleted file mode 100644 index d8363db754..0000000000 --- a/packages/core/src/test/awsService/apigateway/explorer/apiGatewayNodes.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../../utilities/explorerNodeAssertions' -import { asyncGenerator } from '../../../../shared/utilities/collectionUtils' -import { ApiGatewayNode } from '../../../../awsService/apigateway/explorer/apiGatewayNodes' -import { RestApiNode } from '../../../../awsService/apigateway/explorer/apiNodes' -import { ApiGatewayClient } from '../../../../shared/clients/apiGateway' -import { stub } from '../../../utilities/stubber' - -const fakePartitionId = 'aws' -const fakeRegionCode = 'someregioncode' -const unsortedText = [ - { name: 'zebra', id: "it's zee not zed" }, - { name: 'zebra', id: "it's actually zed" }, - { name: 'Antelope', id: 'anti-antelope' }, - { name: 'aardvark', id: 'a-a-r-d-vark' }, - { name: 'elephant', id: 'trunk capacity' }, -] -const sortedText = [ - 'aardvark (a-a-r-d-vark)', - 'Antelope (anti-antelope)', - 'elephant (trunk capacity)', - "zebra (it's actually zed)", - "zebra (it's zee not zed)", -] - -describe('ApiGatewayNode', function () { - let testNode: ApiGatewayNode - - let apiNames: { name: string; id: string }[] - - function createClient() { - const client = stub(ApiGatewayClient, { regionCode: fakeRegionCode }) - client.listApis.callsFake(() => asyncGenerator(apiNames)) - - return client - } - - beforeEach(function () { - apiNames = [ - { name: 'api1', id: '11111' }, - { name: 'api2', id: '22222' }, - ] - - testNode = new ApiGatewayNode(fakePartitionId, fakeRegionCode, createClient()) - }) - - it('returns placeholder node if no children are present', async function () { - apiNames = [] - - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has RestApi child nodes', async function () { - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, apiNames.length, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof RestApiNode, 'Expected child node to be RestApiNode') - } - }) - - it('sorts child nodes', async function () { - apiNames = unsortedText - - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, sortedText, 'Unexpected child sort order') - }) - - it('has an error node for a child if an error happens during loading', async function () { - const client = createClient() - client.listApis.throws(new Error()) - - const node = new ApiGatewayNode(fakePartitionId, fakeRegionCode, client) - assertNodeListOnlyHasErrorNode(await node.getChildren()) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/explorer/detectSamProjects.test.ts b/packages/core/src/test/awsService/appBuilder/explorer/detectSamProjects.test.ts deleted file mode 100644 index 74fafc2084..0000000000 --- a/packages/core/src/test/awsService/appBuilder/explorer/detectSamProjects.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { TestFolder } from '../../../testUtil' -import { detectSamProjects, getFiles } from '../../../../awsService/appBuilder/explorer/detectSamProjects' -import assert from 'assert' -import * as sinon from 'sinon' - -import path from 'path' -import { ToolkitError } from '../../../../shared' -import { assertLogsContain } from '../../../globalSetup.test' - -describe('detectSamProjects', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(async () => { - sandbox = sinon.createSandbox() - }) - - afterEach(async () => { - sandbox.restore() - }) - - it('should return an empty array when no project found', async () => { - const testFolder = await TestFolder.create() - sandbox.stub(vscode.workspace, 'workspaceFolders').value([ - { - index: 0, - name: 'test-workspace-folder', - uri: testFolder, - }, - ]) - const projects = await detectSamProjects() - assert.strictEqual(projects.length, 0) - }) - - it('should return an empty array when (unlikely) workspace folders is undefined', async () => { - sandbox.stub(vscode.workspace, 'workspaceFolders').value(undefined) - const projects = await detectSamProjects() - assert.strictEqual(projects.length, 0) - }) - - it('should return an non empty array and test projects in testFixture', async () => { - const projects = await detectSamProjects() - assert(projects.length >= 20) - assert( - projects.some((p) => { - const projectRootName = path.relative(p.workspaceFolder.uri.fsPath, p.projectRoot.fsPath) - return projectRootName === 'appbuilder-test-app' - }) - ) - }) -}) - -describe('getFiles', () => { - let workspaceFolder: vscode.WorkspaceFolder - - beforeEach(() => { - const workspaceFolders = vscode.workspace.workspaceFolders - assert(workspaceFolders) - workspaceFolder = workspaceFolders[0] - }) - - it('should return an array of one project folder matching pattern without excluded pattern', async () => { - const templateFiles = await getFiles(workspaceFolder, '**/appbuilder-test-app/template.{yml,yaml}') - assert.strictEqual(templateFiles.length, 1) - assert(templateFiles[0].fsPath.endsWith(path.join('appbuilder-test-app', 'template.yaml'))) - }) - - it('should return a non empty array contains all project except the excluded pattern', async () => { - const allTemplateFiles = await getFiles(workspaceFolder, '**/template.{yml,yaml}') - const templateFiles = await getFiles(workspaceFolder, '**/template.{yml,yaml}', '**/appbuilder-test-app/**') - - assert.strictEqual(allTemplateFiles.length - templateFiles.length, 1) - assert(!templateFiles.some((f) => f.fsPath.endsWith(path.join('appbuilder-test-app', 'template.yaml')))) - }) - - it('should return empty array given any error', async () => { - const sandbox = sinon.createSandbox() - sandbox.stub(vscode.workspace, 'findFiles').rejects(new ToolkitError('mock an unlikely error')) - - const templateFiles = await getFiles(workspaceFolder, '**/template.{yml,yaml}', '**/.aws-sam/**') - assert.strictEqual(templateFiles.length, 0) - assertLogsContain('Failed to find files with pattern', false, 'error') - sandbox.restore() - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/explorer/samProject.test.ts b/packages/core/src/test/awsService/appBuilder/explorer/samProject.test.ts deleted file mode 100644 index cf8aaf9ada..0000000000 --- a/packages/core/src/test/awsService/appBuilder/explorer/samProject.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { getApp, getStackName, SamAppLocation } from '../../../../awsService/appBuilder/explorer/samProject' -import * as sinon from 'sinon' -import assert from 'assert' -import { ToolkitError } from '../../../../shared' -import * as CloudformationModule from '../../../../shared/cloudformation/cloudformation' -import path from 'path' -import { TestFolder } from '../../../testUtil' - -import { - generateSamconfigData, - samconfigCompleteData, - samconfigInvalidData, - validTemplateData, - capacityProviderTemplateData, -} from '../../../shared/sam/samTestUtils' -import { assertLogsContain } from '../../../globalSetup.test' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('samProject', () => { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('getStackName', () => { - it('returns stack name and region happy path', async () => { - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const expectedStackName = 'project-1' - const expectedRegion = 'us-west-2' - - const { stackName, region } = await getStackName(projectRoot) - assert.strictEqual(stackName, expectedStackName) - assert.strictEqual(region, expectedRegion) - }) - - it('returns undefined given no stack name or region in samconfig file', async () => { - await testFolder.write( - 'samconfig.toml', - generateSamconfigData({ - deploy: [{ key: 'confirm_changeset', value: 'false' }], - build: [ - { key: 'cached', value: 'true' }, - { key: 'parallel', value: 'true' }, - ], - }) - ) - const { stackName, region } = await getStackName(projectRoot) - assert.equal(stackName, undefined) - assert.equal(region, undefined) - }) - - it('returns {} when (unlikely) given an undefined project root uri', async () => { - const wrapperCall = async (projectRootUri: any) => { - return await getStackName(projectRootUri) - } - - const result = await wrapperCall(undefined) - assert.deepStrictEqual(result, {}) - assertLogsContain( - 'Error parsing stack name and/or region information: No project folder found', - false, - 'warn' - ) - }) - - it('returns empty object given no samconfig file found', async () => { - // simulate error when no samconfig.toml file in directory - const result = await getStackName(projectRoot) - assert.deepStrictEqual(result, {}) - assertLogsContain('Stack name and/or region information not found in samconfig.toml', false, 'info') - }) - - it('returns empty object given error parsing samconfig file', async () => { - // simulate error when parsinf samconfig.toml: missing quote or empty value - await testFolder.write('samconfig.toml', samconfigInvalidData) - - const result = await getStackName(projectRoot) - assert.deepStrictEqual(result, {}) - - assertLogsContain('Error parsing stack name and/or region information from samconfig.toml:', false, 'error') - getTestWindow().getFirstMessage().assertError('Encountered an issue reading samconfig.toml') - }) - }) - - describe('getApp', () => { - let cloudformationTryLoadSpy: sinon.SinonSpy - let mockSamAppLocation: SamAppLocation - - beforeEach(async () => { - // create mock SamConfig instance - cloudformationTryLoadSpy = sandbox.spy(CloudformationModule, 'tryLoad') - mockSamAppLocation = { - samTemplateUri: vscode.Uri.file(path.join(projectRoot.fsPath, 'template.yaml')), - workspaceFolder: { - uri: vscode.Uri.file(projectRoot.fsPath), - name: 'test-workspace-folder', - index: 0, - }, - projectRoot: projectRoot, - } - }) - - it('returns correct location and resource in happy path', async () => { - // Set up a valid template in test folder - await testFolder.write('template.yaml', validTemplateData) - // simulate successful loading template - const { location, resourceTree } = await getApp(mockSamAppLocation) - assert.deepStrictEqual(location, mockSamAppLocation) - assert.strictEqual(resourceTree.length, 2) - assert(cloudformationTryLoadSpy.calledOnce) - // Expect one lambda node and one s3 bucket node - const lambdaResourceNode = resourceTree.find((node) => node.Type === 'AWS::Serverless::Function') - const s3BucketResourceNode = resourceTree.find((node) => node.Type === 'AWS::S3::Bucket') - assert(lambdaResourceNode) - assert(s3BucketResourceNode) - // validate Lambda node - assert('Handler' in lambdaResourceNode && lambdaResourceNode.Handler === 'app.lambda_handler') - assert.strictEqual(lambdaResourceNode.Id, 'ResizerFunction') - assert('Runtime' in lambdaResourceNode && lambdaResourceNode.Runtime === 'python3.12') - assert( - 'Events' in lambdaResourceNode && lambdaResourceNode.Events && lambdaResourceNode.Events.length === 1 - ) - assert('Events' in lambdaResourceNode && lambdaResourceNode.Events) - assert.deepStrictEqual(lambdaResourceNode.Events[0], { - Id: 'FileUpload', - Type: 'S3', - Path: undefined, - Method: undefined, - }) - // validate S3 Bucket - assert.strictEqual(s3BucketResourceNode.Id, 'SourceBucket') - assert(!('Events' in s3BucketResourceNode)) - assert(!('Handler' in s3BucketResourceNode)) - assert(!('Method' in s3BucketResourceNode)) - assert(!('Runtime' in s3BucketResourceNode)) - }) - - it('throws ToolkitError when fails to load Cloudformation template values', async () => { - // simulate unsuccessful CFN loading template when there is no template. - await assert.rejects( - () => getApp(mockSamAppLocation), - new ToolkitError(`Template at ${mockSamAppLocation.samTemplateUri.fsPath} is not valid`) - ) - }) - - it('parses capacity provider resources correctly', async () => { - await testFolder.write('template.yaml', capacityProviderTemplateData) - const { resourceTree } = await getApp(mockSamAppLocation) - - // Should have 2 resources: capacity provider and function - assert.strictEqual(resourceTree.length, 2) - - const capacityProviderNode = resourceTree.find((node) => node.Type === 'AWS::Serverless::CapacityProvider') - const functionNode = resourceTree.find((node) => node.Type === 'AWS::Serverless::Function') - - // Validate capacity provider node - assert(capacityProviderNode) - assert.strictEqual(capacityProviderNode.Id, 'MyCapacityProvider') - assert('Architectures' in capacityProviderNode && capacityProviderNode.Architectures === 'x86_64') - - // Validate function node has capacity provider config with intrinsic function - assert(functionNode) - assert.strictEqual(functionNode.Id, 'MyFunction') - assert('CapacityProviderConfig' in functionNode && typeof functionNode.CapacityProviderConfig === 'object') - - // Verify the intrinsic function is preserved as an object (not stringified) - const config = functionNode.CapacityProviderConfig as any - assert(config.Arn) - assert.strictEqual(typeof config.Arn, 'object') - assert('Fn::GetAtt' in config.Arn) - assert.deepStrictEqual(config.Arn['Fn::GetAtt'], ['MyCapacityProvider', 'Arn']) - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2sam.test.ts b/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2sam.test.ts deleted file mode 100644 index f07343b33a..0000000000 --- a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2sam.test.ts +++ /dev/null @@ -1,273 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import { Template } from '../../../../shared/cloudformation/cloudformation' -import * as lambda2sam from '../../../../awsService/appBuilder/lambda2sam/lambda2sam' -import * as authUtils from '../../../../auth/utils' -import * as utils from '../../../../awsService/appBuilder/utils' - -describe('lambda2sam', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('ifSamTemplate', function () { - it('returns true when transform is a string and starts with AWS::Serverless', function () { - const template: Template = { - Transform: 'AWS::Serverless-2016-10-31', - } - assert.strictEqual(lambda2sam.ifSamTemplate(template), true) - }) - - it('returns false when transform is a string and does not start with AWS::Serverless', function () { - const template: Template = { - Transform: 'AWS::Other-Transform', - } - assert.strictEqual(lambda2sam.ifSamTemplate(template), false) - }) - - it('returns true when transform is an array and at least one starts with AWS::Serverless', function () { - const template: Template = { - Transform: ['AWS::Serverless-2016-10-31', 'AWS::Other-Transform'] as any, - } - assert.strictEqual(lambda2sam.ifSamTemplate(template), true) - }) - - it('returns false when transform is an array and none start with AWS::Serverless', function () { - const template: Template = { - Transform: ['AWS::Other-Transform-1', 'AWS::Other-Transform-2'] as any, - } - assert.strictEqual(lambda2sam.ifSamTemplate(template), false) - }) - - it('returns false when transform is not present', function () { - const template: Template = {} - assert.strictEqual(lambda2sam.ifSamTemplate(template), false) - }) - - it('returns false when transform is an unsupported type', function () { - const template: Template = { - Transform: { some: 'object' } as any, - } - assert.strictEqual(lambda2sam.ifSamTemplate(template), false) - }) - }) - - describe('extractLogicalIdFromIntrinsic', function () { - it('extracts logical ID from Ref intrinsic', function () { - const value = { Ref: 'MyResource' } - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic(value), 'MyResource') - }) - - it('extracts logical ID from GetAtt intrinsic with Arn attribute', function () { - const value = { 'Fn::GetAtt': ['MyResource', 'Arn'] } - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic(value), 'MyResource') - }) - - it('returns undefined for GetAtt intrinsic with non-Arn attribute', function () { - const value = { 'Fn::GetAtt': ['MyResource', 'Name'] } - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic(value), undefined) - }) - - it('returns undefined for non-intrinsic values', function () { - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic('not-an-intrinsic'), undefined) - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic({ NotIntrinsic: 'value' }), undefined) - assert.strictEqual(lambda2sam.extractLogicalIdFromIntrinsic(undefined), undefined) - }) - }) - - describe('callExternalApiForCfnTemplate', function () { - let lambdaClientStub: sinon.SinonStubbedInstance - let cfnClientStub: any - - beforeEach(function () { - lambdaClientStub = sandbox.createStubInstance(DefaultLambdaClient) - // Stub at prototype level to avoid TypeScript errors - sandbox - .stub(DefaultLambdaClient.prototype, 'getFunction') - .callsFake((name) => lambdaClientStub.getFunction(name)) - - // Mock CloudFormation client for the new external API calls - now returns Promises directly - cfnClientStub = { - getGeneratedTemplate: sandbox.stub().resolves({ - Status: 'COMPLETE', - TemplateBody: JSON.stringify({ - AWSTemplateFormatVersion: '2010-09-09', - Resources: { - testFunc: { - DeletionPolicy: 'Retain', - Properties: { - Code: { - S3Bucket: 'aws-sam-cli-managed-default-samclisourcebucket-1n8tvb0jdhsd', - S3Key: '1d1c93ec17af7e2666ee20ea1a215c77', - }, - Environment: { - Variables: { - KEY: 'value', - }, - }, - FunctionName: 'myFunction', - Handler: 'index.handler', - MemorySize: 128, - PackageType: 'Zip', - Role: 'arn:aws:iam::123456789012:role/lambda-role', - Runtime: 'nodejs18.x', - Timeout: 3, - }, - Type: 'AWS::Lambda::Function', - }, - }, - }), - }), - describeGeneratedTemplate: sandbox.stub().resolves({ - Status: 'COMPLETE', - Resources: [ - { - LogicalResourceId: 'testFunc', - ResourceType: 'AWS::Lambda::Function', - ResourceIdentifier: { - FunctionName: 'myFunction', - }, - }, - ], - }), - } - sandbox.stub(utils, 'getCFNClient').resolves(cfnClientStub) - - // Mock IAM connection - const mockConnection = { - type: 'iam' as const, - id: 'test-connection', - label: 'Test Connection', - state: 'valid' as const, - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - endpointUrl: undefined, - } - sandbox.stub(authUtils, 'getIAMConnection').resolves(mockConnection) - - // Mock fetch response - sandbox.stub(global, 'fetch').resolves({ - ok: true, - json: sandbox.stub().resolves({ - cloudFormationTemplateId: 'test-template-id', - }), - } as any) - }) - - it('creates a basic CloudFormation template for the Lambda function', async function () { - const lambdaNode = { - name: 'myFunction', - regionCode: 'us-east-2', - arn: 'arn:aws:lambda:us-east-2:123456789012:function:myFunction', - } as LambdaFunctionNode - - lambdaClientStub.getFunction.resolves({ - Configuration: { - FunctionName: 'myFunction', - Handler: 'index.handler', - Role: 'arn:aws:iam::123456789012:role/lambda-role', - Runtime: 'nodejs18.x', - Timeout: 3, - MemorySize: 128, - Environment: { Variables: { KEY: 'value' } }, - }, - }) - - // Create a simple mock template that matches the Template type - const mockTemplate = { - AWSTemplateFormatVersion: '2010-09-09', - Resources: { - testFunc: { - DeletionPolicy: 'Retain', - Properties: { - Code: { - S3Bucket: 'aws-sam-cli-managed-default-samclisourcebucket-1n8tvb0jdhsd', - S3Key: '1d1c93ec17af7e2666ee20ea1a215c77', - }, - Environment: { - Variables: { - KEY: 'value', - }, - }, - FunctionName: 'myFunction', - Handler: 'index.handler', - MemorySize: 128, - PackageType: 'Zip', - Role: 'arn:aws:iam::123456789012:role/lambda-role', - Runtime: 'nodejs18.x', - Timeout: 3, - }, - Type: 'AWS::Lambda::Function', - }, - }, - } - const mockList = [ - { - LogicalResourceId: 'testFunc', - ResourceIdentifier: { - FunctionName: 'myFunction', - }, - ResourceType: 'AWS::Lambda::Function', - }, - ] - - const result = await lambda2sam.callExternalApiForCfnTemplate(lambdaNode) - // Verify the result structure matches expected format - assert.strictEqual(Array.isArray(result), true) - assert.strictEqual(result.length, 2) - const [template, resourcesToImport] = result - assert.strictEqual(typeof template, 'object') - assert.strictEqual(Array.isArray(resourcesToImport), true) - assert.strictEqual(resourcesToImport.length, 1) - assert.strictEqual(resourcesToImport[0].ResourceType, 'AWS::Lambda::Function') - assert.strictEqual(resourcesToImport[0].LogicalResourceId, 'testFunc') - assert.deepStrictEqual(result, [mockTemplate, mockList]) - }) - }) - - describe('determineStackAssociation', function () { - let lambdaClientStub: sinon.SinonStubbedInstance - - beforeEach(function () { - lambdaClientStub = sandbox.createStubInstance(DefaultLambdaClient) - sandbox - .stub(DefaultLambdaClient.prototype, 'getFunction') - .callsFake((name) => lambdaClientStub.getFunction(name)) - }) - - it('returns undefined when Lambda has no tags', async function () { - const lambdaNode = { - name: 'myFunction', - regionCode: 'us-west-2', - } as LambdaFunctionNode - - lambdaClientStub.getFunction.resolves({}) - - // Skip CloudFormation mocking for now - // This is difficult to mock correctly without errors and would be better tested with integration tests - - const result = await lambda2sam.determineStackAssociation(lambdaNode) - - assert.strictEqual(result, undefined) - assert.strictEqual(lambdaClientStub.getFunction.calledOnceWith(lambdaNode.name), true) - }) - - // For this function, additional testing would require complex mocking of the AWS SDK - // Consider adding more specific test cases in an integration test - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samCoreLogic.test.ts b/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samCoreLogic.test.ts deleted file mode 100644 index c618dac219..0000000000 --- a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samCoreLogic.test.ts +++ /dev/null @@ -1,754 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import * as vscode from 'vscode' -import * as lambda2sam from '../../../../awsService/appBuilder/lambda2sam/lambda2sam' -import * as cloudFormation from '../../../../shared/cloudformation/cloudformation' -import * as utils from '../../../../awsService/appBuilder/utils' -import * as walkthrough from '../../../../awsService/appBuilder/walkthrough' -import * as authUtils from '../../../../auth/utils' -import { getTestWindow } from '../../../shared/vscode/window' -import { fs } from '../../../../shared' -import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import { ToolkitError } from '../../../../shared/errors' -import os from 'os' -import path from 'path' -import { LAMBDA_FUNCTION_TYPE } from '../../../../shared/cloudformation/cloudformation' -import { ResourceToImport } from '@aws-sdk/client-cloudformation' - -describe('lambda2samCoreLogic', function () { - let sandbox: sinon.SinonSandbox - let tempDir: string - let lambdaClientStub: sinon.SinonStubbedInstance - let cfnClientStub: any - let downloadUnzipStub: sinon.SinonStub - - beforeEach(async function () { - sandbox = sinon.createSandbox() - tempDir = path.join(os.tmpdir(), `aws-toolkit-test-${Date.now()}`) - - // Create temp directory for tests - actually create it, don't stub - if (!(await fs.exists(vscode.Uri.file(tempDir)))) { - await fs.mkdir(vscode.Uri.file(tempDir)) - } - - // Create Lambda client stub with necessary properties - lambdaClientStub = sandbox.createStubInstance(DefaultLambdaClient) - Object.defineProperty(lambdaClientStub, 'defaultTimeoutInMs', { - value: 5 * 60 * 1000, // 5 minutes - configurable: true, - }) - Object.defineProperty(lambdaClientStub, 'createSdkClient', { - value: () => Promise.resolve({}), - configurable: true, - }) - - sandbox.stub(utils, 'getLambdaClient').returns(lambdaClientStub as any) - - // Mock CloudFormation client - now returns Promises directly (no .promise() method) - cfnClientStub = { - describeStackResource: sandbox.stub().resolves({ - StackResourceDetail: { - PhysicalResourceId: 'test-physical-id', - }, - }), - describeStackResources: sandbox.stub().resolves({ - StackResources: [ - { LogicalResourceId: 'testResource', PhysicalResourceId: 'test-physical-id' }, - { LogicalResourceId: 'prefixTestResource', PhysicalResourceId: 'prefix-test-physical-id' }, - ], - }), - describeStacks: sandbox.stub().resolves({ - Stacks: [ - { - StackId: 'stack-id', - StackName: 'test-stack', - StackStatus: 'CREATE_COMPLETE', - }, - ], - }), - getTemplate: sandbox.stub().resolves({ - TemplateBody: '{"Resources": {"TestFunc": {"Type": "AWS::Lambda::Function"}}}', - }), - getGeneratedTemplate: sandbox.stub().resolves({ - Status: 'COMPLETE', - TemplateBody: - '{"Resources": {"TestFunc": {"Type": "AWS::Lambda::Function", "Properties": {"FunctionName": "test-function"}}}}', - }), - describeGeneratedTemplate: sandbox.stub().resolves({ - Status: 'COMPLETE', - Resources: [ - { - LogicalResourceId: 'TestFunc', - ResourceType: 'AWS::Lambda::Function', - ResourceIdentifier: { - FunctionName: 'arn:aws:lambda:us-east-2:123456789012:function:test-function', - }, - }, - ], - }), - createChangeSet: sandbox.stub().resolves({ Id: 'change-set-id' }), - waitFor: sandbox.stub().resolves(), - executeChangeSet: sandbox.stub().resolves(), - describeChangeSet: sandbox.stub().resolves({ - StatusReason: 'Test reason', - }), - } - sandbox.stub(utils, 'getCFNClient').resolves(cfnClientStub) - - // Setup test window to return appropriate values - getTestWindow().onDidShowMessage((msg) => { - if (msg.message.includes('Enter Stack Name')) { - msg.selectItem('test-stack') - } - }) - - getTestWindow().onDidShowDialog((dialog) => { - dialog.selectItem(vscode.Uri.file(tempDir)) - }) - - // Stub downloadUnzip function - downloadUnzipStub = sandbox.stub(utils, 'downloadUnzip').callsFake(async (url, outputPath) => { - // Create a mock file structure for testing purposes - if (!(await fs.exists(outputPath))) { - await fs.mkdir(outputPath) - } - - await fs.writeFile( - vscode.Uri.joinPath(outputPath, 'index.js'), - 'exports.handler = async (event) => { return "Hello World" };' - ) - await fs.writeFile( - vscode.Uri.joinPath(outputPath, 'package.json'), - JSON.stringify( - { - name: 'test-lambda', - version: '1.0.0', - description: 'Test Lambda function', - }, - undefined, - 2 - ) - ) - }) - - // Stub workspace functions - sandbox.stub(vscode.workspace, 'openTextDocument').resolves({} as any) - sandbox.stub(vscode.window, 'showTextDocument').resolves() - }) - - afterEach(async function () { - sandbox.restore() - - // Clean up the temp directory - if (await fs.exists(vscode.Uri.file(tempDir))) { - await fs.delete(vscode.Uri.file(tempDir), { recursive: true, force: true }) - } - }) - - describe('processLambdaUrlResources', function () { - it('converts Lambda URL resources to FunctionUrlConfig', async function () { - // Setup resources with Lambda URL - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestFunc: { - Type: cloudFormation.SERVERLESS_FUNCTION_TYPE, - Properties: { - FunctionName: 'test-function', - PackageType: 'Zip', - }, - }, - TestFuncUrl: { - Type: cloudFormation.LAMBDA_URL_TYPE, - Properties: { - TargetFunctionArn: { Ref: 'TestFunc' }, - AuthType: 'NONE', - }, - }, - } as any - - // Call the function - await lambda2sam.processLambdaUrlResources(resources) - - // Verify URL resource is removed - assert.strictEqual(resources['TestFuncUrl'], undefined) - - // Verify FunctionUrlConfig added to function resource using non-null assertion - assert.deepStrictEqual(resources['TestFunc']!.Properties!.FunctionUrlConfig, { - AuthType: 'NONE', - Cors: undefined, - InvokeMode: undefined, - }) - }) - - it('skips URL resources with Qualifier property', async function () { - // Setup resources with Lambda URL including Qualifier - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestFunc: { - Type: cloudFormation.SERVERLESS_FUNCTION_TYPE, - Properties: { - FunctionName: 'test-function', - PackageType: 'Zip', - }, - }, - TestFuncUrl: { - Type: cloudFormation.LAMBDA_URL_TYPE, - Properties: { - TargetFunctionArn: { Ref: 'TestFunc' }, - AuthType: 'NONE', - Qualifier: 'prod', - }, - }, - } as any - - // Call the function - await lambda2sam.processLambdaUrlResources(resources) - - // Verify URL resource is still there (not transformed) - assert.notStrictEqual(resources['TestFuncUrl'], undefined) - - // Verify function resource doesn't have FunctionUrlConfig using non-null assertion - assert.strictEqual(resources['TestFunc']!.Properties!.FunctionUrlConfig, undefined) - }) - }) - - describe('processLambdaResources', function () { - it('transforms AWS::Lambda::Function to AWS::Serverless::Function', async function () { - // Setup resources with Lambda function - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestFunc: { - Type: cloudFormation.LAMBDA_FUNCTION_TYPE, - Properties: { - FunctionName: 'test-function', - Handler: 'index.handler', - Runtime: 'nodejs18.x', - Code: { - S3Bucket: 'test-bucket', - S3Key: 'test-key', - }, - Tags: [ - { Key: 'test-key', Value: 'test-value' }, - { Key: 'lambda:createdBy', Value: 'test' }, - ], - TracingConfig: { - Mode: 'Active', - }, - PackageType: 'Zip', - }, - }, - } as any - - const stackInfo = { - stackId: 'stack-id', - stackName: 'test-stack', - isSamTemplate: false, - template: {}, - } - - const projectDir = vscode.Uri.file(tempDir) - - // Add necessary stub for getFunction - lambdaClientStub.getFunction.resolves({ - Code: { Location: 'https://lambda-function-code.zip' }, - }) - - // Call the function - await lambda2sam.processLambdaResources(resources, projectDir, stackInfo, 'us-west-2') - - // Verify function type was transformed using non-null assertions - assert.strictEqual(resources['TestFunc']!.Type, cloudFormation.SERVERLESS_FUNCTION_TYPE) - - // Verify properties were transformed correctly using non-null assertions - assert.strictEqual(resources['TestFunc']!.Properties!.Code, undefined) - assert.strictEqual(resources['TestFunc']!.Properties!.CodeUri, 'TestFunc') - assert.strictEqual(resources['TestFunc']!.Properties!.Tracing, 'Active') - assert.strictEqual(resources['TestFunc']!.Properties!.TracingConfig, undefined) - assert.deepStrictEqual(resources['TestFunc']!.Properties!.Tags, { - 'test-key': 'test-value', - }) - - // Verify downloadLambdaFunctionCode was called - assert.strictEqual(downloadUnzipStub.calledOnce, true) - }) - - it('updates CodeUri for AWS::Serverless::Function', async function () { - // Setup resources with Serverless function - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestFunc: { - Type: cloudFormation.SERVERLESS_FUNCTION_TYPE, - Properties: { - FunctionName: 'test-function', - Handler: 'index.handler', - Runtime: 'nodejs18.x', - CodeUri: 's3://test-bucket/test-key', - PackageType: 'Zip', - }, - }, - } as any - - const stackInfo = { - stackId: 'stack-id', - stackName: 'test-stack', - isSamTemplate: false, - template: {}, - } - - const projectDir = vscode.Uri.file(tempDir) - - // Add necessary stub for getFunction - lambdaClientStub.getFunction.resolves({ - Code: { Location: 'https://lambda-function-code.zip' }, - }) - - // Call the function - await lambda2sam.processLambdaResources(resources, projectDir, stackInfo, 'us-west-2') - - // Verify CodeUri was updated using non-null assertions - assert.strictEqual(resources['TestFunc']!.Properties!.CodeUri, 'TestFunc') - - // Verify downloadLambdaFunctionCode was called - assert.strictEqual(downloadUnzipStub.calledOnce, true) - }) - }) - - describe('processLambdaLayerResources', function () { - it('transforms AWS::Lambda::LayerVersion to AWS::Serverless::LayerVersion', async function () { - // Setup resources with Lambda layer - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestLayer: { - Type: cloudFormation.LAMBDA_LAYER_TYPE, - Properties: { - LayerName: 'test-layer', - Content: { - S3Bucket: 'test-bucket', - S3Key: 'test-key', - }, - CompatibleRuntimes: ['nodejs18.x'], - }, - }, - } as any - - const stackInfo = { - stackId: 'stack-id', - stackName: 'test-stack', - isSamTemplate: false, - template: {}, - } - - const projectDir = vscode.Uri.file(tempDir) - - // Setup layer version stub - cfnClientStub.describeStackResource.resolves({ - StackResourceDetail: { - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer:1', - }, - }) - - lambdaClientStub.getLayerVersion.resolves({ - Content: { Location: 'https://lambda-layer-code.zip' }, - }) - - // Call the function - await lambda2sam.processLambdaLayerResources(resources, projectDir, stackInfo, 'us-west-2') - - // Verify layer type was transformed using non-null assertions - assert.strictEqual(resources['TestLayer']!.Type, cloudFormation.SERVERLESS_LAYER_TYPE) - - // Verify properties were transformed correctly using non-null assertions - assert.strictEqual(resources['TestLayer']!.Properties!.Content, undefined) - assert.strictEqual(resources['TestLayer']!.Properties!.ContentUri, 'TestLayer') - assert.deepStrictEqual(resources['TestLayer']!.Properties!.CompatibleRuntimes, ['nodejs18.x']) - - // Verify downloadLayerVersionResrouceByName was called (through downloadUnzip) - assert.strictEqual(downloadUnzipStub.calledOnce, true) - }) - - it('preserves AWS::Serverless::LayerVersion properties', async function () { - // Setup resources with Serverless layer - using 'as any' to bypass strict typing for tests - const resources: cloudFormation.TemplateResources = { - TestLayer: { - Type: cloudFormation.SERVERLESS_LAYER_TYPE, - Properties: { - LayerName: 'test-layer', - ContentUri: 's3://test-bucket/test-key', - CompatibleRuntimes: ['nodejs18.x'], - }, - }, - } as any - - const stackInfo = { - stackId: 'stack-id', - stackName: 'test-stack', - isSamTemplate: false, - template: {}, - } - - const projectDir = vscode.Uri.file(tempDir) - - // Setup layer version stub - cfnClientStub.describeStackResource.resolves({ - StackResourceDetail: { - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer:1', - }, - }) - - lambdaClientStub.getLayerVersion.resolves({ - Content: { Location: 'https://lambda-layer-code.zip' }, - }) - - // Call the function - await lambda2sam.processLambdaLayerResources(resources, projectDir, stackInfo, 'us-west-2') - - // Verify layer type is still serverless using non-null assertions - assert.strictEqual(resources['TestLayer']!.Type, cloudFormation.SERVERLESS_LAYER_TYPE) - - // Verify ContentUri was updated using non-null assertions - assert.strictEqual(resources['TestLayer']!.Properties!.ContentUri, 'TestLayer') - - // Verify downloadLayerVersionResrouceByName was called (through downloadUnzip) - assert.strictEqual(downloadUnzipStub.calledOnce, true) - }) - }) - - describe('deployCfnTemplate', function () { - it('deploys a CloudFormation template and returns stack info', async function () { - // Setup CloudFormation template - const template: cloudFormation.Template = mockCloudFormationTemplate() - - // Setup Lambda node - const lambdaNode = mockLambdaNode() - - const resourceToImport: ResourceToImport[] = [ - { - ResourceType: LAMBDA_FUNCTION_TYPE, - LogicalResourceId: 'TestFunc', - ResourceIdentifier: { - FunctionName: lambdaNode.name, - }, - }, - ] - - // Call the function - const result = await lambda2sam.deployCfnTemplate( - template, - resourceToImport, - 'test-stack', - lambdaNode.regionCode - ) - - // Verify createChangeSet was called with correct parameters - assert.strictEqual(cfnClientStub.createChangeSet.called, true) - const createChangeSetArgs = cfnClientStub.createChangeSet.firstCall.args[0] - assert.strictEqual(createChangeSetArgs.StackName, 'test-stack') - assert.strictEqual(createChangeSetArgs.ChangeSetType, 'IMPORT') - - // Verify waitFor and executeChangeSet were called - assert.strictEqual(cfnClientStub.waitFor.calledWith('changeSetCreateComplete'), true) - assert.strictEqual(cfnClientStub.executeChangeSet.called, true) - - // Verify describeStacks was called to get stack ID - assert.strictEqual(cfnClientStub.describeStacks.called, true) - - // Verify result structure - assert.strictEqual(result.stackId, 'stack-id') - assert.strictEqual(result.stackName, 'test-stack') - assert.strictEqual(result.isSamTemplate, false) - assert.deepStrictEqual(result.template, template) - }) - - it('throws an error when change set creation fails', async function () { - // Setup CloudFormation template - const template: cloudFormation.Template = mockCloudFormationTemplate() - - // Setup Lambda node - const lambdaNode = mockLambdaNode() - - // Make createChangeSet fail - cfnClientStub.createChangeSet.resolves({}) // No Id - - const resourceToImport: ResourceToImport[] = [ - { - ResourceType: LAMBDA_FUNCTION_TYPE, - LogicalResourceId: 'TestFunc', - ResourceIdentifier: { - FunctionName: lambdaNode.name, - }, - }, - ] - - // Call the function and expect error - await assert.rejects( - lambda2sam.deployCfnTemplate(template, resourceToImport, 'test-stack', lambdaNode.regionCode), - (err: ToolkitError) => { - assert.strictEqual(err.message.includes('Failed to create change set'), true) - return true - } - ) - }) - }) - - describe('callExternalApiForCfnTemplate', function () { - it('extracts function name from ARN in ResourceIdentifier', async function () { - // Setup Lambda node - const lambdaNode = mockLambdaNode(true) - - // Mock IAM connection - const mockConnection = mockIamConnection() - sandbox.stub(authUtils, 'getIAMConnection').resolves(mockConnection) - - // Mock fetch response - const mockFetch = mockFetchResponse(sandbox) - - // Setup CloudFormation client to return ARN in ResourceIdentifier - cfnClientStub.describeGeneratedTemplate.resolves({ - Status: 'COMPLETE', - Resources: [ - { - LogicalResourceId: 'TestFunc', - ResourceType: 'AWS::Lambda::Function', - ResourceIdentifier: { - FunctionName: 'arn:aws:lambda:us-east-2:123456789012:function:test-function', - }, - }, - ], - }) - - // Call the function - const [_, resourcesToImport] = await lambda2sam.callExternalApiForCfnTemplate(lambdaNode) - - // Verify that the ARN was converted to just the function name - assert.strictEqual(resourcesToImport.length, 1) - assert.strictEqual(resourcesToImport[0].ResourceType, 'AWS::Lambda::Function') - assert.strictEqual(resourcesToImport[0].LogicalResourceId, 'TestFunc') - assert.strictEqual(resourcesToImport[0].ResourceIdentifier!.FunctionName, 'test-function') - - // Verify API calls were made - assert.strictEqual(mockFetch.calledOnce, true) - assert.strictEqual(cfnClientStub.getGeneratedTemplate.calledOnce, true) - assert.strictEqual(cfnClientStub.describeGeneratedTemplate.calledOnce, true) - }) - - it('preserves function name when not an ARN', async function () { - // Setup Lambda node - const lambdaNode = mockLambdaNode(true) - - // Mock IAM connection - const mockConnection = mockIamConnection() - sandbox.stub(authUtils, 'getIAMConnection').resolves(mockConnection) - - // Mock fetch response - mockFetchResponse(sandbox) - - // Setup CloudFormation client to return plain function name - cfnClientStub.describeGeneratedTemplate.resolves({ - Status: 'COMPLETE', - Resources: [ - { - LogicalResourceId: 'TestFunc', - ResourceType: 'AWS::Lambda::Function', - ResourceIdentifier: { - FunctionName: 'test-function', - }, - }, - ], - }) - - // Call the function - const [_, resourcesToImport] = await lambda2sam.callExternalApiForCfnTemplate(lambdaNode) - - // Verify that the function name was preserved - assert.strictEqual(resourcesToImport.length, 1) - assert.strictEqual(resourcesToImport[0].ResourceIdentifier!.FunctionName, 'test-function') - }) - - it('handles non-Lambda resources without modification', async function () { - // Setup Lambda node - const lambdaNode = mockLambdaNode(true) - - // Mock IAM connection - const mockConnection = mockIamConnection() - sandbox.stub(authUtils, 'getIAMConnection').resolves(mockConnection) - - // Mock fetch response - mockFetchResponse(sandbox) - - // Setup CloudFormation client to return mixed resource types - cfnClientStub.describeGeneratedTemplate.resolves({ - Status: 'COMPLETE', - Resources: [ - { - LogicalResourceId: 'TestFunc', - ResourceType: 'AWS::Lambda::Function', - ResourceIdentifier: { - FunctionName: 'arn:aws:lambda:us-east-2:123456789012:function:test-function', - }, - }, - { - LogicalResourceId: 'TestRole', - ResourceType: 'AWS::IAM::Role', - ResourceIdentifier: { - RoleName: 'test-role', - }, - }, - ], - }) - - // Call the function - const [_, resourcesToImport] = await lambda2sam.callExternalApiForCfnTemplate(lambdaNode) - - // Verify that Lambda function ARN was converted but IAM role was not - assert.strictEqual(resourcesToImport.length, 2) - - const lambdaResource = resourcesToImport.find((r) => r.ResourceType === 'AWS::Lambda::Function') - const iamResource = resourcesToImport.find((r) => r.ResourceType === 'AWS::IAM::Role') - - assert.strictEqual(lambdaResource!.ResourceIdentifier!.FunctionName, 'test-function') - assert.strictEqual(iamResource!.ResourceIdentifier!.RoleName, 'test-role') - }) - }) - - describe('lambdaToSam', function () { - it('converts a Lambda function to a SAM project', async function () { - // Setup Lambda node - const lambdaNode = mockLambdaNode() - - // Setup AWS Lambda client responses - lambdaClientStub.getFunction.resolves({ - Tags: { - 'aws:cloudformation:stack-id': 'stack-id', - 'aws:cloudformation:stack-name': 'test-stack', - }, - Configuration: { - FunctionName: 'test-function', - Handler: 'index.handler', - Runtime: 'nodejs18.x', - }, - Code: { - Location: 'https://lambda-function-code.zip', - }, - }) - - // Setup CloudFormation client responses - cfnClientStub.describeStacks.resolves({ - Stacks: [ - { - StackId: 'stack-id', - StackName: 'test-stack', - StackStatus: 'CREATE_COMPLETE', - }, - ], - }) - - cfnClientStub.getTemplate.resolves({ - TemplateBody: JSON.stringify({ - AWSTemplateFormatVersion: '2010-09-09', - Transform: 'AWS::Serverless-2016-10-31', - Resources: { - TestFunc: { - Type: 'AWS::Serverless::Function', - Properties: { - FunctionName: 'test-function', - Handler: 'index.handler', - Runtime: 'nodejs18.x', - CodeUri: 's3://test-bucket/test-key', - PackageType: 'Zip', - }, - }, - }, - }), - }) - - // Setup test window to return a project directory - getTestWindow().onDidShowDialog((dialog) => { - dialog.selectItem(vscode.Uri.file(tempDir)) - }) - // Spy on walkthrough.openProjectInWorkspace - const openProjectStub = sandbox.stub(walkthrough, 'openProjectInWorkspace') - - // Call the function - await lambda2sam.lambdaToSam(lambdaNode) - - assert.strictEqual( - await fs.exists(vscode.Uri.joinPath(vscode.Uri.file(tempDir), 'test-stack', 'template.yaml').fsPath), - true, - 'template.yaml was not written' - ) - assert.strictEqual( - await fs.exists(vscode.Uri.joinPath(vscode.Uri.file(tempDir), 'test-stack', 'README.md').fsPath), - true, - 'README.md was not written' - ) - assert.strictEqual( - await fs.exists(vscode.Uri.joinPath(vscode.Uri.file(tempDir), 'test-stack', 'samconfig.toml').fsPath), - true, - 'samconfig.toml was not written' - ) - - // Verify that project was opened in workspace - assert.strictEqual(openProjectStub.calledOnce, true) - assert.strictEqual( - openProjectStub.firstCall.args[0].fsPath, - vscode.Uri.joinPath(vscode.Uri.file(tempDir), 'test-stack').fsPath - ) - }) - }) - - function mockLambdaNode(withArn: boolean = false) { - if (withArn) { - return { - name: 'test-function', - regionCode: 'us-east-2', - arn: 'arn:aws:lambda:us-east-2:123456789012:function:test-function', - } as LambdaFunctionNode - } else { - return { - name: 'test-function', - regionCode: 'us-east-2', - } as LambdaFunctionNode - } - } - - function mockIamConnection() { - return { - type: 'iam' as const, - id: 'test-connection', - label: 'Test Connection', - state: 'valid' as const, - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - endpointUrl: undefined, - } - } - - function mockCloudFormationTemplate(): cloudFormation.Template { - return { - AWSTemplateFormatVersion: '2010-09-09', - Resources: { - TestFunc: { - Type: cloudFormation.LAMBDA_FUNCTION_TYPE, - Properties: { - FunctionName: 'test-function', - PackageType: 'Zip', - Handler: 'index.handler', - CodeUri: 's3://test-bucket/test-key', - }, - }, - }, - } - } - - function mockFetchResponse(sandbox: sinon.SinonSandbox) { - return sandbox.stub(global, 'fetch').resolves({ - ok: true, - json: sandbox.stub().resolves({ - cloudFormationTemplateId: 'test-template-id', - }), - } as any) - } -}) diff --git a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samDownload.test.ts b/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samDownload.test.ts deleted file mode 100644 index 9c4d312291..0000000000 --- a/packages/core/src/test/awsService/appBuilder/lambda2sam/lambda2samDownload.test.ts +++ /dev/null @@ -1,312 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import * as vscode from 'vscode' -import * as lambda2sam from '../../../../awsService/appBuilder/lambda2sam/lambda2sam' -import * as utils from '../../../../awsService/appBuilder/utils' -import { fs } from '../../../../shared' -import { DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import os from 'os' -import path from 'path' -import { LAMBDA_FUNCTION_TYPE, LAMBDA_LAYER_TYPE } from '../../../../shared/cloudformation/cloudformation' - -describe('lambda2samDownload', function () { - let sandbox: sinon.SinonSandbox - let tempDir: string - let lambdaClientStub: sinon.SinonStubbedInstance - let cfnClientStub: any - let downloadUnzipStub: sinon.SinonStub - - beforeEach(async function () { - sandbox = sinon.createSandbox() - tempDir = path.join(os.tmpdir(), `aws-toolkit-test-${Date.now()}`) - - // Create temp directory for tests - actually create it, don't stub - if (!(await fs.exists(vscode.Uri.file(tempDir)))) { - await fs.mkdir(vscode.Uri.file(tempDir)) - } - - // Create Lambda client stub with necessary properties - lambdaClientStub = sandbox.createStubInstance(DefaultLambdaClient) - // Add required properties that aren't stubbed automatically - Object.defineProperty(lambdaClientStub, 'defaultTimeoutInMs', { - value: 5 * 60 * 1000, // 5 minutes - configurable: true, - }) - Object.defineProperty(lambdaClientStub, 'createSdkClient', { - value: () => Promise.resolve({}), - configurable: true, - }) - - sandbox.stub(utils, 'getLambdaClient').returns(lambdaClientStub as any) - - // Stub CloudFormation client - now returns Promises directly (no .promise() method) - cfnClientStub = { - describeStackResource: sandbox.stub().resolves({ - StackResourceDetail: { - PhysicalResourceId: 'test-physical-id', - }, - }), - describeStackResources: sandbox.stub().resolves({ - StackResources: [ - { LogicalResourceId: 'testResource', PhysicalResourceId: 'test-physical-id' }, - { LogicalResourceId: 'prefixTestResource', PhysicalResourceId: 'prefix-test-physical-id' }, - ], - }), - } - sandbox.stub(utils, 'getCFNClient').resolves(cfnClientStub) - - // Stub downloadUnzip function to create actual files in the temp directory - downloadUnzipStub = sandbox.stub(utils, 'downloadUnzip').callsFake(async (url, outputPath) => { - // Create a mock file structure for testing purposes - - // Create the output directory if it doesn't exist - if (!(await fs.exists(outputPath))) { - await fs.mkdir(outputPath) - } - - // Create a simple file to simulate extracted content - await fs.writeFile( - vscode.Uri.joinPath(outputPath, 'index.js'), - 'exports.handler = async (event) => { return "Hello World" };' - ) - - // Create a package.json file - await fs.writeFile( - vscode.Uri.joinPath(outputPath, 'package.json'), - JSON.stringify( - { - name: 'test-lambda', - version: '1.0.0', - description: 'Test Lambda function', - }, - undefined, - 2 - ) - ) - }) - }) - - afterEach(async function () { - sandbox.restore() - - // Clean up the temp directory after each test - if (await fs.exists(vscode.Uri.file(tempDir))) { - await fs.delete(vscode.Uri.file(tempDir), { recursive: true, force: true }) - } - }) - - describe('getPhysicalIdfromCFNResourceName', function () { - it('returns the physical ID when an exact match is found', async function () { - const result = await lambda2sam.getPhysicalIdfromCFNResourceName( - 'testResource', - 'us-west-2', - 'stack-id', - LAMBDA_FUNCTION_TYPE - ) - - assert.strictEqual(cfnClientStub.describeStackResource.calledOnce, true) - assert.strictEqual(cfnClientStub.describeStackResource.firstCall.args[0].StackName, 'stack-id') - assert.strictEqual(cfnClientStub.describeStackResource.firstCall.args[0].LogicalResourceId, 'testResource') - assert.strictEqual(result, 'test-physical-id') - }) - - it('returns a prefix match when exact match fails', async function () { - // Make exact match fail - cfnClientStub.describeStackResource.rejects(new Error('Resource not found')) - - const result = await lambda2sam.getPhysicalIdfromCFNResourceName( - 'prefix', - 'us-west-2', - 'stack-id', - LAMBDA_LAYER_TYPE - ) - - assert.strictEqual(cfnClientStub.describeStackResources.calledOnce, true) - assert.strictEqual(cfnClientStub.describeStackResources.firstCall.args[0].StackName, 'stack-id') - assert.strictEqual(result, 'prefix-test-physical-id') - }) - - it('returns undefined when no match is found', async function () { - // Make exact match fail - cfnClientStub.describeStackResource.rejects(new Error('Resource not found')) - - // Return empty resources - cfnClientStub.describeStackResources.resolves({ StackResources: [] }) - - const result = await lambda2sam.getPhysicalIdfromCFNResourceName( - 'nonexistent', - 'us-west-2', - 'stack-id', - LAMBDA_LAYER_TYPE - ) - assert.strictEqual(result, undefined) - }) - }) - - describe('downloadLambdaFunctionCode', function () { - it('uses physical ID from CloudFormation when not provided', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testResource' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - lambdaClientStub.getFunction.resolves({ - Code: { Location: 'https://lambda-function-code.zip' }, - }) - - await lambda2sam.downloadLambdaFunctionCode(resourceName, stackInfo, targetDir, 'us-west-2') - - // Verify CloudFormation was called to get physical ID - assert.strictEqual(cfnClientStub.describeStackResource.calledOnce, true) - - // Verify Lambda client was called with correct physical ID - assert.strictEqual(lambdaClientStub.getFunction.calledOnce, true) - assert.strictEqual(lambdaClientStub.getFunction.firstCall.args[0], 'test-physical-id') - - // Verify downloadUnzip was called with correct parameters - assert.strictEqual(downloadUnzipStub.calledOnce, true) - assert.strictEqual(downloadUnzipStub.firstCall.args[0], 'https://lambda-function-code.zip') - assert.strictEqual( - downloadUnzipStub.firstCall.args[1].fsPath, - vscode.Uri.joinPath(targetDir, resourceName).fsPath - ) - - // Verify files were actually created in the temp directory - const outputDir = vscode.Uri.joinPath(targetDir, resourceName) - assert.strictEqual(await fs.exists(outputDir), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'index.js')), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'package.json')), true) - }) - - it('uses provided physical ID when available', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testResource' - const physicalResourceId = 'provided-physical-id' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - lambdaClientStub.getFunction.resolves({ - Code: { Location: 'https://lambda-function-code.zip' }, - }) - - await lambda2sam.downloadLambdaFunctionCode( - resourceName, - stackInfo, - targetDir, - 'us-west-2', - physicalResourceId - ) - - // Verify CloudFormation was NOT called to get physical ID - assert.strictEqual(cfnClientStub.describeStackResource.called, false) - - // Verify Lambda client was called with provided physical ID - assert.strictEqual(lambdaClientStub.getFunction.calledOnce, true) - assert.strictEqual(lambdaClientStub.getFunction.firstCall.args[0], physicalResourceId) - - // Verify files were actually created in the temp directory - const outputDir = vscode.Uri.joinPath(targetDir, resourceName) - assert.strictEqual(await fs.exists(outputDir), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'index.js')), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'package.json')), true) - }) - - it('throws an error when code location is missing', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testResource' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - lambdaClientStub.getFunction.resolves({ - Code: {}, // No Location - }) - - await assert.rejects( - lambda2sam.downloadLambdaFunctionCode(resourceName, stackInfo, targetDir, 'us-west-2'), - /Could not determine code location/ - ) - }) - }) - - describe('downloadLayerVersionResourceByName', function () { - it('extracts layer name and version from ARN and downloads content', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testLayer' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - // Return an ARN for a layer version - cfnClientStub.describeStackResource.resolves({ - StackResourceDetail: { - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer:1', - }, - }) - - lambdaClientStub.getLayerVersion.resolves({ - Content: { Location: 'https://lambda-layer-code.zip' }, - }) - - await lambda2sam.downloadLayerVersionResourceByName(resourceName, stackInfo, targetDir, 'us-west-2') - - // Verify Lambda client was called with correct layer name and version - assert.strictEqual(lambdaClientStub.getLayerVersion.calledOnce, true) - assert.strictEqual(lambdaClientStub.getLayerVersion.firstCall.args[0], 'my-layer') - assert.strictEqual(lambdaClientStub.getLayerVersion.firstCall.args[1], 1) - - // Verify downloadUnzip was called with correct parameters - assert.strictEqual(downloadUnzipStub.calledOnce, true) - assert.strictEqual(downloadUnzipStub.firstCall.args[0], 'https://lambda-layer-code.zip') - assert.strictEqual( - downloadUnzipStub.firstCall.args[1].fsPath, - vscode.Uri.joinPath(targetDir, resourceName).fsPath - ) - - // Verify files were actually created in the temp directory - const outputDir = vscode.Uri.joinPath(targetDir, resourceName) - assert.strictEqual(await fs.exists(outputDir), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'index.js')), true) - assert.strictEqual(await fs.exists(vscode.Uri.joinPath(outputDir, 'package.json')), true) - }) - - it('throws an error when ARN format is invalid', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testLayer' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - // Return an invalid ARN - cfnClientStub.describeStackResource.resolves({ - StackResourceDetail: { - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer', // Missing version - }, - }) - - await assert.rejects( - lambda2sam.downloadLayerVersionResourceByName(resourceName, stackInfo, targetDir, 'us-west-2'), - /Invalid layer ARN format/ - ) - }) - - it('throws an error when layer content location is missing', async function () { - const targetDir = vscode.Uri.file(tempDir) - const resourceName = 'testLayer' - const stackInfo = { stackId: 'stack-id', stackName: 'test-stack', isSamTemplate: false, template: {} } - - // Return an ARN for a layer version - cfnClientStub.describeStackResource.resolves({ - StackResourceDetail: { - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:layer:my-layer:1', - }, - }) - - lambdaClientStub.getLayerVersion.resolves({ - Content: {}, // No Location - }) - - await assert.rejects( - lambda2sam.downloadLayerVersionResourceByName(resourceName, stackInfo, targetDir, 'us-west-2'), - /Could not determine code location for layer/ - ) - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/serverlessLand/main.test.ts b/packages/core/src/test/awsService/appBuilder/serverlessLand/main.test.ts deleted file mode 100644 index 5ed49638dc..0000000000 --- a/packages/core/src/test/awsService/appBuilder/serverlessLand/main.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import * as sinon from 'sinon' -import * as path from 'path' -import { MetadataManager } from '../../../../awsService/appBuilder/serverlessLand/metadataManager' -import * as main from '../../../../awsService/appBuilder/serverlessLand/main' -import { - createNewServerlessLandProject, - openReadmeFile, - getProjectUri, - downloadPatternCode, -} from '../../../../awsService/appBuilder/serverlessLand/main' -import { fs } from '../../../../shared/fs/fs' -import * as downloadPatterns from '../../../../shared/utilities/downloadPatterns' -import { ExtContext } from '../../../../shared/extensions' -import { workspaceUtils } from '../../../../shared' -import * as messages from '../../../../shared/utilities/messages' -import * as downloadPattern from '../../../../shared/utilities/downloadPatterns' -import * as wizardModule from '../../../../awsService/appBuilder/serverlessLand/wizard' - -describe('createNewServerlessLandProject', () => { - let sandbox: sinon.SinonSandbox - let mockExtContext: ExtContext - let mockMetadataManager: sinon.SinonStubbedInstance - let mockWizard: { run: sinon.SinonStub } - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockExtContext = { - awsContext: { - getCredentials: sandbox.stub().resolves({}), - getCredentialDefaultRegion: () => 'us-west-2', - }, - } as unknown as ExtContext - - mockMetadataManager = sandbox.createStubInstance(MetadataManager) - mockMetadataManager.getAssetName.returns('test-asset-name') - sandbox.stub(MetadataManager, 'getInstance').returns(mockMetadataManager as unknown as MetadataManager) - - mockWizard = { run: sandbox.stub() } - sandbox.stub(wizardModule, 'CreateServerlessLandWizard').returns(mockWizard) - - sandbox.stub(vscode.Uri, 'joinPath').returns(vscode.Uri.file('/test')) - sandbox.stub(vscode.commands, 'executeCommand').resolves() - - sandbox.stub(workspaceUtils, 'addFolderToWorkspace').resolves() - sandbox.stub(downloadPattern, 'getPattern').resolves() - }) - afterEach(() => { - sandbox.restore() - }) - it('should complete project creation successfully', async () => { - const mockConfig = { - pattern: 'testPattern', - runtime: 'nodejs', - iac: 'sam', - location: vscode.Uri.file('/test'), - name: 'testProject', - } - mockWizard.run.resolves(mockConfig) - await createNewServerlessLandProject(mockExtContext) - assert.strictEqual(mockWizard.run.calledOnce, true) - assert.strictEqual(mockMetadataManager.getAssetName.calledOnce, true) - assert.strictEqual((downloadPattern.getPattern as sinon.SinonStub).calledOnce, true) - }) - it('should handle wizard cancellation', async () => { - mockWizard.run.resolves(undefined) - await createNewServerlessLandProject(mockExtContext) - assert.strictEqual(mockWizard.run.calledOnce, true) - assert.strictEqual(mockMetadataManager.getAssetName.called, false) - assert.strictEqual((downloadPattern.getPattern as sinon.SinonStub).called, false) - }) -}) - -function assertDownloadPatternCall(getPatternStub: sinon.SinonStub, mockConfig: any) { - const mockAssetName = 'test-project-sam-python.zip' - const serverlessLandOwner = 'aws-samples' - const serverlessLandRepo = 'serverless-patterns' - const mockLocation = vscode.Uri.joinPath(mockConfig.location, mockConfig.name) - - assert(getPatternStub.calledOnce) - assert(getPatternStub.firstCall.args[0] === serverlessLandOwner) - assert(getPatternStub.firstCall.args[1] === serverlessLandRepo) - assert(getPatternStub.firstCall.args[2] === mockAssetName) - assert(getPatternStub.firstCall.args[3].toString() === mockLocation.toString()) - assert(getPatternStub.firstCall.args[4] === true) -} - -describe('downloadPatternCode', () => { - let sandbox: sinon.SinonSandbox - let getPatternStub: sinon.SinonStub - let mockConfig: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - getPatternStub = sandbox.stub(downloadPatterns, 'getPattern') - mockConfig = { - name: 'test-project', - location: vscode.Uri.file('/test'), - pattern: 'test-project-sam-python', - runtime: 'python', - iac: 'sam', - assetName: 'test-project-sam-python', - } - }) - afterEach(function () { - sandbox.restore() - getPatternStub.restore() - }) - it('successfully downloads pattern code', async () => { - sandbox.stub(messages, 'handleOverwriteConflict').resolves(true) - - await downloadPatternCode(mockConfig, mockConfig.assetName) - assertDownloadPatternCall(getPatternStub, mockConfig) - }) - it('downloads pattern when directory exists and user confirms overwrite', async function () { - sandbox.stub(messages, 'handleOverwriteConflict').resolves(true) - - await downloadPatternCode(mockConfig, mockConfig.assetName) - assertDownloadPatternCall(getPatternStub, mockConfig) - }) - it('throws error when directory exists and user cancels overwrite', async function () { - const handleOverwriteStub = sandbox.stub(messages, 'handleOverwriteConflict') - handleOverwriteStub.rejects(new Error('Folder already exists: test-project')) - - await assert.rejects( - () => downloadPatternCode(mockConfig, mockConfig.assetName), - /Folder already exists: test-project/ - ) - }) -}) - -describe('openReadmeFile', () => { - let testsandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - testsandbox = sinon.createSandbox() - spyExecuteCommand = testsandbox.spy(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - testsandbox.restore() - }) - const mockConfig = { - name: 'test-project', - location: vscode.Uri.file('/test'), - pattern: 'test-project-sam-python', - runtime: 'python', - iac: 'sam', - assetName: 'test-project-sam-python', - } - it('successfully opens README file', async () => { - const mockReadmeUri = vscode.Uri.file('/test/README.md') - testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri) - - testsandbox.stub(fs, 'exists').resolves(true) - - // When - await openReadmeFile(mockConfig) - // Then - testsandbox.assert.calledWith(spyExecuteCommand, 'workbench.action.focusFirstEditorGroup') - testsandbox.assert.calledWith(spyExecuteCommand, 'markdown.showPreview') - }) - - it('handles missing README file', async () => { - const mockReadmeUri = vscode.Uri.file('/test/file.md') - testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri) - - testsandbox.stub(fs, 'exists').resolves(false) - - // When - await openReadmeFile(mockConfig) - // Then - testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview') - assert.ok(true, 'Function should return without throwing error when README is not found') - }) - - it('handles error with opening README file', async () => { - const mockReadmeUri = vscode.Uri.file('/test/README.md') - testsandbox.stub(main, 'getProjectUri').resolves(mockReadmeUri) - - testsandbox.stub(fs, 'exists').rejects(new Error('File system error')) - - // When - await assert.rejects(() => openReadmeFile(mockConfig), { - name: 'Error', - message: 'Error processing README file', - }) - // Then - testsandbox.assert.neverCalledWith(spyExecuteCommand, 'markdown.showPreview') - }) -}) - -describe('getProjectUri', () => { - const sandbox = sinon.createSandbox() - beforeEach(() => { - sandbox.restore() - }) - afterEach(() => { - sandbox.restore() - }) - const mockConfig = { - name: 'test-project', - location: vscode.Uri.file('/test'), - pattern: 'test-project-sam-python', - runtime: 'python', - iac: 'sam', - assetName: 'test-project-sam-python', - } - it('returns Uri when file exists', async () => { - const testFile = 'README.md' - const expectedPath = path.resolve('/test', 'test-project', testFile) - const uriFileStub = sandbox.stub(vscode.Uri, 'file').returns({ - fsPath: expectedPath, - } as vscode.Uri) - - const result = await getProjectUri(mockConfig, testFile) - sandbox.assert.calledWith(uriFileStub, expectedPath) - assert.strictEqual(result?.fsPath, expectedPath) - }) - it('handles missing project directory', async () => { - await assert.rejects( - async () => await getProjectUri(mockConfig, ''), - /expected "file" parameter to have at least one item/ - ) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/serverlessLand/metadataManager.test.ts b/packages/core/src/test/awsService/appBuilder/serverlessLand/metadataManager.test.ts deleted file mode 100644 index 27a48a96e3..0000000000 --- a/packages/core/src/test/awsService/appBuilder/serverlessLand/metadataManager.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { MetadataManager } from '../../../../awsService/appBuilder/serverlessLand/metadataManager' -import globals from '../../../../shared/extensionGlobals' -import path from 'path' -import assert from 'assert' -import * as sinon from 'sinon' - -describe('MetadataManager', () => { - let sandbox: sinon.SinonSandbox - let manager: MetadataManager - - beforeEach(() => { - sandbox = sinon.createSandbox() - manager = MetadataManager.getInstance() - ;(manager as any).metadata = { - patterns: { - testPattern: { - implementation: [ - { iac: 'SAM', runtime: 'nodejs', assetName: 'asset1' }, - { iac: 'CDK', runtime: 'nodejs', assetName: 'asset2' }, - { iac: 'SAM', runtime: 'python', assetName: 'asset3' }, - ], - }, - }, - } - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('getInstance', () => { - it('should create a new instance when none exists', () => { - const instance1 = MetadataManager.getInstance() - assert(instance1 instanceof MetadataManager) - }) - - it('should return the same instance when called multiple times', () => { - const instance1 = MetadataManager.getInstance() - const instance2 = MetadataManager.getInstance() - assert.strictEqual(instance1, instance2) - }) - }) - describe('initialize', () => { - it('should initialize metadata manager and return instance', async () => { - const mockPath = '/mock/path/metadata.json' - const loadMetadataStub = sandbox.stub().resolves(undefined) - - const getMetadataPathStub = sandbox.stub(MetadataManager.prototype, 'getMetadataPath').returns(mockPath) - - sandbox.stub(MetadataManager.prototype as any, 'loadMetadata').callsFake(loadMetadataStub) - - const instance = MetadataManager.initialize() - - assert(instance instanceof MetadataManager) - sandbox.assert.calledOnce(getMetadataPathStub) - sandbox.assert.calledWith(loadMetadataStub, mockPath) - }) - }) - - describe('getMetadataPath', () => { - it('should return correct metadata path', () => { - const mockAbsolutePath = '/absolute/path' - const asAbsolutePathStub = sandbox.stub().returns(mockAbsolutePath) - - sandbox.stub(globals, 'context').value({ - asAbsolutePath: asAbsolutePathStub, - }) - - const instance = MetadataManager.getInstance() - const result = instance.getMetadataPath() - const expectedPath = path.join('dist', 'src', 'serverlessLand', 'metadata.json') - - sandbox.assert.calledWith(asAbsolutePathStub, expectedPath) - assert.strictEqual(result, mockAbsolutePath) - }) - }) - - describe('getPatterns', () => { - let manager: MetadataManager - beforeEach(() => { - manager = MetadataManager.getInstance() - }) - it('handles different pattern data types', () => { - ;(manager as any).metadata = { - patterns: { - pattern1: { name: 'test', description: 'object description' }, - }, - } - assert.deepStrictEqual(manager.getPatterns(), [{ label: 'pattern1', description: 'object description' }]) - }) - }) - - describe('getRuntimes', () => { - it('returns empty array when pattern not found', () => { - assert.deepStrictEqual(manager.getRuntimes('nonexistent'), []) - }) - - it('returns unique runtimes for valid pattern', () => { - assert.deepStrictEqual(manager.getRuntimes('testPattern'), [{ label: 'nodejs' }, { label: 'python' }]) - }) - }) - - describe('getUrl', () => { - it('returns empty string when pattern not found', () => { - assert.strictEqual(manager.getUrl('nonexistent'), '') - }) - - it('returns correct URL for valid pattern', () => { - assert.strictEqual(manager.getUrl('testPattern'), 'https://serverlessland.com/patterns/asset1') - }) - }) - - describe('getIacOptions', () => { - it('returns empty array when pattern not found', () => { - assert.deepStrictEqual(manager.getIacOptions('nonexistent'), []) - }) - - it('returns unique IAC options for valid pattern', () => { - assert.deepStrictEqual(manager.getIacOptions('testPattern'), [{ label: 'SAM' }, { label: 'CDK' }]) - }) - }) - - describe('getAssetName', () => { - it('returns empty string when pattern not found', () => { - assert.strictEqual(manager.getAssetName('nonexistent', 'nodejs', 'SAM'), '') - }) - - it('returns correct asset name for matching implementation', () => { - assert.strictEqual(manager.getAssetName('testPattern', 'nodejs', 'SAM'), 'asset1') - }) - - it('returns empty string when no matching implementation found', () => { - assert.strictEqual(manager.getAssetName('testPattern', 'java', 'SAM'), '') - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/serverlessLand/wizard.test.ts b/packages/core/src/test/awsService/appBuilder/serverlessLand/wizard.test.ts deleted file mode 100644 index cb7117736c..0000000000 --- a/packages/core/src/test/awsService/appBuilder/serverlessLand/wizard.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import path from 'path' -import { PrompterTester } from '../../../../test/shared/wizards/prompterTester' -import { describe } from 'mocha' -import { ProjectMetadata } from '../../../../awsService/appBuilder/serverlessLand/metadataManager' -import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports -import fs from '../../../../shared/fs/fs' -import globals from '../../../../shared/extensionGlobals' -import { AppBuilderRootNode } from '../../../../awsService/appBuilder/explorer/nodes/rootNode' -import * as sinon from 'sinon' -import { AppNode } from '../../../../awsService/appBuilder/explorer/nodes/appNode' -import { ResourceNode } from '../../../../awsService/appBuilder/explorer/nodes/resourceNode' -import { getTestWindow } from '../../../../test/shared/vscode/window' - -describe('CreateWizard', async () => { - const metadataPath = globals.context.asAbsolutePath(path.join('dist', 'src', 'serverlessLand', 'metadata.json')) - const metadataContent = nodefs.readFileSync(metadataPath, { encoding: 'utf-8' }) - const parseMetadata = JSON.parse(metadataContent) as ProjectMetadata - const workspaceFolder = vscode.workspace.workspaceFolders![0] - const projectFolder = 'my-project-from-Serverless-Land' - let rootNode: sinon.SinonSpiedInstance - let sandbox: sinon.SinonSandbox - - beforeEach(async () => { - sandbox = sinon.createSandbox() - await fs.delete(path.join(workspaceFolder.uri.fsPath, projectFolder), { recursive: true }) - }) - - afterEach(async () => { - await fs.delete(path.join(workspaceFolder.uri.fsPath, projectFolder), { recursive: true }) - sandbox.restore() - }) - - describe(':) ServerlessLand Path', async () => { - it('creates project with Python runtime and CDK', async () => { - rootNode = sandbox.spy(AppBuilderRootNode.instance) - const testWindow = getTestWindow() - const prompterTester = PrompterTester.init({ testWindow }) - .handleQuickPick('Select a Pattern for your application', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - Object.entries(parseMetadata.patterns).map(([key, pattern]) => { - options.find((option) => option.label === key && option.detail === pattern.description) - }) - assert.strictEqual(options[0].label, 'Image Resizing') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Select Runtime', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) // python - }) - .handleQuickPick('Select IaC', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - quickPick.acceptItem(options[0]) // sam - }) - .handleQuickPick('Select Project Location', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Enter Project Name', (inputBox) => { - inputBox.acceptValue('python-sam-project') - }) - .build() - - await vscode.commands.executeCommand('aws.toolkit.lambda.createServerlessLandProject') - - const projectNode = await rootNode - .getChildren() - .then( - (children) => - children.find( - (node) => - node instanceof AppNode && - node.label === path.normalize('workspaceFolder/python-sam-project') - ) as AppNode | undefined - ) - - assert.ok(projectNode) - const resourceNodes = await projectNode.getChildren() - assert.strictEqual(resourceNodes.length, 3) - assert.ok(resourceNodes[0] instanceof ResourceNode) - - const lambdaResource = resourceNodes[2] as ResourceNode - assert( - 'Runtime' in lambdaResource.resource.resource && - lambdaResource.resource.resource.Runtime === 'python3.14' - ) - - prompterTester.assertCallAll() - }) - }) - describe('Error Handling', async () => { - it('handles empty project name', async () => { - const testWindow = getTestWindow() - const prompterTester = PrompterTester.init({ testWindow }) - .handleQuickPick('Select a Pattern for your application', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select Runtime', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[3]) - }) - .handleQuickPick('Select IaC', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Select Project Location', async (quickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Enter Project Name', (inputBox) => { - inputBox.acceptValue(' ') - }) - .build() - - try { - await vscode.commands.executeCommand('aws.toolkit.lambda.createServerlessLandProject') - assert.fail('Project name cannot be empty') - } catch (err) { - assert.ok(err instanceof Error) - assert.strictEqual(err.message, 'Project name cannot be empty') - } - - prompterTester.assertCallAll() - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/utils.test.ts b/packages/core/src/test/awsService/appBuilder/utils.test.ts deleted file mode 100644 index 36c75d2c6e..0000000000 --- a/packages/core/src/test/awsService/appBuilder/utils.test.ts +++ /dev/null @@ -1,849 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import assert from 'assert' -import { getTestWindow } from '../../shared/vscode/window' - -import fs from '../../../shared/fs/fs' -import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode' -import path from 'path' -import { SERVERLESS_FUNCTION_TYPE } from '../../../shared/cloudformation/cloudformation' -import { FunctionResourceEntity } from '../../../awsService/appBuilder/explorer/samProject' -import { - runOpenHandler, - runOpenTemplate, - isPermissionError, - EnhancedLambdaClient, - EnhancedCloudFormationClient, - getLambdaClient, - getCFNClient, -} from '../../../awsService/appBuilder/utils' -import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider' -import { assertTextEditorContains } from '../../testUtil' -import { DefaultLambdaClient } from '../../../shared/clients/lambdaClient' -import { ToolkitError } from '../../../shared/errors' -import globals from '../../../shared/extensionGlobals' -import { Runtime } from '@aws-sdk/client-lambda' -import { CloudFormationClient } from '@aws-sdk/client-cloudformation' - -interface TestScenario { - runtime: Runtime - handler: string - codeUri: string - fileLocation: string - fileInfo: string - regex: RegExp -} - -const scenarios: TestScenario[] = [ - { - runtime: 'java21', - handler: 'resizer.App::handleRequest', - codeUri: 'ResizerFunction', - fileLocation: 'ResizerFunction/src/main/java/resizer/App.java', - fileInfo: 'testjava', - regex: /App.java/g, - }, - { - runtime: 'dotnet6', - handler: 'ImageResize::ImageResize.Function::FunctionHandler', - codeUri: 'ImageResize/', - fileLocation: 'ImageResize/Function.cs', - fileInfo: 'testdotnet', - regex: /Function.cs/g, - }, - { - runtime: 'python3.9', - handler: 'app.lambda_handler', - codeUri: 'hello_world/', - fileLocation: 'hello_world/app.py', - fileInfo: 'testpython', - regex: /app.py/g, - }, - { - runtime: 'nodejs18.x', - handler: 'app.handler', - codeUri: 'src/', - fileLocation: 'src/app.js', - fileInfo: 'testnode', - regex: /app.js/g, - }, - { - runtime: 'ruby3.2', - handler: 'app.lambda_handler', - codeUri: 'hello_world/', - fileLocation: 'hello_world/app.rb', - fileInfo: 'testruby', - regex: /app.rb/g, - }, - { - runtime: 'java21', - handler: 'resizer.App::handleRequest', - codeUri: 'ResizerFunction', - fileLocation: 'ResizerFunction/src/foo/bar/main/java/resizer/App.java', - fileInfo: 'testjava2', - regex: /App.java/g, - }, - { - runtime: 'dotnet8', - handler: 'ImageResize::ImageResize.Function::FunctionHandler', - codeUri: 'ImageResize/src/test', - fileLocation: 'ImageResize/src/test/Function.cs', - fileInfo: 'testdotnet2', - regex: /Function.cs/g, - }, - { - runtime: 'python3.12', - handler: 'app.foo.bar.lambda_handler', - codeUri: 'hello_world/test123', - fileLocation: 'hello_world/test123/app/foo/bar.py', - fileInfo: 'testpython2', - regex: /bar.py/g, - }, - { - runtime: 'nodejs20.x', - handler: 'app.foo.bar.handler', - codeUri: 'src/test123', - fileLocation: 'src/test123/app/foo/bar.js', - fileInfo: 'testnode2', - regex: /bar.js/g, - }, - { - runtime: 'ruby3.3', - handler: 'app/foo/bar.lambda_handler', - codeUri: 'hello_world/test456', - fileLocation: 'hello_world/test456/app/foo/bar.rb', - fileInfo: 'testruby2', - regex: /bar.rb/g, - }, -] - -describe('AppBuilder Utils', function () { - describe('openHandler', function () { - let sandbox: sinon.SinonSandbox - const workspace = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspace) - const tempFolder = path.join(workspace.uri.fsPath, 'temp') - - beforeEach(async function () { - sandbox = sinon.createSandbox() - await fs.mkdir(tempFolder) - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - sandbox.restore() - }) - - for (const scenario of scenarios) { - it(`should open ${scenario.runtime}`, async function () { - // Given - const rNode = new ResourceNode( - { - samTemplateUri: vscode.Uri.file(path.join(tempFolder, 'template.yaml')), - workspaceFolder: workspace, - projectRoot: vscode.Uri.file(tempFolder), - }, - { - Id: 'MyFunction', - Type: SERVERLESS_FUNCTION_TYPE, - Runtime: scenario.runtime, - Handler: scenario.handler, - CodeUri: scenario.codeUri, - } as FunctionResourceEntity - ) - await fs.mkdir(path.join(tempFolder, ...path.dirname(scenario.fileLocation).split('/'))) - await fs.writeFile(path.join(tempFolder, ...scenario.fileLocation.split('/')), scenario.fileInfo) - await runOpenHandler(rNode) - // Then - assert.strictEqual( - vscode.window.activeTextEditor?.document.fileName, - path.join(tempFolder, ...scenario.fileLocation.split('/')) - ) - await assertTextEditorContains(scenario.fileInfo) - }) - } - }) - - describe('openHandler', function () { - let sandbox: sinon.SinonSandbox - const workspace = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspace) - const tempFolder = path.join(workspace.uri.fsPath, 'temp') - - beforeEach(async function () { - sandbox = sinon.createSandbox() - await fs.mkdir(tempFolder) - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - sandbox.restore() - }) - - for (const scenario of scenarios) { - it(`should open ${scenario.runtime}`, async function () { - // Given - const rNode = new ResourceNode( - { - samTemplateUri: vscode.Uri.file(path.join(tempFolder, 'template.yaml')), - workspaceFolder: workspace, - projectRoot: vscode.Uri.file(tempFolder), - }, - { - Id: 'MyFunction', - Type: SERVERLESS_FUNCTION_TYPE, - Runtime: scenario.runtime, - Handler: scenario.handler, - CodeUri: scenario.codeUri, - } as FunctionResourceEntity - ) - await fs.mkdir(path.join(tempFolder, ...path.dirname(scenario.fileLocation).split('/'))) - await fs.writeFile(path.join(tempFolder, ...scenario.fileLocation.split('/')), scenario.fileInfo) - await runOpenHandler(rNode) - // Then - assert.strictEqual( - vscode.window.activeTextEditor?.document.fileName, - path.join(tempFolder, ...scenario.fileLocation.split('/')) - ) - await assertTextEditorContains(scenario.fileInfo) - }) - } - - it(`should warn for multiple java handler found`, async function () { - const rNode = new ResourceNode( - { - samTemplateUri: vscode.Uri.file(path.join(tempFolder, 'template.yaml')), - workspaceFolder: workspace, - projectRoot: vscode.Uri.file(tempFolder), - }, - { - Id: 'MyFunction', - Type: SERVERLESS_FUNCTION_TYPE, - Runtime: 'java21', - Handler: 'resizer.App::handleRequest', - CodeUri: 'ResizerFunction', - } as FunctionResourceEntity - ) - // When 2 java handler with right name under code URI - await fs.mkdir( - path.join(tempFolder, ...path.dirname('ResizerFunction/src/main/java/resizer/App.java').split('/')) - ) - await fs.writeFile( - path.join(tempFolder, ...'ResizerFunction/src/main/java/resizer/App.java'.split('/')), - 'testjava' - ) - await fs.mkdir( - path.join(tempFolder, ...path.dirname('ResizerFunction/src/main/java/resizer2/App.java').split('/')) - ) - await fs.writeFile( - path.join(tempFolder, ...'ResizerFunction/src/main/java/resizer2/App.java'.split('/')), - 'testjava' - ) - // Then should warn - getTestWindow().onDidShowMessage((msg) => - assert(msg.assertWarn('Multiple handler files found with name App.java"')) - ) - await runOpenHandler(rNode) - }) - }) - - describe('open template', function () { - let sandbox: sinon.SinonSandbox - const workspace = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspace) - const tempFolder = path.join(workspace.uri.fsPath, 'temp') - - beforeEach(async function () { - sandbox = sinon.createSandbox() - await fs.mkdir(tempFolder) - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - sandbox.restore() - }) - - it('select template should succeed', async function () { - const tNode = { - id: 'MyFunction', - resource: { - // this doesn't exist - samTemplateUri: vscode.Uri.file(path.join(tempFolder, 'abc', 'template.yaml')), - workspaceFolder: workspace, - projectRoot: vscode.Uri.file(tempFolder), - }, - } - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem(picker.items[0]) - }) - await fs.mkdir(path.join(tempFolder, 'abc')) - await fs.writeFile(path.join(tempFolder, 'abc', 'template.yaml'), 'testyaml') - - await vscode.commands.executeCommand('aws.appBuilder.openTemplate', tNode) - // Then - assert.strictEqual( - vscode.window.activeTextEditor?.document.fileName, - path.join(tempFolder, 'abc', 'template.yaml') - ) - await assertTextEditorContains('testyaml') - }) - - it('should raise if no template', async function () { - // Given - const openCommand = sandbox.spy(vscode.workspace, 'openTextDocument') - const showCommand = sandbox.spy(vscode.window, 'showTextDocument') - const tNode = { - id: 'MyFunction', - resource: { - // this doesn't exist - samTemplateUri: vscode.Uri.file(path.join(tempFolder, 'template.yaml')), - workspaceFolder: workspace, - projectRoot: vscode.Uri.file(tempFolder), - }, - } - try { - await runOpenTemplate(tNode as TreeNode) - assert.fail('SAM Template not found, cannot open template') - } catch (err) { - assert.strictEqual((err as Error).message, 'SAM Template not found, cannot open template') - } - // Then - assert(openCommand.neverCalledWith(sinon.match.has('fspath', sinon.match(/template.yaml/g)))) - assert(showCommand.notCalled) - }) - }) - - describe('Permission Error Handling', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('isPermissionError', function () { - it('should return true for AccessDeniedException', function () { - const error = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - assert.strictEqual(isPermissionError(error), true) - }) - - it('should return true for UnauthorizedOperation', function () { - const error = Object.assign(new Error('Unauthorized'), { - code: 'UnauthorizedOperation', - time: new Date(), - statusCode: 403, - }) - assert.strictEqual(isPermissionError(error), true) - }) - - it('should return true for Forbidden', function () { - const error = Object.assign(new Error('Forbidden'), { - code: 'Forbidden', - time: new Date(), - statusCode: 403, - }) - assert.strictEqual(isPermissionError(error), true) - }) - - it('should return true for AccessDenied', function () { - const error = Object.assign(new Error('Access denied'), { - code: 'AccessDenied', - time: new Date(), - statusCode: 403, - }) - assert.strictEqual(isPermissionError(error), true) - }) - - it('should return true for 403 status code', function () { - const error = Object.assign(new Error('Forbidden'), { - code: 'SomeError', - statusCode: 403, - time: new Date(), - }) - assert.strictEqual(isPermissionError(error), true) - }) - - it('should return false for non-permission errors', function () { - const error = Object.assign(new Error('Resource not found'), { - code: 'ResourceNotFoundException', - time: new Date(), - statusCode: 404, - }) - assert.strictEqual(isPermissionError(error), false) - }) - - it('should return false for non-AWS errors', function () { - const error = new Error('Regular error') - assert.strictEqual(isPermissionError(error), false) - }) - - it('should return false for undefined', function () { - assert.strictEqual(isPermissionError(undefined), false) - }) - }) - - describe('EnhancedLambdaClient', function () { - let mockLambdaClient: sinon.SinonStubbedInstance - let enhancedClient: EnhancedLambdaClient - - beforeEach(function () { - mockLambdaClient = sandbox.createStubInstance(DefaultLambdaClient) - // Add missing properties that EnhancedLambdaClient expects - Object.defineProperty(mockLambdaClient, 'defaultTimeoutInMs', { - value: 5 * 60 * 1000, - configurable: true, - }) - Object.defineProperty(mockLambdaClient, 'createSdkClient', { - value: sandbox.stub().resolves({}), - configurable: true, - }) - enhancedClient = new EnhancedLambdaClient(mockLambdaClient as any, 'us-east-1') - }) - - it('should enhance permission errors for getFunction', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.getFunction.rejects(permissionError) - - try { - await enhancedClient.getFunction('test-function') - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes('Permission denied: Missing required permissions for lambda:getFunction') - ) - assert(error.message.includes('lambda:GetFunction')) - assert(error.message.includes('arn:aws:lambda:us-east-1:*:function:test-function')) - assert(error.message.includes('To fix this issue:')) - assert(error.message.includes('Documentation:')) - } - }) - - it('should pass through non-permission errors for getFunction', async function () { - const nonPermissionError = new Error('Function not found') - mockLambdaClient.getFunction.rejects(nonPermissionError) - - try { - await enhancedClient.getFunction('test-function') - assert.fail('Expected error to be thrown') - } catch (error) { - assert.strictEqual(error, nonPermissionError) - } - }) - - it('should enhance permission errors for listFunctions', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - - // Create a mock async generator that throws the error - const mockAsyncGenerator = async function* (): AsyncIterableIterator { - throw permissionError - yield // This line will never be reached but satisfies ESLint require-yield rule - } - mockLambdaClient.listFunctions.returns(mockAsyncGenerator()) - - try { - const iterator = enhancedClient.listFunctions() - await iterator.next() - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for lambda:listFunctions' - ) - ) - assert(error.message.includes('lambda:ListFunctions')) - } - }) - - it('should enhance permission errors for deleteFunction', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.deleteFunction.rejects(permissionError) - - try { - await enhancedClient.deleteFunction('test-function') - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for lambda:deleteFunction' - ) - ) - assert(error.message.includes('lambda:DeleteFunction')) - assert(error.message.includes('arn:aws:lambda:us-east-1:*:function:test-function')) - } - }) - - it('should enhance permission errors for invoke', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.invoke.rejects(permissionError) - - try { - await enhancedClient.invoke('test-function', new TextEncoder().encode('{}')) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert(error.message.includes('Permission denied: Missing required permissions for lambda:invoke')) - assert(error.message.includes('lambda:InvokeFunction')) - assert(error.message.includes('arn:aws:lambda:us-east-1:*:function:test-function')) - } - }) - - it('should enhance permission errors for getLayerVersion', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.getLayerVersion.rejects(permissionError) - - try { - await enhancedClient.getLayerVersion('test-layer', 1) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for lambda:getLayerVersion' - ) - ) - assert(error.message.includes('lambda:GetLayerVersion')) - assert(error.message.includes('arn:aws:lambda:us-east-1:*:layer:test-layer:1')) - } - }) - - it('should enhance permission errors for updateFunctionCode', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.updateFunctionCode.rejects(permissionError) - - try { - await enhancedClient.updateFunctionCode('test-function', new Uint8Array()) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for lambda:updateFunctionCode' - ) - ) - assert(error.message.includes('lambda:UpdateFunctionCode')) - assert(error.message.includes('arn:aws:lambda:us-east-1:*:function:test-function')) - } - }) - - it('should return successful results when no errors occur', async function () { - const mockResponse = { Configuration: { FunctionName: 'test-function' } } - mockLambdaClient.getFunction.resolves(mockResponse) - - const result = await enhancedClient.getFunction('test-function') - assert.strictEqual(result, mockResponse) - }) - }) - - describe('EnhancedCloudFormationClient', function () { - let mockCfnClient: sinon.SinonStubbedInstance - let enhancedClient: EnhancedCloudFormationClient - - beforeEach(function () { - // Create a mock CloudFormation client with all required methods - mockCfnClient = sandbox.createStubInstance(CloudFormationClient) - enhancedClient = new EnhancedCloudFormationClient(mockCfnClient as any, 'us-east-1') - }) - - it('should enhance permission errors for describeStacks', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockCfnClient.send.rejects(permissionError) - - try { - await enhancedClient.describeStacks({ StackName: 'test-stack' }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for cloudformation:describeStacks' - ) - ) - assert(error.message.includes('cloudformation:DescribeStacks')) - assert(error.message.includes('arn:aws:cloudformation:us-east-1:*:stack/test-stack/*')) - assert(error.message.includes('To fix this issue:')) - assert(error.message.includes('Documentation:')) - } - }) - - it('should enhance permission errors for getTemplate', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockCfnClient.send.rejects(permissionError) - - try { - await enhancedClient.getTemplate({ StackName: 'test-stack' }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for cloudformation:getTemplate' - ) - ) - assert(error.message.includes('cloudformation:GetTemplate')) - assert(error.message.includes('arn:aws:cloudformation:us-east-1:*:stack/test-stack/*')) - } - }) - - it('should enhance permission errors for createChangeSet', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockCfnClient.send.rejects(permissionError) - - try { - await enhancedClient.createChangeSet({ - StackName: 'test-stack', - ChangeSetName: 'test-changeset', - TemplateBody: '{}', - }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for cloudformation:createChangeSet' - ) - ) - assert(error.message.includes('cloudformation:CreateChangeSet')) - assert(error.message.includes('arn:aws:cloudformation:us-east-1:*:stack/test-stack/*')) - } - }) - - it('should enhance permission errors for describeStackResource', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockCfnClient.send.rejects(permissionError) - - try { - await enhancedClient.describeStackResource({ - StackName: 'test-stack', - LogicalResourceId: 'TestResource', - }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for cloudformation:describeStackResource' - ) - ) - assert(error.message.includes('cloudformation:DescribeStackResource')) - assert(error.message.includes('arn:aws:cloudformation:us-east-1:*:stack/test-stack/*')) - } - }) - - it('should enhance permission errors for describeStackResources', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockCfnClient.send.rejects(permissionError) - - try { - await enhancedClient.describeStackResources({ StackName: 'test-stack' }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert( - error.message.includes( - 'Permission denied: Missing required permissions for cloudformation:describeStackResources' - ) - ) - assert(error.message.includes('cloudformation:DescribeStackResources')) - assert(error.message.includes('arn:aws:cloudformation:us-east-1:*:stack/test-stack/*')) - } - }) - - it('should pass through non-permission errors', async function () { - const nonPermissionError = new Error('Stack not found') - mockCfnClient.send.rejects(nonPermissionError) - - try { - await enhancedClient.describeStacks({ StackName: 'test-stack' }) - assert.fail('Expected error to be thrown') - } catch (error) { - assert.strictEqual(error, nonPermissionError) - } - }) - - it('should return successful results when no errors occur', async function () { - const mockResponse = { Stacks: [{ StackName: 'test-stack' }] } - mockCfnClient.send.resolves(mockResponse) - - const result = await enhancedClient.describeStacks({ StackName: 'test-stack' }) - assert.strictEqual(result, mockResponse) - }) - }) - - describe('Client Factory Functions', function () { - beforeEach(function () { - // Stub the global SDK client builder - sandbox.stub(globals.sdkClientBuilderV3, 'createAwsService').resolves({} as any) - }) - - it('should return EnhancedLambdaClient from getLambdaClient', function () { - const client = getLambdaClient('us-east-1') - assert(client instanceof EnhancedLambdaClient) - }) - - it('should return EnhancedCloudFormationClient from getCFNClient', async function () { - const client = await getCFNClient('us-east-1') - assert(client instanceof EnhancedCloudFormationClient) - }) - }) - - describe('Error Message Content', function () { - let mockLambdaClient: sinon.SinonStubbedInstance - let enhancedClient: EnhancedLambdaClient - - beforeEach(function () { - mockLambdaClient = sandbox.createStubInstance(DefaultLambdaClient) - // Add missing properties that EnhancedLambdaClient expects - Object.defineProperty(mockLambdaClient, 'defaultTimeoutInMs', { - value: 5 * 60 * 1000, - configurable: true, - }) - Object.defineProperty(mockLambdaClient, 'createSdkClient', { - value: sandbox.stub().resolves({}), - configurable: true, - }) - enhancedClient = new EnhancedLambdaClient(mockLambdaClient as any, 'us-west-2') - }) - - it('should include all required elements in enhanced error message', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - mockLambdaClient.getFunction.rejects(permissionError) - - try { - await enhancedClient.getFunction('my-test-function') - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - - // Check that the error message contains all expected elements - const message = error.message - - // Main error description - assert(message.includes('Permission denied: Missing required permissions for lambda:getFunction')) - - // Required permissions section - assert(message.includes('Required permissions:')) - assert(message.includes('- lambda:GetFunction')) - - // Resource ARN - assert(message.includes('Resource: arn:aws:lambda:us-west-2:*:function:my-test-function')) - - // Instructions - assert(message.includes('To fix this issue:')) - assert(message.includes('1. Contact your AWS administrator')) - assert(message.includes('2. Add these permissions to your IAM user/role policy')) - assert(message.includes('3. If using IAM roles, ensure the role has these permissions attached')) - - // Documentation link - assert( - message.includes( - 'Documentation: https://docs.aws.amazon.com/lambda/latest/api/API_GetFunction.html' - ) - ) - - // Check error details - assert.strictEqual(error.code, 'InsufficientPermissions') - assert(error.details) - assert.strictEqual(error.details.service, 'lambda') - assert.strictEqual(error.details.action, 'getFunction') - assert.deepStrictEqual(error.details.requiredPermissions, ['lambda:GetFunction']) - assert.strictEqual( - error.details.resourceArn, - 'arn:aws:lambda:us-west-2:*:function:my-test-function' - ) - } - }) - - it('should handle errors without resource ARN', async function () { - const permissionError = Object.assign(new Error('Access denied'), { - code: 'AccessDeniedException', - time: new Date(), - statusCode: 403, - }) - - // Create a mock async generator that throws the error - const mockAsyncGenerator = async function* (): AsyncIterableIterator { - throw permissionError - yield // This line will never be reached but satisfies ESLint require-yield rule - } - mockLambdaClient.listFunctions.returns(mockAsyncGenerator()) - - try { - const iterator = enhancedClient.listFunctions() - await iterator.next() - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - - const message = error.message - assert(message.includes('Permission denied: Missing required permissions for lambda:listFunctions')) - assert(message.includes('- lambda:ListFunctions')) - // Should not include Resource line for operations without specific resources - assert(!message.includes('Resource: arn:')) - } - }) - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/walkthrough.test.ts b/packages/core/src/test/awsService/appBuilder/walkthrough.test.ts deleted file mode 100644 index add3ee6e54..0000000000 --- a/packages/core/src/test/awsService/appBuilder/walkthrough.test.ts +++ /dev/null @@ -1,576 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import assert from 'assert' -import { - openApplicationComposerAfterReload, - templateToOpenAppComposer, -} from '../../../awsService/appBuilder/activation' -import globals from '../../../shared/extensionGlobals' -import { - RuntimeLocationWizard, - genWalkthroughProject, - openProjectInWorkspace, - installLocalStackExtension, -} from '../../../awsService/appBuilder/walkthrough' -import { createWizardTester } from '../../shared/wizards/wizardTestUtils' -import { fs } from '../../../shared' -import { getTestWindow } from '../../shared/vscode/window' - -import { AwsClis, installCli } from '../../../shared/utilities/cliUtils' -import { ChildProcess } from '../../../shared/utilities/processUtils' -import { assertTelemetryCurried } from '../../testUtil' -import { HttpResourceFetcher } from '../../../shared/resourcefetcher/node/httpResourceFetcher' -import { SamCliInfoInvocation } from '../../../shared/sam/cli/samCliInfo' -import type { ToolId } from '../../../shared/telemetry/telemetry' -import { CodeScansState } from '../../../codewhisperer' - -interface TestScenario { - toolID: AwsClis - platform: string - shouldSucceed: boolean -} - -const scenarios: TestScenario[] = [ - { - toolID: 'aws-cli', - platform: 'win32', - shouldSucceed: true, - }, - { - toolID: 'sam-cli', - platform: 'win32', - shouldSucceed: true, - }, - { - toolID: 'docker', - platform: 'win32', - shouldSucceed: true, - }, - { - toolID: 'finch', - platform: 'win32', - shouldSucceed: false, - }, - { - toolID: 'aws-cli', - platform: 'darwin', - shouldSucceed: true, - }, - { - toolID: 'sam-cli', - platform: 'darwin', - shouldSucceed: true, - }, - { - toolID: 'docker', - platform: 'darwin', - shouldSucceed: true, - }, - { - toolID: 'finch', - platform: 'darwin', - shouldSucceed: true, - }, - { - toolID: 'aws-cli', - platform: 'linux', - shouldSucceed: false, - }, - { - toolID: 'sam-cli', - platform: 'linux', - shouldSucceed: false, - }, - { - toolID: 'docker', - platform: 'linux', - shouldSucceed: false, - }, - { - toolID: 'finch', - platform: 'linux', - shouldSucceed: false, - }, -] - -describe('AppBuilder Walkthrough', function () { - before(async function () { - // ensure auto scan is disabled before testrun - await CodeScansState.instance.setScansEnabled(false) - assert.strictEqual(CodeScansState.instance.isScansEnabled(), false) - }) - - describe('Reopen template after reload', function () { - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sandbox.restore() - }) - - // test openApplicationComposerAfterReload - it('open template in appComposer', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - const templateUri = vscode.Uri.joinPath(workspaceUri, '/path/to/template.yaml') - await globals.globalState.update(templateToOpenAppComposer, [templateUri.fsPath]) - // When - await openApplicationComposerAfterReload() - // Then - sandbox.assert.calledWith(spyExecuteCommand, 'aws.openInApplicationComposer') - }) - - it('not open non-template in appComposer', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - const templateUri = vscode.Uri.joinPath(workspaceUri, '/path/to/non-template.json') - - await globals.globalState.update(templateToOpenAppComposer, [templateUri.fsPath]) - // When - await openApplicationComposerAfterReload() - // Then - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('should not open non-workspace template', async function () { - const templateUri = vscode.Uri.file('/path/to/non-worksace/template.yaml') - - await globals.globalState.update(templateToOpenAppComposer, [templateUri.fsPath]) - - await openApplicationComposerAfterReload() - - sandbox.assert.notCalled(spyExecuteCommand) - }) - }) - - describe('Wizard', function () { - it('should not show runtime, should show file-selecotr', async function () { - // When - const tester = await createWizardTester(new RuntimeLocationWizard(true, 'open project')) - // Then - tester.runtime.assertDoesNotShow() - tester.dir.applyInput('file-selector') - tester.realDir.assertShowFirst() - }) - - it('should show runtime, should not show file-selector', async function () { - // When - const tester = await createWizardTester(new RuntimeLocationWizard(false, 'open project')) - // Then - tester.runtime.assertShowFirst() - tester.dir.assertShowSecond() - tester.runtime.applyInput('python') - tester.dir.applyInput('workspace') - tester.runtime.assertValue('python') - tester.realDir.assertDoesNotShow() - }) - }) - - describe('Create project', function () { - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - const prevInfo = 'random text' - assert.ok(workspaceUri) - - describe('start without existing template', async () => { - beforeEach(async () => { - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), { force: true }) - }) - - it('open existing template', async function () { - // When - await genWalkthroughProject('CustomTemplate', workspaceUri, undefined) - // Then nothing should be created - assert.ok(!(await fs.exists(vscode.Uri.joinPath(workspaceUri, 'template.yaml')))) - }) - }) - - describe('start with an existing template', async () => { - beforeEach(async () => { - await fs.writeFile(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), prevInfo) - }) - - afterEach(async function () { - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), { force: true }) - }) - - describe('override existing template', async () => { - beforeEach(() => { - getTestWindow().onDidShowMessage((message) => { - assert.strictEqual( - message.message, - 'template.yaml already exist in the selected directory, overwrite?' - ) - message.selectItem('Yes') - }) - }) - - it('build an app with appcomposer', async function () { - // When - await genWalkthroughProject('Visual', workspaceUri, undefined) - // Then - assert.notEqual(await fs.readFileText(vscode.Uri.joinPath(workspaceUri, 'template.yaml')), prevInfo) - }) - - it('download serverlessland proj', async function () { - const config = vscode.workspace.getConfiguration('aws.samcli') - await config.update('enableCodeLenses', false, vscode.ConfigurationTarget.Global) - // When - await genWalkthroughProject('API', workspaceUri, 'python') - // Then template should be overwritten - assert.equal(await fs.exists(vscode.Uri.joinPath(workspaceUri, 'template.yaml')), true) - assert.notEqual(await fs.readFileText(vscode.Uri.joinPath(workspaceUri, 'template.yaml')), prevInfo) - await config.update('enableCodeLenses', true, vscode.ConfigurationTarget.Global) - }) - }) - - describe('without override existing template', async () => { - beforeEach(() => { - // Given existing template.yaml - // select do not overwrite - getTestWindow().onDidShowMessage((message) => { - assert.strictEqual( - message.message, - 'template.yaml already exist in the selected directory, overwrite?' - ) - message.selectItem('No') - }) - }) - - it('build an app with appcomposer', async function () { - // When - try { - // When - await genWalkthroughProject('Visual', workspaceUri, undefined) - assert.fail('Expect failure due to template.yaml already exists in this path, but see success.') - } catch (e) { - assert.equal((e as Error).message, 'A file named template.yaml already exists in this path.') - } - // Then - assert.equal(await fs.readFileText(vscode.Uri.joinPath(workspaceUri, 'template.yaml')), prevInfo) - }) - - it('download serverlessland proj', async function () { - try { - // When - await genWalkthroughProject('S3', workspaceUri, 'python') - assert.fail('Expect failure due to template.yaml already exists in this path, but see success.') - } catch (e) { - assert.equal((e as Error).message, 'A file named template.yaml already exists in this path.') - } - // Then no overwrite happens - assert.equal(await fs.readFileText(vscode.Uri.joinPath(workspaceUri, 'template.yaml')), prevInfo) - }) - }) - }) - }) - - describe('Open project', function () { - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sandbox.restore() - }) - - it('open template.yaml', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - // make sure yaml exist - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yml'), { force: true }) - await fs.writeFile(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), '') - // When - await openProjectInWorkspace(workspaceUri) - // Then - sandbox.assert.calledWith(spyExecuteCommand, 'aws.openInApplicationComposer') - }) - - it('open template.yml', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - // make sure yml exist - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), { force: true }) - await fs.writeFile(vscode.Uri.joinPath(workspaceUri, 'template.yml'), '') - // When - await openProjectInWorkspace(workspaceUri) - // Then - sandbox.assert.calledWith(spyExecuteCommand, 'aws.openInApplicationComposer') - }) - - it('both template.yml and template.yaml exist', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - // make sure yml/yaml exist - await fs.writeFile(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), '') - await fs.writeFile(vscode.Uri.joinPath(workspaceUri, 'template.yml'), '') - // When - await openProjectInWorkspace(workspaceUri) - // Then only template.yaml is opened - sandbox.assert.calledWith( - spyExecuteCommand, - 'aws.openInApplicationComposer', - sinon.match.has('path', sinon.match(/template.yaml/g)) - ) - sandbox.assert.neverCalledWith( - spyExecuteCommand, - 'aws.openInApplicationComposer', - sinon.match.has('path', sinon.match(/template.yml/g)) - ) - }) - - it('no template.yml or template.yaml exist', async function () { - // Given - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - // make sure yml/yaml doesn't exist - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yaml'), { force: true }) - await fs.delete(vscode.Uri.joinPath(workspaceUri, 'template.yml'), { force: true }) - // When - await openProjectInWorkspace(workspaceUri) - // Then only template.yaml is opened - sandbox.assert.neverCalledWith(spyExecuteCommand, 'aws.openInApplicationComposer') - }) - }) - - describe('Tool install', function () { - let sandbox: sinon.SinonSandbox - const originalPlatform = process.platform - const originalArch = process.arch - const assertTelemetry = assertTelemetryCurried('aws_toolInstallation') - - before(function () { - Object.defineProperty(process, 'arch', { - value: 'x64', - }) - }) - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - after(function () { - Object.defineProperty(process, 'platform', { - value: originalPlatform, - }) - Object.defineProperty(process, 'arch', { - value: originalArch, - }) - }) - - for (let scenarioIndex = 0; scenarioIndex < scenarios.length; scenarioIndex++) { - const scenario = scenarios[scenarioIndex] - - it(`Install ${scenario.toolID} on ${scenario.platform}`, async function () { - // Given - Object.defineProperty(process, 'platform', { - value: scenario.platform, - }) - const downloader = sandbox.stub(HttpResourceFetcher.prototype, 'get').resolves() - const installer = sandbox - .stub(ChildProcess.prototype, 'run') - .resolves({ exitCode: 0, stderr: '', stdout: '' } as any) - // When - const result = installCli(scenario.toolID, false) - if (scenario.shouldSucceed) { - // If should success then - await result - assert(downloader.called) - assert(installer.called) - assertTelemetry({ - result: 'Succeeded', - toolId: scenario.toolID, - }) - } else { - // If should fail then - await getTestWindow() - .waitForMessage(/install/) - .then((message) => { - message.close() - }) - await assert.rejects(result, /cancelled/) - assertTelemetry({ - result: 'Cancelled', - toolId: scenario.toolID, - }) - } - }) - } - }) - - describe('Tool install Buttons', function () { - let sandbox: sinon.SinonSandbox - const originalPlatform = process.platform - const assertTelemetry = assertTelemetryCurried('appBuilder_installTool') - beforeEach(function () { - sandbox = sinon.createSandbox() - // Install method doesn't work for Linux, setting to mac for functional unit tests - Object.defineProperty(process, 'platform', { - value: 'darwin', - }) - }) - - afterEach(function () { - sandbox.restore() - Object.defineProperty(process, 'platform', { - value: originalPlatform, - }) - }) - - it('click install while exist should not download', async function () { - // Given - const downloader = sandbox.stub(HttpResourceFetcher.prototype, 'get').resolves() - sandbox.stub(ChildProcess.prototype, 'run').resolves({ exitCode: 0, stderr: '', stdout: '' } as any) - await vscode.commands.executeCommand('aws.toolkit.installAWSCLI') - // Then - assert(downloader.notCalled) - assertTelemetry({ - result: 'Succeeded', - toolId: 'aws-cli', - }) - }) - - it('install SAM with low version should popup', async function () { - // Given - sandbox.stub(SamCliInfoInvocation.prototype, 'execute').resolves({ version: '1.87.0' }) - sandbox.stub(ChildProcess.prototype, 'run').resolves({ exitCode: 0, stderr: '', stdout: '' } as any) - const result = vscode.commands.executeCommand('aws.toolkit.installSAMCLI') - - const message = await getTestWindow().waitForMessage(/version/) - message.close() - await result - assertTelemetry({ - result: 'Succeeded', - toolId: 'sam-cli', - }) - }) - - it('install SAM with normal version should not download', async function () { - // Given - const downloader = sandbox.stub(HttpResourceFetcher.prototype, 'get').resolves() - sandbox.stub(SamCliInfoInvocation.prototype, 'execute').resolves({ version: '1.100.0' }) - sandbox.stub(ChildProcess.prototype, 'run').resolves({ exitCode: 0, stderr: '', stdout: '' } as any) - await vscode.commands.executeCommand('aws.toolkit.installSAMCLI') - - assert(downloader.notCalled) - assertTelemetry({ - result: 'Succeeded', - toolId: 'sam-cli', - }) - }) - - describe('Install LocalStack Extension', function () { - // @ts-ignore until TODO from src/awsService/appBuilder/walkthrough.ts:installLocalStackExtension - const expectedLocalStackToolId: ToolId = 'localstack' - - it('should show already installed message when extension exists', async function () { - const mockExtension = { id: 'localstack.localstack' } - sandbox - .stub(vscode.extensions, 'getExtension') - .withArgs('localstack.localstack') - .returns(mockExtension as any) - const spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - await installLocalStackExtension('test-source') - - const message = await getTestWindow().waitForMessage(/LocalStack extension is already installed/) - message.close() - - // Verify installation command was not called - sandbox.assert.neverCalledWith(spyExecuteCommand, 'workbench.extensions.installExtension') - - // Verify telemetry - assertTelemetry({ - result: 'Succeeded', - source: 'test-source', - toolId: expectedLocalStackToolId, - }) - }) - - it('should successfully install extension when not present', async function () { - sandbox.stub(vscode.extensions, 'getExtension').withArgs('localstack.localstack').returns(undefined) - const spyExecuteCommand = sandbox.stub(vscode.commands, 'executeCommand').resolves() - - await installLocalStackExtension('test-source') - - const message = await getTestWindow().waitForMessage(/LocalStack extension has been installed/) - message.close() - - // Verify installation command was called with correct extension ID - sandbox.assert.calledWith( - spyExecuteCommand, - 'workbench.extensions.installExtension', - 'localstack.localstack' - ) - - // Verify telemetry - assertTelemetry({ - result: 'Succeeded', - source: 'test-source', - toolId: expectedLocalStackToolId, - }) - }) - - it('should handle installation failure and throw ToolkitError', async function () { - sandbox.stub(vscode.extensions, 'getExtension').withArgs('localstack.localstack').returns(undefined) - const installError = new Error('Installation failed') - sandbox.stub(vscode.commands, 'executeCommand').rejects(installError) - - await assert.rejects(installLocalStackExtension('test-source'), (error: any) => { - assert.strictEqual(error.message, 'Failed to install LocalStack extension') - assert.strictEqual(error.cause, installError) - return true - }) - - // Verify telemetry is still recorded even on failure - assertTelemetry({ - result: 'Failed', - source: 'test-source', - toolId: expectedLocalStackToolId, - }) - }) - - it('should record telemetry with correct source parameter', async function () { - const mockExtension = { id: 'localstack.localstack' } - sandbox - .stub(vscode.extensions, 'getExtension') - .withArgs('localstack.localstack') - .returns(mockExtension as any) - - await installLocalStackExtension('walkthrough-button') - - const message = await getTestWindow().waitForMessage(/LocalStack extension is already installed/) - message.close() - - // Verify telemetry includes the correct source - assertTelemetry({ - result: 'Succeeded', - source: 'walkthrough-button', - toolId: expectedLocalStackToolId, - }) - }) - }) - }) -}) diff --git a/packages/core/src/test/awsService/appBuilder/wizards/deployTypeWizard.test.ts b/packages/core/src/test/awsService/appBuilder/wizards/deployTypeWizard.test.ts deleted file mode 100644 index 8b26cedb1e..0000000000 --- a/packages/core/src/test/awsService/appBuilder/wizards/deployTypeWizard.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' - -import { PrompterTester } from '../../../shared/wizards/prompterTester' -import assert from 'assert' -import { DeployTypeWizard } from '../../../../awsService/appBuilder/wizards/deployTypeWizard' -import { getSyncWizard } from '../../../../shared/sam/sync' -import { TestFolder } from '../../../testUtil' -import { samconfigCompleteData, validTemplateData } from '../../../shared/sam/samTestUtils' -import { getDeployWizard } from '../../../../shared/sam/deploy' - -describe('DeployTypeWizard', function () { - let testFolder: TestFolder - let templateFile: vscode.Uri - let syncWizard: any - let deployWizard: any - - let deployTypeWizard: DeployTypeWizard - - before(async () => { - testFolder = await TestFolder.create() - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - await testFolder.write('samconfig.toml', samconfigCompleteData) - - deployWizard = await getDeployWizard(templateFile) - syncWizard = await getSyncWizard('infra', templateFile, undefined, false) - }) - - it('customer abort wizard should not call any command function', async function () { - // Given - const prompterTester = PrompterTester.init() - .handleQuickPick('Select deployment command', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'Sync') - assert.strictEqual(picker.items[1].label, 'Deploy') - assert.strictEqual(picker.items.length, 2) - picker.dispose() - }) - .build() - deployTypeWizard = new DeployTypeWizard(syncWizard, deployWizard) - const choices = await deployTypeWizard.run() - // Then - assert(!choices) - prompterTester.assertCall('Select deployment command', 1) - }) - - it('deploy is selected', async function () { - /** - * This test focus on that deploy wizard get triggered when customer choose to use sam deploy - * Selection for deploy wizard speficy here focus on only one case - * More cases are test in Deploy.test.ts - * - */ - const prompterTester = PrompterTester.init() - .handleQuickPick('Select deployment command', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'Sync') - assert.strictEqual(picker.items[1].label, 'Deploy') - assert.strictEqual(picker.items.length, 2) - picker.acceptItem(picker.items[1]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - deployTypeWizard = new DeployTypeWizard(syncWizard, deployWizard) - const choices = await deployTypeWizard.run() - // Then - assert.strictEqual(choices?.choice, 'deploy') - prompterTester.assertCallAll() - }) - - it('sync is selected', async function () { - /** - * This test focus on that sync wizard get triggered when customer choose to use sam sync - * Selection for deploy wizard speficy here focus on only one case - * More cases are test in Sync.test.ts - * - */ - const prompterTester = PrompterTester.init() - .handleQuickPick('Select deployment command', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'Sync') - assert.strictEqual(picker.items[1].label, 'Deploy') - assert.strictEqual(picker.items.length, 2) - picker.acceptItem(picker.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - deployTypeWizard = new DeployTypeWizard(syncWizard, deployWizard) - const choices = await deployTypeWizard.run() - // Then - assert.strictEqual(choices?.choice, 'sync') - prompterTester.assertCallAll() - }) -}) diff --git a/packages/core/src/test/awsService/apprunner/explorer/apprunnerNode.test.ts b/packages/core/src/test/awsService/apprunner/explorer/apprunnerNode.test.ts deleted file mode 100644 index 96f405b124..0000000000 --- a/packages/core/src/test/awsService/apprunner/explorer/apprunnerNode.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as FakeTimers from '@sinonjs/fake-timers' -import * as sinon from 'sinon' -import { AppRunnerNode } from '../../../../awsService/apprunner/explorer/apprunnerNode' -import { AppRunnerServiceNode } from '../../../../awsService/apprunner/explorer/apprunnerServiceNode' -import { AppRunnerClient, ServiceSummary } from '../../../../shared/clients/apprunner' -import { PlaceholderNode } from '../../../../shared/treeview/nodes/placeholderNode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { installFakeClock } from '../../../testUtil' -import { intoCollection } from '../../../../shared/utilities/collectionUtils' - -describe('AppRunnerNode', function () { - let mockApprunnerClient: AppRunnerClient - let node: AppRunnerNode - let clock: FakeTimers.InstalledClock - let refreshStub: sinon.SinonStub<[], void> - - const exampleSummaries: ServiceSummary[] = [ - { - ServiceName: 'test1', - Status: 'RUNNING', - ServiceArn: 'test-arn1', - ServiceId: 'Amazon', - }, - ] - - before(function () { - clock = installFakeClock() - refreshStub = sinon.stub(AWSTreeNodeBase.prototype, 'refresh') - }) - - beforeEach(function () { - mockApprunnerClient = { - paginateServices: sinon.stub().returns(intoCollection([exampleSummaries])), - listOperations: sinon.stub().resolves({ OperationSummaryList: [] }), - } as any as AppRunnerClient - node = new AppRunnerNode('', mockApprunnerClient) - clock.reset() - }) - - after(function () { - clock.uninstall() - sinon.restore() - }) - - it('creates AppRunnerServiceNodes', async function () { - const children = await node.getChildren() - assert.ok(children[0] instanceof AppRunnerServiceNode) - }) - - it('updates AppRunnerServiceNodes', async function () { - const childNode = (await node.getChildren())[0] as AppRunnerServiceNode - - const updatedSummary = { ...exampleSummaries[0], Status: 'PAUSED' } - const serviceStub = sinon.stub().returns(intoCollection([[updatedSummary]])) - mockApprunnerClient.paginateServices = serviceStub - - await node.getChildren() - assert.strictEqual(childNode.info.Status, 'PAUSED') - }) - - it('deletes AppRunnerServiceNodes', async function () { - await node.getChildren() - const serviceStub = sinon.stub().returns(intoCollection([[]])) - mockApprunnerClient.paginateServices = serviceStub - - assert.ok((await node.getChildren())[0] instanceof PlaceholderNode) - }) - - it('polls children nodes', async function () { - const transientService = { ...exampleSummaries[0], Status: 'OPERATION_IN_PROGRESS' } - const pausedService = { ...transientService, Status: 'PAUSED' } - const serviceStub = sinon - .stub() - .onFirstCall() - .returns(intoCollection([[transientService]])) - .onSecondCall() - .returns(intoCollection([[pausedService]])) - mockApprunnerClient.paginateServices = serviceStub - const opStub = sinon.stub().resolves({ - OperationSummaryList: [{ Id: 'test-id', Type: 'PAUSE_SERVICE' }], - }) - mockApprunnerClient.listOperations = opStub - - const childNode = (await node.getChildren())[0] as AppRunnerServiceNode - await clock.tickAsync(100000) - sinon.assert.calledOn(refreshStub, node) - await node.getChildren() - await clock.tickAsync(100000) - assert(serviceStub.calledTwice) - - assert.strictEqual(childNode.info.Status, 'PAUSED') - }) -}) diff --git a/packages/core/src/test/awsService/apprunner/explorer/apprunnerServiceNode.test.ts b/packages/core/src/test/awsService/apprunner/explorer/apprunnerServiceNode.test.ts deleted file mode 100644 index 6137797a54..0000000000 --- a/packages/core/src/test/awsService/apprunner/explorer/apprunnerServiceNode.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { AppRunnerNode } from '../../../../awsService/apprunner/explorer/apprunnerNode' -import { AppRunnerServiceNode } from '../../../../awsService/apprunner/explorer/apprunnerServiceNode' -import { AppRunnerClient, ServiceSummary } from '../../../../shared/clients/apprunner' -import { CloudWatchLogsClient } from '../../../../shared/clients/cloudWatchLogs' -import { asyncGenerator } from '../../../../shared/utilities/collectionUtils' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { stub } from '../../../utilities/stubber' -import { getLabel } from '../../../../shared/treeview/utils' - -describe('AppRunnerServiceNode', function () { - let mockApprunnerClient: ReturnType> - let mockParentNode: AppRunnerNode - let node: AppRunnerServiceNode - - const exampleInfo: ServiceSummary = { - ServiceName: 'test1', - Status: 'RUNNING', - ServiceArn: 'test-arn1', - ServiceUrl: '', - ServiceId: 'id', - } as any - - before(function () { - sinon.stub(AWSTreeNodeBase.prototype, 'refresh') - }) - - beforeEach(function () { - const cloudwatchClient = stub(CloudWatchLogsClient, { regionCode: 'us-east-1' }) - cloudwatchClient.describeLogGroups.returns(asyncGenerator([{ logGroupName: 'logs' }])) - - mockApprunnerClient = stub(AppRunnerClient, { regionCode: 'us-east-1' }) - mockApprunnerClient.listOperations.resolves({ OperationSummaryList: [] }) - // jscpd:ignore-start - mockParentNode = stub(AppRunnerNode, { - regionCode: '', - client: mockApprunnerClient, - serviceId: undefined, - label: undefined, - id: undefined, - iconPath: undefined, - description: undefined, - resourceUri: undefined, - tooltip: undefined, - command: undefined, - collapsibleState: undefined, - contextValue: undefined, - accessibilityInformation: undefined, - checkboxState: undefined, - }) - // jscpd:ignore-end - node = new AppRunnerServiceNode(mockParentNode, mockApprunnerClient, exampleInfo, {}, cloudwatchClient) - }) - - after(function () { - sinon.restore() - }) - - it('can pause', async function () { - mockApprunnerClient.pauseService.resolves({ Service: { ...exampleInfo, Status: 'PAUSED' } }) - await node.pause() - assert.ok(getLabel(node).includes('Paused')) - }) - - it('can resume', async function () { - node.update({ ...exampleInfo, Status: 'PAUSED' }) - mockApprunnerClient.resumeService.resolves({ Service: { ...exampleInfo, Status: 'RUNNING' } }) - await node.resume() - assert.ok(getLabel(node).includes('Running')) - }) - - it('can deploy', async function () { - mockApprunnerClient.startDeployment.resolves({ OperationId: '123' }) - node.update({ ...exampleInfo, Status: 'OPERATION_IN_PROGRESS' }) - await node.deploy() - assert.ok(getLabel(node).includes('Deploying')) - }) - - it('can describe', async function () { - mockApprunnerClient.describeService.resolves({ Service: { ...exampleInfo, Status: 'CREATE_FAILED' } }) - assert.strictEqual((await node.describe()).Status, 'CREATE_FAILED') - assert.ok(getLabel(node).includes('Create failed')) - }) - - it('can update', async function () { - mockApprunnerClient.updateService.resolves({ - Service: { ...exampleInfo, Status: 'OPERATION_IN_PROGRESS' }, - OperationId: '123', - }) - await node.updateService({} as any) - assert.ok(getLabel(node).includes('Updating')) - }) - - it('can delete', async function () { - mockApprunnerClient.deleteService.resolves({ - Service: { ...exampleInfo, Status: 'DELETED' }, - OperationId: '123', - }) - const deleteStub = sinon.stub() - mockParentNode.deleteNode = deleteStub - await node.delete() - assert(deleteStub.calledOnceWith(exampleInfo.ServiceArn)) - }) - - it('can log', async function () { - const logs = await node.getChildren() - assert.strictEqual(logs[0]?.label, 'logs') - }) -}) diff --git a/packages/core/src/test/awsService/apprunner/wizards/codeRepositoryWizard.test.ts b/packages/core/src/test/awsService/apprunner/wizards/codeRepositoryWizard.test.ts deleted file mode 100644 index 1021201fc1..0000000000 --- a/packages/core/src/test/awsService/apprunner/wizards/codeRepositoryWizard.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { createWizardTester, WizardTester } from '../../../shared/wizards/wizardTestUtils' -import { - AppRunnerCodeRepositoryWizard, - createConnectionPrompter, -} from '../../../../awsService/apprunner/wizards/codeRepositoryWizard' -import { AppRunnerClient, CodeRepository, SourceConfiguration } from '../../../../shared/clients/apprunner' -import { WIZARD_EXIT } from '../../../../shared/wizards/wizard' -import { apprunnerConnectionHelpUrl } from '../../../../shared/constants' -import { createQuickPickPrompterTester } from '../../../shared/ui/testUtils' -import { stub } from '../../../utilities/stubber' -import { getOpenExternalStub } from '../../../globalSetup.test' - -describe('AppRunnerCodeRepositoryWizard', function () { - let tester: WizardTester - let repoTester: WizardTester - - beforeEach(async function () { - // apprunner client and git api will never be called - const wizard = new AppRunnerCodeRepositoryWizard({} as any, {} as any) - tester = await createWizardTester(wizard) - repoTester = tester.CodeRepository - }) - - it('prompts for GitHub connection first', function () { - tester.AuthenticationConfiguration.ConnectionArn.assertShowFirst() - }) - - it('prompts for repository URL and branch', function () { - repoTester.RepositoryUrl.assertShow() - repoTester.SourceCodeVersion.Value.assertShow() - repoTester.SourceCodeVersion.Type.assertValue('BRANCH') - }) - - it('does not prompt for code configuration if "API" is not set', function () { - repoTester.CodeConfiguration.ConfigurationSource.applyInput('REPOSITORY') - repoTester.CodeConfiguration.CodeConfigurationValues.assertDoesNotShowAny() - }) - - it('adds all steps under CodeConfigurationValues if "API" is set', function () { - const codeconfig = repoTester.CodeConfiguration - const codevalues = codeconfig.CodeConfigurationValues - - codevalues.assertDoesNotShowAny() - codeconfig.ConfigurationSource.applyInput('API') - codevalues.BuildCommand.assertShow() - codevalues.Runtime.assertShow() - codevalues.RuntimeEnvironmentVariables.assertShow() - codevalues.StartCommand.assertShow() - codevalues.Port.assertShow() - }) - - it('sets "AutoDeploymentsEnabled" to false by default', function () { - tester.AutoDeploymentsEnabled.assertValue(false) - }) -}) - -type ConnectionStatus = 'AVAILABLE' | 'PENDING_HANDSHAKE' | 'ERROR' | 'DELETED' - -describe('createConnectionPrompter', function () { - const defaultConnections = [ - makeConnection('connection-name-1', 'connection-arn-1'), - makeConnection('connection-name-2', 'connection-arn-2'), - ] - - function makeTester(connections = defaultConnections) { - const client = stub(AppRunnerClient, { regionCode: '' }) - client.listConnections.resolves(connections) - - return createQuickPickPrompterTester(createConnectionPrompter(client)) - } - - function makeConnection(name: string, arn: string, status: ConnectionStatus = 'AVAILABLE') { - return { - ConnectionName: name, - ConnectionArn: arn, - Status: status, - } - } - - it('lists connections', async function () { - const tester = makeTester() - tester.assertItems(['connection-name-1', 'connection-name-2']) - tester.acceptItem('connection-name-2') - await tester.result(defaultConnections[1]) - }) - - it('lists only available connections', async function () { - const connections = [...defaultConnections] - connections.push(makeConnection('pending', 'pending', 'PENDING_HANDSHAKE')) - connections.push(makeConnection('error', 'error', 'ERROR')) - connections.unshift(makeConnection('deleted', 'deleted', 'DELETED')) - - const tester = makeTester(connections) - tester.assertItems(['connection-name-1', 'connection-name-2']) - tester.hide() - await tester.result() - }) - - it('can refresh connections', async function () { - const connections = [...defaultConnections] - const newConnection = makeConnection('new-connection', 'new-arn') - const tester = makeTester(connections) - - tester.addCallback(() => connections.push(newConnection)) - tester.pressButton('Refresh') - tester.assertItems(['connection-name-1', 'connection-name-2', 'new-connection']) - tester.acceptItem('new-connection') - await tester.result(newConnection) - }) - - it('shows an option to go to documentation when no connections are available', async function () { - getOpenExternalStub().resolves(true) - const tester = makeTester([]) - tester.assertItems(['No connections found']) - tester.acceptItem('No connections found') - tester.hide() - await tester.result(WIZARD_EXIT) - assert.strictEqual(getOpenExternalStub().firstCall.args[0].toString(), apprunnerConnectionHelpUrl) - }) -}) diff --git a/packages/core/src/test/awsService/apprunner/wizards/createServiceWizard.test.ts b/packages/core/src/test/awsService/apprunner/wizards/createServiceWizard.test.ts deleted file mode 100644 index 28a38df79f..0000000000 --- a/packages/core/src/test/awsService/apprunner/wizards/createServiceWizard.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../../shared/wizards/wizardTestUtils' -import { CreateAppRunnerServiceWizard } from '../../../../awsService/apprunner/wizards/apprunnerCreateServiceWizard' -import { stub } from '../../../utilities/stubber' -import { IamClient } from '../../../../shared/clients/iam' -import { DefaultEcrClient } from '../../../../shared/clients/ecrClient' -import { AppRunnerClient, CreateServiceRequest } from '../../../../shared/clients/apprunner' - -describe('CreateServiceWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const regionCode = 'us-east-1' - const wizard = new CreateAppRunnerServiceWizard( - regionCode, - {}, - {}, - { - iam: stub(IamClient, { regionCode }), - ecr: stub(DefaultEcrClient, { regionCode }), - apprunner: stub(AppRunnerClient, { regionCode }), - } - ) - - tester = await createWizardTester(wizard) - }) - - describe('CreateAppRunnerServiceWizard', function () { - it('prompts for source first', function () { - tester.SourceConfiguration.assertShowFirst() - }) - - it('prompts for role ARN if choosing private ECR', function () { - tester.SourceConfiguration.applyInput({ ImageRepository: { ImageRepositoryType: 'ECR' } as any }) - tester.SourceConfiguration.AuthenticationConfiguration.AccessRoleArn.assertShow() - }) - - it('prompts for connection ARN if choosing Repository', function () { - tester.SourceConfiguration.applyInput({ CodeRepository: {} as any }) - tester.SourceConfiguration.AuthenticationConfiguration.ConnectionArn.assertShow() - }) - - it('prompts for name before instance', function () { - tester.ServiceName.assertShowSecond() // TODO: write a 'assertShowBefore' that accepts another form element as input - tester.InstanceConfiguration.assertShowThird() - }) - - it('sets auto-deployment to "off" by default', function () { - tester.SourceConfiguration.AutoDeploymentsEnabled.assertValue(false) - }) - - it('prompts for ECR or GitHub repository', function () { - tester.SourceConfiguration.assertShow() - tester.SourceConfiguration.CodeRepository.assertDoesNotShowAny() - tester.SourceConfiguration.ImageRepository.assertDoesNotShowAny() - - tester.SourceConfiguration.applyInput({ CodeRepository: {} as any }) - tester.SourceConfiguration.CodeRepository.assertShowAny() - - tester.SourceConfiguration.applyInput({ ImageRepository: {} as any }) - tester.SourceConfiguration.ImageRepository.assertShowAny() - }) - }) -}) diff --git a/packages/core/src/test/awsService/apprunner/wizards/imageRepositoryWizard.test.ts b/packages/core/src/test/awsService/apprunner/wizards/imageRepositoryWizard.test.ts deleted file mode 100644 index 28c54d03d7..0000000000 --- a/packages/core/src/test/awsService/apprunner/wizards/imageRepositoryWizard.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../../shared/wizards/wizardTestUtils' -import { - AppRunnerImageRepositoryWizard, - ImageIdentifierForm, - TaggedEcrRepository, -} from '../../../../awsService/apprunner/wizards/imageRepositoryWizard' -import { ImageRepository, SourceConfiguration } from '../../../../shared/clients/apprunner' - -describe('AppRunnerImageRepositoryWizard', function () { - let tester: WizardTester - let repoTester: WizardTester - - beforeEach(async function () { - const wizard = new AppRunnerImageRepositoryWizard({} as any, {} as any) // the clients will never be called - tester = await createWizardTester(wizard) - repoTester = tester.ImageRepository - }) - - it('prompts for identifier, port, and environment variables', function () { - repoTester.ImageIdentifier.assertShow() - repoTester.ImageConfiguration.Port.assertShow() - repoTester.ImageConfiguration.RuntimeEnvironmentVariables.assertShow() - repoTester.assertShowCount(3) - }) - - it('sets image repository type', function () { - repoTester.ImageRepositoryType.assertValue(undefined) - repoTester.ImageIdentifier.applyInput('public.ecr.aws.com/testimage:latest') - repoTester.ImageRepositoryType.assertValue('ECR_PUBLIC') - repoTester.ImageIdentifier.applyInput('12351232424.dkr.ecr.us-east-1.amazonaws.com/testrepo:latest') - repoTester.ImageRepositoryType.assertValue('ECR') - }) - - it('sets "AutoDeploymentsEnabled" to false by default', function () { - tester.AutoDeploymentsEnabled.assertValue(false) - }) - - it('prompts for role if not public', function () { - repoTester.ImageRepositoryType.applyInput('ECR') - tester.AuthenticationConfiguration.AccessRoleArn.assertShow() - - repoTester.ImageRepositoryType.applyInput('ECR_PUBLIC') - tester.AuthenticationConfiguration.AccessRoleArn.assertDoesNotShow() - }) -}) - -describe('ImageIdentifierForm', function () { - let tester: WizardTester<{ repo: TaggedEcrRepository }> - - beforeEach(async function () { - const form = new ImageIdentifierForm({} as any) // ecr will never be called - tester = await createWizardTester(form) - }) - - it('asks for tag if not provided', function () { - tester.repo.tag.assertDoesNotShow() - tester.repo.applyInput({ repositoryName: 'name', repositoryArn: '', repositoryUri: '' }) - tester.repo.tag.assertShow() - }) - - it('skips tag step if given', function () { - tester.repo.applyInput({ repositoryName: 'name', repositoryArn: '', repositoryUri: '', tag: 'latest' }) - tester.repo.tag.assertDoesNotShow() - }) -}) diff --git a/packages/core/src/test/awsService/cdk/detectCdkProjects.test.ts b/packages/core/src/test/awsService/cdk/detectCdkProjects.test.ts deleted file mode 100644 index f642436e98..0000000000 --- a/packages/core/src/test/awsService/cdk/detectCdkProjects.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' -import { detectCdkProjects } from '../../../awsService/cdk/explorer/detectCdkProjects' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { saveCdkJson } from './treeTestUtils' -import { createTestWorkspaceFolder } from '../../testUtil' -import { FakeExtensionContext } from '../../fakeExtensionContext' -import { waitUntil } from '../../../shared/utilities/timeoutUtils' -import { fs } from '../../../shared' - -describe('detectCdkProjects', function () { - const workspacePaths: string[] = [] - const workspaceFolders: vscode.WorkspaceFolder[] = [] - - // eslint-disable-next-line @typescript-eslint/naming-convention - async function detectCdkProjects_wait(dirs: any) { - return ( - (await waitUntil( - async () => { - return await detectCdkProjects(dirs) - }, - { - timeout: 10000, - interval: 250, - truthy: true, - } - )) ?? [] - ) - } - - beforeEach(async function () { - await FakeExtensionContext.getFakeExtContext() - const workspaceFolder = await createTestWorkspaceFolder('vsctk-cdk') - - workspacePaths.push(workspaceFolder.uri.path) - workspaceFolders.push(workspaceFolder) - }) - - afterEach(async function () { - for (const path of workspacePaths) { - await fs.delete(path, { recursive: true }) - } - - workspacePaths.length = 0 - workspaceFolders.length = 0 - }) - - it('detects no projects when workspaceFolders is undefined', async function () { - const actual = await detectCdkProjects(undefined) - - assert.ok(actual) - assert.strictEqual(actual.length, 0) - }) - - it('detects no projects when workspaceFolders is empty', async function () { - const actual = await detectCdkProjects([]) - - assert.ok(actual) - assert.strictEqual(actual.length, 0) - }) - - it('detects no projects when cdk.json does not exist', async function () { - const actual = await detectCdkProjects(workspaceFolders) - - assert.ok(actual) - assert.strictEqual(actual.length, 0) - }) - - it('detects CDK project when cdk.json exists', async function () { - const cdkJsonUri = vscode.Uri.joinPath(workspaceFolders[0].uri, 'cdk.json') - await saveCdkJson(cdkJsonUri.fsPath) - const actual = await detectCdkProjects_wait(workspaceFolders) - - assert.ok(actual) - - const project = actual[0] - assert.ok(project) - assert.strictEqual(project.cdkJsonUri.fsPath, cdkJsonUri.fsPath) - assert.strictEqual(project.treeUri.fsPath, vscode.Uri.joinPath(cdkJsonUri, '..', 'cdk.out', 'tree.json').fsPath) - }) - - it('detects deep projects', async function () { - const cdkJsonUri = vscode.Uri.joinPath(workspaceFolders[0].uri, 'directory1', 'directory2', 'cdk.json') - await fs.mkdir(path.dirname(cdkJsonUri.fsPath)) - await saveCdkJson(cdkJsonUri.fsPath) - const actual = await detectCdkProjects_wait(workspaceFolders) - assert.strictEqual(actual[0]?.cdkJsonUri.fsPath, cdkJsonUri.fsPath) - }) - - it('ignores projects in `node_modules`', async function () { - const cdkJsonPath = path.join(workspaceFolders[0].uri.fsPath, 'node_modules', 'lib', 'cdk.json') - await fs.mkdir(path.dirname(cdkJsonPath)) - await saveCdkJson(cdkJsonPath) - const actual = await detectCdkProjects_wait(workspaceFolders) - assert.strictEqual(actual.length, 0) - }) - - it('de-dupes identical projects`', async function () { - workspaceFolders.push(workspaceFolders[0]) - - try { - const cdkJsonUri = vscode.Uri.joinPath(workspaceFolders[0].uri, 'cdk.json') - await saveCdkJson(cdkJsonUri.fsPath) - - const actual = await detectCdkProjects_wait(workspaceFolders) - assert.strictEqual(actual.length, 1) - } finally { - workspaceFolders.pop() - } - }) - - it('takes into account `output` from cdk.json to build tree.json path', async function () { - const cdkJsonUri = vscode.Uri.joinPath(workspaceFolders[0].uri, 'cdk.json') - const cdkJsonStr = JSON.stringify({ app: 'npx ts-node bin/demo-nov7.ts', output: 'build/cdk.out' }) - await fs.writeFile(cdkJsonUri.fsPath, cdkJsonStr) - const actual = await detectCdkProjects_wait(workspaceFolders) - - assert.ok(actual) - - const project = actual[0] - assert.ok(project) - assert.strictEqual(project.cdkJsonUri.fsPath, cdkJsonUri.fsPath) - assert.strictEqual( - project.treeUri.fsPath, - vscode.Uri.joinPath(cdkJsonUri, '..', 'build/cdk.out', 'tree.json').fsPath - ) - }) - - it('detects CDK projects in multi-folder workspace', async function () { - assert.strictEqual(workspacePaths.length, 1) - - workspacePaths.push(await makeTemporaryToolkitFolder('vsctk2')) - workspaceFolders.push({ - uri: vscode.Uri.file(workspacePaths[1]), - name: path.basename(workspacePaths[1]), - index: 1, - }) - - const projectPath1 = vscode.Uri.joinPath(workspaceFolders[0].uri, 'cdk.json') - const projectPath2 = vscode.Uri.joinPath(workspaceFolders[1].uri, 'cdk.json') - - await saveCdkJson(projectPath1.fsPath) - await saveCdkJson(projectPath2.fsPath) - const actual = await detectCdkProjects_wait(workspaceFolders) - assert.ok(actual) - assert.strictEqual(actual.length, 2) - - const project1 = actual[0] - const project2 = actual[1] - - assert.ok(project1) - assert.strictEqual(project1.cdkJsonUri.fsPath, projectPath1.fsPath) - assert.strictEqual( - project1.treeUri.fsPath, - vscode.Uri.joinPath(projectPath1, '..', 'cdk.out', 'tree.json').fsPath - ) - assert.ok(project2) - assert.strictEqual(project2.cdkJsonUri.fsPath, projectPath2.fsPath) - assert.strictEqual( - project2.treeUri.fsPath, - vscode.Uri.joinPath(projectPath2, '..', 'cdk.out', 'tree.json').fsPath - ) - }) -}) diff --git a/packages/core/src/test/awsService/cdk/explorer/appNode.test.ts b/packages/core/src/test/awsService/cdk/explorer/appNode.test.ts deleted file mode 100644 index 6f9d804d63..0000000000 --- a/packages/core/src/test/awsService/cdk/explorer/appNode.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import * as app from '../../../../awsService/cdk/explorer/cdkProject' -import * as appNode from '../../../../awsService/cdk/explorer/nodes/appNode' -import { ConstructNode } from '../../../../awsService/cdk/explorer/nodes/constructNode' -import { getTestWorkspaceFolder } from '../../../../testInteg/integrationTestsUtilities' -import { getIcon } from '../../../../shared/icons' -import * as treeUtils from '../treeTestUtils' -import { getLabel } from '../../../../shared/treeview/utils' - -describe('AppNode', function () { - afterEach(function () { - sinon.restore() - }) - - const workspaceFolderPath = getTestWorkspaceFolder() - const workspaceFolderName = 'cdk-test-folder' - const cdkJsonPath = path.join(getTestWorkspaceFolder(), workspaceFolderName, 'cdk.json') - const treePath = path.join(cdkJsonPath, '..', 'cdk.out', 'tree.json') - - it('uses the `cdk.json` uri as its id', async function () { - const testNode = getTestNode() - - assert.strictEqual(testNode.id, vscode.Uri.file(cdkJsonPath).toString()) - }) - - it('initializes label, tooltip, and icon', async function () { - const testNode = getTestNode().getTreeItem() - - assert.strictEqual(testNode.label, path.relative(workspaceFolderPath, path.dirname(cdkJsonPath))) - assert.strictEqual(testNode.tooltip, vscode.Uri.file(cdkJsonPath).path) - assert.strictEqual(testNode.iconPath, getIcon('aws-cdk-logo')) - }) - - it('returns placeholder node when app contains no stacks', async function () { - const testNode = getTestNode() - sinon.stub(app, 'getApp').resolves({ - constructTree: treeUtils.getTreeWithNoStack(), - location: testNode.resource, - }) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1) - assert.ok(getLabel(await childNodes[0].getTreeItem()).includes('No stacks')) - }) - - it('returns construct node when app has stacks', async function () { - const testNode = getTestNode() - sinon.stub(app, 'getApp').resolves({ - constructTree: treeUtils.getTree(), - location: testNode.resource, - }) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0] instanceof ConstructNode, true) - }) - - it('returns placeholder node when tree.json cannot be loaded', async function () { - const testNode = getTestNode() - sinon.stub(app, 'getApp').throws() - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1) - assert.ok(getLabel(await childNodes[0].getTreeItem()).includes('Unable to load construct tree')) - }) - - function getTestNode(): appNode.AppNode { - const appLocation: app.CdkAppLocation = { - cdkJsonUri: vscode.Uri.file(cdkJsonPath), - treeUri: vscode.Uri.file(treePath), - } - - return new appNode.AppNode(appLocation) - } -}) diff --git a/packages/core/src/test/awsService/cdk/explorer/awsCdkExplorer.test.ts b/packages/core/src/test/awsService/cdk/explorer/awsCdkExplorer.test.ts deleted file mode 100644 index 4678ba50d0..0000000000 --- a/packages/core/src/test/awsService/cdk/explorer/awsCdkExplorer.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import * as detectCdkProjects from '../../../../awsService/cdk/explorer/detectCdkProjects' -import { CdkAppLocation } from '../../../../awsService/cdk/explorer/cdkProject' -import { CdkRootNode } from '../../../../awsService/cdk/explorer/rootNode' - -describe('CdkRootNode', function () { - it('shows CDK projects', async function () { - const appLocation: CdkAppLocation = { - cdkJsonUri: vscode.Uri.file('/cdk.json'), - treeUri: vscode.Uri.file('/cdk.out/tree.json'), - } - - sinon.stub(detectCdkProjects, 'detectCdkProjects').resolves([appLocation]) - - const treeNodes = await CdkRootNode.instance.getChildren() - assert.strictEqual(treeNodes.length, 1) - assert.deepStrictEqual(treeNodes[0].resource, appLocation) - }) -}) diff --git a/packages/core/src/test/awsService/cdk/explorer/constructNode.test.ts b/packages/core/src/test/awsService/cdk/explorer/constructNode.test.ts deleted file mode 100644 index 39d0a6d2f3..0000000000 --- a/packages/core/src/test/awsService/cdk/explorer/constructNode.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' -import * as treeUtils from '../treeTestUtils' -import { ConstructNode } from '../../../../awsService/cdk/explorer/nodes/constructNode' -import { PropertyNode } from '../../../../awsService/cdk/explorer/nodes/propertyNode' -import { ConstructTreeEntity } from '../../../../awsService/cdk/explorer/tree/types' -import { isStateMachine } from '../../../../awsService/cdk/explorer/nodes/constructNode' -import { CdkAppLocation } from '../../../../awsService/cdk/explorer/cdkProject' -import { getIcon } from '../../../../shared/icons' - -describe('ConstructNode', function () { - const label = 'MyConstruct' - const constructTreePath = 'Path/To/MyConstruct' - const cdkJsonPath = path.join('/', 'the', 'road', 'to', 'cdk.json') - const location: CdkAppLocation = { - cdkJsonUri: vscode.Uri.file(cdkJsonPath), - treeUri: vscode.Uri.file(path.join(cdkJsonPath, '..', 'cdk.out', 'tree.json')), - } - - it('initializes label, tooltip, and icon', async function () { - const testNode = generateTestNode(label).getTreeItem() - - assert.strictEqual(testNode.label, label) - assert.strictEqual(testNode.tooltip, constructTreePath) - assert.strictEqual(testNode.iconPath, getIcon('aws-cdk-logo')) - assert.strictEqual(testNode.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - - it('returns a uri with a fragment pointing to the resource', async () => { - const testNode = generateTestNode(label) - const resourceUri = testNode.resource.location - - assert.strictEqual(resourceUri.path, location.treeUri.path) - assert.strictEqual(resourceUri.fragment, testNode.resource.construct.path) - }) - - it('initializes icon paths for CDK constructs', async function () { - const testNode = new ConstructNode(location, { - ...treeUtils.generateConstructTreeEntity('', constructTreePath), - attributes: treeUtils.generateAttributes(), - }).getTreeItem() - - assert.strictEqual(testNode.iconPath, getIcon('aws-cloudformation-stack')) - }) - - it('returns no child nodes if construct does not have any', async function () { - const testNode = new ConstructNode(location, treeUtils.generateConstructTreeEntity(label, constructTreePath)) - - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 0, 'Unexpected child nodes') - }) - - it('child node has no collapsible state if it has no children or attributes', async function () { - const treeEntity: ConstructTreeEntity = { - id: label, - path: constructTreePath, - children: treeUtils.generateTreeChildResource(), - } - const testNode = new ConstructNode(location, treeEntity) - - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual((await childNodes[0].getTreeItem()).collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - - it('child node is collapsed if construct has child with attributes', async function () { - const childWithAttributes = treeUtils.generateTreeChildResource() - childWithAttributes.Resource.attributes = treeUtils.generateAttributes() - - const treeEntity: ConstructTreeEntity = { - id: label, - path: constructTreePath, - children: childWithAttributes, - } - - const testNode = new ConstructNode(location, treeEntity) - - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 1, 'Expected child node with attributes to be collapsed') - assert.strictEqual( - (await childNodes[0].getTreeItem()).collapsibleState, - vscode.TreeItemCollapsibleState.Collapsed - ) - }) - - it('returns child node of PropertyNode when construct has props', async function () { - const treeEntity: ConstructTreeEntity = { - id: label, - path: constructTreePath, - attributes: treeUtils.generateAttributes(), - } - - const testNode = new ConstructNode(location, treeEntity) - - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0] instanceof PropertyNode, true, 'Expected child node to be a PropertyNode') - }) - - it('returns child nodes of PropertyNode and ConstructNode when construct has props and children', async function () { - const treeWithChildrenAndProps: ConstructTreeEntity = { - id: label, - path: constructTreePath, - children: { child: treeUtils.generateConstructTreeEntity(label, constructTreePath) }, - attributes: treeUtils.generateAttributes(), - } - - const testNode = new ConstructNode(location, treeWithChildrenAndProps) - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 2) - assert.strictEqual(childNodes[0] instanceof PropertyNode, true, 'Expected child node to be a PropertyNode') - assert.strictEqual(childNodes[1] instanceof ConstructNode, true, 'Expected child node to be a ConstructNode') - }) - - it('returns child node if a construct has grandchildren', async function () { - const testNode = new ConstructNode( - location, - treeUtils.generateConstructTreeEntity(label, constructTreePath, true) - ) - - const childNodes = testNode.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0] instanceof ConstructNode, true, 'Expected child node to be a ConstructNode') - }) - - function generateTestNode(displayLabel: string): ConstructNode { - return new ConstructNode(location, treeUtils.generateConstructTreeEntity(displayLabel, constructTreePath)) - } -}) - -describe('Check if ConstructNode is a state machine', function () { - it('returns true when tree node contains a node with id === "Resource" and type === "StateMachine"', async function () { - const construct: ConstructTreeEntity = { - id: 'StateMachine', - path: 'aws-stepfunctions-integ/StateMachine', - children: { - Resource: { - id: 'Resource', - path: 'aws-stepfunctions-integ/StateMachine/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::StateMachine', - }, - }, - }, - } - - assert.ok(isStateMachine(construct)) - }) - - it('returns true when tree node contains a node with id !== "Resource" and type === "StateMachine"', async function () { - const construct: ConstructTreeEntity = { - id: 'StateMachine', - path: 'aws-stepfunctions-integ/StateMachine', - children: { - Other: { - id: 'Other', - path: 'aws-stepfunctions-integ/StateMachine/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::StateMachine', - }, - }, - }, - } - - assert.strictEqual(isStateMachine(construct), false) - }) - - it('returns false when tree node contains a node with id !== "Resource" and type !== "StateMachine"', async function () { - const construct: ConstructTreeEntity = { - id: 'StateMachine', - path: 'aws-stepfunctions-integ/LambdaFunction', - children: { - Other: { - id: 'Other', - path: 'aws-stepfunctions-integ/LambdaFunction/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::LambdaFunction', - }, - }, - }, - } - assert.strictEqual(isStateMachine(construct), false) - }) - - it('returns false when tree node contains a node with id === "Resource" and type !== "StateMachine"', async function () { - const construct: ConstructTreeEntity = { - id: 'StateMachine', - path: 'aws-stepfunctions-integ/LambdaFunction', - children: { - Resource: { - id: 'Resource', - path: 'aws-stepfunctions-integ/LambdaFunction/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::LambdaFunction', - }, - }, - }, - } - assert.strictEqual(isStateMachine(construct), false) - }) -}) diff --git a/packages/core/src/test/awsService/cdk/explorer/propertyNode.test.ts b/packages/core/src/test/awsService/cdk/explorer/propertyNode.test.ts deleted file mode 100644 index 36baf9a6e3..0000000000 --- a/packages/core/src/test/awsService/cdk/explorer/propertyNode.test.ts +++ /dev/null @@ -1,95 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { PropertyNode } from '../../../../awsService/cdk/explorer/nodes/propertyNode' - -describe('PropertyNode', function () { - const label = 'myProperty' - - it('initializes label', async function () { - const testNode = new PropertyNode(label, 'blue').getTreeItem() - assert.strictEqual(testNode.label, `${label}: blue`) - }) - - it('returns no children when property does not have nested values', async function () { - const testNode = new PropertyNode(label, 'purple') - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 0, 'Expected no child nodes for a property with non-nested properties') - }) - - it('returns single child when property has a string value', async function () { - const value = 'string value' - const children: { [key: string]: any } = { key: value } - const testNode = new PropertyNode(label, children) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1, 'Expected a single property node for a string property') - assert.strictEqual((await childNodes[0].getTreeItem()).label, `key: ${value}`) - }) - - it('returns single child when property has a boolean value', async function () { - const children: { [key: string]: any } = { key: true } - const testNode = new PropertyNode(label, children) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1, 'Expected a single property node for a boolean property') - assert.strictEqual((await childNodes[0].getTreeItem()).label, 'key: true') - }) - - it('returns single child when property has an int value', async function () { - const value = 100 - const children: { [key: string]: any } = { key: value } - const testNode = new PropertyNode(label, children) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1, 'Expected a single property node for an int property') - assert.strictEqual( - (await childNodes[0].getTreeItem()).label, - `key: ${value}`, - 'Unexpected label on PropertyNode' - ) - }) - - it('returns a nested property node with a property node for each value in the array', async function () { - const values = ['one', 'two', 'three'] - const children: { [key: string]: any } = { key: values } - - const testNode = new PropertyNode(label, children) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, 1, 'expected nested property to have a child node for array') - assert.strictEqual((await childNodes[0].getTreeItem()).label, 'key', 'Unexpected label on node') - - const childPropertyNodes = await childNodes[0].getChildren?.() - assert.strictEqual(childPropertyNodes?.length, 3, 'Expected each value in nested array to have a child node') - }) - - it('returns a nested property node with nested object as child property nodes', async function () { - const nestedObject = { - evenMoreNested: 'nestedValue', - } - const value = { - nestedKey: nestedObject, - } - - const children: { [key: string]: any } = { key: value } - const testNode = new PropertyNode(label, children) - - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 1, 'Expected nested property of object type to have child nodes') - assert.strictEqual((await childNodes[0].getTreeItem()).label, 'key') - - const grandChildren = await childNodes[0]?.getChildren?.() - assert.strictEqual(grandChildren?.length, 1, 'Expected nested property of object type to have grandchild nodes') - assert.strictEqual((await grandChildren[0].getTreeItem()).label, 'nestedKey') - }) -}) diff --git a/packages/core/src/test/awsService/cdk/tree/resources/templateJsonTester.template.json b/packages/core/src/test/awsService/cdk/tree/resources/templateJsonTester.template.json deleted file mode 100644 index 04caa22561..0000000000 --- a/packages/core/src/test/awsService/cdk/tree/resources/templateJsonTester.template.json +++ /dev/null @@ -1,370 +0,0 @@ -{ - "Resources": { - "SubmitJobFB773A16": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterSubmitJob293E36D9" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/SubmitJob/Resource" - } - }, - "CheckJob5FFC1D6F": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterCheckJobE79CEC2E" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CheckJob/Resource" - } - }, - "SubmitJob2215322E1": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterSubmitJob2F34CA86A" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/SubmitJob2/Resource" - } - }, - "CheckJob25E896F6F": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterCheckJob2A285E37A" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CheckJob2/Resource" - } - }, - "MyStateMachineRoleD59FFEBC": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine/Role/Resource" - } - }, - "MyStateMachine6C968CA5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachineRoleD59FFEBC", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Submit Job\",\"States\":{\"Submit Job\":{\"Next\":\"Wait X Seconds\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "SubmitJobFB773A16" - }, - "\",\"ResultPath\":\"$.guid\"},\"Wait X Seconds\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status\"},\"Get Job Status\":{\"Next\":\"Job Complete?\",\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status\"}],\"Default\":\"Wait X Seconds\"},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status\":{\"End\":true,\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\"}},\"TimeoutSeconds\":30}" - ] - ] - } - }, - "DependsOn": ["MyStateMachineRoleD59FFEBC"], - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine/Resource" - } - }, - "MyStateMachine2Role9449E860": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine2/Role/Resource" - } - }, - "MyStateMachine268DAC43E": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachine2Role9449E860", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Submit Job2\",\"States\":{\"Submit Job2\":{\"Next\":\"Wait X Seconds2\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "SubmitJob2215322E1" - }, - "\",\"ResultPath\":\"$.guid\"},\"Wait X Seconds2\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status2\"},\"Get Job Status2\":{\"Next\":\"Job Complete?2\",\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob25E896F6F" - }, - "\",\"ResultPath\":\"$.status\"},\"Job Complete?2\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed2\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status2\"}],\"Default\":\"Wait X Seconds2\"},\"Job Failed2\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status2\":{\"End\":true,\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob25E896F6F" - }, - "\"}},\"TimeoutSeconds\":30}" - ] - ] - } - }, - "DependsOn": ["MyStateMachine2Role9449E860"], - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine2/Resource" - } - }, - "CDKMetadata": { - "Type": "AWS::CDK::Metadata", - "Properties": { - "Analytics": "v2:deflate64:H4sIAAAAAAAAE02OTwvCMAzFP4v3LttAEG/KwJuXKXguXceyP6006URKv7vtBPH0XvKSX1JDXR2h2p3kiwrVTWVQ1mkIN5ZqEo01xM4rFk1vWk3WO6WzT0GHjNZEkRcDsX723qjcIgjnZFbkdx79+bukSTwkJthgMXEuEmeRDrG+SjWg2cj/dRQoFwitnbcoa4xRGNtpGKlc6z3Uh/T8SIiF84Zx0dB+9QO3SjCU2QAAAA==" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CDKMetadata/Default" - }, - "Condition": "CDKMetadataAvailable" - } - }, - "Conditions": { - "CDKMetadataAvailable": { - "Fn::Or": [ - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "af-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-northeast-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-northeast-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-southeast-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-southeast-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ca-central-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "cn-north-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "cn-northwest-1" - ] - } - ] - }, - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-central-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-north-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-3" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "me-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "sa-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-east-2" - ] - } - ] - }, - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-west-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-west-2" - ] - } - ] - } - ] - } - } -} diff --git a/packages/core/src/test/awsService/cdk/tree/treeInspector.test.ts b/packages/core/src/test/awsService/cdk/tree/treeInspector.test.ts deleted file mode 100644 index 4a22280f59..0000000000 --- a/packages/core/src/test/awsService/cdk/tree/treeInspector.test.ts +++ /dev/null @@ -1,118 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as treeInspector from '../../../../awsService/cdk/explorer/tree/treeInspector' -import { CfnResourceKeys, ConstructProps, ConstructTreeEntity } from '../../../../awsService/cdk/explorer/tree/types' -import * as treeUtils from '../treeTestUtils' - -describe('TreeInspector', function () { - const testLabel = 'my label' - const treePath = 'my-project/cdk.out/tree.json' - - it('returns label when construct is not named Resource', async function () { - const construct = treeUtils.generateConstructTreeEntity(testLabel, treePath) - - const label = treeInspector.getDisplayLabel(construct) - - assert.strictEqual(label, construct.id, 'Unexpected label') - }) - - it('includes type when construct is named Resource and type exists', async function () { - const id = 'Resource' - const type = 'AWS::S3::Bucket' - const attributes = { 'aws:cdk:cloudformation:type': type } - - const construct: ConstructTreeEntity = { id, path: treePath, attributes } - - const label = treeInspector.getDisplayLabel(construct) - - assert.strictEqual(label, `${id} (${type})`) - }) - - it('returns unchanged label when construct is named Resource and no type information exists', async function () { - const id = 'Resource' - - const construct: ConstructTreeEntity = { id, path: treePath } - - const label = treeInspector.getDisplayLabel(construct) - - assert.strictEqual(label, id) - }) - - it('returns default if no type information exists', async function () { - const construct = treeUtils.generateConstructTreeEntity(testLabel, treePath) - - const type = treeInspector.getTypeAttributeOrDefault(construct, '') - - assert.strictEqual(type, '') - }) - - it('returns type when type information exists', async function () { - const testType = 'AWS:SQS::Queue' - const construct: ConstructTreeEntity = { - id: testLabel, - path: treePath, - attributes: { 'aws:cdk:cloudformation:type': testType }, - } - - const type = treeInspector.getTypeAttributeOrDefault(construct, '') - - assert.strictEqual(type, testType) - }) - - it('includes construct in tree', async function () { - const construct = treeUtils.generateConstructTreeEntity(testLabel, treePath) - - const includeConstruct = treeInspector.includeConstructInTree(construct) - - assert.strictEqual(includeConstruct, true, 'expected construct to be included in the tree') - }) - - it('excludes the `Tree` construct that the CDK adds by default', async function () { - const construct: ConstructTreeEntity = { id: 'Tree', path: 'Tree' } - - const includeConstruct = treeInspector.includeConstructInTree(construct) - - assert.strictEqual(includeConstruct, false, 'Tree construct should be excluded from the tree') - }) - - it('returns properties when a construct has attributes', async function () { - const attributes = treeUtils.generateAttributes() - const expectedProps: ConstructProps = attributes[CfnResourceKeys.PROPS] - const construct: ConstructTreeEntity = { - id: 'test-label', - path: 'a rare path', - attributes: treeUtils.generateAttributes(), - } - - const props = treeInspector.getProperties(construct) - - assert.deepStrictEqual(props, expectedProps) - }) - - it('returns undefined when construct does not have attributes', async function () { - const construct: ConstructTreeEntity = { - id: 'no', - path: 'attributes', - } - - const props = treeInspector.getProperties(construct) - - assert.strictEqual(props, undefined) - }) - - it('returns undefined when construct does not have CloudFormation properties', async function () { - const construct: ConstructTreeEntity = { - id: 'no-cloudformation', - path: 'attributes', - attributes: { random: 'stuff' }, - } - - const props = treeInspector.getProperties(construct) - - assert.strictEqual(props, undefined) - }) -}) diff --git a/packages/core/src/test/awsService/cdk/treeTestUtils.ts b/packages/core/src/test/awsService/cdk/treeTestUtils.ts deleted file mode 100644 index 5d1c5117fe..0000000000 --- a/packages/core/src/test/awsService/cdk/treeTestUtils.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ConstructTree, ConstructTreeEntity } from '../../../awsService/cdk/explorer/tree/types' -import { fs } from '../../../shared' - -export async function saveCdkJson(cdkJsonPath: string) { - const cdkJsonContent = '{ "app": "npx ts-node bin/demo-nov7.ts"}' - - await fs.writeFile(cdkJsonPath, cdkJsonContent, 'utf8') -} - -export function generateConstructTreeEntity(label: string, treePath: string, children?: boolean): ConstructTreeEntity { - return { - id: label, - path: treePath, - children: children ? generateTreeChildResource() : {}, - } -} - -export function generateTreeChildResource(): { [key: string]: any } { - return { - Resource: { - id: 'Resource', - path: 'MyStack/MyQueue/Resource', - }, - } -} - -export function generateAttributes(): { [key: string]: any } { - return { - 'aws:cdk:cloudformation:type': 'AWS::SNS::Topic', - 'aws:cdk:cloudformation:props': { - topicName: 'CoolTopic', - }, - } -} - -export function getTreeWithNoStack(): ConstructTree { - return { - version: 'tree-0.1', - tree: { - id: 'App', - path: '', - children: { - Tree: { - id: 'Tree', - path: 'Tree', - }, - }, - }, - } -} - -export function getTree(): ConstructTree { - return { - version: 'tree-0.1', - tree: { - id: 'App', - path: '', - children: { - Tree: { - id: 'Tree', - path: 'Tree', - }, - TestStack: { - id: 'TestStack', - path: 'TestStack', - children: { - MyQueue: { - id: 'MyQueue', - path: 'TestStack/MyQueue', - children: { - Resource: { - id: 'Resource', - path: 'TestStack/MyQueue/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::SQS::Queue', - 'aws:cdk:cloudformation:props': { - visibilityTimeout: 300, - }, - }, - }, - }, - }, - }, - }, - }, - }, - } -} diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/addLogEvents.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/addLogEvents.test.ts deleted file mode 100644 index 82c21a4c43..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/addLogEvents.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as FakeTimers from '@sinonjs/fake-timers' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { addLogEvents } from '../../../../awsService/cloudWatchLogs/commands/addLogEvents' -import { CloudWatchLogsEvent, LogDataRegistry } from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { CLOUDWATCH_LOGS_SCHEME } from '../../../../shared/constants' -import { installFakeClock } from '../../../testUtil' - -describe('addLogEvents', async function () { - let sandbox: sinon.SinonSandbox - let clock: FakeTimers.InstalledClock - - before(function () { - clock = installFakeClock() - }) - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - clock.reset() - sandbox.restore() - }) - - after(function () { - clock.uninstall() - }) - - it('runs updateLog and sets busy status correctly', async function () { - const uri = vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:group:region:stream`) - const setBusyStatus = sandbox.stub<[vscode.Uri, boolean], void>() - const fetchNextLogEvents = sandbox.stub< - [ - vscode.Uri, - ('head' | 'tail' | undefined)?, - ( - | (( - logGroupInfo: { - groupName: string - streamName: string - regionName: string - }, - nextToken?: string | undefined - ) => Promise) - | undefined - )?, - ] - >() - - const document: vscode.TextDocument = { - uri: uri, - } as any as vscode.TextDocument - - const fakeRegistry: LogDataRegistry = { - setBusyStatus: setBusyStatus, - fetchNextLogEvents: fetchNextLogEvents, - } as any as LogDataRegistry - - const fakeEvent = sandbox.createStubInstance(vscode.EventEmitter) - - await addLogEvents(document, fakeRegistry, 'head', fakeEvent) - - sandbox.assert.calledTwice(setBusyStatus) - sandbox.assert.calledWith(setBusyStatus.firstCall, uri, true) - sandbox.assert.calledWith(setBusyStatus.secondCall, uri, false) - // eslint-disable-next-line @typescript-eslint/unbound-method - sandbox.assert.calledTwice(fakeEvent.fire) - sandbox.assert.calledWith(setBusyStatus.secondCall, uri, false) - sandbox.assert.calledOnce(fetchNextLogEvents) - sandbox.assert.calledWith(fetchNextLogEvents.firstCall, uri, 'head') - }) - - it('async-locks to prevent more than one execution at a time', async function () { - const uri = vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:group:stream:region`) - const setBusyStatus = sandbox.stub<[vscode.Uri, boolean], void>() - const fetchNextLogEvents = sandbox.stub< - [ - vscode.Uri, - ('head' | 'tail' | undefined)?, - ( - | (( - logGroupInfo: { - groupName: string - streamName: string - regionName: string - }, - nextToken?: string | undefined - ) => Promise) - | undefined - )?, - ] - >() - - // simulates a long network call. Doesn't need to do anything otherwise - fetchNextLogEvents.onFirstCall().callsFake(async () => { - clock.setTimeout(() => {}, 5000) - }) - // simulates another network call. Shorter so that way this one triggers before the initial lock is over - fetchNextLogEvents.onThirdCall().callsFake(async () => { - clock.setTimeout(() => {}, 500) - }) - // simulates another network call. Shorter so that way this one triggers before the initial lock is over - fetchNextLogEvents.onSecondCall().callsFake(async () => { - clock.setTimeout(() => {}, 100) - }) - - const document: vscode.TextDocument = { - uri: uri, - } as any as vscode.TextDocument - - const fakeRegistry: LogDataRegistry = { - setBusyStatus: setBusyStatus, - fetchNextLogEvents: fetchNextLogEvents, - } as any as LogDataRegistry - - const fakeEvent = sandbox.createStubInstance(vscode.EventEmitter) - - void addLogEvents(document, fakeRegistry, 'head', fakeEvent) - void addLogEvents(document, fakeRegistry, 'head', fakeEvent) - void addLogEvents(document, fakeRegistry, 'head', fakeEvent) - - void new Promise((resolve) => { - clock.setTimeout(() => { - sandbox.assert.calledTwice(setBusyStatus) - sandbox.assert.calledWith(setBusyStatus.firstCall, uri, true) - sandbox.assert.calledWith(setBusyStatus.secondCall, uri, false) - // eslint-disable-next-line @typescript-eslint/unbound-method - sandbox.assert.calledTwice(fakeEvent.fire) - sandbox.assert.calledWith(setBusyStatus.secondCall, uri, false) - sandbox.assert.calledOnce(fetchNextLogEvents) - sandbox.assert.calledWith(fetchNextLogEvents.firstCall, uri, 'head') - resolve() - }, 10000) - }) - await clock.nextAsync() - await clock.nextAsync() - await clock.nextAsync() - await clock.nextAsync() - await clock.nextAsync() - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/copyLogResource.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/copyLogResource.test.ts deleted file mode 100644 index 3dc329c8c3..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/copyLogResource.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { copyLogResource } from '../../../../awsService/cloudWatchLogs/commands/copyLogResource' -import { cwlUriSchema } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' - -describe('copyLogResource', async function () { - beforeEach(async function () { - await vscode.env.clipboard.writeText('') - }) - - afterEach(async function () { - await vscode.env.clipboard.writeText('') - }) - - it('copies stream names from valid URIs with stream open', async function () { - const logGroupWithStream = { - groupName: 'group', - regionName: 'region', - streamName: 'stream', - } - const uri = cwlUriSchema.form({ logGroupInfo: logGroupWithStream, parameters: {} }) - - await copyLogResource(uri) - - assert.strictEqual(await vscode.env.clipboard.readText(), logGroupWithStream.streamName) - }) - - it('copies group names from valid URIs with group open', async function () { - const logGroup = { - groupName: 'group2', - regionName: 'region2', - } - const uri = cwlUriSchema.form({ logGroupInfo: logGroup, parameters: {} }) - - await copyLogResource(uri) - - assert.strictEqual(await vscode.env.clipboard.readText(), logGroup.groupName) - }) - - it('does not copy anything new if the URI is invalid', async function () { - await vscode.env.clipboard.writeText('default text') - await copyLogResource(vscode.Uri.parse(`notCloudWatch:hahahaha`)) - - assert.strictEqual(await vscode.env.clipboard.readText(), 'default text') - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.test.ts deleted file mode 100644 index cd43c91150..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/saveCurrentLogDataContent.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' - -import { saveCurrentLogDataContent } from '../../../../awsService/cloudWatchLogs/commands/saveCurrentLogDataContent' -import { fileExists, makeTemporaryToolkitFolder, readFileAsString } from '../../../../shared/filesystemUtilities' -import { getTestWindow } from '../../../shared/vscode/window' -import { - CloudWatchLogsGroupInfo, - CloudWatchLogsParameters, - CloudWatchLogsResponse, - LogDataRegistry, -} from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { assertTextEditorContains } from '../../../testUtil' -import { cwlUriSchema } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' -import { fs } from '../../../../shared' - -async function testFilterLogEvents( - logGroupInfo: CloudWatchLogsGroupInfo, - apiParameters: CloudWatchLogsParameters, - nextToken?: string -): Promise { - return { - events: [ - { message: 'The first log event', logStreamName: 'stream1', timestamp: 1675451113 }, - { message: 'The second log event', logStreamName: 'stream2', timestamp: 1675451114 }, - ], - } -} - -describe('saveCurrentLogDataContent', async function () { - let filename: string - let tempDir: string - const expectedText = `1970-01-20T09:24:11.113-08:00\tThe first log event -1970-01-20T09:24:11.114-08:00\tThe second log event -` - - beforeEach(async function () { - tempDir = await makeTemporaryToolkitFolder() - filename = path.join(tempDir, 'bobLoblawsLawB.log') - }) - - afterEach(async function () { - await fs.delete(tempDir, { recursive: true }) - }) - - it('saves log content to a file', async function () { - const logGroupInfo = { - groupName: 'g', - regionName: 'r', - streamName: 's', - } - const uri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} }) - LogDataRegistry.instance.registerInitialLog(uri, testFilterLogEvents) - await LogDataRegistry.instance.fetchNextLogEvents(uri) - await vscode.window.showTextDocument(uri) - await assertTextEditorContains(expectedText, false) // Wait for document provider. - - getTestWindow().onDidShowDialog((d) => d.selectItem(vscode.Uri.file(filename))) - await saveCurrentLogDataContent() - - assert.ok(await fileExists(filename)) - assert.strictEqual(await readFileAsString(filename), expectedText) - }) - - it('does not do anything if the URI is invalid', async function () { - getTestWindow().onDidShowDialog((d) => d.selectItem(vscode.Uri.file(filename))) - await assert.rejects( - async () => await vscode.window.showTextDocument(vscode.Uri.parse(`notCloudWatch:hahahaha`)) - ) - await saveCurrentLogDataContent() - assert.strictEqual(await fileExists(filename), false) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/searchLogGroup.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/searchLogGroup.test.ts deleted file mode 100644 index 9e63a3f59e..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/searchLogGroup.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - createSearchPatternPrompter, - SearchLogGroupWizard, -} from '../../../../awsService/cloudWatchLogs/commands/searchLogGroup' -import { - CloudWatchLogsGroupInfo, - CloudWatchLogsParameters, -} from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { TimeFilterResponse, TimeFilterSubmenu } from '../../../../awsService/cloudWatchLogs/timeFilterSubmenu' -import { createQuickPickPrompterTester, QuickPickPrompterTester } from '../../../shared/ui/testUtils' -import { getTestWindow } from '../../../shared/vscode/window' -import { createWizardTester } from '../../../shared/wizards/wizardTestUtils' -import { DeployedResourceNode, DeployedResource } from '../../../../awsService/appBuilder/explorer/nodes/deployedNode' - -describe('searchLogGroup', async function () { - describe('Wizard', async function () { - it('shows steps in correct order', async function () { - const testWizard = await createWizardTester(new SearchLogGroupWizard()) - testWizard.submenuResponse.assertShowFirst() - testWizard.timeRange.assertShowSecond() - testWizard.filterPattern.assertShowThird() - }) - - it('skips steps if parameters are given', async function () { - const nodeTestWizard = await createWizardTester( - new SearchLogGroupWizard({ - groupName: 'group-test', - regionName: 'region-test', - }) - ) - nodeTestWizard.timeRange.assertShowFirst() - nodeTestWizard.filterPattern.assertShowSecond() - nodeTestWizard.submenuResponse.assertDoesNotShow() - }) - - it('prompts for filterPattern and accepts input', async function () { - const testInput = 'this is my filterPattern' - getTestWindow().onDidShowInputBox((input) => { - // assert.strictEqual(input.prompt, '...') - // assert.strictEqual(input.placeholder, '...') - input.acceptValue(testInput) - }) - const logGroup: CloudWatchLogsGroupInfo = { - groupName: 'est-loggroup', - regionName: 'us-east-1', - } - const logParams: CloudWatchLogsParameters = {} - - const filterPatternPrompter = createSearchPatternPrompter(logGroup, logParams, {}, false, true) - const result = await filterPatternPrompter.prompt() - assert.strictEqual(result, testInput) - }) - - it('should get correct input from a deployed Lambda function with LoggingConfig', async function () { - const mockResource: DeployedResource = { - stackName: 'test-stack', - regionCode: 'us-east-1', - explorerNode: { - FunctionName: 'my-lambda-function', - LoggingConfig: { - LogGroup: 'my-lambda-loggroup', - }, - }, - arn: 'arn:aws:lambda:us-east-1:111111111:function:my-lambda-function', - contextValue: 'awsRegionFunctionNode', - } - const mockLambdaNode = new DeployedResourceNode(mockResource) - const nodeTestWizard = await createWizardTester( - new SearchLogGroupWizard({ - groupName: mockLambdaNode.resource.explorerNode.LoggingConfig!.LogGroup || '', - regionName: mockLambdaNode.resource.regionCode, - }) - ) - nodeTestWizard.timeRange.assertShowFirst() - nodeTestWizard.filterPattern.assertShowSecond() - nodeTestWizard.submenuResponse.assertDoesNotShow() - }) - - it('should get correct input from a deployed Lambda function without LoggingConfig', async function () { - const mockResource: DeployedResource = { - stackName: 'test-stack', - regionCode: 'us-east-1', - explorerNode: { - FunctionName: 'my-lambda-function', - }, - arn: 'arn:aws:lambda:us-east-1:111111111:function:my-lambda-function', - contextValue: 'awsRegionFunctionNode', - } - const mockLambdaNode = new DeployedResourceNode(mockResource) - const nodeTestWizard = await createWizardTester( - new SearchLogGroupWizard({ - groupName: '/aws/lambda/' + mockLambdaNode.resource.explorerNode.FunctionName, - regionName: mockLambdaNode.resource.regionCode, - }) - ) - nodeTestWizard.timeRange.assertShowFirst() - nodeTestWizard.filterPattern.assertShowSecond() - nodeTestWizard.submenuResponse.assertDoesNotShow() - }) - }) - - describe('TimeFilterSubmenu', async function () { - let testTimeRangeMenu: TimeFilterSubmenu - let defaultTimeRangePrompter: QuickPickPrompterTester - - beforeEach(async () => { - testTimeRangeMenu = new TimeFilterSubmenu() - defaultTimeRangePrompter = createQuickPickPrompterTester(testTimeRangeMenu.defaultPrompter) - }) - - it('gives option for custom input', async function () { - defaultTimeRangePrompter.assertContainsItems('Custom time range') - defaultTimeRangePrompter.acceptItem('Custom time range') - await defaultTimeRangePrompter.result() - }) - - describe('custom date input', async function () { - it('accepts valid date (YYYY/MM/DD-YYYY/MM/DD)', async function () { - const validInput = '2000/10/06-2001/11/08' - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(validInput) - }) - const customTimeRangePrompter = testTimeRangeMenu.createDateBox() - const result = customTimeRangePrompter.prompt() - assert.strictEqual(await result, validInput) - }) - - it('validates date format', function () { - // Invalid. - assert(testTimeRangeMenu.validateDate('2000/11/07,2000/12/08')) // No separator - assert(testTimeRangeMenu.validateDate('2002/13/06-2001/11/05')) // Invalid start date - assert(testTimeRangeMenu.validateDate('2002/09/06-2001/00/05')) // Invalid end date - assert(testTimeRangeMenu.validateDate('10/02/2001-08/05/2001')) // Invalid format of dates - assert(testTimeRangeMenu.validateDate('2000/10/01-2000/10/01')) // Same date - assert(testTimeRangeMenu.validateDate('2000/10/02-2000/10/01')) // End before start - - // Valid. - assert(!testTimeRangeMenu.validateDate('2000/10/01-2000/10/03')) - assert(!testTimeRangeMenu.validateDate('2000/12/01-2001/10/03')) - assert(!testTimeRangeMenu.validateDate('2022/01/01-2022/05/03')) - assert(!testTimeRangeMenu.validateDate('2000/12/01-2099/10/03')) // Future date. - }) - }) - - it('uses previously selected range', async function () { - const january1st = 1672531200000 - const january10th = 1673308800000 - // Has previous selection range from Jan 1st to Jan 10th - const prompter = new TimeFilterSubmenu({ startTime: january1st, endTime: january10th }) - - getTestWindow().onDidShowQuickPick((input) => { - input.acceptItem('Custom time range') - }) - - getTestWindow().onDidShowInputBox((input) => { - // Should be our previously selected range - input.acceptValue(input.value) - }) - - const res = (await prompter.prompt()) as TimeFilterResponse - - assert.strictEqual(res.start, january1st) - assert.strictEqual(res.end, january10th) - }) - - describe('formatTimesToDateRange()', async function () { - it('converts valid start and end milliseconds', async function () { - assert.strictEqual( - testTimeRangeMenu.formatTimesToDateRange(1672578000000, 1673355600000), - '2023/01/01-2023/01/10' - ) - }) - - it('returns undefined on missing input', function () { - assert.strictEqual(testTimeRangeMenu.formatTimesToDateRange(undefined, Date.now()), undefined) - assert.strictEqual(testTimeRangeMenu.formatTimesToDateRange(Date.now(), undefined), undefined) - assert.strictEqual(testTimeRangeMenu.formatTimesToDateRange(undefined, undefined), undefined) - }) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts deleted file mode 100644 index fbaa9ea069..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/tailLogGroup.test.ts +++ /dev/null @@ -1,277 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' - -import assert from 'assert' -import { clearDocument, closeSession, tailLogGroup } from '../../../../awsService/cloudWatchLogs/commands/tailLogGroup' -import { StartLiveTailResponseStream } from '@aws-sdk/client-cloudwatch-logs' -import { LiveTailSessionRegistry } from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry' -import { LiveTailSession } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' -import { - TailLogGroupWizard, - TailLogGroupWizardResponse, -} from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard' -import { getTestWindow } from '../../../shared/vscode/window' -import { CloudWatchLogsSettings, uriToKey } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' -import { DefaultAwsContext, ToolkitError, waitUntil } from '../../../../shared' -import { LiveTailCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/liveTailCodeLensProvider' -import { PrompterTester } from '../../../shared/wizards/prompterTester' - -describe('TailLogGroup', function () { - const testLogGroup = 'test-log-group' - const testRegion = 'test-region' - const testMessage = 'test-message' - const testAwsAccountId = '1234' - const testSource = 'test-source' - const testAwsCredentials = {} as any as AWS.Credentials - - let sandbox: sinon.SinonSandbox - let registry: LiveTailSessionRegistry - let codeLensProvider: LiveTailCodeLensProvider - let startLiveTailSessionSpy: sinon.SinonSpy - let stopLiveTailSessionSpy: sinon.SinonSpy - let cloudwatchSettingsSpy: sinon.SinonSpy - let wizardSpy: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - registry = new LiveTailSessionRegistry() - codeLensProvider = new LiveTailCodeLensProvider(registry) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('starts LiveTailSession and writes to document. Closes tab and asserts session gets closed.', async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) - sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(testAwsCredentials)) - - const startTimestamp = 1732276800000 // 11-22-2024 12:00:00PM GMT - const updateFrames: StartLiveTailResponseStream[] = [ - getSessionUpdateFrame(false, `${testMessage}-1`, startTimestamp + 1000), - getSessionUpdateFrame(false, `${testMessage}-2`, startTimestamp + 2000), - getSessionUpdateFrame(false, `${testMessage}-3`, startTimestamp + 3000), - ] - // Returns the configured update frames and then blocks until an AbortController is signaled. - // This keeps the stream 'open', simulating an open network stream waiting for new events. - // If the stream were to close, the event listeners in the TailLogGroup command would dispose, - // breaking the 'closes tab closes session' assertions this test makes. - const controller = new AbortController() - const p = new Promise((resolve, reject) => { - controller.signal.addEventListener('abort', () => { - reject() - }) - }) - async function* generator() { - for (const frame of updateFrames) { - yield frame - } - await p - } - - startLiveTailSessionSpy = sandbox - .stub(LiveTailSession.prototype, 'startLiveTailSession') - .returns(Promise.resolve(generator())) - stopLiveTailSessionSpy = sandbox - .stub(LiveTailSession.prototype, 'stopLiveTailSession') - .callsFake(async function () { - return - }) - wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () { - return getTestWizardResponse() - }) - // Set maxLines to 1. - cloudwatchSettingsSpy = sandbox.stub(CloudWatchLogsSettings.prototype, 'get').callsFake(() => { - return 1 - }) - - // The mock stream doesn't 'close', causing tailLogGroup to not return. If we `await`, it will never resolve. - // Run it in the background and use waitUntil to poll its state. Due to the test setup, we expect this to throw - // after the abortController is fired at the end of the test. - void tailLogGroup(registry, testSource, codeLensProvider, { - groupName: testLogGroup, - regionName: testRegion, - }).catch((e) => { - const err = e as Error - assert.strictEqual(err.message.startsWith('Unexpected on-stream exception while tailing session:'), true) - }) - await waitUntil(async () => registry.size !== 0, { interval: 100, timeout: 1000 }) - - // registry is asserted to have only one entry, so this is assumed to be the session that was - // started in this test. - let sessionUri: vscode.Uri | undefined - for (const [_, session] of registry) { - sessionUri = session.uri - } - if (sessionUri === undefined) { - throw Error - } - - assert.strictEqual(wizardSpy.calledOnce, true) - assert.strictEqual(cloudwatchSettingsSpy.calledOnce, true) - assert.strictEqual(startLiveTailSessionSpy.calledOnce, true) - assert.strictEqual(registry.size, 1) - - // Validate writing to the document. - // MaxLines is set to 1, and "testMessage3" is the last event in the stream, its contents should be the only thing in the doc. - const window = getTestWindow() - const document = window.activeTextEditor?.document - assert.strictEqual(sessionUri.toString(), document?.uri.toString()) - const doesDocumentContainExpectedContent = await waitUntil( - async () => document?.getText().trim() === `12:00:03\t${testMessage}-3`, - { interval: 100, timeout: 1000 } - ) - assert.strictEqual(doesDocumentContainExpectedContent, true) - - // Test that closing all tabs the session's document is open in will cause the session to close - let tabs: vscode.Tab[] = [] - for (const tabGroup of window.tabGroups.all) { - tabs = tabs.concat(getLiveTailSessionTabsFromTabGroup(tabGroup, sessionUri!)) - } - await Promise.all(tabs.map((tab) => window.tabGroups.close(tab))) - - // Before the test ends, signal the abort controller, interrupting the mock response stream. This - // causes `handleSessionStream` in TailLogGroup to throw, triggering the disposables to dispose. - controller.abort() - - assert.strictEqual(registry.size, 0) - assert.strictEqual(stopLiveTailSessionSpy.calledOnce, true) - }) - - it(`doesn't ask for stream filter if passed as parameter`, async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) - sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(testAwsCredentials)) - - // Returns one frame - async function* generator(): AsyncIterable { - yield getSessionUpdateFrame(false, `${testMessage}-1`, 1732276800000) - } - - startLiveTailSessionSpy = sandbox - .stub(LiveTailSession.prototype, 'startLiveTailSession') - .returns(Promise.resolve(generator())) - - const prompterTester = PrompterTester.init() - .handleInputBox('Provide log event filter pattern', (inputBox) => { - inputBox.acceptValue('filter') - }) - .build() - - // Set maxLines to 1. - cloudwatchSettingsSpy = sandbox.stub(CloudWatchLogsSettings.prototype, 'get').returns(1) - - // The mock stream doesn't 'close', causing tailLogGroup to not return. If we `await`, it will never resolve. - // Run it in the background and use waitUntil to poll its state. - void tailLogGroup( - registry, - testSource, - codeLensProvider, - { - groupName: testLogGroup, - regionName: testRegion, - }, - { type: 'all' } - ) - await waitUntil(async () => registry.size !== 0, { interval: 100, timeout: 1000 }) - - prompterTester.assertCall('Provide log event filter pattern', 1) - - assert.strictEqual(startLiveTailSessionSpy.calledOnce, true) - assert.strictEqual(registry.size, 1) - }) - - it('throws if crendentials are undefined', async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentials').returns(Promise.resolve(undefined)) - wizardSpy = sandbox.stub(TailLogGroupWizard.prototype, 'run').callsFake(async function () { - return getTestWizardResponse() - }) - await assert.rejects(async () => { - await tailLogGroup(registry, testSource, codeLensProvider, { - groupName: testLogGroup, - regionName: testRegion, - }) - }, ToolkitError) - }) - - it('closeSession removes session from registry and calls underlying stopLiveTailSession function.', function () { - stopLiveTailSessionSpy = sandbox - .stub(LiveTailSession.prototype, 'stopLiveTailSession') - .callsFake(async function () { - return - }) - - const session = new LiveTailSession({ - logGroupArn: testLogGroup, - region: testRegion, - awsCredentials: testAwsCredentials, - }) - registry.set(uriToKey(session.uri), session) - - closeSession(session.uri, registry, testSource, codeLensProvider) - assert.strictEqual(0, registry.size) - assert.strictEqual(true, stopLiveTailSessionSpy.calledOnce) - }) - - it('clearDocument clears all text from document', async function () { - const session = new LiveTailSession({ - logGroupArn: testLogGroup, - region: testRegion, - awsCredentials: testAwsCredentials, - }) - const testData = 'blah blah blah' - const document = await vscode.workspace.openTextDocument(session.uri) - const edit = new vscode.WorkspaceEdit() - edit.insert(document.uri, new vscode.Position(0, 0), testData) - await vscode.workspace.applyEdit(edit) - assert.strictEqual(document.getText(), testData) - - await clearDocument(document) - assert.strictEqual(document.getText(), '') - }) - - function getLiveTailSessionTabsFromTabGroup(tabGroup: vscode.TabGroup, sessionUri: vscode.Uri): vscode.Tab[] { - return tabGroup.tabs.filter((tab) => { - if (tab.input instanceof vscode.TabInputText) { - return sessionUri!.toString() === tab.input.uri.toString() - } - }) - } - - function getTestWizardResponse(): TailLogGroupWizardResponse { - return { - regionLogGroupSubmenuResponse: { - region: testRegion, - data: testLogGroup, - }, - filterPattern: '', - logStreamFilter: { - type: 'all', - }, - } - } - - function getSessionUpdateFrame( - isSampled: boolean, - message: string, - timestamp: number - ): StartLiveTailResponseStream { - return { - sessionUpdate: { - sessionMetadata: { - sampled: isSampled, - }, - sessionResults: [ - { - message: message, - timestamp: timestamp, - }, - ], - }, - } - } -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/commands/viewLogStream.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/commands/viewLogStream.test.ts deleted file mode 100644 index fa2da7204e..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/commands/viewLogStream.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -const localize = nls.loadMessageBundle() - -import assert from 'assert' -import { - SelectLogStreamWizardContext, - SelectLogStreamWizard, - convertDescribeLogToQuickPickItems, - LogSearchChoice, -} from '../../../../awsService/cloudWatchLogs/commands/viewLogStream' -import { LogGroupNode } from '../../../../awsService/cloudWatchLogs/explorer/logGroupNode' -import globals from '../../../../shared/extensionGlobals' -import { formatLocalized } from '../../../../shared/datetime' - -class MockSelectLogStreamWizardContext implements SelectLogStreamWizardContext { - public constructor(private readonly pickLogStreamResponses: LogSearchChoice[] = []) { - this.pickLogStreamResponses = pickLogStreamResponses.reverse() - } - - public async pickLogStream(): Promise { - if (this.pickLogStreamResponses.length <= 0) { - throw new Error('pickLogStream was called more times than expected') - } - - const response = this.pickLogStreamResponses.pop() - if (!response) { - return { kind: 'cancelled' } - } - - return response - } -} - -describe('viewLogStreamWizard', async function () { - it('exits when cancelled', async function () { - const wizard = new SelectLogStreamWizard( - new LogGroupNode('region', {}), - new MockSelectLogStreamWizardContext([{ kind: 'cancelled' }]) - ) - const result = await wizard.run() - - assert.strictEqual(result?.kind, 'cancelled') - }) - - it('returns the selected log stream name', async function () { - const streamName = 'stream/name' - const region = 'us-weast-99' - const groupName = 'grouper' - const wizard = new SelectLogStreamWizard( - new LogGroupNode(region, { logGroupName: groupName }), - new MockSelectLogStreamWizardContext([ - { kind: 'selectedLogStream', region: region, logGroupName: groupName, logStreamName: streamName }, - ]) - ) - const result = await wizard.run() - - assert.ok(result) - // HACK: using '===' since following this assertion the exact - // type of 'result' is narrowed down to 'selectedLogStream'. - // Otherwise we get TS2339 error. - assert.ok(result.kind === 'selectedLogStream') - assert.strictEqual(result.logGroupName, groupName) - assert.strictEqual(result.logStreamName, streamName) - assert.strictEqual(result.region, region) - }) -}) - -describe('convertDescribeLogToQuickPickItems', function () { - it('converts things correctly', function () { - const time = new globals.clock.Date().getTime() - const results = convertDescribeLogToQuickPickItems({ - logStreams: [ - { - logStreamName: 'streamWithoutTimestamp', - }, - { - logStreamName: 'streamWithTimestamp', - lastEventTimestamp: time, - }, - ], - }) - - assert.strictEqual(results.length, 2) - assert.deepStrictEqual(results[0], { - label: 'streamWithoutTimestamp', - detail: localize('AWS.cwl.viewLogStream.workflow.noStreams', '[No Log Events found]'), - }) - assert.deepStrictEqual(results[1], { - label: 'streamWithTimestamp', - detail: formatLocalized(new Date(time)), - }) - const noResults = convertDescribeLogToQuickPickItems({}) - assert.strictEqual(noResults.length, 0) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/document/logDataDocumentProvider.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/document/logDataDocumentProvider.test.ts deleted file mode 100644 index 3457623cb7..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/document/logDataDocumentProvider.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { - CloudWatchLogsSettings, - cwlUriSchema, - isLogStreamUri, -} from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' -import { LogDataDocumentProvider } from '../../../../awsService/cloudWatchLogs/document/logDataDocumentProvider' -import { - LogDataRegistry, - CloudWatchLogsData, - CloudWatchLogsGroupInfo, - CloudWatchLogsParameters, - CloudWatchLogsResponse, -} from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { Settings } from '../../../../shared/settings' -import { LogDataCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/logDataCodeLensProvider' -import { CLOUDWATCH_LOGS_SCHEME } from '../../../../shared/constants' -import { FilteredLogEvent } from '@aws-sdk/client-cloudwatch-logs' - -const getLogEventsMessage = 'This is from getLogEvents' - -export async function testGetLogEvents( - logGroupInfo: CloudWatchLogsGroupInfo, - apiParameters: CloudWatchLogsParameters, - nextToken?: string -): Promise { - return { - events: [ - { - message: getLogEventsMessage, - }, - ], - } -} - -async function testFilterLogEvents( - logGroupInfo: CloudWatchLogsGroupInfo, - apiParameters: CloudWatchLogsParameters, - nextToken?: string -): Promise { - return Promise.resolve({ - events: [ - { - message: 'This is from filterLogEvents', - }, - ], - }) -} - -describe('LogDataDocumentProvider', async function () { - let provider: LogDataDocumentProvider - let registry: LogDataRegistry - let codeLensProvider: LogDataCodeLensProvider - let config: Settings - - const getLogsLogGroupInfo: CloudWatchLogsGroupInfo = { - groupName: 'group', - regionName: 'region', - streamName: 'stream', - } - const getLogsUri = cwlUriSchema.form({ logGroupInfo: getLogsLogGroupInfo, parameters: {} }) - - const filterLogsStream: CloudWatchLogsData = { - events: [], - parameters: { filterPattern: 'lookForThis' }, - logGroupInfo: { - groupName: 'group', - regionName: 'region', - }, - retrieveLogsFunction: testFilterLogEvents, - busy: false, - } - - const filterLogsUri = cwlUriSchema.form({ - logGroupInfo: filterLogsStream.logGroupInfo, - parameters: filterLogsStream.parameters, - }) - - before(function () { - config = new Settings(vscode.ConfigurationTarget.Workspace) - - registry = new LogDataRegistry(new CloudWatchLogsSettings(config)) - registry.registerInitialLog(getLogsUri, testGetLogEvents) - registry.registerInitialLog(filterLogsUri, testFilterLogEvents) - - provider = new LogDataDocumentProvider(registry) - - codeLensProvider = new LogDataCodeLensProvider(registry) - }) - - it('provides content if it exists', async function () { - await registry.fetchNextLogEvents(getLogsUri) - - const result = provider.provideTextDocumentContent(getLogsUri) - const expected = ` \t${getLogEventsMessage}\n` - assert.strictEqual(result, expected) - }) - - it('throws error on attempt to get content from non-cwl uri', async function () { - await registry.fetchNextLogEvents(getLogsUri) - - const emptyUri = vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:has:Not`) - assert.throws(() => provider.provideTextDocumentContent(emptyUri), { - message: `Uri is not a CWL Uri, so no text can be provided: ${emptyUri.toString()}`, - }) - }) - - it("Give backward codelense if viewing log stream and doesn't if not", async function () { - const fakeGetLogsDocument = await vscode.workspace.openTextDocument(getLogsUri) - const fakeFilterLogsDocument = await vscode.workspace.openTextDocument(filterLogsUri) - const fakeVscodeToken = {} as vscode.CancellationToken - - const fakeGetLogsCodeLens = codeLensProvider.provideCodeLenses(fakeGetLogsDocument, fakeVscodeToken) - const fakeFilterLogsCodeLens = codeLensProvider.provideCodeLenses(fakeFilterLogsDocument, fakeVscodeToken) - - assert(fakeGetLogsCodeLens) - assert(fakeFilterLogsCodeLens) - - assert.strictEqual(isLogStreamUri(filterLogsUri), false) - assert.strictEqual(isLogStreamUri(getLogsUri), true) - assert.notStrictEqual(fakeGetLogsCodeLens, fakeFilterLogsCodeLens) - }) - - describe('getLogStreamNameAtLine', () => { - const logGroupInfo: CloudWatchLogsGroupInfo = { - groupName: 'groupA', - regionName: 'regionA', - streamName: 'streamA', - } - const logStreamNameUri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} }) - - const events: FilteredLogEvent[] = [ - { - message: 'This is the first message', - logStreamName: 'logStream0', - }, - { - message: 'This is the second message', - logStreamName: 'logStream0', - }, - { - message: 'This is the third message', - logStreamName: 'logStream1', - }, - { - message: 'This is the fourth message', - logStreamName: 'logStream2', - }, - ] - - async function getLogEventsFromMultipleStreams( - logGroupInfo: CloudWatchLogsGroupInfo, - apiParameters: CloudWatchLogsParameters, - nextToken?: string - ): Promise { - return { - events, - } - } - - before(async () => { - registry.registerInitialLog(logStreamNameUri, getLogEventsFromMultipleStreams) - await registry.fetchNextLogEvents(logStreamNameUri) - provider.provideTextDocumentContent(logStreamNameUri) - }) - - it('gives the correct stream id for each line', () => { - for (let i = 0; i < events.length; i++) { - assert.strictEqual(provider.getLogStreamNameAtLine(logStreamNameUri, i), events[i].logStreamName) - } - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.test.ts deleted file mode 100644 index ae222fd25d..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/document/logStreamsCodeLensProvider.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { describe } from 'mocha' -import { LogStreamCodeLensProvider } from '../../../../awsService/cloudWatchLogs/document/logStreamsCodeLensProvider' -import { LogDataRegistry } from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { LogDataDocumentProvider } from '../../../../awsService/cloudWatchLogs/document/logDataDocumentProvider' -import { CancellationToken, CodeLens, TextDocument } from 'vscode' -import assert = require('assert') -import { createStubInstance, SinonStubbedInstance } from 'sinon' -import { cwlUriSchema } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' - -describe('LogStreamCodeLensProvider', async () => { - describe('provideCodeLenses()', async () => { - let codeLensProvider: LogStreamCodeLensProvider - let documentProvider: SinonStubbedInstance - - const logGroupInfo = { groupName: 'MyGroupName', regionName: 'MyRegionName' } - const logUri = cwlUriSchema.form({ logGroupInfo: logGroupInfo, parameters: {} }) - - before(async () => { - const registry: LogDataRegistry = {} as LogDataRegistry - documentProvider = createStubInstance(LogDataDocumentProvider) - - codeLensProvider = new LogStreamCodeLensProvider(registry, documentProvider as any) - }) - it('gets codelenses from a typical file', async () => { - const logStreamIdAtLine = ['streamId0', 'streamId0', 'streamId0', 'streamId1', 'streamId1', 'streamId2'] - // Stub method which provides the log stream ID at a given line - for (let i = 0; i < logStreamIdAtLine.length; i++) { - documentProvider.getLogStreamNameAtLine.onCall(i).returns(logStreamIdAtLine[i]) - } - - const result = await codeLensProvider.provideCodeLenses( - { uri: logUri, lineCount: logStreamIdAtLine.length + 1 } as TextDocument, - {} as CancellationToken - ) - - // Create code lenses at the first occurence of each Log Stream - const expected = [ - createCodeLensAtFirstOccurence('streamId0', logStreamIdAtLine), - createCodeLensAtFirstOccurence('streamId1', logStreamIdAtLine), - createCodeLensAtFirstOccurence('streamId2', logStreamIdAtLine), - ] - assert.deepStrictEqual(result, expected) - }) - - /** - * Creates a code lens at the first occurence of a stream id in the - * given string array. - */ - function createCodeLensAtFirstOccurence(streamId: string, logStreamIdAtLine: string[]): CodeLens { - const firstOccurenceIndex = logStreamIdAtLine.findIndex((value) => value === streamId) - return codeLensProvider.createLogStreamCodeLens(logGroupInfo, { - streamId: logStreamIdAtLine[firstOccurenceIndex], - lineNum: firstOccurenceIndex, - }) - } - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/document/textContent.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/document/textContent.test.ts deleted file mode 100644 index abf89cee0c..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/document/textContent.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - generateTextFromLogEvents, - timestampSpaceEquivalent, -} from '../../../../awsService/cloudWatchLogs/document/textContent' -import { CloudWatchLogsEvent } from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' - -describe('generateTextFromLogEvents', function () { - it('no formatting', () => { - const events: CloudWatchLogsEvent[] = [ - { message: 'The first log event', logStreamName: 'stream1' }, - { message: 'The second log event', logStreamName: 'stream2' }, - ] - - const { text, streamIdMap } = generateTextFromLogEvents(events) - - const expectedText = `The first log event -The second log event -` - const expectedStreamIdMap = new Map([ - [0, 'stream1'], - [1, 'stream2'], - ]) - assert.strictEqual(text, expectedText) - assert.deepStrictEqual(streamIdMap, expectedStreamIdMap) - }) - - it('has timestamps', () => { - const events: CloudWatchLogsEvent[] = [ - { message: 'The first log event', logStreamName: 'stream1', timestamp: 1675451113 }, - { message: 'The second log event', logStreamName: 'stream2', timestamp: 1675451114 }, - ] - - const { text, streamIdMap } = generateTextFromLogEvents(events, { timestamps: true }) - - const expectedText = `1970-01-20T09:24:11.113-08:00\tThe first log event -1970-01-20T09:24:11.114-08:00\tThe second log event -` - const expectedStreamIdMap = new Map([ - [0, 'stream1'], - [1, 'stream2'], - ]) - assert.strictEqual(text, expectedText) - assert.deepStrictEqual(streamIdMap, expectedStreamIdMap) - }) - - it('has a multiline message + timestamps', () => { - const events: CloudWatchLogsEvent[] = [ - { message: 'The first log event', logStreamName: 'stream1', timestamp: 1675451113 }, - { - message: 'The second log event\nwith another line\nand another', - logStreamName: 'stream2', - timestamp: 1675451114, - }, - ] - - const { text, streamIdMap } = generateTextFromLogEvents(events, { timestamps: true }) - - const expectedText = `1970-01-20T09:24:11.113-08:00\tThe first log event -1970-01-20T09:24:11.114-08:00\tThe second log event -${timestampSpaceEquivalent}\twith another line -${timestampSpaceEquivalent}\tand another -` - - const expectedStreamIdMap = new Map([ - [0, 'stream1'], - [1, 'stream2'], - [2, 'stream2'], - [3, 'stream2'], - ]) - assert.strictEqual(text, expectedText) - assert.deepStrictEqual(streamIdMap, expectedStreamIdMap) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.test.ts deleted file mode 100644 index 34eb3b1cd2..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/explorer/cloudWatchLogsNode.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { contextValueCloudwatchLog, LogGroupNode } from '../../../../awsService/cloudWatchLogs/explorer/logGroupNode' -import { CloudWatchLogsNode } from '../../../../awsService/cloudWatchLogs/explorer/cloudWatchLogsNode' -import { asyncGenerator } from '../../../../shared/utilities/collectionUtils' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../../utilities/explorerNodeAssertions' -import { CloudWatchLogsClient } from '../../../../shared/clients/cloudWatchLogs' -import { stub } from '../../../utilities/stubber' - -const fakeRegionCode = 'someregioncode' -const unsortedText = ['zebra', 'Antelope', 'aardvark', 'elephant'] -const sortedText = ['aardvark', 'Antelope', 'elephant', 'zebra'] - -describe('CloudWatchLogsNode', function () { - let testNode: CloudWatchLogsNode - - // Mocked Lambda Client returns Log Groups for anything listed in logGroupNames - let logGroupNames: string[] - - function createClient() { - const client = stub(CloudWatchLogsClient, { regionCode: fakeRegionCode }) - client.describeLogGroups.callsFake(() => asyncGenerator(logGroupNames.map((name) => ({ logGroupName: name })))) - - return client - } - - beforeEach(function () { - logGroupNames = ['group1', 'group2'] - testNode = new CloudWatchLogsNode(fakeRegionCode, createClient()) - }) - - it('returns placeholder node if no children are present', async function () { - logGroupNames = [] - - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has LogGroupNode child nodes', async function () { - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, logGroupNames.length, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof LogGroupNode, 'Expected child node to be LogGroupNode') - } - }) - - it('has child nodes with CloudWatch Log contextValue', async function () { - const childNodes = await testNode.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - contextValueCloudwatchLog, - 'expected the node to have a CloudWatch Log contextValue' - ) - } - }) - - it('sorts child nodes', async function () { - logGroupNames = unsortedText - - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, sortedText, 'Unexpected child sort order') - }) - - it('has an error node for a child if an error happens during loading', async function () { - const client = createClient() - client.describeLogGroups.throws(new Error()) - - const node = new CloudWatchLogsNode(fakeRegionCode, client) - assertNodeListOnlyHasErrorNode(await node.getChildren()) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/explorer/logGroupNode.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/explorer/logGroupNode.test.ts deleted file mode 100644 index 88930b6c5c..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/explorer/logGroupNode.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import { LogGroupNode } from '../../../../awsService/cloudWatchLogs/explorer/logGroupNode' -import { LogGroup } from '@aws-sdk/client-cloudwatch-logs' - -describe('LogGroupNode', function () { - let testNode: LogGroupNode - let fakeLogGroup: LogGroup - - before(function () { - fakeLogGroup = { - logGroupName: "it'sBig/it'sHeavy/it'sWood", - arn: "it'sBetterThanBadIt'sGood", - } - - testNode = new LogGroupNode('someregioncode', fakeLogGroup) - }) - - it('instantiates without issue', async function () { - assert.ok(testNode) - }) - - it('initializes the region code', async function () { - assert.strictEqual(testNode.regionCode, 'someregioncode') - }) - - it('initializes the label', async function () { - assert.strictEqual(testNode.label, fakeLogGroup.logGroupName) - }) - - it('initializes the functionName', async function () { - assert.strictEqual(testNode.name, fakeLogGroup.logGroupName) - }) - - it('initializes the tooltip', async function () { - assert.strictEqual(testNode.tooltip, `${fakeLogGroup.logGroupName}${os.EOL}${fakeLogGroup.arn}`) - }) - - it('has no children', async function () { - const childNodes = await testNode.getChildren() - assert.ok(childNodes) - assert.strictEqual(childNodes.length, 0, 'Expected node to have no children') - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts deleted file mode 100644 index 1aeeec9e6a..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailRegistry.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import assert from 'assert' -import { LiveTailSessionConfiguration } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' -import { createLiveTailURIFromArgs } from '../../../../awsService/cloudWatchLogs/registry/liveTailSessionRegistry' -import { cloudwatchLogsLiveTailScheme } from '../../../../shared/constants' - -describe('LiveTailSession URI', async function () { - const testLogGroupName = 'test-log-group' - const testRegion = 'test-region' - const testAwsCredentials = {} as any as AWS.Credentials - const expectedUriBase = `${cloudwatchLogsLiveTailScheme}:${testRegion}:${testLogGroupName}` - - it('is correct with no logStream filter, no filter pattern', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(expectedUriBase) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) - - it('is correct with no logStream filter, with filter pattern', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - logEventFilterPattern: 'test-filter', - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(`${expectedUriBase}:test-filter`) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) - - it('is correct with ALL logStream filter', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - logStreamFilter: { - type: 'all', - }, - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(`${expectedUriBase}:all`) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) - - it('is correct with prefix logStream filter', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - logStreamFilter: { - type: 'prefix', - filter: 'test-prefix', - }, - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(`${expectedUriBase}:prefix:test-prefix`) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) - - it('is correct with specific logStream filter', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - logStreamFilter: { - type: 'specific', - filter: 'test-stream', - }, - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(`${expectedUriBase}:specific:test-stream`) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) - - it('is correct with specific logStream filter and filter pattern', function () { - const config: LiveTailSessionConfiguration = { - logGroupArn: testLogGroupName, - region: testRegion, - logStreamFilter: { - type: 'specific', - filter: 'test-stream', - }, - logEventFilterPattern: 'test-filter', - awsCredentials: testAwsCredentials, - } - const expectedUri = vscode.Uri.parse(`${expectedUriBase}:specific:test-stream:test-filter`) - const uri = createLiveTailURIFromArgs(config) - assert.deepEqual(uri, expectedUri) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts deleted file mode 100644 index 514114750b..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/registry/liveTailSession.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as sinon from 'sinon' -import * as FakeTimers from '@sinonjs/fake-timers' -import assert from 'assert' -import { LiveTailSession } from '../../../../awsService/cloudWatchLogs/registry/liveTailSession' -import { - CloudWatchLogsClient, - StartLiveTailCommand, - StartLiveTailResponseStream, -} from '@aws-sdk/client-cloudwatch-logs' -import { LogStreamFilterResponse } from '../../../../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu' -import { installFakeClock } from '../../../testUtil' - -describe('LiveTailSession', async function () { - const testLogGroupArn = 'arn:aws:test-log-group' - const testRegion = 'test-region' - const testFilter = 'test-filter' - const testAwsCredentials = {} as any as AWS.Credentials - - let sandbox: sinon.SinonSandbox - let clock: FakeTimers.InstalledClock - - before(function () { - clock = installFakeClock() - }) - - beforeEach(function () { - clock.reset() - sandbox = sinon.createSandbox() - }) - - after(function () { - clock.uninstall() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('builds StartLiveTailCommand: no stream Filter, no event filter.', function () { - const session = buildLiveTailSession({ type: 'all' }, undefined) - assert.deepStrictEqual( - session.buildStartLiveTailCommand().input, - new StartLiveTailCommand({ - logGroupIdentifiers: [testLogGroupArn], - logEventFilterPattern: undefined, - logStreamNamePrefixes: undefined, - logStreamNames: undefined, - }).input - ) - }) - - it('builds StartLiveTailCommand: with prefix stream Filter', function () { - const session = buildLiveTailSession({ type: 'prefix', filter: testFilter }, undefined) - assert.deepStrictEqual( - session.buildStartLiveTailCommand().input, - new StartLiveTailCommand({ - logGroupIdentifiers: [testLogGroupArn], - logEventFilterPattern: undefined, - logStreamNamePrefixes: [testFilter], - logStreamNames: undefined, - }).input - ) - }) - - it('builds StartLiveTailCommand: with specific stream Filter', function () { - const session = buildLiveTailSession({ type: 'specific', filter: testFilter }, undefined) - assert.deepStrictEqual( - session.buildStartLiveTailCommand().input, - new StartLiveTailCommand({ - logGroupIdentifiers: [testLogGroupArn], - logEventFilterPattern: undefined, - logStreamNamePrefixes: undefined, - logStreamNames: [testFilter], - }).input - ) - }) - - it('builds StartLiveTailCommand: with log event Filter', function () { - const session = buildLiveTailSession({ type: 'all' }, testFilter) - assert.deepStrictEqual( - session.buildStartLiveTailCommand().input, - new StartLiveTailCommand({ - logGroupIdentifiers: [testLogGroupArn], - logEventFilterPattern: testFilter, - logStreamNamePrefixes: undefined, - logStreamNames: undefined, - }).input - ) - }) - - it('closes a started session', async function () { - const startLiveTailStub = sinon.stub(CloudWatchLogsClient.prototype, 'send').callsFake(function () { - return { - responseStream: mockResponseStream(), - } - }) - const session = buildLiveTailSession({ type: 'all' }, testFilter) - assert.strictEqual(session.getLiveTailSessionDuration(), 0) - - const returnedResponseStream = await session.startLiveTailSession() - assert.strictEqual(startLiveTailStub.calledOnce, true) - const requestArgs = startLiveTailStub.getCall(0).args - assert.deepEqual(requestArgs[0].input, session.buildStartLiveTailCommand().input) - assert.strictEqual(requestArgs[1].abortSignal !== undefined && !requestArgs[1].abortSignal.aborted, true) - assert.strictEqual(session.isAborted, false) - assert.strictEqual(clock.countTimers(), 1) - assert.deepStrictEqual(returnedResponseStream, mockResponseStream()) - - clock.tick(1000) - assert.strictEqual(session.getLiveTailSessionDuration(), 1000) - - session.stopLiveTailSession() - assert.strictEqual(session.isAborted, true) - assert.strictEqual(clock.countTimers(), 0) - - // Session is stopped; ticking the clock forward should not change the session duration - clock.tick(1000) - assert.strictEqual(session.getLiveTailSessionDuration(), 1000) - }) - - function buildLiveTailSession( - logStreamFilter: LogStreamFilterResponse, - logEventFilterPattern: string | undefined - ): LiveTailSession { - return new LiveTailSession({ - logGroupArn: testLogGroupArn, - logStreamFilter: logStreamFilter, - logEventFilterPattern: logEventFilterPattern, - region: testRegion, - awsCredentials: testAwsCredentials, - }) - } - - async function* mockResponseStream(): AsyncIterable { - const frame: StartLiveTailResponseStream = { - sessionUpdate: { - sessionMetadata: { - sampled: false, - }, - sessionResults: [], - }, - } - yield frame - } -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/registry/logDataRegistry.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/registry/logDataRegistry.test.ts deleted file mode 100644 index 1a00bb49c6..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/registry/logDataRegistry.test.ts +++ /dev/null @@ -1,231 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { - LogDataRegistry, - CloudWatchLogsAction, - CloudWatchLogsGroupInfo, - CloudWatchLogsParameters, - CloudWatchLogsResponse, - CloudWatchLogsData, -} from '../../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { Settings } from '../../../../shared/settings' -import { CloudWatchLogsSettings, cwlUriSchema } from '../../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' -import { - backwardToken, - fakeGetLogEvents, - fakeSearchLogGroup, - logGroupsData, - newLineData, - paginatedData, - testLogData, - unregisteredData, -} from '../utils.test' -import { FilteredLogEvent } from '@aws-sdk/client-cloudwatch-logs' -import { formatDateTimestamp } from '../../../../shared/datetime' - -describe('LogDataRegistry', async function () { - let registry: GetSetLogDataRegistry - - const config = new Settings(vscode.ConfigurationTarget.Workspace) - - const registeredUri = cwlUriSchema.form({ - logGroupInfo: testLogData.logGroupInfo, - parameters: testLogData.parameters, - }) - const unregisteredUri = cwlUriSchema.form({ - logGroupInfo: unregisteredData.logGroupInfo, - parameters: unregisteredData.parameters, - }) - const newLineUri = cwlUriSchema.form({ logGroupInfo: newLineData.logGroupInfo, parameters: newLineData.parameters }) - const searchLogGroupUri = cwlUriSchema.form({ - logGroupInfo: logGroupsData.logGroupInfo, - parameters: logGroupsData.parameters, - }) - const paginatedUri = cwlUriSchema.form({ - logGroupInfo: paginatedData.logGroupInfo, - parameters: paginatedData.parameters, - }) - - /** - * Only intended to expose the {get|set}LogData methods for testing purposes. - */ - class GetSetLogDataRegistry extends LogDataRegistry { - override getLogData(uri: vscode.Uri): CloudWatchLogsData | undefined { - return super.getLogData(uri) - } - - override setLogData(uri: vscode.Uri, newData: CloudWatchLogsData): void { - super.setLogData(uri, newData) - } - } - - beforeEach(function () { - registry = new GetSetLogDataRegistry(new CloudWatchLogsSettings(config)) - registry.setLogData(registeredUri, testLogData) - registry.setLogData(newLineUri, newLineData) - registry.setLogData(searchLogGroupUri, logGroupsData) - registry.setLogData(paginatedUri, paginatedData) - }) - - describe('hasLog', function () { - it('correctly returns whether or not the log is registered', function () { - assert.strictEqual(registry.isRegistered(registeredUri), true) - assert.strictEqual(registry.isRegistered(unregisteredUri), false) - }) - }) - - describe('registerInitialLog', function () { - it('throws when attempting to register a pre-existing uri', () => { - assert.throws( - () => { - registry.registerInitialLog(registeredUri) - }, - new Error(`Already registered: ${registeredUri.toString()}`) - ) - }) - }) - - describe('fetchNextLogEvents head/tail management', async function () { - beforeEach(() => { - // Verify existing data - const oldEvents = registry.fetchCachedLogEvents(paginatedUri) - assert.deepStrictEqual(oldEvents, paginatedData.events) - }) - - it("properly paginates the results with 'head'", async () => { - // Manually set a backwards token to exist - registry.setLogData(paginatedUri, { ...paginatedData, previous: { token: backwardToken } }) - - const newEvents = await registry.fetchNextLogEvents(paginatedUri, 'head') - - // // check that newData is changed to what it should be. - const expected = (await fakeSearchLogGroup()).events - assert.deepStrictEqual(newEvents, expected) - }) - - it("properly paginates the results with 'tail'", async () => { - const newEvents = await registry.fetchNextLogEvents(paginatedUri, 'tail') - - // // check that newData is changed to what it should be. - const expected = (await fakeGetLogEvents()).events - assert.deepStrictEqual(newEvents, expected) - }) - - it("returns empty list if no 'head' token", async () => { - const newEvents = await registry.fetchNextLogEvents(paginatedUri, 'head') - // // check that newData is changed to what it should be. - assert.deepStrictEqual(newEvents, []) - }) - }) - - describe('fetchNextLogEvents pagination handling', async function () { - const pageToken0 = undefined // Absence of token implies inital page - const pageToken1 = 'page1Token' - const pageToken2 = 'page2Token' - - function createCwlEvents(id: string, count: number): FilteredLogEvent[] { - let events: FilteredLogEvent[] = [] - for (let i = 0; i < count; i++) { - events = events.concat({ message: `message-${id}`, logStreamName: `stream-${id}` }) - } - return events - } - - async function buildCwlAction(isPage1Empty: boolean): Promise { - return async function ( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters, - nextToken?: string - ) { - return getSimulatedCwlResponse(nextToken, isPage1Empty) - } - } - - /** - * This function returns a simulated cloud watch logs reponse for a given token. - * @param token The page token - * @param isPage1Empty A flag to indicate if Page 1 should have data/isn't the tail. - * @returns - */ - function getSimulatedCwlResponse(token: string | undefined, isPage1Empty: boolean): CloudWatchLogsResponse { - switch (token) { - case pageToken1: - if (isPage1Empty) { - return { events: [], nextForwardToken: undefined, nextBackwardToken: pageToken0 } - } - return { - events: createCwlEvents('P1', 1), - nextForwardToken: pageToken2, - nextBackwardToken: pageToken0, - } - case pageToken2: - return { events: [], nextForwardToken: undefined, nextBackwardToken: pageToken1 } - default: // pageToken0 - return { - events: createCwlEvents('P0', 1), - nextForwardToken: pageToken1, - nextBackwardToken: undefined, - } - } - } - - it('can retrieve new events when they become available', async () => { - // Swap to use other paginate testing function - const noEventsOnPage1 = true - paginatedData.retrieveLogsFunction = await buildCwlAction(noEventsOnPage1) - - // -- Make first call. - const firstUpdatedEvents = await registry.fetchNextLogEvents(paginatedUri) - const firstExpectedEvents = getSimulatedCwlResponse(pageToken0, noEventsOnPage1).events - assert.deepStrictEqual(firstUpdatedEvents, firstExpectedEvents) - - // -- Make second call. - const secondUpdatedData = await registry.fetchNextLogEvents(paginatedUri) - // Expect the output to equal the first call since page 1 'does not exist yet' - assert.deepStrictEqual(secondUpdatedData, firstExpectedEvents) - - // Simulate page 1 now getting data - const logData = registry.getLogData(paginatedUri) - assert(logData) - logData.retrieveLogsFunction = await buildCwlAction(!noEventsOnPage1) - - // -- Make third call. - const thirdActual = await registry.fetchNextLogEvents(paginatedUri) - const thirdExpected = firstExpectedEvents.concat( - getSimulatedCwlResponse(pageToken1, !noEventsOnPage1).events - ) - assert.deepStrictEqual(thirdActual, thirdExpected) - }) - }) - - describe('disposeRegistryData', function () { - it('deletes a log', function () { - assert.strictEqual(registry.isRegistered(registeredUri), true) - registry.disposeRegistryData(registeredUri) - assert.strictEqual(registry.isRegistered(registeredUri), false) - }) - - it('does not error if the log does not exist in the registry', function () { - assert.strictEqual(registry.isRegistered(unregisteredUri), false) - registry.disposeRegistryData(unregisteredUri) - assert.strictEqual(registry.isRegistered(unregisteredUri), false) - }) - }) - - describe('Timestamp', function () { - it('matches CloudWatch insights timestamps', function () { - const time = 1624201162222 // 2021-06-20 14:59:22.222 GMT+0 - const timestamp = formatDateTimestamp(true, new Date(time)) - assert.strictEqual( - timestamp, - '2021-06-20T14:59:22.222-07:00', - '`process.env.TZ` assignment may have been modified' - ) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/utils.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/utils.test.ts deleted file mode 100644 index e51f249c1f..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/utils.test.ts +++ /dev/null @@ -1,289 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { cwlUriSchema, uriToKey } from '../../../awsService/cloudWatchLogs/cloudWatchLogsUtils' -import { - CloudWatchLogsParameters, - CloudWatchLogsData, - CloudWatchLogsResponse, - CloudWatchLogsGroupInfo, -} from '../../../awsService/cloudWatchLogs/registry/logDataRegistry' -import { CLOUDWATCH_LOGS_SCHEME } from '../../../shared/constants' -import { findOccurencesOf } from '../../../shared/utilities/textDocumentUtilities' - -export const newText = 'a little longer now\n' -export const addedEvent = { - message: 'this is an additional event', -} -export const forwardToken = 'forward' -export const backwardToken = 'backward' - -export async function returnPaginatedEvents( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters, - nextToken?: string -) { - switch (nextToken) { - case forwardToken: - return fakeGetLogEvents() - case backwardToken: - return fakeSearchLogGroup() - default: - return { - events: [], - nextForwardToken: forwardToken, - nextBackwardToken: backwardToken, - } - } -} - -export async function returnNonEmptyPaginatedEvents( - logGroupInfo: CloudWatchLogsGroupInfo, - parameters: CloudWatchLogsParameters, - nextToken?: string -) { - const result = await returnPaginatedEvents(logGroupInfo, parameters, nextToken) - if (result.events.length === 0) { - result.events = [addedEvent] - } - return result -} - -export async function fakeGetLogEvents(): Promise { - return { - events: [ - { - message: newText, - }, - ], - } -} -export const testStreamNames = ['stream0', 'stream1'] -export async function fakeSearchLogGroup(): Promise { - return { - events: [ - { - message: newText, - logStreamName: testStreamNames[0], - }, - { - message: newText, - logStreamName: testStreamNames[1], - }, - ], - } -} - -export const testComponents = { - logGroupInfo: { - groupName: 'this-is-a-group', - regionName: 'this-is-a-region', - streamName: 'this-is-a-stream', - }, - parameters: { filterPattern: 'this is a bad filter!' }, -} - -export const testLogData: CloudWatchLogsData = { - events: [ - { - timestamp: 1, - message: 'is the loneliest number\n', - }, - { - timestamp: 2, - message: 'can be as sad as one\n', - }, - { - timestamp: 3, - message: '...dog night covered this song\n', - }, - { - message: 'does anybody really know what time it is? does anybody really care?\n', - }, - ], - parameters: {}, - logGroupInfo: { - groupName: 'This', - regionName: 'Is', - streamName: 'Registered', - }, - retrieveLogsFunction: fakeGetLogEvents, - busy: false, -} - -export const newLineData: CloudWatchLogsData = { - events: [ - { - timestamp: 12745641600000, - message: 'the\nline\rmust\r\nbe\ndrawn\rHERE\nright\nhere\r\nno\nfurther\n', - }, - ], - parameters: {}, - logGroupInfo: { - groupName: 'Not', - regionName: 'Here', - streamName: 'Dude', - }, - retrieveLogsFunction: fakeGetLogEvents, - busy: false, -} - -export const unregisteredData: CloudWatchLogsData = { - events: [], - parameters: {}, - logGroupInfo: { - groupName: 'ANOTHER', - regionName: 'LINE', - streamName: 'PIEEEECCCEEEEEE', - }, - retrieveLogsFunction: fakeGetLogEvents, - busy: false, -} - -export const logGroupsData: CloudWatchLogsData = { - events: [], - parameters: {}, - logGroupInfo: { - groupName: 'thisIsAGroupName', - regionName: 'thisIsARegionCode', - }, - retrieveLogsFunction: fakeSearchLogGroup, - busy: false, -} - -export const paginatedData: CloudWatchLogsData = { - events: [], - parameters: {}, - logGroupInfo: { - groupName: 'g', - regionName: 'r', - }, - retrieveLogsFunction: returnPaginatedEvents, - busy: false, -} -export const goodUri = cwlUriSchema.form({ - logGroupInfo: testComponents.logGroupInfo, - parameters: testComponents.parameters, -}) - -describe('parseCloudWatchLogsUri', async function () { - it('converts a valid URI to components', function () { - const result = cwlUriSchema.parse(goodUri) - assert.deepStrictEqual(result.logGroupInfo, testComponents.logGroupInfo) - assert.deepStrictEqual(result.parameters, testComponents.parameters) - }) - - it('does not convert URIs with an invalid scheme', async function () { - assert.throws(() => { - cwlUriSchema.parse(vscode.Uri.parse('wrong:scheme')) - }) - }) - - it('does not convert URIs with more or less than three elements', async function () { - assert.throws(() => { - cwlUriSchema.parse(vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:elementOne:elementTwo`)) - }) - - assert.throws(() => { - cwlUriSchema.parse( - vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:elementOne:elementTwo:elementThree:whoopsAllElements`) - ) - }) - }) -}) - -describe('createURIFromArgs', function () { - it('converts components to a valid URI that can be parsed.', function () { - const testUri = vscode.Uri.parse( - `${CLOUDWATCH_LOGS_SCHEME}:${testComponents.logGroupInfo.regionName}:${ - testComponents.logGroupInfo.groupName - }:${testComponents.logGroupInfo.streamName}?${encodeURIComponent( - JSON.stringify(testComponents.parameters) - )}` - ) - assert.deepStrictEqual(testUri, goodUri) - const newTestComponents = cwlUriSchema.parse(testUri) - assert.deepStrictEqual(testComponents, newTestComponents) - }) -}) - -describe('uriToKey', function () { - let testUri: vscode.Uri - before(function () { - testUri = vscode.Uri.parse( - `${CLOUDWATCH_LOGS_SCHEME}:g:r - ?${encodeURIComponent(JSON.stringify(testComponents.parameters))}` - ) - }) - - it('throws error if query not parsable', function () { - const badUri = vscode.Uri.parse(`${CLOUDWATCH_LOGS_SCHEME}:g:r?ThisIsNotAJson`) - assert.throws(() => uriToKey(badUri)) - }) - - it('creates the same key for different order query', function () { - const param1: CloudWatchLogsParameters = { filterPattern: 'same', startTime: 0 } - const param2: CloudWatchLogsParameters = { startTime: 0, filterPattern: 'same' } - const firstOrder = cwlUriSchema.form({ logGroupInfo: testComponents.logGroupInfo, parameters: param1 }) - const secondOrder = cwlUriSchema.form({ logGroupInfo: testComponents.logGroupInfo, parameters: param2 }) - - assert.notDeepStrictEqual(firstOrder, secondOrder) - assert.strictEqual(uriToKey(firstOrder), uriToKey(secondOrder)) - }) - - it('creates unique strings for Uri with different parameters', function () { - const diffParameters = { streamName: 'NotIslandsInTheStream' } - - const uriDiffParameters = vscode.Uri.parse( - `${CLOUDWATCH_LOGS_SCHEME}:g:r - ?${encodeURIComponent(JSON.stringify(diffParameters))}` - ) - - assert.notStrictEqual(uriToKey(testUri), uriToKey(uriDiffParameters)) - }) - - it('creates unique strings for Uri with different log group info', function () { - const uriDiffGroup = vscode.Uri.parse( - `${CLOUDWATCH_LOGS_SCHEME}:g2:r2 - ?${encodeURIComponent(JSON.stringify(testComponents.parameters))}` - ) - - assert.notStrictEqual(uriToKey(testUri), uriToKey(uriDiffGroup)) - }) -}) - -describe('findOccurrencesOf', function () { - it('finds correct number', async function () { - const testDoc1 = await vscode.workspace.openTextDocument({ content: 'Arr' }) - const result1 = findOccurencesOf(testDoc1, 'rr') - const expected1 = [new vscode.Range(new vscode.Position(0, 1), new vscode.Position(0, 3))] - - const testDoc2 = await vscode.workspace.openTextDocument({ - content: - 'Here is a bunch of text (here) \n and it even spans multiple lines (here) \n to make it more interesting (here) \n (here) because why not \n', - }) - const result2 = findOccurencesOf(testDoc2, 'here') - const expected2 = [ - new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 4)), - new vscode.Range(new vscode.Position(0, 25), new vscode.Position(0, 29)), - new vscode.Range(new vscode.Position(1, 35), new vscode.Position(1, 39)), - new vscode.Range(new vscode.Position(2, 30), new vscode.Position(2, 34)), - new vscode.Range(new vscode.Position(3, 2), new vscode.Position(3, 6)), - ] - - const testDoc3 = await vscode.workspace.openTextDocument({ content: 'Here is a \n weird \n one\n zzz' }) - const result3 = findOccurencesOf(testDoc3, 'zz') - const expected3 = [ - new vscode.Range(new vscode.Position(3, 1), new vscode.Position(3, 3)), - new vscode.Range(new vscode.Position(3, 2), new vscode.Position(3, 4)), - ] - - assert.deepStrictEqual(result1, expected1) - assert.deepStrictEqual(result2, expected2) - assert.deepStrictEqual(result3, expected3) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts deleted file mode 100644 index 32946c3805..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { LogStreamFilterSubmenu } from '../../../../awsService/cloudWatchLogs/wizard/liveTailLogStreamSubmenu' -import { createQuickPickPrompterTester, QuickPickPrompterTester } from '../../../shared/ui/testUtils' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('liveTailLogStreamSubmenu', async function () { - let logStreamFilterSubmenu: LogStreamFilterSubmenu - let logStreamMenuPrompter: QuickPickPrompterTester - const testRegion = 'us-east-1' - const testLogGroupArn = 'my-log-group-arn' - - beforeEach(async function () { - logStreamFilterSubmenu = new LogStreamFilterSubmenu(testRegion, testLogGroupArn) - logStreamMenuPrompter = createQuickPickPrompterTester(logStreamFilterSubmenu.defaultPrompter) - }) - - describe('Menu prompter', async function () { - it('gives option for each filter type', async function () { - logStreamMenuPrompter.assertContainsItems( - 'All Log Streams', - 'Specific Log Stream', - 'Log Streams matching prefix' - ) - logStreamMenuPrompter.acceptItem('All Log Streams') - await logStreamMenuPrompter.result() - }) - }) - - describe('LogStream Prefix Submenu', function () { - it('accepts valid input', async function () { - const validInput = 'my-log-stream' - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(validInput) - }) - const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() - const result = inputBox.prompt() - assert.strictEqual(await result, validInput) - }) - - it('rejects invalid input (:)', async function () { - const invalidInput = 'my-log-stream:' - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(invalidInput) - assert.deepEqual(input.validationMessage, 'Log Stream prefix must match pattern: [^:*]*') - input.hide() - }) - const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() - await inputBox.prompt() - }) - - it('rejects invalid input (*)', async function () { - const invalidInput = 'my-log-stream*' - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(invalidInput) - assert.deepEqual(input.validationMessage, 'Log Stream prefix must match pattern: [^:*]*') - input.hide() - }) - const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() - await inputBox.prompt() - }) - - it('rejects invalid input (length)', async function () { - const invalidInput = 'a'.repeat(520) - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(invalidInput) - assert.deepEqual(input.validationMessage, 'Log Stream prefix cannot be longer than 512 characters') - input.hide() - }) - const inputBox = logStreamFilterSubmenu.createLogStreamPrefixBox() - await inputBox.prompt() - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts b/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts deleted file mode 100644 index 2cae8b964b..0000000000 --- a/packages/core/src/test/awsService/cloudWatchLogs/wizard/tailLogGroupWizard.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as sinon from 'sinon' - -import assert from 'assert' -import { buildLogGroupArn, TailLogGroupWizard } from '../../../../awsService/cloudWatchLogs/wizard/tailLogGroupWizard' -import { createWizardTester } from '../../../shared/wizards/wizardTestUtils' -import { DefaultAwsContext } from '../../../../shared' - -describe('TailLogGroupWizard', async function () { - let sandbox: sinon.SinonSandbox - - const testLogGroupName = 'testLogGroup' - const testRegion = 'testRegion' - const testAwsAccountId = '1234' - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts regionLogGroup submenu first if context not provided', async function () { - const wizard = new TailLogGroupWizard() - const tester = await createWizardTester(wizard) - tester.regionLogGroupSubmenuResponse.assertShowFirst() - tester.logStreamFilter.assertShowSecond() - tester.filterPattern.assertShowThird() - }) - - it('skips regionLogGroup submenu if context provided', async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) - const wizard = new TailLogGroupWizard({ - groupName: testLogGroupName, - regionName: testRegion, - }) - const tester = await createWizardTester(wizard) - tester.regionLogGroupSubmenuResponse.assertDoesNotShow() - tester.logStreamFilter.assertShowFirst() - tester.filterPattern.assertShowSecond() - }) - - it('skips logStream filter when logStream info is provided', async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) - const wizard = new TailLogGroupWizard( - { - groupName: testLogGroupName, - regionName: testRegion, - }, - { type: 'specific', filter: 'log-group-name' } - ) - const tester = await createWizardTester(wizard) - tester.regionLogGroupSubmenuResponse.assertDoesNotShow() - tester.logStreamFilter.assertDoesNotShow() - tester.filterPattern.assertShowFirst() - }) - - it('builds LogGroup Arn properly', async function () { - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns(testAwsAccountId) - const arn = buildLogGroupArn(testLogGroupName, testRegion) - assert.strictEqual(arn, `arn:aws:logs:${testRegion}:${testAwsAccountId}:log-group:${testLogGroupName}`) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/auth/credentials.test.ts b/packages/core/src/test/awsService/cloudformation/auth/credentials.test.ts deleted file mode 100644 index 138c581228..0000000000 --- a/packages/core/src/test/awsService/cloudformation/auth/credentials.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as jose from 'jose' -import { AwsCredentialsService, encryptionKey } from '../../../../awsService/cloudformation/auth/credentials' - -describe('AwsCredentialsService', function () { - let sandbox: sinon.SinonSandbox - let mockStacksManager: any - let mockResourcesManager: any - let mockRegionManager: any - let mockClient: any - let credentialsService: AwsCredentialsService - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockStacksManager = { reload: sandbox.stub(), hasMore: sandbox.stub().returns(false), clear: sandbox.stub() } - mockResourcesManager = { reload: sandbox.stub() } - mockClient = { sendRequest: sandbox.stub() } - - const mockRegionManager = { getSelectedRegion: () => 'us-east-1' } as any - - credentialsService = new AwsCredentialsService(mockStacksManager, mockResourcesManager, mockRegionManager) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('createEncryptedCredentialsRequest', function () { - beforeEach(function () { - credentialsService = new AwsCredentialsService(mockStacksManager, mockResourcesManager, mockRegionManager) - }) - - it('should create encrypted request with correct structure', async function () { - const mockCredentials = { - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - expiration: new Date(), - } - - const result = await (credentialsService as any).createEncryptedCredentialsRequest(mockCredentials) - - assert.strictEqual(typeof result.data, 'string') - assert.strictEqual(result.encrypted, true) - }) - - it('should encrypt credentials that can be decrypted', async function () { - const mockCredentials = { - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - expiration: new Date(), - } - - const encryptedRequest = await (credentialsService as any).createEncryptedCredentialsRequest( - mockCredentials - ) - - // Verify we can decrypt it back - const decrypted = await jose.compactDecrypt(encryptedRequest.data, encryptionKey) - const decryptedData = JSON.parse(new TextDecoder().decode(decrypted.plaintext)) - - // Compare with expected serialized format (Date becomes string in JSON) - const expectedCredentials = { - ...mockCredentials, - expiration: mockCredentials.expiration.toISOString(), - } - assert.deepStrictEqual(decryptedData.data, expectedCredentials) - }) - }) - - describe('initialize', function () { - it('should accept language client', async function () { - credentialsService = new AwsCredentialsService(mockStacksManager, mockResourcesManager, mockRegionManager) - await credentialsService.initialize(mockClient) - // Test passes if no error thrown - assert(true) - }) - - it('should clear stacks', async function () { - credentialsService = new AwsCredentialsService(mockStacksManager, mockResourcesManager, mockRegionManager) - await credentialsService.initialize(mockClient) - assert(mockStacksManager.clear.calledOnce) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/cfn-init/cfnEnvironmentManager.test.ts b/packages/core/src/test/awsService/cloudformation/cfn-init/cfnEnvironmentManager.test.ts deleted file mode 100644 index 0bc4c5098f..0000000000 --- a/packages/core/src/test/awsService/cloudformation/cfn-init/cfnEnvironmentManager.test.ts +++ /dev/null @@ -1,471 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { strict as assert } from 'assert' -import * as sinon from 'sinon' -import { CfnEnvironmentManager } from '../../../../awsService/cloudformation/cfn-init/cfnEnvironmentManager' -import { Auth } from '../../../../auth/auth' -import { Connection } from '../../../../auth/connection' -import { globals } from '../../../../shared' -import { workspace, commands } from 'vscode' -import fs from '../../../../shared/fs/fs' -import { CfnEnvironmentSelector } from '../../../../awsService/cloudformation/ui/cfnEnvironmentSelector' -import { CfnEnvironmentFileSelector } from '../../../../awsService/cloudformation/ui/cfnEnvironmentFileSelector' -import { OnStackFailure } from '@aws-sdk/client-cloudformation' -import * as environmentApi from '../../../../awsService/cloudformation/cfn-init/cfnEnvironmentApi' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('CfnEnvironmentManager', () => { - let environmentManager: CfnEnvironmentManager - let mockAuth: sinon.SinonStubbedInstance - let mockWorkspaceState: any - let mockEnvironmentSelector: sinon.SinonStubbedInstance - let mockEnvironmentFileSelector: sinon.SinonStubbedInstance - let fsStub: sinon.SinonStub - let workspaceStub: sinon.SinonStub - let parseEnvironmentFilesStub: sinon.SinonStub - let mockClient: any - - let existsDirStub: sinon.SinonStub - let existsFileStub: sinon.SinonStub - - beforeEach(() => { - mockAuth = { - getConnection: sinon.stub(), - useConnection: sinon.stub(), - activeConnection: { - id: 'profile:test-profile', - type: 'iam', - label: 'test-profile', - state: 'valid', - } as unknown as Connection, - } as sinon.SinonStubbedInstance - - sinon.stub(Auth, 'instance').get(() => mockAuth) - - mockWorkspaceState = { - get: sinon.stub(), - update: sinon.stub(), - } - sinon.stub(globals, 'context').value({ workspaceState: mockWorkspaceState }) - - mockEnvironmentSelector = { - selectEnvironment: sinon.stub(), - } as unknown as sinon.SinonStubbedInstance - - mockEnvironmentFileSelector = { - selectEnvironmentFile: sinon.stub(), - } as unknown as sinon.SinonStubbedInstance - - fsStub = sinon.stub(fs, 'readFileText') - // Mock project as initialized by default - existsDirStub = sinon.stub(fs, 'existsDir').resolves(true) - existsFileStub = sinon.stub(fs, 'existsFile').resolves(true) - - workspaceStub = sinon.stub(workspace, 'workspaceFolders').value([{ uri: { fsPath: '/test/workspace' } }]) - parseEnvironmentFilesStub = sinon.stub(environmentApi, 'parseCfnEnvironmentFiles') - mockClient = {} - - environmentManager = new CfnEnvironmentManager(mockClient, mockEnvironmentSelector, mockEnvironmentFileSelector) - }) - - afterEach(() => { - sinon.restore() - }) - - describe('getSelectedEnvironmentName', () => { - it('should return selected environment from workspace state', () => { - mockWorkspaceState.get.returns('test-env') - - const result = environmentManager.getSelectedEnvironmentName() - - assert.strictEqual(result, 'test-env') - assert(mockWorkspaceState.get.calledWith('aws.cloudformation.selectedEnvironment')) - }) - }) - - describe('promptInitializeIfNeeded', () => { - it('should return false when project is already initialized', async () => { - // Project is initialized by default in beforeEach - const result = await environmentManager.promptInitializeIfNeeded('Test Operation') - - assert.strictEqual(result, false) - const messages = getTestWindow().shownMessages - assert.strictEqual(messages.length, 0) - }) - - it('should show warning and execute command when user clicks Initialize Project', async () => { - existsDirStub.resolves(false) - existsFileStub.resolves(false) - - getTestWindow().onDidShowMessage((message) => { - if (message.message === 'You must initialize your CFN Project to perform Test Operation') { - message.selectItem('Initialize Project') - } - }) - - const executeCommandStub = sinon.stub(commands, 'executeCommand') - - const result = await environmentManager.promptInitializeIfNeeded('Test Operation') - - assert.strictEqual(result, true) - const messages = getTestWindow().shownMessages - assert(messages.some((m) => m.message === 'You must initialize your CFN Project to perform Test Operation')) - assert(executeCommandStub.calledWith('aws.cloudformation.init.initializeProject')) - }) - }) - - describe('selectEnvironment', () => { - it('should show warning when project is not initialized', async () => { - // Override default - mock project as not initialized - existsDirStub.resolves(false) - existsFileStub.resolves(false) - - // Set up message handler to simulate user clicking "Initialize Project" - getTestWindow().onDidShowMessage((message) => { - if (message.message === 'You must initialize your CFN Project to perform Environment Selection') { - // Simulate user clicking the "Initialize Project" button - message.selectItem('Initialize Project') - } - }) - - const executeCommandStub = sinon.stub(commands, 'executeCommand') - - await environmentManager.selectEnvironment() - - const messages = getTestWindow().shownMessages - assert( - messages.some( - (m) => m.message === 'You must initialize your CFN Project to perform Environment Selection' - ) - ) - assert(executeCommandStub.calledWith('aws.cloudformation.init.initializeProject')) - assert(mockEnvironmentSelector.selectEnvironment.notCalled) - }) - - it('should select environment successfully', async () => { - const mockEnvironmentLookup = { 'test-env': { name: 'test-env', profile: 'test-profile' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - mockEnvironmentSelector.selectEnvironment.resolves('test-env') - - const mockConnection = { - id: 'profile:test-profile', - type: 'iam', - label: 'test-profile', - state: 'valid', - } as unknown as Connection - mockAuth.getConnection.resolves(mockConnection) - - const listener = sinon.stub() - environmentManager.addListener(listener) - - await environmentManager.selectEnvironment() - - assert(mockEnvironmentSelector.selectEnvironment.calledWith(mockEnvironmentLookup)) - assert(mockWorkspaceState.update.calledWith('aws.cloudformation.selectedEnvironment', 'test-env')) - assert(mockAuth.getConnection.calledWith({ id: 'profile:test-profile' })) - assert(mockAuth.useConnection.calledWith(mockConnection)) - assert(listener.called) - }) - - it('should handle fetch error gracefully', async () => { - fsStub.rejects(new Error('File not found')) - - await environmentManager.selectEnvironment() - - assert(mockEnvironmentSelector.selectEnvironment.notCalled) - }) - - it('should handle no environment selected', async () => { - const mockEnvironmentLookup = { 'test-env': { name: 'test-env', profile: 'test-profile' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - mockEnvironmentSelector.selectEnvironment.resolves(undefined) - - await environmentManager.selectEnvironment() - - assert(mockWorkspaceState.update.notCalled) - assert(mockAuth.getConnection.notCalled) - }) - - it('should handle missing connection gracefully', async () => { - const mockEnvironmentLookup = { 'test-env': { name: 'test-env', profile: 'missing-profile' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - mockEnvironmentSelector.selectEnvironment.resolves('test-env') - mockAuth.getConnection.resolves(undefined) - - await environmentManager.selectEnvironment() - - assert(mockWorkspaceState.update.calledWith('aws.cloudformation.selectedEnvironment', 'test-env')) - assert(mockAuth.useConnection.notCalled) - }) - }) - - describe('fetchAvailableEnvironments', () => { - it('should fetch environments successfully', async () => { - const mockEnvironmentLookup = { env1: { name: 'env1', profile: 'profile1' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - - const result = await environmentManager.fetchAvailableEnvironments() - - assert.deepStrictEqual(result, mockEnvironmentLookup) - }) - - it('should throw error when workspace not found', async () => { - workspaceStub.value(undefined) - - await assert.rejects( - environmentManager.fetchAvailableEnvironments(), - /You must open a workspace to use CFN environment commands/ - ) - }) - - it('should throw error when file read fails', async () => { - fsStub.rejects(new Error('File not found')) - - await assert.rejects(environmentManager.fetchAvailableEnvironments(), /File not found/) - }) - }) - - describe('selectEnvironmentFile', () => { - let readdirStub: sinon.SinonStub - - beforeEach(() => { - readdirStub = sinon.stub(fs, 'readdir') - }) - - it('should return undefined when no environment selected', async () => { - mockWorkspaceState.get.returns(undefined) - - const result = await environmentManager.selectEnvironmentFile('template.yaml', [{ name: 'Param1' }]) - - assert.strictEqual(result, undefined) - }) - - it('should collect all environment files and pass to selector', async () => { - mockWorkspaceState.get.returns('test-env') - - // Mock multiple files - readdirStub.resolves([ - ['params1.json', 1], - ['params2.yaml', 1], - ['params3.yml', 1], - ]) - - // Mock file contents - fsStub.onCall(0).resolves( - JSON.stringify({ - parameters: { Param1: 'value1' }, - tags: { Tag1: 'value1' }, - 'on-stack-failure': OnStackFailure.DO_NOTHING, - 'import-existing-resources': true, - 'include-nested-stacks': false, - }) - ) - fsStub.onCall(1).resolves('template-file-path: template.yaml\nparameters:\n Param2: value2') - fsStub.onCall(2).resolves('template-file-path: wrong-file.yaml\nparameters:\n Param3: value3') - - // Mock parseEnvironmentFiles response - parseEnvironmentFilesStub.resolves([ - { - fileName: 'params1.json', - deploymentConfig: { - parameters: { Param1: 'value1' }, - tags: { Tag1: 'value1' }, - onStackFailure: OnStackFailure.DO_NOTHING, - importExistingResources: true, - includeNestedStacks: false, - }, - }, - { - fileName: 'params2.yaml', - deploymentConfig: { - templateFilePath: 'template.yaml', - parameters: { Param2: 'value2' }, - }, - }, - { - fileName: 'params3.yml', - deploymentConfig: { - templateFilePath: 'wrong-file.yaml', - parameters: { Param3: 'value3' }, - }, - }, - ]) - - // Mock workspace.asRelativePath to return matching path for template.yaml - sinon.stub(workspace, 'asRelativePath').returns('template.yaml') - - const mockSelectorItem = { - fileName: 'selected.json', - hasMatchingTemplatePath: true, - compatibleParameters: [{ ParameterKey: 'Param1', ParameterValue: 'value1' }], - } - mockEnvironmentFileSelector.selectEnvironmentFile.resolves(mockSelectorItem) - - const result = await environmentManager.selectEnvironmentFile('template.yaml', [ - { name: 'Param1' }, - { name: 'Param2' }, - { name: 'Param3' }, - ]) - - const [selectorItems, paramCount] = mockEnvironmentFileSelector.selectEnvironmentFile.getCall(0).args - - // Assert call arguments - assert(mockEnvironmentFileSelector.selectEnvironmentFile.calledOnce) - assert.strictEqual(selectorItems.length, 3) - assert.strictEqual(paramCount, 3) - - // Check params1.json - assert.strictEqual(selectorItems[0].fileName, 'params1.json') - assert.strictEqual(selectorItems[0].hasMatchingTemplatePath, false) - assert.deepStrictEqual(selectorItems[0].compatibleParameters, [ - { ParameterKey: 'Param1', ParameterValue: 'value1' }, - ]) - assert.deepStrictEqual(selectorItems[0].optionalFlags?.tags, [{ Key: 'Tag1', Value: 'value1' }]) - assert.deepStrictEqual(selectorItems[0].optionalFlags?.includeNestedStacks, false), - assert.deepStrictEqual(selectorItems[0].optionalFlags?.importExistingResources, true), - assert.deepStrictEqual(selectorItems[0].optionalFlags?.onStackFailure, OnStackFailure.DO_NOTHING), - // Check params2.yaml - assert.strictEqual(selectorItems[1].fileName, 'params2.yaml') - assert.strictEqual(selectorItems[1].hasMatchingTemplatePath, true) - assert.deepStrictEqual(selectorItems[1].compatibleParameters, [ - { ParameterKey: 'Param2', ParameterValue: 'value2' }, - ]) - - // Check params3.yml - assert.strictEqual(selectorItems[2].fileName, 'params3.yml') - assert.strictEqual(selectorItems[2].hasMatchingTemplatePath, false) - assert.deepStrictEqual(selectorItems[2].compatibleParameters, [ - { ParameterKey: 'Param3', ParameterValue: 'value3' }, - ]) - assert.strictEqual(result, mockSelectorItem) - }) - - it('should only use files returned from parser', async () => { - mockWorkspaceState.get.returns('test-env') - readdirStub.resolves([ - ['valid1.json', 1], - ['malformed1.json', 1], - ['valid2.yaml', 1], - ['malformed2.yaml', 1], - ['malformed3.yml', 1], - ]) - - // Mock file contents for all 5 files - fsStub.onCall(0).resolves(JSON.stringify({ parameters: { Param1: 'value1' } })) - fsStub.onCall(1).resolves('invalid json') - fsStub.onCall(2).resolves('parameters:\n Param2: value2') - fsStub.onCall(3).resolves('invalid: yaml: content') - fsStub.onCall(4).resolves('null') - - // Parser only returns 2 valid files out of 5 - parseEnvironmentFilesStub.resolves([ - { - fileName: 'valid1.json', - deploymentConfig: { - parameters: { Param1: 'value1' }, - }, - }, - { - fileName: 'valid2.yaml', - deploymentConfig: { - parameters: { Param2: 'value2' }, - }, - }, - ]) - - const mockSelectorItem = { fileName: 'selected.json' } - mockEnvironmentFileSelector.selectEnvironmentFile.resolves(mockSelectorItem) - - await environmentManager.selectEnvironmentFile('template.yaml', [{ name: 'Param1' }, { name: 'Param2' }]) - - // Verify parseEnvironmentFiles was called with all files - assert( - parseEnvironmentFilesStub.calledOnceWith(mockClient, { - documents: [ - { fileName: 'valid1.json', type: 'JSON', content: '{"parameters":{"Param1":"value1"}}' }, - { fileName: 'malformed1.json', type: 'JSON', content: 'invalid json' }, - { fileName: 'valid2.yaml', type: 'YAML', content: 'parameters:\n Param2: value2' }, - { fileName: 'malformed2.yaml', type: 'YAML', content: 'invalid: yaml: content' }, - { fileName: 'malformed3.yml', type: 'YAML', content: 'null' }, - ], - }) - ) - - const [selectorItems, paramCount] = mockEnvironmentFileSelector.selectEnvironmentFile.getCall(0).args - - assert(mockEnvironmentFileSelector.selectEnvironmentFile.calledOnce) - assert.strictEqual(selectorItems.length, 2) - assert.strictEqual(paramCount, 2) - - // Check valid1.json - assert.strictEqual(selectorItems[0].fileName, 'valid1.json') - assert.strictEqual(selectorItems[0].hasMatchingTemplatePath, false) - assert.deepStrictEqual(selectorItems[0].compatibleParameters, [ - { ParameterKey: 'Param1', ParameterValue: 'value1' }, - ]) - - // Check valid2.yaml - assert.strictEqual(selectorItems[1].fileName, 'valid2.yaml') - assert.strictEqual(selectorItems[1].hasMatchingTemplatePath, false) - assert.deepStrictEqual(selectorItems[1].compatibleParameters, [ - { ParameterKey: 'Param2', ParameterValue: 'value2' }, - ]) - }) - - it('should return undefined when parameter file selector returns undefined', async () => { - mockWorkspaceState.get.returns('test-env') - readdirStub.resolves([['params.json', 1]]) - fsStub.resolves(JSON.stringify({ parameters: { Param1: 'value1' } })) - - parseEnvironmentFilesStub.resolves([ - { - fileName: 'params.json', - deploymentConfig: { - parameters: { Param1: 'value1' }, - }, - }, - ]) - - mockEnvironmentFileSelector.selectEnvironmentFile.resolves(undefined) - - const result = await environmentManager.selectEnvironmentFile('template.yaml', [{ name: 'Param1' }]) - - assert.strictEqual(result, undefined) - }) - }) - - describe('refreshSelectedEnvironment', () => { - it('should unselect environment if environment not in config', async () => { - mockWorkspaceState.get.returns('not-found') - const mockEnvironmentLookup = { env1: { name: 'env1', profile: 'profile1' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - - await environmentManager.refreshSelectedEnvironment() - - assert(mockWorkspaceState.update.calledWith('aws.cloudformation.selectedEnvironment', undefined)) - }) - - it('should keep environment if environment is valid', async () => { - mockWorkspaceState.get.returns('env1') - const mockEnvironmentLookup = { env1: { name: 'env1', profile: 'profile1' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - - await environmentManager.refreshSelectedEnvironment() - - assert(mockWorkspaceState.update.notCalled) - }) - - it('should keep environment if environment is undefined', async () => { - mockWorkspaceState.get.returns(undefined) - const mockEnvironmentLookup = { env1: { name: 'env1', profile: 'profile1' } } - fsStub.resolves(JSON.stringify({ environments: mockEnvironmentLookup })) - - await environmentManager.refreshSelectedEnvironment() - - assert(mockWorkspaceState.update.notCalled) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/commands/cfnCommands.test.ts b/packages/core/src/test/awsService/cloudformation/commands/cfnCommands.test.ts deleted file mode 100644 index 493a187578..0000000000 --- a/packages/core/src/test/awsService/cloudformation/commands/cfnCommands.test.ts +++ /dev/null @@ -1,421 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { OnStackFailure, Parameter } from '@aws-sdk/client-cloudformation' -import { - rerunValidateAndDeployCommand, - extractToParameterPositionCursorCommand, - promptForOptionalFlags, - promptToSaveToFile, - addResourceTypesCommand, - removeResourceTypeCommand, -} from '../../../../awsService/cloudformation/commands/cfnCommands' -import { OptionalFlagMode } from '../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import * as inputBox from '../../../../awsService/cloudformation/ui/inputBox' -import { fs } from '../../../../shared/fs/fs' -import { ResourceTypeNode } from '../../../../awsService/cloudformation/explorer/nodes/resourceTypeNode' - -describe('CfnCommands', function () { - let sandbox: sinon.SinonSandbox - let registerCommandStub: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - registerCommandStub = sandbox.stub(vscode.commands, 'registerCommand').returns({ - dispose: () => {}, - } as vscode.Disposable) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('rerunValidateAndDeployCommand', function () { - it('should register rerun last validation command', function () { - const result = rerunValidateAndDeployCommand() - assert.ok(result) - assert.ok(registerCommandStub.calledOnce) - assert.strictEqual(registerCommandStub.firstCall.args[0], 'aws.cloudformation.api.rerunValidateAndDeploy') - }) - }) - - describe('extractToParameterPositionCursorCommand', function () { - it('should register extract to parameter command', function () { - const mockClient = {} as any - const result = extractToParameterPositionCursorCommand(mockClient) - assert.ok(result) - assert.ok(registerCommandStub.calledOnce) - assert.strictEqual( - registerCommandStub.firstCall.args[0], - 'aws.cloudformation.extractToParameter.positionCursor' - ) - }) - }) - - describe('promptForOptionalFlags', function () { - let chooseOptionalFlagModeStub: sinon.SinonStub - let getOnStackFailureStub: sinon.SinonStub - let getIncludeNestedStacksStub: sinon.SinonStub - let getTagsStub: sinon.SinonStub - let getImportExistingResourcesStub: sinon.SinonStub - let getDeploymentModeStub: sinon.SinonStub - - beforeEach(function () { - chooseOptionalFlagModeStub = sandbox.stub(inputBox, 'chooseOptionalFlagSuggestion') - getOnStackFailureStub = sandbox.stub(inputBox, 'getOnStackFailure') - getIncludeNestedStacksStub = sandbox.stub(inputBox, 'getIncludeNestedStacks') - getTagsStub = sandbox.stub(inputBox, 'getTags') - getImportExistingResourcesStub = sandbox.stub(inputBox, 'getImportExistingResources') - getDeploymentModeStub = sandbox.stub(inputBox, 'getDeploymentMode') - }) - - it('should return skip mode with existing file flags', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: [{ Key: 'test', Value: 'value' }], - importExistingResources: false, - } - - const result = await promptForOptionalFlags(fileFlags) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: [{ Key: 'test', Value: 'value' }], - importExistingResources: false, - shouldSaveOptions: false, - }) - }) - - it('should use dev friendly defaults', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.DevFriendly) - getTagsStub.resolves(undefined) - - const result = await promptForOptionalFlags() - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: undefined, - importExistingResources: true, - deploymentMode: undefined, - }) - }) - - it('should set shouldSaveOptions to true when input mode collects new values', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Input) - getDeploymentModeStub.resolves(undefined) - getOnStackFailureStub.resolves(OnStackFailure.DELETE) - getIncludeNestedStacksStub.resolves(true) - getTagsStub.resolves([{ Key: 'Environment', Value: 'prod' }]) - getImportExistingResourcesStub.resolves(false) - - const result = await promptForOptionalFlags() - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.DELETE, - includeNestedStacks: true, - tags: [{ Key: 'Environment', Value: 'prod' }], - importExistingResources: false, - deploymentMode: undefined, - shouldSaveOptions: true, - }) - }) - - it('should not prompt for deployment mode for CREATE stack', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Input) - getOnStackFailureStub.resolves(OnStackFailure.ROLLBACK) - getIncludeNestedStacksStub.resolves(false) - getTagsStub.resolves(undefined) - getImportExistingResourcesStub.resolves(false) - - const result = await promptForOptionalFlags() - - assert.ok(getDeploymentModeStub.notCalled) - assert.ok(getOnStackFailureStub.calledOnce) - assert.ok(getIncludeNestedStacksStub.calledOnce) - assert.ok(getImportExistingResourcesStub.calledOnce) - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - deploymentMode: undefined, - shouldSaveOptions: true, - }) - }) - - it('should not prompt for deployment mode when stack is REVIEW_IN_PROGRESS', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Input) - getOnStackFailureStub.resolves(OnStackFailure.ROLLBACK) - getIncludeNestedStacksStub.resolves(false) - getTagsStub.resolves(undefined) - getImportExistingResourcesStub.resolves(false) - - const stackDetails = { StackName: 'test-stack', StackStatus: 'REVIEW_IN_PROGRESS' as any } - const result = await promptForOptionalFlags(undefined, stackDetails as any) - - assert.ok(getDeploymentModeStub.notCalled) - assert.strictEqual(result?.deploymentMode, undefined) - }) - - it('should prompt for deployment mode and other flags when not REVERT_DRIFT', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Input) - getDeploymentModeStub.resolves(undefined) - getOnStackFailureStub.resolves(OnStackFailure.DELETE) - getIncludeNestedStacksStub.resolves(true) - getTagsStub.resolves(undefined) - getImportExistingResourcesStub.resolves(true) - - const stackDetails = { StackName: 'test-stack' } - await promptForOptionalFlags(undefined, stackDetails as any) - - assert.ok(getDeploymentModeStub.calledOnce) - assert.ok(getOnStackFailureStub.calledOnce) - assert.ok(getIncludeNestedStacksStub.calledOnce) - assert.ok(getImportExistingResourcesStub.calledOnce) - }) - - it('should skip other prompts when deploymentMode is REVERT_DRIFT', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Input) - getDeploymentModeStub.resolves('REVERT_DRIFT') - getTagsStub.resolves(undefined) - - const stackDetails = { StackName: 'test-stack' } - const result = await promptForOptionalFlags(undefined, stackDetails as any) - - assert.ok(getDeploymentModeStub.calledOnce) - assert.ok(getOnStackFailureStub.notCalled) - assert.ok(getIncludeNestedStacksStub.notCalled) - assert.ok(getImportExistingResourcesStub.notCalled) - assert.deepStrictEqual(result, { - onStackFailure: undefined, - includeNestedStacks: undefined, - tags: undefined, - importExistingResources: undefined, - deploymentMode: 'REVERT_DRIFT', - shouldSaveOptions: true, - }) - }) - - it('should include deploymentMode from fileFlags in skip mode', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: undefined, - importExistingResources: false, - deploymentMode: 'COMPLETE_REPLACEMENT' as any, - } - - const result = await promptForOptionalFlags(fileFlags) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.DO_NOTHING, - includeNestedStacks: true, - tags: undefined, - importExistingResources: false, - deploymentMode: 'COMPLETE_REPLACEMENT', - shouldSaveOptions: false, - }) - }) - - it('should default to REVERT_DRIFT in skip mode when conditions are met', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - } - - const stackDetails = { StackName: 'test-stack' } - const result = await promptForOptionalFlags(fileFlags, stackDetails as any) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - deploymentMode: 'REVERT_DRIFT', - shouldSaveOptions: false, - }) - }) - - it('should not default to REVERT_DRIFT in skip mode when stack does not exist', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - } - - const result = await promptForOptionalFlags(fileFlags) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - deploymentMode: undefined, - shouldSaveOptions: false, - }) - }) - - it('should not default to REVERT_DRIFT in skip mode when stack is REVIEW_IN_PROGRESS', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - } - - const stackDetails = { StackName: 'test-stack', StackStatus: 'REVIEW_IN_PROGRESS' as any } - const result = await promptForOptionalFlags(fileFlags, stackDetails as any) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: undefined, - importExistingResources: false, - deploymentMode: undefined, - shouldSaveOptions: false, - }) - }) - - it('should not default to REVERT_DRIFT in skip mode when includeNestedStacks is true', async function () { - chooseOptionalFlagModeStub.resolves(OptionalFlagMode.Skip) - - const fileFlags = { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: true, - tags: undefined, - importExistingResources: false, - } - - const stackDetails = { StackName: 'test-stack' } - const result = await promptForOptionalFlags(fileFlags, stackDetails as any) - - assert.deepStrictEqual(result, { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: true, - tags: undefined, - importExistingResources: false, - deploymentMode: undefined, - shouldSaveOptions: false, - }) - }) - }) - - describe('promptToSaveToFile', function () { - let shouldSaveFlagsToFileStub: sinon.SinonStub - let getFilePathStub: sinon.SinonStub - let workspaceConfigStub: sinon.SinonStub - let workspaceAsRelativePathStub: sinon.SinonStub - let fsWriteFileStub: sinon.SinonStub - - beforeEach(function () { - shouldSaveFlagsToFileStub = sandbox.stub(inputBox, 'shouldSaveFlagsToFile') - getFilePathStub = sandbox.stub(inputBox, 'getFilePath') - workspaceConfigStub = sandbox.stub(vscode.workspace, 'getConfiguration') - workspaceAsRelativePathStub = sandbox.stub(vscode.workspace, 'asRelativePath') - fsWriteFileStub = sandbox.stub(fs, 'writeFile') - }) - - it('should return early when user chooses not to save', async function () { - shouldSaveFlagsToFileStub.resolves(false) - - await promptToSaveToFile('/test/env', undefined, undefined) - - assert(getFilePathStub.notCalled) - assert(fsWriteFileStub.notCalled) - }) - - it('should save JSON file with correct format', async function () { - shouldSaveFlagsToFileStub.resolves(true) - getFilePathStub.resolves('/test/env/config.json') - workspaceAsRelativePathStub.returns('config.json') - - const mockConfig = { - get: sandbox.stub(), - } - mockConfig.get.withArgs('tabSize', 2).returns(2) - mockConfig.get.withArgs('insertSpaces', true).returns(true) - workspaceConfigStub.returns(mockConfig) - - const parameters: Parameter[] = [ - { ParameterKey: 'Environment', ParameterValue: 'test' }, - { ParameterKey: 'InstanceType', ParameterValue: 't3.micro' }, - ] - - const optionalFlags = { - onStackFailure: OnStackFailure.ROLLBACK, - includeNestedStacks: false, - tags: [{ Key: 'Project', Value: 'MyApp' }], - importExistingResources: true, - } - - await promptToSaveToFile('/test/env', optionalFlags, parameters) - - assert(fsWriteFileStub.calledOnce) - const [filePath, content] = fsWriteFileStub.getCall(0).args - assert.strictEqual(filePath, '/test/env/config.json') - - const parsed = JSON.parse(content) - assert.deepStrictEqual(parsed['parameters'], { - Environment: 'test', - InstanceType: 't3.micro', - }) - assert.deepStrictEqual(parsed['tags'], { Project: 'MyApp' }) - assert.strictEqual(parsed['on-stack-failure'], OnStackFailure.ROLLBACK) - assert.strictEqual(parsed['include-nested-stacks'], false) - assert.strictEqual(parsed['import-existing-resources'], true) - }) - }) - - describe('addResourceTypesCommand', function () { - it('should register add resource types command', function () { - const mockResourcesManager = { selectResourceTypes: sinon.stub() } as any - const result = addResourceTypesCommand(mockResourcesManager) - assert.ok(result) - assert.ok(registerCommandStub.calledOnce) - assert.strictEqual(registerCommandStub.firstCall.args[0], 'aws.cloudformation.api.addResourceTypes') - }) - }) - - describe('removeResourceTypeCommand', function () { - it('should register remove resource type command', function () { - const mockResourcesManager = { removeResourceType: sinon.stub() } as any - const result = removeResourceTypeCommand(mockResourcesManager) - assert.ok(result) - assert.ok(registerCommandStub.calledOnce) - assert.strictEqual(registerCommandStub.firstCall.args[0], 'aws.cloudformation.removeResourceType') - }) - - it('should call removeResourceType with node typeName', async function () { - const mockResourcesManager = { removeResourceType: sinon.stub().resolves() } as any - removeResourceTypeCommand(mockResourcesManager) - - const commandHandler = registerCommandStub.firstCall.args[1] - const mockNode = { typeName: 'AWS::S3::Bucket' } as ResourceTypeNode - - await commandHandler(mockNode) - - assert.ok(mockResourcesManager.removeResourceType.calledOnceWith('AWS::S3::Bucket')) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/commands/cursorPositioning.test.ts b/packages/core/src/test/awsService/cloudformation/commands/cursorPositioning.test.ts deleted file mode 100644 index 4ad80eb216..0000000000 --- a/packages/core/src/test/awsService/cloudformation/commands/cursorPositioning.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' - -describe('CursorPositioning', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('cursor positioning', function () { - it('should position cursor correctly', function () { - // Basic test structure - implementation depends on actual CursorPositioning module - assert.ok(true, 'CursorPositioning test placeholder') - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/consoleLinksUtils.test.ts b/packages/core/src/test/awsService/cloudformation/consoleLinksUtils.test.ts deleted file mode 100644 index 9f8dd774b9..0000000000 --- a/packages/core/src/test/awsService/cloudformation/consoleLinksUtils.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { - arnToConsoleUrl, - arnToConsoleTabUrl, - operationIdToConsoleUrl, -} from '../../../awsService/cloudformation/consoleLinksUtils' - -describe('consoleLinksUtils', () => { - const testArn = 'arn:aws:cloudformation:us-west-2:123456789012:stack/test-stack/abc-123' - - describe('arnToConsoleUrl', () => { - it('should generate correct console URL', () => { - const url = arnToConsoleUrl(testArn) - assert.strictEqual( - url, - 'https://console.aws.amazon.com/go/view?arn=arn%3Aaws%3Acloudformation%3Aus-west-2%3A123456789012%3Astack%2Ftest-stack%2Fabc-123' - ) - }) - }) - - describe('arnToConsoleTabUrl', () => { - it('should generate correct events tab URL', () => { - const url = arnToConsoleTabUrl(testArn, 'events') - assert.strictEqual( - url, - 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/events?stackId=arn%3Aaws%3Acloudformation%3Aus-west-2%3A123456789012%3Astack%2Ftest-stack%2Fabc-123' - ) - }) - - it('should generate correct resources tab URL', () => { - const url = arnToConsoleTabUrl(testArn, 'resources') - assert.strictEqual( - url, - 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/resources?stackId=arn%3Aaws%3Acloudformation%3Aus-west-2%3A123456789012%3Astack%2Ftest-stack%2Fabc-123' - ) - }) - - it('should generate correct outputs tab URL', () => { - const url = arnToConsoleTabUrl(testArn, 'outputs') - assert.strictEqual( - url, - 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/outputs?stackId=arn%3Aaws%3Acloudformation%3Aus-west-2%3A123456789012%3Astack%2Ftest-stack%2Fabc-123' - ) - }) - }) - - describe('operationIdToConsoleUrl', () => { - it('should generate correct operation details URL', () => { - const operationId = '056a1310-6307-466a-a167-2cbbd353b29f' - const url = operationIdToConsoleUrl(testArn, operationId) - assert.strictEqual( - url, - 'https://us-west-2.console.aws.amazon.com/cloudformation/home?region=us-west-2#/stacks/operations/info?stackId=arn%3Aaws%3Acloudformation%3Aus-west-2%3A123456789012%3Astack%2Ftest-stack%2Fabc-123&operationId=056a1310-6307-466a-a167-2cbbd353b29f' - ) - }) - - it('should handle different regions', () => { - const euArn = 'arn:aws:cloudformation:eu-west-1:123456789012:stack/test-stack/abc-123' - const operationId = 'op-456' - const url = operationIdToConsoleUrl(euArn, operationId) - assert.ok(url?.includes('eu-west-1.console.aws.amazon.com')) - assert.ok(url?.includes('region=eu-west-1')) - assert.ok(url?.includes('operationId=op-456')) - }) - - it('should return undefined for malformed ARN', () => { - const url = operationIdToConsoleUrl('not-a-valid-arn', 'op-123') - assert.strictEqual(url, undefined) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceNode.test.ts b/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceNode.test.ts deleted file mode 100644 index 03c2a765d7..0000000000 --- a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceNode.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { TreeItemCollapsibleState } from 'vscode' -import { ResourceNode } from '../../../../../awsService/cloudformation/explorer/nodes/resourceNode' - -describe('ResourceNode', function () { - let resourceNode: ResourceNode - - beforeEach(function () { - resourceNode = new ResourceNode('my-bucket-123', 'AWS::S3::Bucket') - }) - - describe('constructor', function () { - it('should set correct properties', function () { - assert.strictEqual(resourceNode.label, 'my-bucket-123') - assert.strictEqual(resourceNode.resourceIdentifier, 'my-bucket-123') - assert.strictEqual(resourceNode.resourceType, 'AWS::S3::Bucket') - assert.strictEqual(resourceNode.contextValue, 'resource') - assert.strictEqual(resourceNode.collapsibleState, TreeItemCollapsibleState.None) - }) - }) - - describe('getChildren', function () { - it('should return empty array', async function () { - const children = await resourceNode.getChildren() - assert.strictEqual(children.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceTypeNode.test.ts b/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceTypeNode.test.ts deleted file mode 100644 index afb7866c26..0000000000 --- a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourceTypeNode.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { TreeItemCollapsibleState } from 'vscode' -import { ResourceTypeNode } from '../../../../../awsService/cloudformation/explorer/nodes/resourceTypeNode' -import { ResourceList } from '../../../../../awsService/cloudformation/resources/resourceRequestTypes' -import { ResourcesManager } from '../../../../../awsService/cloudformation/resources/resourcesManager' - -describe('ResourceTypeNode', function () { - let mockResourceList: ResourceList - let resourceTypeNode: ResourceTypeNode - let mockResourcesManager: ResourcesManager - - beforeEach(function () { - mockResourceList = { - typeName: 'AWS::S3::Bucket', - resourceIdentifiers: ['bucket-1', 'bucket-2', 'bucket-3'], - } - - mockResourcesManager = {} as ResourcesManager - - resourceTypeNode = new ResourceTypeNode('AWS::S3::Bucket', mockResourcesManager, mockResourceList) - }) - - describe('constructor', function () { - it('should set correct properties when resourceList is provided', function () { - assert.strictEqual(resourceTypeNode.label, 'AWS::S3::Bucket') - assert.strictEqual(resourceTypeNode.description, '(3)') - assert.strictEqual(resourceTypeNode.contextValue, 'resourceType') - assert.strictEqual(resourceTypeNode.collapsibleState, TreeItemCollapsibleState.Collapsed) - }) - - it('should set correct properties when resourceList is undefined', function () { - const lazyNode = new ResourceTypeNode('AWS::Lambda::Function', mockResourcesManager) - assert.strictEqual(lazyNode.label, 'AWS::Lambda::Function') - assert.strictEqual(lazyNode.description, undefined) - assert.strictEqual(lazyNode.contextValue, 'resourceType') - }) - }) - - describe('getChildren', function () { - it('should return resource nodes for each identifier', async function () { - const children = await resourceTypeNode.getChildren() - assert.strictEqual(children.length, 3) - - const labels = children.map((child) => child.label) - assert(labels.includes('bucket-1')) - assert(labels.includes('bucket-2')) - assert(labels.includes('bucket-3')) - }) - - it('should lazy load resources when not provided', async function () { - const lazyResourceList: ResourceList = { - typeName: 'AWS::DynamoDB::Table', - resourceIdentifiers: ['table-1'], - } - - mockResourcesManager.loadResourceType = async () => {} - mockResourcesManager.get = () => [lazyResourceList] - - const lazyNode = new ResourceTypeNode('AWS::DynamoDB::Table', mockResourcesManager) - const children = await lazyNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].label, 'table-1') - }) - }) - - describe('empty resource list', function () { - it('should handle empty resource identifiers', async function () { - const emptyResourceList: ResourceList = { - typeName: 'AWS::Lambda::Function', - resourceIdentifiers: [], - } - - const emptyNode = new ResourceTypeNode('AWS::Lambda::Function', mockResourcesManager, emptyResourceList) - assert.strictEqual(emptyNode.description, '(0)') - - const children = await emptyNode.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].label, 'No resources found') - }) - }) - - describe('pagination', function () { - it('should show load more node when nextToken exists', async function () { - const paginatedList: ResourceList = { - typeName: 'AWS::EC2::Instance', - resourceIdentifiers: ['i-1', 'i-2'], - nextToken: 'token123', - } - - const paginatedNode = new ResourceTypeNode('AWS::EC2::Instance', mockResourcesManager, paginatedList) - assert.strictEqual(paginatedNode.description, '(2+)') - assert.strictEqual(paginatedNode.contextValue, 'resourceTypeWithMore') - - const children = await paginatedNode.getChildren() - assert.strictEqual(children.length, 3) - assert.strictEqual(children[2].label, '[Load More...]') - }) - - it('should not show load more node when no nextToken', async function () { - const children = await resourceTypeNode.getChildren() - assert.strictEqual(children.length, 3) - assert(!children.some((child) => child.label === '[Load More...]')) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourcesNode.test.ts b/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourcesNode.test.ts deleted file mode 100644 index 47d5807835..0000000000 --- a/packages/core/src/test/awsService/cloudformation/explorer/nodes/resourcesNode.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { TreeItemCollapsibleState } from 'vscode' -import { ResourcesNode } from '../../../../../awsService/cloudformation/explorer/nodes/resourcesNode' -import { ResourcesManager } from '../../../../../awsService/cloudformation/resources/resourcesManager' -import { ResourceList } from '../../../../../awsService/cloudformation/resources/resourceRequestTypes' - -describe('ResourcesNode', function () { - let resourcesNode: ResourcesNode - let mockResourcesManager: ResourcesManager - - beforeEach(function () { - mockResourcesManager = {} as ResourcesManager - resourcesNode = new ResourcesNode(mockResourcesManager) - }) - - describe('constructor', function () { - it('should set correct properties', function () { - assert.strictEqual(resourcesNode.label, 'Resources') - assert.strictEqual(resourcesNode.contextValue, 'resourceSection') - assert.strictEqual(resourcesNode.collapsibleState, TreeItemCollapsibleState.Collapsed) - }) - }) - - describe('getChildren', function () { - it('should return ResourceTypeNode for each selected type', async function () { - mockResourcesManager.getSelectedResourceTypes = () => ['AWS::S3::Bucket', 'AWS::Lambda::Function'] - mockResourcesManager.get = () => [] - - const children = await resourcesNode.getChildren() - assert.strictEqual(children.length, 2) - assert.strictEqual(children[0].label, 'AWS::S3::Bucket') - assert.strictEqual(children[1].label, 'AWS::Lambda::Function') - }) - - it('should pass loaded resourceList when available', async function () { - const loadedResource: ResourceList = { - typeName: 'AWS::S3::Bucket', - resourceIdentifiers: ['bucket-1', 'bucket-2'], - } - - mockResourcesManager.getSelectedResourceTypes = () => ['AWS::S3::Bucket'] - mockResourcesManager.get = () => [loadedResource] - - const children = await resourcesNode.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].label, 'AWS::S3::Bucket') - assert.strictEqual(children[0].description, '(2)') - }) - - it('should return empty array when no types selected', async function () { - mockResourcesManager.getSelectedResourceTypes = () => [] - mockResourcesManager.get = () => [] - - const children = await resourcesNode.getChildren() - assert.strictEqual(children.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/explorer/nodes/stacksNode.test.ts b/packages/core/src/test/awsService/cloudformation/explorer/nodes/stacksNode.test.ts deleted file mode 100644 index d37d847432..0000000000 --- a/packages/core/src/test/awsService/cloudformation/explorer/nodes/stacksNode.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { TreeItemCollapsibleState } from 'vscode' -import { StacksNode } from '../../../../../awsService/cloudformation/explorer/nodes/stacksNode' -import { StacksManager } from '../../../../../awsService/cloudformation/stacks/stacksManager' -import { ChangeSetsManager } from '../../../../../awsService/cloudformation/stacks/changeSetsManager' -import { StackSummary } from '@aws-sdk/client-cloudformation' - -describe('StacksNode', function () { - let stacksNode: StacksNode - let mockStacksManager: sinon.SinonStubbedInstance - let mockChangeSetsManager: ChangeSetsManager - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockStacksManager = { - get: sandbox.stub(), - hasMore: sandbox.stub(), - isLoaded: sandbox.stub(), - ensureLoaded: sandbox.stub(), - loadMoreStacks: sandbox.stub(), - } as any - mockChangeSetsManager = {} as ChangeSetsManager - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('constructor', function () { - it('should set correct properties when not loaded', function () { - mockStacksManager.get.returns([]) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(false) - - stacksNode = new StacksNode(mockStacksManager as any, mockChangeSetsManager) - - assert.strictEqual(stacksNode.label, 'Stacks') - assert.strictEqual(stacksNode.collapsibleState, TreeItemCollapsibleState.Collapsed) - assert.strictEqual(stacksNode.description, undefined) - assert.strictEqual(stacksNode.contextValue, 'stackSection') - }) - - it('should set description when loaded', function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - { StackName: 'stack-2', StackStatus: 'UPDATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(true) - - stacksNode = new StacksNode(mockStacksManager as any, mockChangeSetsManager) - - assert.strictEqual(stacksNode.description, '(2)') - assert.strictEqual(stacksNode.contextValue, 'stackSection') - }) - - it('should set contextValue to stackSectionWithMore when hasMore', function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(true) - mockStacksManager.isLoaded.returns(true) - - stacksNode = new StacksNode(mockStacksManager as any, mockChangeSetsManager) - - assert.strictEqual(stacksNode.description, '(1+)') - assert.strictEqual(stacksNode.contextValue, 'stackSectionWithMore') - }) - }) - - describe('getChildren', function () { - beforeEach(function () { - mockStacksManager.get.returns([]) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(false) - stacksNode = new StacksNode(mockStacksManager as any, mockChangeSetsManager) - }) - - it('should call ensureLoaded', async function () { - mockStacksManager.ensureLoaded.resolves() - - await stacksNode.getChildren() - - assert.strictEqual(mockStacksManager.ensureLoaded.calledOnce, true) - }) - - it('should return StackNode for each stack', async function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - { StackName: 'stack-2', StackStatus: 'UPDATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.ensureLoaded.resolves() - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(true) - - const children = await stacksNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.strictEqual(children[0].label, 'stack-1') - assert.strictEqual(children[1].label, 'stack-2') - }) - - it('should include LoadMoreStacksNode when hasMore', async function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.ensureLoaded.resolves() - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(true) - mockStacksManager.isLoaded.returns(true) - - const children = await stacksNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.strictEqual(children[0].label, 'stack-1') - assert.strictEqual(children[1].label, '[Load More...]') - assert.strictEqual(children[1].contextValue, 'loadMoreStacks') - }) - - it('should update node description after load', async function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.ensureLoaded.resolves() - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(true) - - await stacksNode.getChildren() - - assert.strictEqual(stacksNode.description, '(1)') - assert.strictEqual(stacksNode.contextValue, 'stackSection') - }) - - it('should return empty array when no stacks', async function () { - mockStacksManager.ensureLoaded.resolves() - mockStacksManager.get.returns([]) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(true) - - const children = await stacksNode.getChildren() - - assert.strictEqual(children.length, 0) - }) - }) - - describe('loadMoreStacks', function () { - beforeEach(function () { - mockStacksManager.get.returns([]) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(false) - stacksNode = new StacksNode(mockStacksManager as any, mockChangeSetsManager) - }) - - it('should call stacksManager.loadMoreStacks', async function () { - mockStacksManager.loadMoreStacks.resolves() - - await stacksNode.loadMoreStacks() - - assert.strictEqual(mockStacksManager.loadMoreStacks.calledOnce, true) - }) - - it('should update node description after loading more', async function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - { StackName: 'stack-2', StackStatus: 'UPDATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.loadMoreStacks.resolves() - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(true) - mockStacksManager.isLoaded.returns(true) - - await stacksNode.loadMoreStacks() - - assert.strictEqual(stacksNode.description, '(2+)') - assert.strictEqual(stacksNode.contextValue, 'stackSectionWithMore') - }) - - it('should update contextValue when no more stacks', async function () { - const mockStacks: StackSummary[] = [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' } as StackSummary, - ] - mockStacksManager.loadMoreStacks.resolves() - mockStacksManager.get.returns(mockStacks) - mockStacksManager.hasMore.returns(false) - mockStacksManager.isLoaded.returns(true) - - await stacksNode.loadMoreStacks() - - assert.strictEqual(stacksNode.description, '(1)') - assert.strictEqual(stacksNode.contextValue, 'stackSection') - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/explorer/regionManager.test.ts b/packages/core/src/test/awsService/cloudformation/explorer/regionManager.test.ts deleted file mode 100644 index 73428afddb..0000000000 --- a/packages/core/src/test/awsService/cloudformation/explorer/regionManager.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { CloudFormationRegionManager } from '../../../../awsService/cloudformation/explorer/regionManager' -import { RegionProvider } from '../../../../shared/regions/regionProvider' - -describe('CloudFormationRegionManager', function () { - let sandbox: sinon.SinonSandbox - let mockRegionProvider: RegionProvider - let regionManager: CloudFormationRegionManager - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockRegionProvider = { - getRegions: () => [ - { id: 'us-east-1', name: 'US East (N. Virginia)' }, - { id: 'us-west-2', name: 'US West (Oregon)' }, - ], - } as any - regionManager = new CloudFormationRegionManager(mockRegionProvider) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('getSelectedRegion', function () { - it('should return a region string', function () { - const region = regionManager.getSelectedRegion() - assert(typeof region === 'string') - }) - }) - - describe('updateSelectedRegion', function () { - it('should accept a region string', async function () { - const testRegion = 'us-east-1' - await regionManager.updateSelectedRegion(testRegion) - // Test passes if no error thrown - assert(true) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/grammar.test.ts b/packages/core/src/test/awsService/cloudformation/grammar.test.ts deleted file mode 100644 index 5490611286..0000000000 --- a/packages/core/src/test/awsService/cloudformation/grammar.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { fs } from '../../../shared/fs/fs' -import * as path from 'path' - -describe('CloudFormation Grammar', function () { - let grammar: any - - before(async function () { - // Load grammar from toolkit syntaxes directory - const grammarPath = path.join(__dirname, '../../../../../../toolkit/syntaxes/cloudformation.tmLanguage.json') - const content = await fs.readFileText(grammarPath) - grammar = JSON.parse(content) - }) - - describe('Grammar Structure', function () { - it('should have correct basic structure', function () { - assert.strictEqual(grammar.name, 'CloudFormation') - assert.strictEqual(grammar.scopeName, 'source.cloudformation') - assert.ok(grammar.fileTypes.includes('template')) - assert.ok(grammar.fileTypes.includes('cfn')) - }) - - it('should include dual-format detection patterns', function () { - assert.strictEqual(grammar.patterns.length, 2) - - // JSON detection pattern - assert.strictEqual(grammar.patterns[0].begin, '^\\s*\\{') - assert.strictEqual(grammar.patterns[0].name, 'meta.cloudformation.json') - assert.strictEqual(grammar.patterns[0].patterns[0].include, 'source.json') - - // YAML detection pattern - assert.strictEqual(grammar.patterns[1].begin, '^(?!\\s*\\{)') - assert.strictEqual(grammar.patterns[1].name, 'meta.cloudformation.yaml') - }) - - it('should have repository with required patterns', function () { - const requiredPatterns = ['cfn-top-level-keys', 'cfn-logical-ids', 'cfn-functions'] - - for (const pattern of requiredPatterns) { - assert.ok(grammar.repository[pattern], `Pattern ${pattern} should be defined`) - } - }) - }) - - describe('CloudFormation-Specific Patterns', function () { - it('should match top-level CloudFormation sections', function () { - const pattern = grammar.repository['cfn-top-level-keys'].patterns[0] - assert.ok(pattern) - }) - - it('should have logical ID patterns for all major sections', function () { - const logicalIds = grammar.repository['cfn-logical-ids'] - assert.ok(logicalIds) - assert.ok(logicalIds.patterns) - - // Check that we have patterns for Resources, Parameters, Conditions, Outputs, and Mappings - const sectionNames: (string | undefined)[] = logicalIds.patterns.map((pattern: any) => { - const match = (pattern.begin as string).match(/\^\(([^)]+)\)/) - return match ? match[1] : undefined - }) - - const expectedSections = ['Resources', 'Parameters', 'Conditions', 'Outputs', 'Mappings'] - for (const section of expectedSections) { - assert.ok( - sectionNames.some((name) => name && name.includes(section)), - `Should have pattern for ${section} section` - ) - } - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/lsp-server/utils.test.ts b/packages/core/src/test/awsService/cloudformation/lsp-server/utils.test.ts deleted file mode 100644 index 7ebfeb282b..0000000000 --- a/packages/core/src/test/awsService/cloudformation/lsp-server/utils.test.ts +++ /dev/null @@ -1,311 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { - addWindows, - dedupeAndGetLatestVersions, - extractPlatformAndArch, - useOldLinuxVersion, - mapLegacyLinux, - CfnTarget, - CfnLspVersion, -} from '../../../../awsService/cloudformation/lsp-server/utils' -import { CLibCheck } from '../../../../awsService/cloudformation/lsp-server/CLibCheck' -import { LspVersion } from '../../../../shared/lsp/types' - -describe('addWindows', () => { - it('adds windows target when win32 exists and windows does not', () => { - const targets: CfnTarget[] = [ - { platform: 'darwin', arch: 'arm64', contents: [] }, - { platform: 'linux', arch: 'x64', contents: [] }, - { platform: 'win32', arch: 'x64', contents: [] }, - ] - - const result = addWindows(targets) - - assert.strictEqual(result.length, 4) - assert.ok(result.some((t) => t.platform === 'windows' && t.arch === 'x64')) - }) - - it('does not add windows target when windows already exists', () => { - const targets: CfnTarget[] = [ - { platform: 'darwin', arch: 'arm64', contents: [] }, - { platform: 'win32', arch: 'x64', contents: [] }, - { platform: 'windows', arch: 'x64', contents: [] }, - ] - - const result = addWindows(targets) - - assert.strictEqual(result.length, 3) - }) - - it('does not add windows target when no win32 exists', () => { - const targets: CfnTarget[] = [ - { platform: 'darwin', arch: 'arm64', contents: [] }, - { platform: 'linux', arch: 'x64', contents: [] }, - ] - - const result = addWindows(targets) - - assert.strictEqual(result.length, 2) - }) - - it('adds windows for multiple win32 architectures', () => { - const targets: CfnTarget[] = [ - { platform: 'win32', arch: 'x64', contents: [] }, - { platform: 'win32', arch: 'arm64', contents: [] }, - ] - - const result = addWindows(targets) - - assert.strictEqual(result.length, 4) - assert.strictEqual(result.filter((t) => t.platform === 'windows' && t.arch === 'x64').length, 1) - assert.strictEqual(result.filter((t) => t.platform === 'windows' && t.arch === 'arm64').length, 1) - }) -}) - -describe('extractPlatformAndArch', () => { - it('extracts platform, arch, and nodejs from standard filename', () => { - const result = extractPlatformAndArch('cloudformation-languageserver-1.2.0-beta-darwin-arm64-node22.zip') - - assert.strictEqual(result.platform, 'darwin') - assert.strictEqual(result.arch, 'arm64') - assert.strictEqual(result.nodejs, '22') - }) - - it('extracts linux platform with x64 arch', () => { - const result = extractPlatformAndArch('cloudformation-languageserver-1.2.0-beta-linux-x64-node22.zip') - - assert.strictEqual(result.platform, 'linux') - assert.strictEqual(result.arch, 'x64') - assert.strictEqual(result.nodejs, '22') - }) - - it('extracts linuxglib2.28 platform', () => { - const result = extractPlatformAndArch('cloudformation-languageserver-1.2.0-beta-linuxglib2.28-arm64-node18.zip') - - assert.strictEqual(result.platform, 'linuxglib2.28') - assert.strictEqual(result.arch, 'arm64') - assert.strictEqual(result.nodejs, '18') - }) - - it('extracts win32 platform', () => { - const result = extractPlatformAndArch('cloudformation-languageserver-1.2.0-beta-win32-x64-node22.zip') - - assert.strictEqual(result.platform, 'win32') - assert.strictEqual(result.arch, 'x64') - assert.strictEqual(result.nodejs, '22') - }) - - it('handles filename without node version', () => { - const result = extractPlatformAndArch('cloudformation-languageserver-1.1.0-darwin-arm64.zip') - - assert.strictEqual(result.platform, 'darwin') - assert.strictEqual(result.arch, 'arm64') - assert.strictEqual(result.nodejs, undefined) - }) - - it('handles alpha version with timestamp', () => { - const result = extractPlatformAndArch( - 'cloudformation-languageserver-1.2.0-202512020323-alpha-darwin-arm64-node22.zip' - ) - - assert.strictEqual(result.platform, 'darwin') - assert.strictEqual(result.arch, 'arm64') - assert.strictEqual(result.nodejs, '22') - }) - - it('throws error for invalid filename', () => { - assert.throws(() => extractPlatformAndArch('invalid-file.zip'), /Could not extract platform/) - }) - - it('throws error for unsupported architecture', () => { - assert.throws( - () => extractPlatformAndArch('cloudformation-languageserver-1.0.0-darwin-arm32-node22.zip'), - /Could not extract platform/ - ) - }) -}) - -describe('useOldLinuxVersion', () => { - let sandbox: sinon.SinonSandbox - let originalPlatform: PropertyDescriptor | undefined - - beforeEach(() => { - sandbox = sinon.createSandbox() - originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform') - }) - - afterEach(() => { - sandbox.restore() - if (originalPlatform) { - Object.defineProperty(process, 'platform', originalPlatform) - } - delete process.env.SNAP - }) - - it('returns false on non-linux platforms', () => { - Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true }) - - assert.strictEqual(useOldLinuxVersion(), false) - }) - - it('returns true in SNAP environment on linux', () => { - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - process.env.SNAP = '/snap/something' - - assert.strictEqual(useOldLinuxVersion(), true) - }) - - it('returns false when GLIBCXX version cannot be determined', () => { - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - sandbox.stub(CLibCheck, 'getGLibCXXVersions').returns({ maxFound: undefined, allAvailable: [] }) - - assert.strictEqual(useOldLinuxVersion(), false) - }) - - it('returns true when GLIBCXX version is older than 3.4.29', () => { - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - sandbox.stub(CLibCheck, 'getGLibCXXVersions').returns({ maxFound: '3.4.28', allAvailable: ['3.4.28'] }) - - assert.strictEqual(useOldLinuxVersion(), true) - }) - - it('returns false when GLIBCXX version is 3.4.29', () => { - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - sandbox.stub(CLibCheck, 'getGLibCXXVersions').returns({ maxFound: '3.4.29', allAvailable: ['3.4.29'] }) - - assert.strictEqual(useOldLinuxVersion(), false) - }) - - it('returns false when GLIBCXX version is newer than 3.4.29', () => { - Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }) - sandbox.stub(CLibCheck, 'getGLibCXXVersions').returns({ maxFound: '3.4.32', allAvailable: ['3.4.32'] }) - - assert.strictEqual(useOldLinuxVersion(), false) - }) -}) - -describe('dedupeAndGetLatestVersions', () => { - for (const prefix of ['v', '']) { - it(`handles versions with timestamp: ${prefix}`, () => { - const result = dedupeAndGetLatestVersions( - generateLspVersion(['0.0.1-2020', '0.0.2-2024', '0.0.3-2026', '0.0.2-2025', '0.0.3-2030'], prefix) - ) - - assert.strictEqual(result.length, 3) - assert.strictEqual(result[0].serverVersion, '0.0.3-2030') - assert.strictEqual(result[1].serverVersion, '0.0.2-2025') - assert.strictEqual(result[2].serverVersion, '0.0.1-2020') - }) - - it('handles versions with timestamp and environment', () => { - const result = dedupeAndGetLatestVersions( - generateLspVersion( - ['0.0.1-2020-alpha', '0.0.2-2024-beta', '0.0.3-2026-alpha', '0.0.2-2025-prod', '0.0.3-2030-beta'], - prefix - ) - ) - - assert.strictEqual(result.length, 3) - assert.strictEqual(result[0].serverVersion, '0.0.3-2030-beta') - assert.strictEqual(result[1].serverVersion, '0.0.2-2025-prod') - assert.strictEqual(result[2].serverVersion, '0.0.1-2020-alpha') - }) - } - - function generateLspVersion(versions: string[], prefix: string = ''): LspVersion[] { - return versions.map((version) => { - return { serverVersion: `${prefix}${version}`, targets: [], isDelisted: false } - }) - } -}) - -describe('mapLegacyLinux', () => { - const darwinContent = { filename: 'darwin.zip', url: 'https://example.com/darwin.zip', hashes: ['abc'], bytes: 100 } - const linuxContent = { filename: 'linux.zip', url: 'https://example.com/linux.zip', hashes: ['def'], bytes: 200 } - const legacyContent = { filename: 'legacy.zip', url: 'https://example.com/legacy.zip', hashes: ['ghi'], bytes: 300 } - const winContent = { filename: 'win.zip', url: 'https://example.com/win.zip', hashes: ['jkl'], bytes: 400 } - - it('remaps linuxglib2.28 to linux and removes original linux target', () => { - const versions: CfnLspVersion[] = [ - { - serverVersion: '1.0.0', - isDelisted: false, - targets: [ - { platform: 'darwin', arch: 'arm64', contents: [darwinContent] }, - { platform: 'linux', arch: 'x64', contents: [linuxContent] }, - { platform: 'linuxglib2.28', arch: 'x64', contents: [legacyContent], nodejs: '18' }, - { platform: 'win32', arch: 'x64', contents: [winContent] }, - ], - }, - ] - - const result = mapLegacyLinux(versions) - - assert.strictEqual(result.length, 1) - assert.strictEqual(result[0].serverVersion, '1.0.0') - assert.strictEqual(result[0].isDelisted, false) - assert.strictEqual(result[0].targets.length, 3) - assert.deepStrictEqual(result[0].targets[0], { platform: 'darwin', arch: 'arm64', contents: [darwinContent] }) - assert.deepStrictEqual(result[0].targets[1], { - platform: 'linux', - arch: 'x64', - contents: [legacyContent], - nodejs: '18', - }) - assert.deepStrictEqual(result[0].targets[2], { platform: 'win32', arch: 'x64', contents: [winContent] }) - }) - - it('returns version unchanged when no linuxglib2.28 target exists', () => { - const versions: CfnLspVersion[] = [ - { - serverVersion: '2.0.0', - isDelisted: true, - targets: [ - { platform: 'darwin', arch: 'arm64', contents: [darwinContent] }, - { platform: 'linux', arch: 'x64', contents: [linuxContent] }, - ], - }, - ] - - const result = mapLegacyLinux(versions) - - assert.strictEqual(result.length, 1) - assert.deepStrictEqual(result[0], versions[0]) - }) - - it('handles multiple versions with mixed legacy targets', () => { - const versions: CfnLspVersion[] = [ - { - serverVersion: '1.0.0', - isDelisted: false, - targets: [ - { platform: 'darwin', arch: 'arm64', contents: [] }, - { platform: 'linuxglib2.28', arch: 'x64', contents: [legacyContent] }, - ], - }, - { - serverVersion: '2.0.0', - isDelisted: false, - targets: [{ platform: 'darwin', arch: 'arm64', contents: [] }], - }, - ] - - const result = mapLegacyLinux(versions) - - assert.strictEqual(result.length, 2) - assert.strictEqual(result[0].serverVersion, '1.0.0') - assert.strictEqual(result[0].targets.length, 2) - assert.deepStrictEqual(result[0].targets[1], { platform: 'linux', arch: 'x64', contents: [legacyContent] }) - assert.deepStrictEqual(result[1], versions[1]) - }) - - it('handles empty versions array', () => { - assert.deepStrictEqual(mapLegacyLinux([]), []) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/relatedResources/relatedResourcesApi.test.ts b/packages/core/src/test/awsService/cloudformation/relatedResources/relatedResourcesApi.test.ts deleted file mode 100644 index 31b4b3b926..0000000000 --- a/packages/core/src/test/awsService/cloudformation/relatedResources/relatedResourcesApi.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { - getAuthoredResourceTypes, - getRelatedResourceTypes, - insertRelatedResources, -} from '../../../../awsService/cloudformation/relatedResources/relatedResourcesApi' -import { - GetAuthoredResourceTypesRequest, - GetRelatedResourceTypesRequest, - InsertRelatedResourcesRequest, -} from '../../../../awsService/cloudformation/relatedResources/relatedResourcesProtocol' - -describe('RelatedResourcesApi', function () { - let sandbox: sinon.SinonSandbox - let mockClient: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub(), - } - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('getAuthoredResourceTypes', function () { - it('should send request with template URI and return resource types', async function () { - const templateUri = 'file:///test/template.yaml' - const expectedTypes = ['AWS::S3::Bucket', 'AWS::Lambda::Function'] - - mockClient.sendRequest.resolves(expectedTypes) - - const result = await getAuthoredResourceTypes(mockClient, templateUri) - - assert.deepStrictEqual(result, expectedTypes) - assert.ok(mockClient.sendRequest.calledOnce) - assert.ok(mockClient.sendRequest.calledWith(GetAuthoredResourceTypesRequest, templateUri)) - }) - - it('should return empty array when no resources found', async function () { - const templateUri = 'file:///test/empty.yaml' - - mockClient.sendRequest.resolves([]) - - const result = await getAuthoredResourceTypes(mockClient, templateUri) - - assert.deepStrictEqual(result, []) - }) - }) - - describe('getRelatedResourceTypes', function () { - it('should send request with resource type and return related types', async function () { - const params = { parentResourceType: 'AWS::S3::Bucket' } - const expectedTypes = ['AWS::Lambda::Function', 'AWS::IAM::Role'] - - mockClient.sendRequest.resolves(expectedTypes) - - const result = await getRelatedResourceTypes(mockClient, params) - - assert.deepStrictEqual(result, expectedTypes) - assert.ok(mockClient.sendRequest.calledOnce) - assert.ok(mockClient.sendRequest.calledWith(GetRelatedResourceTypesRequest, params)) - }) - - it('should return empty array when no related types found', async function () { - const params = { parentResourceType: 'AWS::Custom::Resource' } - - mockClient.sendRequest.resolves([]) - - const result = await getRelatedResourceTypes(mockClient, params) - - assert.deepStrictEqual(result, []) - }) - }) - - describe('insertRelatedResources', function () { - it('should send request and return code action', async function () { - const params = { - templateUri: 'file:///test/template.yaml', - relatedResourceTypes: ['AWS::Lambda::Function'], - parentResourceType: 'AWS::S3::Bucket', - } - const expectedAction = { - title: 'Insert 1 related resources', - kind: 'refactor', - edit: { - changes: { - 'file:///test/template.yaml': [], - }, - }, - data: { - scrollToPosition: { line: 5, character: 0 }, - firstLogicalId: 'LambdaFunctionRelatedToS3Bucket', - }, - } - - mockClient.sendRequest.resolves(expectedAction) - - const result = await insertRelatedResources(mockClient, params) - - assert.deepStrictEqual(result, expectedAction) - assert.ok(mockClient.sendRequest.calledOnce) - assert.ok(mockClient.sendRequest.calledWith(InsertRelatedResourcesRequest, params)) - }) - - it('should handle multiple resource types', async function () { - const params = { - templateUri: 'file:///test/template.yaml', - relatedResourceTypes: ['AWS::Lambda::Function', 'AWS::IAM::Role'], - parentResourceType: 'AWS::S3::Bucket', - } - const expectedAction = { - title: 'Insert 2 related resources', - kind: 'refactor', - edit: { - changes: { - 'file:///test/template.yaml': [], - }, - }, - } - - mockClient.sendRequest.resolves(expectedAction) - - const result = await insertRelatedResources(mockClient, params) - - assert.strictEqual(result.title, 'Insert 2 related resources') - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/resources/resourcesManager.test.ts b/packages/core/src/test/awsService/cloudformation/resources/resourcesManager.test.ts deleted file mode 100644 index b1ac434c5f..0000000000 --- a/packages/core/src/test/awsService/cloudformation/resources/resourcesManager.test.ts +++ /dev/null @@ -1,279 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { ResourcesManager } from '../../../../awsService/cloudformation/resources/resourcesManager' -import { ResourceSelector } from '../../../../awsService/cloudformation/ui/resourceSelector' -import { ResourceStateResult } from '../../../../awsService/cloudformation/resources/resourceRequestTypes' -import { Range, SnippetString, TextEditor, window } from 'vscode' -import { getLogger } from '../../../../shared/logger' -import globals from '../../../../shared/extensionGlobals' - -describe('ResourcesManager - applyCompletionSnippet', () => { - let sandbox: sinon.SinonSandbox - let mockClient: any - let mockResourceSelector: ResourceSelector - let resourcesManager: ResourcesManager - let mockEditor: Partial - let windowStub: sinon.SinonStub - const baseResourceStateResult = { - successfulImports: new Map(), - failedImports: new Map(), - } - - const createResult = (overrides?: Partial): ResourceStateResult => ({ - ...baseResourceStateResult, - ...overrides, - }) - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub(), - } - mockResourceSelector = {} as ResourceSelector - - mockEditor = { - insertSnippet: sandbox.stub().resolves(true), - edit: sandbox.stub().resolves(true), - document: { - lineCount: 100, - lineAt: sandbox.stub().returns({ range: { end: { line: 99, character: 0 } } }), - } as any, - } - - windowStub = sandbox.stub(window, 'activeTextEditor').get(() => mockEditor) - - sandbox.stub(getLogger(), 'warn') - sandbox.stub(getLogger(), 'info') - sandbox.stub(getLogger(), 'error') - - resourcesManager = new ResourcesManager(mockClient, mockResourceSelector) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should insert snippet at server-provided position', async () => { - const result = createResult({ - completionItem: { - label: 'Import Resource', - textEdit: { - range: { - start: { line: 5, character: 10 }, - end: { line: 5, character: 10 }, - }, - newText: ' "MyBucket": {\n "Type": "AWS::S3::Bucket"\n }', - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).calledOnce) - const [snippetArg, rangeArg] = (mockEditor.insertSnippet as sinon.SinonStub).firstCall.args - - assert.ok(snippetArg instanceof SnippetString) - assert.strictEqual(snippetArg.value, result.completionItem!.textEdit!.newText) - - assert.ok(rangeArg instanceof Range) - assert.strictEqual(rangeArg.start.line, 5) - assert.strictEqual(rangeArg.start.character, 10) - assert.strictEqual(rangeArg.end.line, 5) - assert.strictEqual(rangeArg.end.character, 10) - }) - - it('should handle snippet with tabstops', async () => { - const result = createResult({ - completionItem: { - label: 'Clone Resource', - textEdit: { - range: { - start: { line: 10, character: 0 }, - end: { line: 10, character: 0 }, - }, - newText: '"BucketName": "${1:enter new identifier for MyBucket}"', - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).calledOnce) - const [snippetArg] = (mockEditor.insertSnippet as sinon.SinonStub).firstCall.args - assert.strictEqual(snippetArg.value, result.completionItem!.textEdit!.newText) - }) - - it('should not insert when completionItem is missing', async () => { - const result = createResult() - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).notCalled) - }) - - it('should not insert when textEdit is missing', async () => { - const result = createResult({ - completionItem: { - label: 'Test', - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).notCalled) - }) - - it('should not insert when no active editor', async () => { - windowStub.get(() => undefined) - - const result = createResult({ - completionItem: { - label: 'Test', - textEdit: { - range: { - start: { line: 0, character: 0 }, - end: { line: 0, character: 0 }, - }, - newText: 'test', - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).notCalled) - }) - - it('should handle different range positions', async () => { - const result = createResult({ - completionItem: { - label: 'Test', - textEdit: { - range: { - start: { line: 100, character: 50 }, - end: { line: 105, character: 20 }, - }, - newText: 'replacement text', - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - const [, rangeArg] = (mockEditor.insertSnippet as sinon.SinonStub).firstCall.args - assert.strictEqual(rangeArg.start.line, 100) - assert.strictEqual(rangeArg.start.character, 50) - assert.strictEqual(rangeArg.end.line, 105) - assert.strictEqual(rangeArg.end.character, 20) - }) - - it('should handle multi-line snippet text', async () => { - const multiLineText = `"MyResource": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": "\${1:enter new identifier}" - } -}` - - const result = createResult({ - completionItem: { - label: 'Test', - textEdit: { - range: { - start: { line: 20, character: 4 }, - end: { line: 20, character: 4 }, - }, - newText: multiLineText, - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).calledOnce) - const [snippetArg] = (mockEditor.insertSnippet as sinon.SinonStub).firstCall.args - assert.strictEqual(snippetArg.value, multiLineText) - }) - - it('should add newlines when target line does not exist', async () => { - const mockDocument = { - lineCount: 10, - lineAt: sinon.stub().returns({ range: { end: { line: 9, character: 20 } } }), - } - ;(mockEditor as any).document = mockDocument - ;(mockEditor as any).edit = sinon.stub().resolves(true) - - const result = createResult({ - completionItem: { - label: 'Test', - textEdit: { - range: { - start: { line: 15, character: 0 }, - end: { line: 15, character: 0 }, - }, - newText: 'test content', - }, - }, - }) - - await (resourcesManager as any).applyCompletionSnippet(result) - - // Should call edit to add newlines - assert.ok(((mockEditor as any).edit as sinon.SinonStub).calledOnce) - - // Should still call insertSnippet - assert.ok((mockEditor.insertSnippet as sinon.SinonStub).calledOnce) - }) -}) - -describe('ResourcesManager - removeResourceType', () => { - let sandbox: sinon.SinonSandbox - let mockClient: any - let mockResourceSelector: ResourceSelector - let resourcesManager: ResourcesManager - let globalStateStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { sendRequest: sandbox.stub() } - mockResourceSelector = {} as ResourceSelector - globalStateStub = sandbox.stub(globals.globalState, 'update').resolves() - sandbox.stub(globals.globalState, 'tryGet').returns(['AWS::S3::Bucket', 'AWS::Lambda::Function']) - resourcesManager = new ResourcesManager(mockClient, mockResourceSelector) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should remove resource type from selected types', async () => { - await resourcesManager.removeResourceType('AWS::S3::Bucket') - - assert.ok(globalStateStub.calledOnce) - const [key, updatedTypes] = globalStateStub.firstCall.args - assert.strictEqual(key, 'aws.cloudformation.selectedResourceTypes') - assert.deepStrictEqual(updatedTypes, ['AWS::Lambda::Function']) - }) - - it('should notify listeners after removing resource type', async () => { - const listener = sandbox.stub() - resourcesManager.addListener(listener) - - await resourcesManager.removeResourceType('AWS::Lambda::Function') - - assert.ok(listener.calledOnce) - }) - - it('should handle removing non-existent resource type', async () => { - await resourcesManager.removeResourceType('AWS::DynamoDB::Table') - - assert.ok(globalStateStub.calledOnce) - const [, updatedTypes] = globalStateStub.firstCall.args - assert.deepStrictEqual(updatedTypes, ['AWS::S3::Bucket', 'AWS::Lambda::Function']) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/resources/sample-template.yaml b/packages/core/src/test/awsService/cloudformation/resources/sample-template.yaml deleted file mode 100644 index e354cc7a3a..0000000000 --- a/packages/core/src/test/awsService/cloudformation/resources/sample-template.yaml +++ /dev/null @@ -1,54 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Description: 'Sample CloudFormation template for testing' - -Parameters: - InstanceType: - Type: String - Default: t2.micro - Description: EC2 instance type - -Resources: - MyInstance: - Type: AWS::EC2::Instance - Properties: - ImageId: ami-12345678 - InstanceType: !Ref InstanceType - SecurityGroups: - - !Ref MySecurityGroup - UserData: - Fn::Base64: !Sub | - #!/bin/bash - echo "Hello World" - - MySecurityGroup: - Type: 'AWS::EC2::SecurityGroup' - Properties: - GroupDescription: Security group for testing - SecurityGroupIngress: - - IpProtocol: tcp - FromPort: 80 - ToPort: 80 - CidrIp: 0.0.0.0/0 - - MyBucket: - Type: AWS::S3::Bucket - Properties: - BucketName: !Sub '${AWS::StackName}-test-bucket' - VersioningConfiguration: - Status: Enabled - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - -Outputs: - InstanceId: - Description: Instance ID - Value: !Ref MyInstance - Export: - Name: !Sub '${AWS::StackName}-InstanceId' - - BucketName: - Description: S3 Bucket Name - Value: !Ref MyBucket diff --git a/packages/core/src/test/awsService/cloudformation/resources/template-with-json.yaml b/packages/core/src/test/awsService/cloudformation/resources/template-with-json.yaml deleted file mode 100644 index 92975737ad..0000000000 --- a/packages/core/src/test/awsService/cloudformation/resources/template-with-json.yaml +++ /dev/null @@ -1,77 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Description: 'CloudFormation template with embedded JSON' - -Resources: - MyRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: | - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - } - Policies: - - PolicyName: MyPolicy - PolicyDocument: > - { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "*" - } - ] - } - - MyBucket: - Type: AWS::S3::Bucket - Properties: - NotificationConfiguration: - CloudWatchConfigurations: - - Event: s3:ObjectCreated:* - CloudWatchConfiguration: - LogGroupName: !Ref MyLogGroup - FilterPattern: | - { - "eventSource": ["aws:s3"], - "eventName": { - "prefix": "ObjectCreated" - } - } - - MyFunction: - Type: AWS::Lambda::Function - Properties: - Code: - ZipFile: | - import json - def lambda_handler(event, context): - response = { - "statusCode": 200, - "body": json.dumps({ - "message": "Hello from Lambda!", - "event": event - }) - } - return response - Environment: - Variables: - CONFIG: '{"debug": true, "timeout": 30}' - - MyLogGroup: - Type: AWS::Logs::LogGroup - Properties: - LogGroupName: /aws/lambda/my-function diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts deleted file mode 100644 index 9dec3c1e3d..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SinonSandbox, SinonStub, createSandbox } from 'sinon' -import { commands } from 'vscode' -import { ChangeSetDeletion } from '../../../../../awsService/cloudformation/stacks/actions/changeSetDeletionWorkflow' -import { - StackActionPhase, - StackActionState, -} from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import { commandKey } from '../../../../../awsService/cloudformation/utils' -import { globals } from '../../../../../shared' - -describe('ChangeSetDeletion', function () { - let sandbox: SinonSandbox - - beforeEach(function () { - sandbox = createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('delete', function () { - let mockClient: any - let executeCommandStub: SinonStub - let getChangeSetDeletionStatusStub: SinonStub - let describeChangeSetDeletionStatusStub: SinonStub - - beforeEach(function () { - mockClient = { sendRequest: sandbox.stub().resolves({}) } - executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves() - - const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi') - getChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'getChangeSetDeletionStatus') - describeChangeSetDeletionStatusStub = sandbox.stub(stackActionApi, 'describeChangeSetDeletionStatus') - sandbox.stub(stackActionApi, 'deleteChangeSet').resolves() - - sandbox.stub(globals.clock, 'setInterval').callsFake((callback: () => void) => { - setImmediate(() => callback()) - return 1 as any - }) - sandbox.stub(globals.clock, 'clearInterval') - }) - - it('should call refresh command after successful deletion', async function () { - getChangeSetDeletionStatusStub.resolves({ - phase: StackActionPhase.DELETION_COMPLETE, - state: StackActionState.SUCCESSFUL, - }) - - const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) - await deletion.delete() - await new Promise((resolve) => setImmediate(resolve)) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh command after failed deletion', async function () { - getChangeSetDeletionStatusStub.resolves({ phase: StackActionPhase.DELETION_FAILED }) - describeChangeSetDeletionStatusStub.resolves({ FailureReason: 'Test failure' }) - - const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) - await deletion.delete() - await new Promise((resolve) => setImmediate(resolve)) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should not call refresh command when polling encounters error', async function () { - getChangeSetDeletionStatusStub.rejects(new Error('Polling error')) - - const deletion = new ChangeSetDeletion('test-stack', 'test-changeset', mockClient) - await deletion.delete() - await new Promise((resolve) => setImmediate(resolve)) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/deployment.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/deployment.test.ts deleted file mode 100644 index 22b82d14b8..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/deployment.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SinonSandbox, SinonStub, createSandbox } from 'sinon' -import { commands } from 'vscode' -import { Deployment } from '../../../../../awsService/cloudformation/stacks/actions/deploymentWorkflow' -import { - StackActionPhase, - StackActionState, -} from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import { commandKey } from '../../../../../awsService/cloudformation/utils' - -describe('Deployment', function () { - let sandbox: SinonSandbox - - beforeEach(function () { - sandbox = createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('pollForProgress', function () { - let mockClient: any - let mockCoordinator: any - let executeCommandStub: SinonStub - let getDeploymentStatusStub: SinonStub - let describeDeploymentStatusStub: SinonStub - let clock: any - - beforeEach(function () { - mockClient = { sendRequest: sandbox.stub().resolves({}) } - mockCoordinator = { setStack: sandbox.stub().resolves() } - executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves() - - const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi') - getDeploymentStatusStub = sandbox.stub(stackActionApi, 'getDeploymentStatus') - describeDeploymentStatusStub = sandbox.stub(stackActionApi, 'describeDeploymentStatus') - sandbox.stub(stackActionApi, 'deploy').resolves() - clock = sandbox.useFakeTimers() - }) - - it('should call refresh command after successful deployment', async function () { - getDeploymentStatusStub.resolves({ - phase: StackActionPhase.DEPLOYMENT_COMPLETE, - state: StackActionState.SUCCESSFUL, - }) - - const deployment = new Deployment('test-stack', 'test-changeset', mockClient, mockCoordinator) - await deployment.deploy() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh command after failed deployment', async function () { - getDeploymentStatusStub.resolves({ phase: StackActionPhase.DEPLOYMENT_FAILED }) - describeDeploymentStatusStub.resolves({ FailureReason: 'Test failure' }) - - const deployment = new Deployment('test-stack', 'test-changeset', mockClient, mockCoordinator) - await deployment.deploy() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh command when polling encounters error', async function () { - getDeploymentStatusStub.rejects(new Error('Polling error')) - - const deployment = new Deployment('test-stack', 'test-changeset', mockClient, mockCoordinator) - await deployment.deploy() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/stackActionInputValidation.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/stackActionInputValidation.test.ts deleted file mode 100644 index ed7f4a1778..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/stackActionInputValidation.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { validateParameterValue } from '../../../../../awsService/cloudformation/stacks/actions/stackActionInputValidation' -import { TemplateParameter } from '../../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' - -describe('validateParameterValue', function () { - describe('String parameters', function () { - it('should pass valid string with AllowedValues', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedValues: ['value1', 'value2'], - } - assert.strictEqual(validateParameterValue('value1', param), undefined) - }) - - it('should fail invalid string with AllowedValues', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedValues: ['value1', 'value2'], - } - assert.strictEqual(validateParameterValue('invalid', param), 'Value must be one of: value1, value2') - }) - - it('should pass valid pattern', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedPattern: '^[0-9]+$', - } - assert.strictEqual(validateParameterValue('123', param), undefined) - }) - - it('should fail invalid pattern', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedPattern: '^[0-9]+$', - } - assert.strictEqual(validateParameterValue('abc', param), 'Value must match pattern: ^[0-9]+$') - }) - - it('should handle boolean string values', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedValues: ['true', 'false'], - } - assert.strictEqual(validateParameterValue('true', param), undefined) - assert.strictEqual(validateParameterValue('false', param), undefined) - assert.strictEqual(validateParameterValue('invalid', param), 'Value must be one of: true, false') - }) - - it('should handle numeric string values', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - AllowedValues: ['1', '2'], - } - assert.strictEqual(validateParameterValue('1', param), undefined) - assert.strictEqual(validateParameterValue('2', param), undefined) - assert.strictEqual(validateParameterValue('3', param), 'Value must be one of: 1, 2') - }) - - it('should handle empty string values', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'String', - } - - assert.strictEqual(validateParameterValue('', param), undefined) - }) - }) - - describe('Number parameters', function () { - it('should pass valid number', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'Number', - } - assert.strictEqual(validateParameterValue('42', param), undefined) - }) - - it('should fail invalid number', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'Number', - } - assert.strictEqual(validateParameterValue('abc', param), 'Value must be a number') - }) - - it('should validate MinValue', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'Number', - MinValue: 10, - } - assert.strictEqual(validateParameterValue('5', param), 'Value must be at least 10') - assert.strictEqual(validateParameterValue('15', param), undefined) - }) - }) - - describe('CommaDelimitedList parameters', function () { - it('should pass valid comma-delimited list with AllowedValues', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'CommaDelimitedList', - AllowedValues: ['80', '443', '8080'], - } - assert.strictEqual(validateParameterValue('80,443', param), undefined) - }) - - it('should fail invalid items in comma-delimited list', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'CommaDelimitedList', - AllowedValues: ['80', '443', '8080'], - } - assert.strictEqual( - validateParameterValue('80,9000', param), - 'Invalid values: 9000. Must be one of: 80, 443, 8080' - ) - }) - - it('should validate pattern for each item', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'CommaDelimitedList', - AllowedPattern: '^[0-9]+$', - } - assert.strictEqual(validateParameterValue('80,443', param), undefined) - assert.strictEqual(validateParameterValue('80,abc', param), 'Values must match pattern: ^[0-9]+$') - }) - - it('should handle whitespace in comma-delimited list', function () { - const param: TemplateParameter = { - name: 'TestParam', - Type: 'CommaDelimitedList', - AllowedValues: ['80', '443'], - } - assert.strictEqual(validateParameterValue('80, 443', param), undefined) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/validation.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/validation.test.ts deleted file mode 100644 index e8d317d658..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/validation.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SinonSandbox, SinonStub, createSandbox } from 'sinon' -import { commands } from 'vscode' -import { - getLastValidation, - setLastValidation, - Validation, -} from '../../../../../awsService/cloudformation/stacks/actions/validationWorkflow' -import { commandKey } from '../../../../../awsService/cloudformation/utils' - -describe('Validation', function () { - let sandbox: SinonSandbox - - beforeEach(function () { - sandbox = createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('last validation tracking', function () { - it('should get and set last validation', function () { - assert.strictEqual(getLastValidation(), undefined) - - const validation: any = { uri: 'test.yaml', stackName: 'test-stack' } - setLastValidation(validation) - assert.strictEqual(getLastValidation(), validation) - - setLastValidation(undefined) - assert.strictEqual(getLastValidation(), undefined) - }) - }) - - describe('refresh command', function () { - let mockClient: any - let mockDiffProvider: any - let executeCommandStub: SinonStub - let validateStub: any - let getValidationStatusStub: any - let describeValidationStatusStub: any - let clock: any - - beforeEach(function () { - mockClient = { sendRequest: sandbox.stub().resolves({ changeSetName: 'test-changeset' }) } - mockDiffProvider = { updateData: sandbox.stub() } - executeCommandStub = sandbox.stub(commands, 'executeCommand').resolves() - - const stackActionApi = require('../../../../../awsService/cloudformation/stacks/actions/stackActionApi') - validateStub = sandbox.stub(stackActionApi, 'validate').resolves({ changeSetName: 'test-changeset' }) - getValidationStatusStub = sandbox.stub(stackActionApi, 'getValidationStatus') - describeValidationStatusStub = sandbox.stub(stackActionApi, 'describeValidationStatus') - clock = sandbox.useFakeTimers() - }) - - it('should call refresh after validation starts', async function () { - const validation = new Validation('file:///test.yaml', 'test-stack', mockClient, mockDiffProvider) - await validation.validate() - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should not call refresh when validation API fails', async function () { - validateStub.rejects(new Error('Validation API error')) - - const validation = new Validation('file:///test.yaml', 'test-stack', mockClient, mockDiffProvider) - await validation.validate() - - assert.ok(!executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh after successful validation', async function () { - getValidationStatusStub.resolves({ - phase: 'VALIDATION_COMPLETE', - state: 'SUCCESSFUL', - changes: [], - }) - describeValidationStatusStub.resolves({ ValidationDetails: [] }) - - const validation = new Validation('file:///test.yaml', 'test-stack', mockClient, mockDiffProvider) - await validation.validate() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh after failed validation', async function () { - getValidationStatusStub.resolves({ phase: 'VALIDATION_FAILED' }) - describeValidationStatusStub.resolves({ FailureReason: 'Test failure' }) - - const validation = new Validation('file:///test.yaml', 'test-stack', mockClient, mockDiffProvider) - await validation.validate() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - - it('should call refresh when polling encounters error', async function () { - getValidationStatusStub.rejects(new Error('Polling error')) - - const validation = new Validation('file:///test.yaml', 'test-stack', mockClient, mockDiffProvider) - await validation.validate() - await clock.tickAsync(1000) - - assert.ok(executeCommandStub.calledWith(commandKey('stacks.refresh'))) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/actions/validationEnhanced.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/actions/validationEnhanced.test.ts deleted file mode 100644 index b059ffa760..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/actions/validationEnhanced.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' - -describe('ValidationEnhanced', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('enhanced validation', function () { - it('should perform enhanced validation correctly', function () { - // Basic test structure - implementation depends on actual ValidationEnhanced module - assert.ok(true, 'ValidationEnhanced test placeholder') - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/stacks/stacksManager.test.ts b/packages/core/src/test/awsService/cloudformation/stacks/stacksManager.test.ts deleted file mode 100644 index 6140cdc453..0000000000 --- a/packages/core/src/test/awsService/cloudformation/stacks/stacksManager.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { StacksManager } from '../../../../awsService/cloudformation/stacks/stacksManager' - -describe('StacksManager', () => { - let sandbox: sinon.SinonSandbox - let manager: StacksManager - let mockClient: any - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub().resolves({ - stacks: [ - { StackName: 'stack-1', StackStatus: 'CREATE_COMPLETE' }, - { StackName: 'stack-2', StackStatus: 'UPDATE_IN_PROGRESS' }, - ], - nextToken: undefined, - }), - } - manager = new StacksManager(mockClient) - }) - - afterEach(() => { - manager.dispose() - sandbox.restore() - }) - - describe('updateStackStatus', () => { - beforeEach(async () => { - await new Promise((resolve) => { - manager.addListener(() => resolve()) - manager.reload() - }) - }) - - it('should update status of existing stack', () => { - manager.updateStackStatus('stack-1', 'UPDATE_COMPLETE') - - const stacks = manager.get() - const updatedStack = stacks.find((s) => s.StackName === 'stack-1') - assert.strictEqual(updatedStack?.StackStatus, 'UPDATE_COMPLETE') - }) - - it('should not affect other stacks', () => { - manager.updateStackStatus('stack-1', 'UPDATE_COMPLETE') - - const stacks = manager.get() - const otherStack = stacks.find((s) => s.StackName === 'stack-2') - assert.strictEqual(otherStack?.StackStatus, 'UPDATE_IN_PROGRESS') - }) - - it('should notify listeners when status updated', () => { - let listenerCalled = false - manager.addListener(() => { - listenerCalled = true - }) - - manager.updateStackStatus('stack-1', 'UPDATE_COMPLETE') - - assert.strictEqual(listenerCalled, true) - }) - - it('should do nothing if stack not found', () => { - const stacksBefore = manager.get() - manager.updateStackStatus('non-existent-stack', 'CREATE_COMPLETE') - const stacksAfter = manager.get() - - assert.deepStrictEqual(stacksBefore, stacksAfter) - }) - }) - - describe('lazy loading', () => { - it('should not load stacks on construction', () => { - assert.strictEqual(mockClient.sendRequest.called, false) - }) - - it('should return empty array when not loaded', () => { - const stacks = manager.get() - assert.deepStrictEqual(stacks, []) - }) - - it('should report not loaded initially', () => { - assert.strictEqual(manager.isLoaded(), false) - }) - - it('should load stacks on ensureLoaded', async () => { - await manager.ensureLoaded() - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - }) - - it('should not reload on subsequent ensureLoaded calls', async () => { - await manager.ensureLoaded() - await manager.ensureLoaded() - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - }) - - it('should report loaded after ensureLoaded', async () => { - await manager.ensureLoaded() - assert.strictEqual(manager.isLoaded(), true) - }) - - it('should return stacks after ensureLoaded', async () => { - await manager.ensureLoaded() - const stacks = manager.get() - assert.strictEqual(stacks.length, 2) - assert.strictEqual(stacks[0].StackName, 'stack-1') - }) - }) - - describe('clear', () => { - beforeEach(async () => { - await manager.ensureLoaded() - }) - - it('should clear stacks', () => { - manager.clear() - const stacks = manager.get() - assert.deepStrictEqual(stacks, []) - }) - - it('should reset loaded state', () => { - manager.clear() - assert.strictEqual(manager.isLoaded(), false) - }) - - it('should clear nextToken', () => { - manager.clear() - assert.strictEqual(manager.hasMore(), false) - }) - - it('should notify listeners', () => { - let listenerCalled = false - manager.addListener(() => { - listenerCalled = true - }) - manager.clear() - assert.strictEqual(listenerCalled, true) - }) - - it('should allow reload after clear', async () => { - manager.clear() - mockClient.sendRequest.resetHistory() - await manager.ensureLoaded() - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/telemetryOptIn.test.ts b/packages/core/src/test/awsService/cloudformation/telemetryOptIn.test.ts deleted file mode 100644 index d28fde1dbf..0000000000 --- a/packages/core/src/test/awsService/cloudformation/telemetryOptIn.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { ExtensionContext } from 'vscode' -import { handleTelemetryOptIn } from '../../../awsService/cloudformation/telemetryOptIn' -import { CloudFormationTelemetrySettings } from '../../../awsService/cloudformation/extensionConfig' -import { commandKey } from '../../../awsService/cloudformation/utils' -import globals from '../../../shared/extensionGlobals' - -describe('telemetryOptIn', function () { - let mockContext: ExtensionContext - let mockSettings: CloudFormationTelemetrySettings - let globalState: Map - - beforeEach(function () { - globalState = new Map() - - mockContext = { - globalState: { - get: (key: string, defaultValue?: any) => globalState.get(key) ?? defaultValue, - update: async (key: string, value: any) => { - globalState.set(key, value) - }, - }, - } as any - - mockSettings = { - get: sinon.stub().returns(false), - update: sinon.stub().resolves(), - } as any - }) - - describe('promptTelemetryOptIn - automation mode', function () { - it('should return current setting without prompting in automation mode', async function () { - sinon.stub(require('../../../shared/vscode/env'), 'isAutomation').returns(true) - ;(mockSettings.get as sinon.SinonStub).returns(true) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, true) - }) - }) - - describe('promptTelemetryOptIn - user has responded', function () { - it('should return current setting if user has permanently responded', async function () { - globalState.set(commandKey('telemetry.hasResponded'), true) - ;(mockSettings.get as sinon.SinonStub).returns(true) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, true) - }) - }) - - describe('promptTelemetryOptIn - prompt timing', function () { - it('should not prompt if less than 30 days since last prompt', async function () { - const now = globals.clock.Date.now() - const twentyDaysAgo = now - 20 * 24 * 60 * 60 * 1000 - globalState.set(commandKey('telemetry.lastPromptDate'), twentyDaysAgo) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, false) - }) - }) - - describe('promptTelemetryOptIn - unpersisted response', function () { - it('should persist unpersisted Allow response', async function () { - globalState.set(commandKey('telemetry.unpersistedResponse'), 'Yes, Allow') - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, true) - assert.ok((mockSettings.update as sinon.SinonStub).calledWith('enabled', true)) - assert.strictEqual(globalState.get(commandKey('telemetry.unpersistedResponse')), undefined) - }) - - it('should persist unpersisted Never response', async function () { - globalState.set(commandKey('telemetry.unpersistedResponse'), 'Never') - ;(mockSettings.update as sinon.SinonStub).resolves(true) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, false) - assert.ok((mockSettings.update as sinon.SinonStub).calledWith('enabled', false)) - assert.strictEqual(globalState.get(commandKey('telemetry.unpersistedResponse')), undefined) - }) - - it('should persist unpersisted Later response', async function () { - const lastPromptDate = globals.clock.Date.now() - 1000 - globalState.set(commandKey('telemetry.unpersistedResponse'), 'Not Now') - globalState.set(commandKey('telemetry.lastPromptDate'), lastPromptDate) - ;(mockSettings.update as sinon.SinonStub).resolves(true) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, false) - assert.ok((mockSettings.update as sinon.SinonStub).calledWith('enabled', false)) - assert.strictEqual(globalState.get(commandKey('telemetry.lastPromptDate')), lastPromptDate) - assert.strictEqual(globalState.get(commandKey('telemetry.unpersistedResponse')), undefined) - }) - - it('should clear all state if setting save fails', async function () { - globalState.set(commandKey('telemetry.unpersistedResponse'), 'Yes, Allow') - globalState.set(commandKey('telemetry.hasResponded'), true) - globalState.set(commandKey('telemetry.lastPromptDate'), globals.clock.Date.now()) - ;(mockSettings.update as sinon.SinonStub).resolves(false) - - const result = await handleTelemetryOptIn(mockContext, mockSettings) - - assert.strictEqual(result, true) - assert.strictEqual(globalState.get(commandKey('telemetry.unpersistedResponse')), undefined) - assert.strictEqual(globalState.get(commandKey('telemetry.hasResponded')), undefined) - assert.strictEqual(globalState.get(commandKey('telemetry.lastPromptDate')), undefined) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/diffViewHelper.test.ts b/packages/core/src/test/awsService/cloudformation/ui/diffViewHelper.test.ts deleted file mode 100644 index 0fa0da96df..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/diffViewHelper.test.ts +++ /dev/null @@ -1,578 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import * as path from 'path' -import * as os from 'os' -import { DiffViewHelper } from '../../../../awsService/cloudformation/ui/diffViewHelper' -import { StackChange } from '../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import { fs } from '../../../../shared/fs/fs' - -describe('DiffViewHelper', function () { - let sandbox: sinon.SinonSandbox - let writeFileStub: sinon.SinonStub - let executeCommandStub: sinon.SinonStub - let openTextDocumentStub: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - writeFileStub = sandbox.stub(fs, 'writeFile') - executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand') - openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument') - }) - - afterEach(function () { - sandbox.restore() - }) - - async function testDiffGeneration(stackName: string, changes: StackChange[]) { - await DiffViewHelper.openDiff(stackName, changes) - - const tmpDir = os.tmpdir() - const beforePath = path.join(tmpDir, `${stackName}-before.json`) - const afterPath = path.join(tmpDir, `${stackName}-after.json`) - - return { beforePath, afterPath } - } - - function assertFileCallsAndParseData() { - assert.ok(writeFileStub.calledTwice) - const beforeCall = writeFileStub.getCall(0) - const afterCall = writeFileStub.getCall(1) - - const beforeData = JSON.parse(beforeCall.args[1]) - const afterData = JSON.parse(afterCall.args[1]) - - return { beforeData, afterData } - } - - describe('openDiff', function () { - it('should create diff files and open diff view for Add action', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - logicalResourceId: 'TestResource', - afterContext: '{"Type": "AWS::S3::Bucket", "Properties": {"BucketName": "new-bucket"}}', - }, - }, - ] - - const { beforePath, afterPath } = await testDiffGeneration(stackName, changes) - - assert.ok(writeFileStub.calledTwice) - assert.ok(writeFileStub.calledWith(beforePath, '{}')) - assert.ok(writeFileStub.calledWith(afterPath, sinon.match.string)) - assert.ok( - executeCommandStub.calledWith( - 'vscode.diff', - sinon.match.any, - sinon.match.any, - `${stackName}: Before ↔ After` - ) - ) - }) - - it('should create diff files and open diff view for Remove action', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Remove', - logicalResourceId: 'TestResource', - beforeContext: '{"Type": "AWS::S3::Bucket", "Properties": {"BucketName": "old-bucket"}}', - }, - }, - ] - - const { beforePath, afterPath } = await testDiffGeneration(stackName, changes) - - assert.ok(writeFileStub.calledTwice) - assert.ok(writeFileStub.calledWith(beforePath, sinon.match.string)) - assert.ok(writeFileStub.calledWith(afterPath, '{}')) - assert.ok( - executeCommandStub.calledWith( - 'vscode.diff', - sinon.match.any, - sinon.match.any, - `${stackName}: Before ↔ After` - ) - ) - }) - - it('should create diff files for Modify action with beforeContext and afterContext', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'TestResource', - beforeContext: '{"Type": "AWS::S3::Bucket", "Properties": {"BucketName": "old-bucket"}}', - afterContext: '{"Type": "AWS::S3::Bucket", "Properties": {"BucketName": "new-bucket"}}', - }, - }, - ] - - await testDiffGeneration(stackName, changes) - - const { beforeData, afterData } = assertFileCallsAndParseData() - - assert.ok(beforeData.TestResource) - assert.ok(afterData.TestResource) - }) - - it('should handle Modify action with details when no context provided', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'ModifiedResource', - details: [ - { - Target: { - Name: 'BucketName', - BeforeValue: 'old-bucket', - AfterValue: 'new-bucket', - }, - }, - ], - }, - }, - ] - - await testDiffGeneration(stackName, changes) - - const { beforeData, afterData } = assertFileCallsAndParseData() - - assert.strictEqual(beforeData.ModifiedResource.BucketName, 'old-bucket') - assert.strictEqual(afterData.ModifiedResource.BucketName, 'new-bucket') - }) - - it('should handle invalid JSON in context gracefully', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'InvalidResource', - beforeContext: 'invalid-json', - afterContext: 'also-invalid-json', - }, - }, - ] - - await DiffViewHelper.openDiff(stackName, changes) - - const { beforeData, afterData } = assertFileCallsAndParseData() - - assert.deepStrictEqual(beforeData.InvalidResource, {}) - assert.deepStrictEqual(afterData.InvalidResource, {}) - }) - - it('should skip changes without logicalResourceId', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - // Missing logicalResourceId - }, - }, - ] - - await DiffViewHelper.openDiff(stackName, changes) - - assert.ok(writeFileStub.calledTwice) - assert.ok(writeFileStub.calledWith(sinon.match.any, '{}')) - }) - - it('should open diff with selection when resourceId is provided', async function () { - const stackName = 'test-stack' - const resourceId = 'TargetResource' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - logicalResourceId: resourceId, - afterContext: '{"Type": "AWS::S3::Bucket"}', - }, - }, - ] - - const mockDocument = { - getText: () => `{\n "${resourceId}": {\n "Type": "AWS::S3::Bucket"\n }\n}`, - } - openTextDocumentStub.resolves(mockDocument) - - await DiffViewHelper.openDiff(stackName, changes, resourceId) - - assert.ok(executeCommandStub.calledTwice) - const secondCall = executeCommandStub.getCall(1) - assert.ok(secondCall.args[4]?.selection) - }) - - it('should handle resourceId not found in document', async function () { - const stackName = 'test-stack' - const resourceId = 'NonExistentResource' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - logicalResourceId: 'DifferentResource', - afterContext: '{"Type": "AWS::S3::Bucket"}', - }, - }, - ] - - const mockDocument = { - getText: () => '{\n "DifferentResource": {\n "Type": "AWS::S3::Bucket"\n }\n}', - } - openTextDocumentStub.resolves(mockDocument) - - await DiffViewHelper.openDiff(stackName, changes, resourceId) - - // Should only call diff once (without selection) - assert.ok(executeCommandStub.calledOnce) - }) - - it('should handle details with missing BeforeValue/AfterValue', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'ModifiedResource', - details: [ - { - Target: { - Name: 'Property1', - // Missing BeforeValue and AfterValue - }, - }, - ], - }, - }, - ] - - await testDiffGeneration(stackName, changes) - - const { beforeData, afterData } = assertFileCallsAndParseData() - - assert.strictEqual(beforeData.ModifiedResource.Property1, '') - assert.strictEqual(afterData.ModifiedResource.Property1, '') - }) - - it('should handle empty changes array', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [] - - await DiffViewHelper.openDiff(stackName, changes) - - assert.ok(writeFileStub.calledTwice) - assert.ok(writeFileStub.calledWith(sinon.match.any, '{}')) - assert.ok(executeCommandStub.calledOnce) - }) - }) - - describe('drift decorations', function () { - let createTextEditorDecorationTypeStub: sinon.SinonStub - let setDecorationsStub: sinon.SinonStub - let clock: sinon.SinonFakeTimers - - beforeEach(function () { - createTextEditorDecorationTypeStub = sandbox.stub(vscode.window, 'createTextEditorDecorationType') - setDecorationsStub = sandbox.stub() - clock = sandbox.useFakeTimers() - }) - - function setupMockEditor(stackName: string, documentText: string) { - const tmpDir = os.tmpdir() - const beforePath = path.join(tmpDir, `${stackName}-before.json`) - const beforeUri = vscode.Uri.file(beforePath).toString() - - const mockEditor = { - document: { - uri: { toString: () => beforeUri }, - getText: () => documentText, - }, - setDecorations: setDecorationsStub, - } - - sandbox.stub(vscode.window, 'visibleTextEditors').get(() => [mockEditor]) - } - - async function runDriftTest(stackName: string, changes: StackChange[]) { - await DiffViewHelper.openDiff(stackName, changes) - clock.tick(500) - } - - function assertDecorationCount(expectedCount: number) { - assert.ok(setDecorationsStub.called) - const decorations = setDecorationsStub.getCall(0).args[1] - assert.strictEqual(decorations.length, expectedCount) - return decorations - } - - function createDriftChange( - logicalResourceId: string, - beforeContext: string, - afterContext: string, - details: any[] - ): StackChange { - return { - resourceChange: { - action: 'Modify', - logicalResourceId, - beforeContext, - afterContext, - details, - }, - } - } - - function createDetailTarget( - name: string, - path: string, - beforeValue: string, - afterValue: string, - drift?: { PreviousValue: string; ActualValue: string } - ) { - return { - Target: { - Name: name, - Path: path, - BeforeValue: beforeValue, - AfterValue: afterValue, - ...(drift && { LiveResourceDrift: drift }), - }, - } - } - - it('should add drift decoration when LiveResourceDrift is present', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - createDriftChange( - 'MyQueue', - '{"Properties":{"DelaySeconds":"5"}}', - '{"Properties":{"DelaySeconds":"1"}}', - [ - createDetailTarget('DelaySeconds', '/Properties/DelaySeconds', '5', '1', { - PreviousValue: '1', - ActualValue: '5', - }), - ] - ), - ] - - setupMockEditor( - stackName, - '{\n "MyQueue": {\n "Properties": {\n "DelaySeconds": "5"\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - assert.ok(createTextEditorDecorationTypeStub.called) - const decorations = assertDecorationCount(1) - assert.ok(decorations[0].hoverMessage.includes('Resource Drift Detected')) - assert.ok(decorations[0].hoverMessage.includes('MyQueue')) - }) - - it('should not add decoration when LiveResourceDrift is not present', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - createDriftChange( - 'MyQueue', - '{"Properties":{"DelaySeconds":"5"}}', - '{"Properties":{"DelaySeconds":"1"}}', - [createDetailTarget('DelaySeconds', '/Properties/DelaySeconds', '5', '1')] - ), - ] - - setupMockEditor( - stackName, - '{\n "MyQueue": {\n "Properties": {\n "DelaySeconds": "5"\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - assertDecorationCount(0) - }) - - it('should handle nested property paths correctly', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - createDriftChange( - 'MyResource', - '{"Properties":{"Config":{"Setting":"old"}}}', - '{"Properties":{"Config":{"Setting":"new"}}}', - [ - createDetailTarget('Setting', '/Properties/Config/Setting', 'old', 'new', { - PreviousValue: 'new', - ActualValue: 'old', - }), - ] - ), - ] - - setupMockEditor( - stackName, - '{\n "MyResource": {\n "Properties": {\n "Config": {\n "Setting": "old"\n }\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - const decorations = assertDecorationCount(1) - assert.ok(decorations[0].hoverMessage.includes('/Properties/Config/Setting')) - }) - - it('should handle multiple drift decorations for different properties', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - createDriftChange( - 'MyQueue', - '{"Properties":{"DelaySeconds":"5","MessageRetentionPeriod":"100"}}', - '{"Properties":{"DelaySeconds":"1","MessageRetentionPeriod":"200"}}', - [ - createDetailTarget('DelaySeconds', '/Properties/DelaySeconds', '5', '1', { - PreviousValue: '1', - ActualValue: '5', - }), - createDetailTarget( - 'MessageRetentionPeriod', - '/Properties/MessageRetentionPeriod', - '100', - '200', - { PreviousValue: '100', ActualValue: '150' } - ), - ] - ), - ] - - setupMockEditor( - stackName, - '{\n "MyQueue": {\n "Properties": {\n "DelaySeconds": "5",\n "MessageRetentionPeriod": "100"\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - assertDecorationCount(2) - }) - - it('should add drift decoration for DELETED resources', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - logicalResourceId: 'DeletedResource', - resourceDriftStatus: 'DELETED', - }, - }, - ] - - setupMockEditor(stackName, '{\n "DeletedResource": {}\n}') - await runDriftTest(stackName, changes) - - const decorations = assertDecorationCount(1) - assert.ok(decorations[0].hoverMessage.includes('Resource Deleted')) - assert.ok(decorations[0].hoverMessage.includes('deleted sometime after the previous deployment')) - }) - - it('should handle array indices in property paths', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - createDriftChange( - 'MyRole', - '{"Properties":{"Policies":[{"PolicyDocument":"old"}]}}', - '{"Properties":{"Policies":[{"PolicyDocument":"new"}]}}', - [ - createDetailTarget('PolicyDocument', '/Properties/Policies/0/PolicyDocument', 'old', 'new', { - PreviousValue: 'old', - ActualValue: 'drifted', - }), - ] - ), - ] - - setupMockEditor( - stackName, - '{\n "MyRole": {\n "Properties": {\n "Policies": [\n {\n "PolicyDocument": "old"\n }\n ]\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - const decorations = assertDecorationCount(1) - assert.ok(decorations[0].hoverMessage.includes('/Properties/Policies/0/PolicyDocument')) - }) - - it('should not add decoration when property is not in afterContext', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'MyQueue', - beforeContext: '{"Properties":{"DelaySeconds":"5","MessageRetentionPeriod":"100"}}', - afterContext: '{"Properties":{"MessageRetentionPeriod":"200"}}', - details: [ - { - Target: { - Name: 'DelaySeconds', - Path: '/Properties/DelaySeconds', - BeforeValue: '5', - AfterValue: '1', - Drift: { - PreviousValue: '1', - ActualValue: '5', - }, - }, - }, - ], - }, - }, - ] - - setupMockEditor( - stackName, - '{\n "MyQueue": {\n "Properties": {\n "DelaySeconds": "5",\n "MessageRetentionPeriod": "100"\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - assertDecorationCount(0) - }) - - it('should not add decoration when ActualValue is undefined', async function () { - const stackName = 'test-stack' - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'MyQueue', - beforeContext: '{"Properties":{"DelaySeconds":"5"}}', - afterContext: '{"Properties":{"DelaySeconds":"1"}}', - details: [ - { - Target: { - Name: 'DelaySeconds', - Path: '/Properties/DelaySeconds', - AfterValue: '1', - Drift: { - PreviousValue: '1', - }, - }, - }, - ], - }, - }, - ] - - setupMockEditor( - stackName, - '{\n "MyQueue": {\n "Properties": {\n "DelaySeconds": "5"\n }\n }\n}' - ) - await runDriftTest(stackName, changes) - - assertDecorationCount(0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/diffWebviewProvider.test.ts b/packages/core/src/test/awsService/cloudformation/ui/diffWebviewProvider.test.ts deleted file mode 100644 index ff87dbf72d..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/diffWebviewProvider.test.ts +++ /dev/null @@ -1,556 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { DiffWebviewProvider } from '../../../../awsService/cloudformation/ui/diffWebviewProvider' -import { - DeploymentMode, - StackChange, -} from '../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import { ChangeSetStatus } from '@aws-sdk/client-cloudformation' - -describe('DiffWebviewProvider', function () { - let sandbox: sinon.SinonSandbox - let provider: DiffWebviewProvider - - beforeEach(function () { - sandbox = sinon.createSandbox() - const mockCoordinator = { - onDidChangeStack: sandbox.stub().returns({ dispose: () => {} }), - setChangeSetMode: sandbox.stub().resolves(), - } as any - provider = new DiffWebviewProvider(mockCoordinator) - }) - - afterEach(function () { - sandbox.restore() - }) - - function createMockWebview() { - return { - webview: { - options: {}, - html: '', - onDidReceiveMessage: sandbox.stub(), - }, - } - } - - function setupProviderWithChanges(stackName: string, changes: StackChange[]) { - void provider.updateData(stackName, changes) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - return mockWebview.webview.html - } - - describe('updateData', function () { - it('should update stack name and changes', function () { - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - logicalResourceId: 'TestResource', - resourceType: 'AWS::S3::Bucket', - }, - }, - ] - - const html = setupProviderWithChanges('test-stack', changes) - - // The HTML should contain the resource information (stack name doesn't appear in table HTML) - assert.ok(html.includes('TestResource')) - assert.ok(html.includes('Add')) - assert.ok(html.includes('AWS::S3::Bucket')) - // Verify it's not the "No changes detected" message - assert.ok(!html.includes('No changes detected')) - }) - - it('should handle empty changes array', function () { - const html = setupProviderWithChanges('empty-stack', []) - assert.ok(html.includes('No changes detected')) - assert.ok(html.includes('empty-stack')) - }) - }) - - describe('resolveWebviewView', function () { - it('should configure webview options and set HTML content', function () { - const mockWebview = createMockWebview() - - void provider.updateData('test-stack', []) - provider.resolveWebviewView(mockWebview as any) - - assert.deepStrictEqual(mockWebview.webview.options, { enableScripts: true }) - assert.ok(mockWebview.webview.html.length > 0) - assert.ok(mockWebview.webview.onDidReceiveMessage.calledOnce) - }) - }) - - describe('HTML generation', function () { - it('should generate table with correct columns for changes with details', function () { - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Modify', - logicalResourceId: 'TestBucket', - physicalResourceId: 'test-bucket-123', - resourceType: 'AWS::S3::Bucket', - replacement: 'False', - scope: ['Properties'], - details: [ - { - Target: { - Name: 'BucketName', - RequiresRecreation: 'Never', - BeforeValue: 'old-bucket', - AfterValue: 'new-bucket', - AttributeChangeType: 'Modify', - }, - ChangeSource: 'DirectModification', - CausingEntity: 'user-change', - }, - ], - }, - }, - ] - - const html = setupProviderWithChanges('test-stack', changes) - - // Verify main table headers - assert.ok(html.includes('Action')) - assert.ok(html.includes('LogicalResourceId')) - assert.ok(html.includes('ResourceType')) - assert.ok(html.includes('Replacement')) - - // Verify main row data - assert.ok(html.includes('Modify')) - assert.ok(html.includes('TestBucket')) - assert.ok(html.includes('test-bucket-123')) - assert.ok(html.includes('AWS::S3::Bucket')) - - // Verify detail data (in expandable section) - assert.ok(html.includes('BucketName')) - assert.ok(html.includes('old-bucket')) - assert.ok(html.includes('new-bucket')) - assert.ok(html.includes('DirectModification')) - assert.ok(html.includes('user-change')) - - // Verify expandable structure - assert.ok(html.includes('toggleDetails')) - assert.ok(html.includes('display: none')) - assert.ok(html.includes(' ({ - resourceChange: { - action: 'Add', - logicalResourceId: `Resource${i}`, - resourceType: 'AWS::S3::Bucket', - }, - })) - - const html = setupProviderWithChanges('test-stack', changes) - - assert.ok(html.includes('Page 1 of 2')) - assert.ok(html.includes('nextPage()')) - assert.ok(html.includes('prevPage()')) - assert.ok(html.includes('pagination-controls')) - }) - - it('should not show pagination for small change sets', function () { - const changes: StackChange[] = [ - { - resourceChange: { - action: 'Add', - logicalResourceId: 'SingleResource', - resourceType: 'AWS::S3::Bucket', - }, - }, - ] - - const html = setupProviderWithChanges('test-stack', changes) - - assert.ok(!html.includes('pagination-controls')) - assert.ok(!html.includes('Page 1 of')) - }) - - it('should display correct page numbers and navigation state', function () { - const changes: StackChange[] = Array.from({ length: 150 }, (_, i) => ({ - resourceChange: { - action: 'Add', - logicalResourceId: `Resource${i}`, - }, - })) - - const html = setupProviderWithChanges('test-stack', changes) - - assert.ok(html.includes('Page 1 of 3')) - // Previous button should be disabled on first page - assert.ok(html.includes('opacity: 0.5')) - assert.ok(html.includes('cursor: not-allowed')) - }) - }) - - describe('empty changes handling', function () { - it('should show no changes message when changes is undefined', function () { - void provider.updateData( - 'test-stack', - undefined as any, - 'test-changeset', - undefined, - undefined, - undefined, - ChangeSetStatus.FAILED - ) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - assert.ok(mockWebview.webview.html.includes('No changes detected')) - assert.ok(mockWebview.webview.html.includes('test-stack')) - assert.ok(mockWebview.webview.html.includes('Delete Changeset')) - }) - - it('should show no changes message when changes array is empty', function () { - void provider.updateData( - 'empty-stack', - [], - 'test-changeset', - undefined, - undefined, - undefined, - ChangeSetStatus.FAILED - ) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - const html = mockWebview.webview.html - - assert.ok(html.includes('No changes detected')) - assert.ok(html.includes('empty-stack')) - assert.ok(html.includes('Delete Changeset')) - }) - - it('should not show delete button when no changeset status', function () { - void provider.updateData('empty-stack', [], 'test-changeset', undefined, undefined, undefined, undefined) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - const html = mockWebview.webview.html - - assert.ok(html.includes('No changes detected')) - assert.ok(html.includes('empty-stack')) - assert.ok(!html.includes('Delete Changeset')) - }) - - it('should not show table when no changes', function () { - const html = setupProviderWithChanges('empty-stack', []) - - assert.ok(!html.includes(' { - let sandbox: sinon.SinonSandbox - let provider: StackEventsWebviewProvider - let mockClient: any - let coordinatorCallback: any - - function createMockView() { - return { - webview: { - onDidReceiveMessage: sandbox.stub(), - html: '', - options: {}, - }, - onDidChangeVisibility: sandbox.stub(), - visible: true, - onDidDispose: sandbox.stub(), - } - } - - function mockSingleEventWithOperationId() { - mockClient.sendRequest.resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_COMPLETE', - OperationId: 'op-123', - }, - ], - nextToken: undefined, - }) - } - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub().resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_IN_PROGRESS', - }, - ], - nextToken: undefined, - }), - } - const mockCoordinator: any = { - onDidChangeStack: sandbox.stub().callsFake((callback: any) => { - coordinatorCallback = callback - return { dispose: () => {} } - }), - } - provider = new StackEventsWebviewProvider(mockClient, mockCoordinator) - }) - - afterEach(() => { - provider.dispose() - sandbox.restore() - }) - - it('should load stack events', async () => { - await provider.showStackEvents('test-stack') - - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - }) - - it('should group events by operation ID', async () => { - mockClient.sendRequest.resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_COMPLETE', - OperationId: 'op-123', - }, - { - EventId: 'event-2', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_IN_PROGRESS', - OperationId: 'op-123', - }, - { - EventId: 'event-3', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'UPDATE_COMPLETE', - }, - ], - nextToken: undefined, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(html.includes('op-123')) - assert.ok(html.includes('parent-row')) - assert.ok(html.includes('child-row')) - }) - - it('should expand first operation group by default', async () => { - mockSingleEventWithOperationId() - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(html.includes('expanded')) - }) - - it('should stop auto-refresh on terminal state', async () => { - const clock = sandbox.useFakeTimers() - - await provider.showStackEvents('test-stack') - - // Simulate terminal state notification - await coordinatorCallback({ - stackName: 'test-stack', - isChangeSetMode: false, - stackStatus: 'CREATE_COMPLETE', - }) - - clock.tick(10000) - - // Should not continue refreshing after terminal state - const callCount = mockClient.sendRequest.callCount - clock.tick(5000) - assert.strictEqual(mockClient.sendRequest.callCount, callCount) - - clock.restore() - }) - - it('should continue auto-refresh during in-progress state', async () => { - const clock = sandbox.useFakeTimers() - - await provider.showStackEvents('test-stack') - - await coordinatorCallback({ - stackName: 'test-stack', - isChangeSetMode: false, - stackStatus: 'UPDATE_IN_PROGRESS', - }) - - const initialCalls = mockClient.sendRequest.callCount - clock.tick(5000) - - assert.strictEqual(mockClient.sendRequest.callCount > initialCalls, true) - - clock.restore() - }) - - it('should include console link with ARN when stackArn is set', async () => { - const view = createMockView() - provider.resolveWebviewView(view as any) - - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: 'arn:aws:cloudformation:us-west-2:123456789012:stack/test-stack/xyz-456', - isChangeSetMode: false, - }) - - const html = view.webview.html - assert.ok(html.includes('href="https://us-west-2.console.aws.amazon.com')) - assert.ok(html.includes('/stacks/events?stackId=')) - assert.ok(html.includes('View in AWS Console')) - }) - - it('should not include console link when stackArn is missing', async () => { - const view = createMockView() - provider.resolveWebviewView(view as any) - - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: undefined, - isChangeSetMode: false, - }) - - const html = view.webview.html - assert.ok(!html.includes('href="https://')) - }) - - it('should show "X events loaded" in header when nextToken is available', async () => { - mockClient.sendRequest.resolves({ - events: Array.from({ length: 50 }, (_, i) => ({ - EventId: `event-${i}`, - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_IN_PROGRESS', - })), - nextToken: 'token123', - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(html.includes('(50 events loaded)')) - }) - - it('should show "X events" in header when nextToken is not available', async () => { - mockClient.sendRequest.resolves({ - events: Array.from({ length: 50 }, (_, i) => ({ - EventId: `event-${i}`, - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_IN_PROGRESS', - })), - nextToken: undefined, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(html.includes('(50 events)')) - assert.ok(!html.includes('(50 events loaded)')) - }) - - it('should show Hook Invocation column when events have hooks', async () => { - mockClient.sendRequest.resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_COMPLETE', - HookType: 'AWS::CloudFormation::Hook', - HookStatus: 'HOOK_COMPLETE_SUCCEEDED', - }, - ], - nextToken: undefined, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(html.includes('Hook Invocation')) - assert.ok(html.includes('AWS::CloudFormation::Hook')) - assert.ok(html.includes('HOOK_COMPLETE_SUCCEEDED')) - }) - - it('should not show Hook Invocation column when no hooks present', async () => { - mockClient.sendRequest.resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_COMPLETE', - }, - ], - nextToken: undefined, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackEvents('test-stack') - - const html = view.webview.html - assert.ok(!html.includes('Hook Invocation')) - }) - - it('should include operation ID link in events', async () => { - mockClient.sendRequest.resolves({ - events: [ - { - EventId: 'event-1', - StackName: 'test-stack', - Timestamp: new Date(), - ResourceStatus: 'CREATE_COMPLETE', - OperationId: 'op-123', - }, - ], - nextToken: undefined, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: 'arn:aws:cloudformation:us-west-2:123456789012:stack/test-stack/xyz-456', - isChangeSetMode: false, - }) - - const html = view.webview.html - assert.ok(html.includes('/stacks/operations/info')) - assert.ok(html.includes('operationId=op-123')) - }) - - it('should not hyperlink operation ID when stackArn is malformed', async () => { - mockSingleEventWithOperationId() - - const view = createMockView() - provider.resolveWebviewView(view as any) - - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: 'not-a-valid-arn', - isChangeSetMode: false, - }) - - const html = view.webview.html - assert.ok(html.includes('op-123')) - assert.ok(!html.includes('href="https://') || !html.includes('operationId=op-123')) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/stackOutputsWebviewProvider.test.ts b/packages/core/src/test/awsService/cloudformation/ui/stackOutputsWebviewProvider.test.ts deleted file mode 100644 index 03a62fe349..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/stackOutputsWebviewProvider.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { StackOutputsWebviewProvider } from '../../../../awsService/cloudformation/ui/stackOutputsWebviewProvider' - -describe('StackOutputsWebviewProvider', () => { - let sandbox: sinon.SinonSandbox - let provider: StackOutputsWebviewProvider - let mockClient: any - let mockCoordinator: any - - function createMockView() { - return { - webview: { - options: {}, - html: '', - }, - onDidChangeVisibility: sandbox.stub(), - visible: true, - } - } - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub().resolves({ - stack: { - StackName: 'test-stack', - StackStatus: 'CREATE_COMPLETE', - Outputs: [ - { - OutputKey: 'BucketName', - OutputValue: 'my-bucket', - Description: 'S3 bucket name', - }, - ], - }, - }), - } - mockCoordinator = { - onDidChangeStack: sandbox.stub().returns({ dispose: () => {} }), - setStack: sandbox.stub().resolves(), - currentStackStatus: undefined, - } - provider = new StackOutputsWebviewProvider(mockClient, mockCoordinator) - }) - - afterEach(() => { - provider.dispose() - sandbox.restore() - }) - - it('should use DescribeStackRequest to load outputs', async () => { - await provider.resolveWebviewView(createMockView() as any) - await provider.showOutputs('test-stack') - - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - const requestArgs = mockClient.sendRequest.firstCall.args - assert.strictEqual(requestArgs[1].stackName, 'test-stack') - }) - - it('should extract outputs from stack object', async () => { - const mockView = createMockView() - await provider.resolveWebviewView(mockView as any) - - await provider.showOutputs('test-stack') - - assert.strictEqual(mockView.webview.html.includes('BucketName'), true) - assert.strictEqual(mockView.webview.html.includes('my-bucket'), true) - }) - - it('should update coordinator with stack status', async () => { - await provider.resolveWebviewView(createMockView() as any) - await provider.showOutputs('test-stack') - - assert.strictEqual(mockCoordinator.setStack.calledWith('test-stack', 'CREATE_COMPLETE'), true) - }) - - it('should not update coordinator if status unchanged', async () => { - mockCoordinator.currentStackStatus = 'CREATE_COMPLETE' - - await provider.resolveWebviewView(createMockView() as any) - await provider.showOutputs('test-stack') - - assert.strictEqual(mockCoordinator.setStack.called, false) - }) - - it('should include console link with ARN when stackArn is set', async () => { - const mockView = createMockView() - await provider.resolveWebviewView(mockView as any) - - const coordinatorCallback = mockCoordinator.onDidChangeStack.firstCall.args[0] - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: 'arn:aws:cloudformation:eu-west-1:123456789012:stack/test-stack/def-789', - isChangeSetMode: false, - }) - - const html = mockView.webview.html - assert.ok(html.includes('href="https://eu-west-1.console.aws.amazon.com')) - assert.ok(html.includes('/stacks/outputs?stackId=')) - assert.ok(html.includes('View in AWS Console')) - }) - - it('should not include console link when stackArn is missing', async () => { - const mockView = createMockView() - await provider.resolveWebviewView(mockView as any) - - const coordinatorCallback = mockCoordinator.onDidChangeStack.firstCall.args[0] - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: undefined, - isChangeSetMode: false, - }) - - const html = mockView.webview.html - assert.ok(!html.includes('href="https://')) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/stackOverviewWebviewProvider.test.ts b/packages/core/src/test/awsService/cloudformation/ui/stackOverviewWebviewProvider.test.ts deleted file mode 100644 index a835c3cb87..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/stackOverviewWebviewProvider.test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { StackOverviewWebviewProvider } from '../../../../awsService/cloudformation/ui/stackOverviewWebviewProvider' - -describe('StackOverviewWebviewProvider', () => { - let sandbox: sinon.SinonSandbox - let provider: StackOverviewWebviewProvider - let mockClient: any - let mockCoordinator: any - let coordinatorCallback: any - - function createMockView() { - return { - webview: { - options: {}, - html: '', - }, - onDidChangeVisibility: sandbox.stub(), - onDidDispose: sandbox.stub(), - visible: true, - } - } - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub().resolves({ - stack: { - StackName: 'test-stack', - StackStatus: 'CREATE_COMPLETE', - StackId: 'stack-id-123', - CreationTime: new Date(), - }, - }), - } - mockCoordinator = { - onDidChangeStack: sandbox.stub().callsFake((callback: any) => { - coordinatorCallback = callback - return { dispose: () => {} } - }), - setStack: sandbox.stub().resolves(), - currentStackStatus: undefined, - } - provider = new StackOverviewWebviewProvider(mockClient, mockCoordinator) - }) - - afterEach(() => { - provider.dispose() - sandbox.restore() - }) - - it('should load stack overview', async () => { - provider.resolveWebviewView(createMockView() as any) - await provider.showStackOverview('test-stack') - - assert.strictEqual(mockClient.sendRequest.calledOnce, true) - assert.strictEqual(mockCoordinator.setStack.calledOnce, true) - }) - - it('should update coordinator with stack status', async () => { - provider.resolveWebviewView(createMockView() as any) - await provider.showStackOverview('test-stack') - - assert.strictEqual(mockCoordinator.setStack.calledWith('test-stack', 'CREATE_COMPLETE'), true) - }) - - it('should not update coordinator if status unchanged', async () => { - mockCoordinator.currentStackStatus = 'CREATE_COMPLETE' - - provider.resolveWebviewView(createMockView() as any) - await provider.showStackOverview('test-stack') - - assert.strictEqual(mockCoordinator.setStack.called, false) - }) - - it('should start auto-refresh on stack change', async () => { - const clock = sandbox.useFakeTimers() - - await coordinatorCallback({ - stackName: 'test-stack', - isChangeSetMode: false, - stackStatus: 'CREATE_IN_PROGRESS', - }) - - clock.tick(5000) - - assert.strictEqual(mockClient.sendRequest.callCount >= 2, true) - - clock.restore() - }) - - it('should stop auto-refresh on terminal state', async () => { - const clock = sandbox.useFakeTimers() - - await coordinatorCallback({ - stackName: 'test-stack', - isChangeSetMode: false, - stackStatus: 'CREATE_COMPLETE', - }) - - clock.tick(10000) - - // Should only be called once (initial load), not refreshed - assert.strictEqual(mockClient.sendRequest.callCount, 1) - - clock.restore() - }) - - it('should include console link with ARN in HTML', async () => { - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackOverview('test-stack') - - const html = view.webview.html - assert.ok(html.includes('href="https://console.aws.amazon.com/go/view?arn=')) - assert.ok(html.includes('stack-id-123')) - assert.ok(html.includes('View in AWS Console')) - }) - - it('should not include console link when ARN is missing', async () => { - mockClient.sendRequest.resolves({ - stack: { - StackName: 'test-stack', - StackStatus: 'CREATE_COMPLETE', - StackId: undefined, - }, - }) - - const view = createMockView() - provider.resolveWebviewView(view as any) - await provider.showStackOverview('test-stack') - - const html = view.webview.html - assert.ok(!html.includes('href="https://')) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/stackResourcesWebviewProvider.test.ts b/packages/core/src/test/awsService/cloudformation/ui/stackResourcesWebviewProvider.test.ts deleted file mode 100644 index effe7e2e3c..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/stackResourcesWebviewProvider.test.ts +++ /dev/null @@ -1,354 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { StackResourcesWebviewProvider } from '../../../../awsService/cloudformation/ui/stackResourcesWebviewProvider' - -describe('StackResourcesWebviewProvider', function () { - let sandbox: sinon.SinonSandbox - let provider: StackResourcesWebviewProvider - let mockClient: any - let mockCoordinator: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockClient = { - sendRequest: sandbox.stub(), - } - mockCoordinator = { - onDidChangeStack: sandbox.stub().returns({ dispose: () => {} }), - setStack: sandbox.stub().resolves(), - currentStackStatus: undefined, - } as any - provider = new StackResourcesWebviewProvider(mockClient, mockCoordinator) - }) - - afterEach(function () { - sandbox.restore() - }) - - function createMockWebview() { - return { - webview: { - options: {}, - html: '', - onDidReceiveMessage: sandbox.stub(), - }, - onDidChangeVisibility: sandbox.stub(), - onDidDispose: sandbox.stub(), - visible: true, - } - } - - function createMockResources(count: number, startIndex = 0) { - return Array.from({ length: count }, (_, i) => ({ - LogicalResourceId: `Resource${i + startIndex}`, - PhysicalResourceId: `resource-${i + startIndex}-123`, - ResourceType: 'AWS::S3::Bucket', - ResourceStatus: 'CREATE_COMPLETE', - })) - } - - async function setupProviderWithResources(stackName: string, resources: any[], nextToken?: string) { - mockClient.sendRequest.resolves({ resources, nextToken }) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - await provider.updateData(stackName) - return mockWebview - } - - describe('updateData', function () { - it('should update stack name and fetch resources', async function () { - const mockResources = createMockResources(1) - mockClient.sendRequest.resolves({ resources: mockResources }) - - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - await provider.updateData('test-stack') - - assert.ok(mockClient.sendRequest.calledOnce) - const [, params] = mockClient.sendRequest.firstCall.args - assert.strictEqual(params.stackName, 'test-stack') - }) - - it('should handle client request errors gracefully', async function () { - mockClient.sendRequest.rejects(new Error('Network error')) - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - // Should not throw - await provider.updateData('test-stack') - }) - }) - - describe('resolveWebviewView', function () { - it('should configure webview options and set HTML content', function () { - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - assert.deepStrictEqual(mockWebview.webview.options, { enableScripts: true }) - assert.ok(mockWebview.webview.html.length > 0) - }) - - it('should set up visibility change handlers', function () { - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - assert.ok(mockWebview.onDidChangeVisibility.calledOnce) - assert.ok(mockWebview.onDidDispose.calledOnce) - }) - - it('should set up message handlers for pagination', function () { - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - assert.ok(mockWebview.webview.onDidReceiveMessage.calledOnce) - }) - }) - - describe('HTML generation', function () { - it('should show no resources message when empty', async function () { - const mockWebview = await setupProviderWithResources('test-stack', []) - assert.ok(mockWebview.webview.html.includes('No resources found')) - }) - - it('should generate table with resources', async function () { - const mockResources = [ - { - LogicalResourceId: 'TestBucket', - PhysicalResourceId: 'test-bucket-123', - ResourceType: 'AWS::S3::Bucket', - ResourceStatus: 'CREATE_COMPLETE', - }, - ] - - const mockWebview = await setupProviderWithResources('test-stack', mockResources) - const html = mockWebview.webview.html - - // Verify table headers and data - assert.ok(html.includes('Logical ID')) - assert.ok(html.includes('Physical ID')) - assert.ok(html.includes('Type')) - assert.ok(html.includes('Status')) - assert.ok(html.includes('TestBucket')) - assert.ok(html.includes('test-bucket-123')) - assert.ok(html.includes('AWS::S3::Bucket')) - assert.ok(html.includes('CREATE_COMPLETE')) - }) - - it('should handle resources without physical ID', async function () { - const mockResources = [ - { - LogicalResourceId: 'TestResource', - ResourceType: 'AWS::CloudFormation::WaitConditionHandle', - ResourceStatus: 'CREATE_COMPLETE', - }, - ] - - const mockWebview = await setupProviderWithResources('test-stack', mockResources) - const html = mockWebview.webview.html - - assert.ok(html.includes('TestResource')) - assert.ok(html.includes('AWS::CloudFormation::WaitConditionHandle')) - assert.ok(html.includes('CREATE_COMPLETE')) - }) - - it('should show pagination controls with buttons disabled when there is only one page', async function () { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(10)) - const html = mockWebview.webview.html - - // Pagination is always shown, but buttons should be disabled for single page - assert.ok(html.includes('Previous')) - assert.ok(html.includes('Next')) - assert.ok(html.includes('disabled')) - }) - - it('should show pagination controls when there are multiple pages', async function () { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(60)) - const html = mockWebview.webview.html - - // Should show pagination buttons for multiple pages - assert.ok(html.includes('Previous')) - assert.ok(html.includes('Next')) - }) - - it('should disable Previous button on first page', async function () { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(60)) - const html = mockWebview.webview.html - - // Previous button should be disabled on first page - assert.ok(html.includes('disabled')) - assert.ok(html.includes('Previous')) - }) - - it('should include console link with ARN when stackArn is set', async function () { - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - const coordinatorCallback = mockCoordinator.onDidChangeStack.firstCall.args[0] - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: 'arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc-123', - isChangeSetMode: false, - }) - - const html = mockWebview.webview.html - assert.ok(html.includes('href="https://us-east-1.console.aws.amazon.com')) - assert.ok(html.includes('/stacks/resources?stackId=')) - assert.ok(html.includes('View in AWS Console')) - }) - - it('should not include console link when stackArn is missing', async function () { - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - - const coordinatorCallback = mockCoordinator.onDidChangeStack.firstCall.args[0] - await coordinatorCallback({ - stackName: 'test-stack', - stackArn: undefined, - isChangeSetMode: false, - }) - - const html = mockWebview.webview.html - assert.ok(!html.includes('href="https://')) - }) - - it('should show "X resources loaded" in header when nextToken is available', async function () { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(50), 'token123') - const html = mockWebview.webview.html - assert.ok(html.includes('(50 resources loaded)')) - }) - - it('should show "X resources" in header when nextToken is not available', async function () { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(60)) - const html = mockWebview.webview.html - assert.ok(html.includes('(60 resources)')) - assert.ok(!html.includes('(60 resources loaded)')) - }) - }) - - describe('pagination functionality', function () { - let clock: sinon.SinonFakeTimers - - beforeEach(function () { - clock = sandbox.useFakeTimers() - }) - - afterEach(function () { - clock.restore() - }) - - async function testPaginationMessage(command: string) { - const mockWebview = await setupProviderWithResources('test-stack', createMockResources(60)) - const messageHandler = mockWebview.webview.onDidReceiveMessage.firstCall.args[0] - await messageHandler({ command }) - assert.ok(mockWebview.webview.html.length > 0) - } - - it('should handle nextPage message', async function () { - await testPaginationMessage('nextPage') - }) - - it('should handle prevPage message', async function () { - await testPaginationMessage('prevPage') - }) - - it('should start auto-update when webview becomes visible', async function () { - mockCoordinator.currentStackStatus = 'UPDATE_IN_PROGRESS' - const mockWebview = await setupProviderWithResources('test-stack', []) - const visibilityHandler = mockWebview.onDidChangeVisibility.firstCall.args[0] - mockWebview.visible = true - visibilityHandler() - - const initialCallCount = mockClient.sendRequest.callCount - clock.tick(5000) - - assert.ok(mockClient.sendRequest.callCount >= initialCallCount + 1) - }) - - it('should stop auto-update when webview becomes hidden', async function () { - const mockWebview = await setupProviderWithResources('test-stack', []) - const visibilityHandler = mockWebview.onDidChangeVisibility.firstCall.args[0] - - // Start then stop auto-update - mockWebview.visible = true - visibilityHandler() - mockWebview.visible = false - visibilityHandler() - - const callCountAfterStop = mockClient.sendRequest.callCount - clock.tick(10000) - assert.strictEqual(mockClient.sendRequest.callCount, callCountAfterStop) - }) - }) - - describe('loadResources', function () { - async function setupPaginatedTest() { - const firstBatch = createMockResources(50) - const secondBatch = createMockResources(10, 50) - - mockClient.sendRequest - .onFirstCall() - .resolves({ resources: firstBatch, nextToken: 'token123' }) - .onSecondCall() - .resolves({ resources: secondBatch }) - - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - await provider.updateData('test-stack') - - return mockWebview - } - - it('should handle nextToken for pagination', async function () { - const mockWebview = await setupPaginatedTest() - const messageHandler = mockWebview.webview.onDidReceiveMessage.firstCall.args[0] - await messageHandler({ command: 'nextPage' }) - - assert.strictEqual(mockClient.sendRequest.callCount, 2) - }) - - it('should return early if no client or stack name', async function () { - const mockCoordinator = { - onDidChangeStack: sandbox.stub().returns({ dispose: () => {} }), - } as any - const providerWithoutClient = new StackResourcesWebviewProvider(undefined as any, mockCoordinator) - const mockWebview = createMockWebview() - providerWithoutClient.resolveWebviewView(mockWebview as any) - - await providerWithoutClient.updateData('') - }) - - it('should not duplicate resources when updateData is called multiple times', async function () { - const mockResources = createMockResources(5) - mockClient.sendRequest.resolves({ resources: mockResources }) - - const mockWebview = createMockWebview() - provider.resolveWebviewView(mockWebview as any) - await provider.updateData('test-stack') - await provider.updateData('test-stack') - - const html = mockWebview.webview.html - const resource0Count = (html.match(/Resource0/g) || []).length - assert.strictEqual(resource0Count, 1) - }) - - it('should overwrite resources on initial load and append on subsequent paginated loads', async function () { - const mockWebview = await setupPaginatedTest() - - let html = mockWebview.webview.html - assert.ok(html.includes('Resource0')) - assert.ok(html.includes('Resource49')) - - const messageHandler = mockWebview.webview.onDidReceiveMessage.firstCall.args[0] - await messageHandler({ command: 'nextPage' }) - - html = mockWebview.webview.html - assert.ok(html.includes('Resource50')) - assert.ok(html.includes('Resource59')) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/stackViewCoordinator.test.ts b/packages/core/src/test/awsService/cloudformation/ui/stackViewCoordinator.test.ts deleted file mode 100644 index 8f307010d1..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/stackViewCoordinator.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { StackViewCoordinator } from '../../../../awsService/cloudformation/ui/stackViewCoordinator' - -describe('StackViewCoordinator', () => { - let coordinator: StackViewCoordinator - - beforeEach(() => { - coordinator = new StackViewCoordinator() - }) - - afterEach(() => { - coordinator.dispose() - }) - - it('should initialize with undefined state', () => { - assert.strictEqual(coordinator.currentStackName, undefined) - assert.strictEqual(coordinator.currentStackStatus, undefined) - assert.strictEqual(coordinator.isChangeSetMode, false) - }) - - it('should set stack name and status', async () => { - await coordinator.setStack('test-stack', 'CREATE_COMPLETE') - - assert.strictEqual(coordinator.currentStackName, 'test-stack') - assert.strictEqual(coordinator.currentStackStatus, 'CREATE_COMPLETE') - assert.strictEqual(coordinator.isChangeSetMode, false) - }) - - it('should fire event when stack changes', async () => { - let eventFired = false - let receivedState: any - - coordinator.onDidChangeStack((state) => { - eventFired = true - receivedState = state - }) - - await coordinator.setStack('test-stack', 'CREATE_IN_PROGRESS') - - assert.strictEqual(eventFired, true) - assert.strictEqual(receivedState.stackName, 'test-stack') - assert.strictEqual(receivedState.stackStatus, 'CREATE_IN_PROGRESS') - assert.strictEqual(receivedState.isChangeSetMode, false) - }) - - it('should call status update callback when status changes', async () => { - let callbackCount = 0 - let receivedStackName: string | undefined - let receivedStatus: string | undefined - - coordinator.setStackStatusUpdateCallback((stackName, status) => { - callbackCount++ - receivedStackName = stackName - receivedStatus = status - }) - - await coordinator.setStack('test-stack', 'CREATE_COMPLETE') - - assert.strictEqual(callbackCount, 1) - assert.strictEqual(receivedStackName, 'test-stack') - assert.strictEqual(receivedStatus, 'CREATE_COMPLETE') - - await coordinator.setStack('test-stack', 'UPDATE_IN_PROGRESS') - - assert.strictEqual(callbackCount, 2) - assert.strictEqual(receivedStatus, 'UPDATE_IN_PROGRESS') - }) - - it('should not call callback if status unchanged', async () => { - let callbackCount = 0 - - coordinator.setStackStatusUpdateCallback(() => { - callbackCount++ - }) - - await coordinator.setStack('test-stack', 'CREATE_COMPLETE') - assert.strictEqual(callbackCount, 1) - - await coordinator.setStack('test-stack', 'CREATE_COMPLETE') - assert.strictEqual(callbackCount, 1) - }) - - it('should set change set mode', async () => { - await coordinator.setChangeSetMode('test-stack', true) - - assert.strictEqual(coordinator.currentStackName, 'test-stack') - assert.strictEqual(coordinator.isChangeSetMode, true) - }) - - it('should clear stack', async () => { - await coordinator.setStack('test-stack', 'CREATE_COMPLETE') - await coordinator.clearStack() - - assert.strictEqual(coordinator.currentStackName, undefined) - assert.strictEqual(coordinator.currentStackStatus, undefined) - assert.strictEqual(coordinator.isChangeSetMode, false) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/ui/statusBar.test.ts b/packages/core/src/test/awsService/cloudformation/ui/statusBar.test.ts deleted file mode 100644 index 57aa75d0b5..0000000000 --- a/packages/core/src/test/awsService/cloudformation/ui/statusBar.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { createDeploymentStatusBar, updateWorkflowStatus } from '../../../../awsService/cloudformation/ui/statusBar' -import { StackActionPhase } from '../../../../awsService/cloudformation/stacks/actions/stackActionRequestType' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('StatusBar', function () { - let sandbox: sinon.SinonSandbox - let clock: sinon.SinonFakeTimers - - beforeEach(function () { - sandbox = sinon.createSandbox() - clock = sandbox.useFakeTimers() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('createDeploymentStatusBar', function () { - it('creates status bar handle', function () { - const handle = createDeploymentStatusBar('stack1', 'Validation') - - assert.ok(handle) - assert.strictEqual(typeof handle.update, 'function') - assert.strictEqual(typeof handle.release, 'function') - - handle.release() - clock.tick(5000) - }) - - it('creates handle for deployment with changeset', function () { - const handle = createDeploymentStatusBar('stack1', 'Deployment', 'changeset1') - - assert.ok(handle) - - handle.release() - clock.tick(5000) - }) - - it('shows single operation with stack name', function () { - const handle = createDeploymentStatusBar('my-stack', 'Validation') - const statusBar = getTestWindow().statusBar - - const messages = statusBar.messages - assert.ok(messages.some((msg) => msg.includes('Validating my-stack'))) - - handle.release() - clock.tick(5000) - }) - - it('shows count for multiple operations', function () { - const handle1 = createDeploymentStatusBar('stack1', 'Validation') - const handle2 = createDeploymentStatusBar('stack2', 'Deployment', 'changeset1') - const statusBar = getTestWindow().statusBar - - const messages = statusBar.messages - assert.ok(messages.some((msg) => msg.includes('AWS CloudFormation (2)'))) - - handle1.release() - handle2.release() - clock.tick(5000) - }) - }) - - describe('updateWorkflowStatus', function () { - it('updates single operation display', function () { - const handle = createDeploymentStatusBar('my-stack', 'Validation') - const statusBar = getTestWindow().statusBar - - updateWorkflowStatus(handle, StackActionPhase.VALIDATION_IN_PROGRESS) - assert.ok(statusBar.messages.some((msg) => msg.includes('Validating my-stack'))) - - updateWorkflowStatus(handle, StackActionPhase.VALIDATION_COMPLETE) - assert.ok(statusBar.messages.some((msg) => msg.includes('Validated my-stack'))) - - handle.release() - clock.tick(5000) - }) - - it('shows failure for single operation', function () { - const handle = createDeploymentStatusBar('my-stack', 'Validation') - const statusBar = getTestWindow().statusBar - - updateWorkflowStatus(handle, StackActionPhase.VALIDATION_FAILED) - assert.ok(statusBar.messages.some((msg) => msg.includes('Validation Failed: my-stack'))) - - handle.release() - clock.tick(5000) - }) - - it('handles terminal phases', function () { - const handle = createDeploymentStatusBar('stack1', 'Validation') - - updateWorkflowStatus(handle, StackActionPhase.VALIDATION_COMPLETE) - handle.release() - - clock.tick(5000) - }) - - it('handles multiple concurrent operations', function () { - const handle1 = createDeploymentStatusBar('stack1', 'Validation') - const handle2 = createDeploymentStatusBar('stack2', 'Deployment', 'changeset1') - - updateWorkflowStatus(handle1, StackActionPhase.VALIDATION_COMPLETE) - updateWorkflowStatus(handle2, StackActionPhase.DEPLOYMENT_COMPLETE) - - handle1.release() - handle2.release() - - clock.tick(5000) - }) - - it('handles deployment operations', function () { - const handle = createDeploymentStatusBar('my-stack', 'Deployment', 'changeset1') - const statusBar = getTestWindow().statusBar - - updateWorkflowStatus(handle, StackActionPhase.DEPLOYMENT_IN_PROGRESS) - assert.ok(statusBar.messages.some((msg) => msg.includes('Deploying my-stack'))) - - updateWorkflowStatus(handle, StackActionPhase.DEPLOYMENT_COMPLETE) - assert.ok(statusBar.messages.some((msg) => msg.includes('Deployed my-stack'))) - - handle.release() - clock.tick(5000) - }) - - it('shows deployment failure', function () { - const handle = createDeploymentStatusBar('my-stack', 'Deployment', 'changeset1') - const statusBar = getTestWindow().statusBar - - updateWorkflowStatus(handle, StackActionPhase.DEPLOYMENT_FAILED) - assert.ok(statusBar.messages.some((msg) => msg.includes('Deployment Failed: my-stack'))) - - handle.release() - clock.tick(5000) - }) - - it('disposes after all operations complete', function () { - const handle1 = createDeploymentStatusBar('stack1', 'Validation') - const handle2 = createDeploymentStatusBar('stack2', 'Deployment', 'changeset1') - const statusBar = getTestWindow().statusBar - - updateWorkflowStatus(handle1, StackActionPhase.VALIDATION_COMPLETE) - updateWorkflowStatus(handle2, StackActionPhase.DEPLOYMENT_COMPLETE) - - handle1.release() - handle2.release() - - const beforeDispose = statusBar.items.length - clock.tick(5000) - const afterDispose = statusBar.items.filter((item) => !item.disposed).length - - assert.ok(afterDispose < beforeDispose) - }) - }) -}) diff --git a/packages/core/src/test/awsService/cloudformation/utils/onlineErrorHandler.test.ts b/packages/core/src/test/awsService/cloudformation/utils/onlineErrorHandler.test.ts deleted file mode 100644 index 2b7b93f025..0000000000 --- a/packages/core/src/test/awsService/cloudformation/utils/onlineErrorHandler.test.ts +++ /dev/null @@ -1,222 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { ResponseError } from 'vscode-languageclient' -import { handleLspError } from '../../../../awsService/cloudformation/utils/onlineErrorHandler' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('handleLspError', function () { - let sandbox: sinon.SinonSandbox - let executeCommandStub: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - executeCommandStub = sandbox.stub(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('Non-LSP Errors', function () { - it('should handle regular Error without context', async function () { - const error = new Error('Something went wrong') - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Something went wrong') - }) - - it('should handle regular Error with context', async function () { - const error = new Error('Something went wrong') - await handleLspError(error, 'Error deploying template') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Error deploying template: Something went wrong') - }) - - it('should handle string error', async function () { - await handleLspError('String error') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'String error') - }) - - it('should handle unknown error type', async function () { - await handleLspError({ random: 'object' }) - assert.strictEqual(getTestWindow().shownMessages.length, 1) - }) - }) - - describe('ExpiredCredentials Error (-32003)', function () { - it('should show Re-authenticate button when requiresReauth is true', async function () { - const error = new ResponseError(-32_003, 'AWS credentials are invalid or expired', { - requiresReauth: true, - retryable: false, - }) - getTestWindow().onDidShowMessage((message) => { - message.selectItem('Re-authenticate') - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'AWS credentials are invalid or expired') - assert.ok(executeCommandStub.calledWith('aws.toolkit.login')) - }) - - it('should not trigger login if user dismisses', async function () { - const error = new ResponseError(-32_003, 'AWS credentials are invalid or expired', { - requiresReauth: true, - retryable: false, - }) - getTestWindow().onDidShowMessage((message) => { - message.dispose() - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'AWS credentials are invalid or expired') - assert.ok(executeCommandStub.notCalled) - }) - - it('should show message without button when requiresReauth is false', async function () { - const error = new ResponseError(-32_003, 'AWS credentials are invalid or expired', { - requiresReauth: false, - retryable: false, - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'AWS credentials are invalid or expired') - assert.ok(executeCommandStub.notCalled) - }) - - it('should include context in message', async function () { - const error = new ResponseError(-32_003, 'AWS credentials are invalid or expired', { - requiresReauth: true, - retryable: false, - }) - getTestWindow().onDidShowMessage((message) => { - message.dispose() - }) - await handleLspError(error, 'Error deploying template') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Error deploying template: AWS credentials are invalid or expired') - }) - }) - - describe('NoAuthentication Error (-32002)', function () { - it('should show Re-authenticate button when requiresReauth is true', async function () { - const error = new ResponseError(-32_002, 'No AWS credentials configured', { - requiresReauth: true, - retryable: false, - }) - getTestWindow().onDidShowMessage((message) => { - message.selectItem('Re-authenticate') - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'No AWS credentials configured') - assert.ok(executeCommandStub.calledWith('aws.toolkit.login')) - }) - - it('should show message without button when requiresReauth is false', async function () { - const error = new ResponseError(-32_002, 'No AWS credentials configured', { - requiresReauth: false, - retryable: false, - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'No AWS credentials configured') - assert.ok(executeCommandStub.notCalled) - }) - }) - - describe('NoInternet Error (-32001)', function () { - it('should show message without retry button', async function () { - const error = new ResponseError(-32_001, 'Network error occurred while contacting AWS', { - retryable: true, - requiresReauth: false, - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Network error occurred while contacting AWS') - }) - - it('should include context in message', async function () { - const error = new ResponseError(-32_001, 'Network error occurred while contacting AWS', { - retryable: true, - requiresReauth: false, - }) - await handleLspError(error, 'Error loading stacks') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Error loading stacks: Network error occurred while contacting AWS') - }) - }) - - describe('AwsServiceError (-32004)', function () { - it('should show message when retryable is true', async function () { - const error = new ResponseError(-32_004, 'AWS service error: Stack not found', { - retryable: true, - requiresReauth: false, - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'AWS service error: Stack not found') - }) - - it('should show message when retryable is false', async function () { - const error = new ResponseError(-32_004, 'AWS service error: Access denied', { - retryable: false, - requiresReauth: false, - }) - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'AWS service error: Access denied') - }) - - it('should include context in message', async function () { - const error = new ResponseError(-32_004, 'AWS service error: Stack not found', { - retryable: false, - requiresReauth: false, - }) - await handleLspError(error, 'Error viewing stack') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Error viewing stack: AWS service error: Stack not found') - }) - }) - - describe('Unknown LSP Error Code', function () { - it('should show message for unknown error code', async function () { - const error = new ResponseError(-99_999, 'Unknown error') - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Unknown error') - }) - - it('should include context for unknown error code', async function () { - const error = new ResponseError(-99_999, 'Unknown error') - await handleLspError(error, 'Error performing operation') - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Error performing operation: Unknown error') - }) - }) - - describe('Edge Cases', function () { - it('should handle LSP error without data field', async function () { - const error = new ResponseError(-32_003, 'Credentials expired') - await handleLspError(error) - const message = getTestWindow().getFirstMessage() - assert.strictEqual(message.message, 'Credentials expired') - }) - - it('should handle null error', async function () { - await handleLspError(undefined) - assert.strictEqual(getTestWindow().shownMessages.length, 1) - }) - - it('should handle undefined error', async function () { - await handleLspError(undefined) - assert.strictEqual(getTestWindow().shownMessages.length, 1) - }) - }) -}) diff --git a/packages/core/src/test/awsService/ec2/activation.test.ts b/packages/core/src/test/awsService/ec2/activation.test.ts deleted file mode 100644 index a34d7cfc0c..0000000000 --- a/packages/core/src/test/awsService/ec2/activation.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { assertTelemetry } from '../../testUtil' -import { Ec2InstanceNode } from '../../../awsService/ec2/explorer/ec2InstanceNode' -import { Ec2ParentNode } from '../../../awsService/ec2/explorer/ec2ParentNode' -import { Ec2Client } from '../../../shared/clients/ec2' -import { Ec2Connecter } from '../../../awsService/ec2/model' -import { PollingSet } from '../../../shared/utilities/pollingSet' - -describe('ec2 activation', function () { - let testNode: Ec2InstanceNode - - before(function () { - const testRegion = 'test-region' - const testPartition = 'test-partition' - // Don't want to be polling here, that is tested in ../ec2ParentNode.test.ts - // disabled here for convenience (avoiding race conditions with timeout) - sinon.stub(PollingSet.prototype, 'add') - const testClient = new Ec2Client(testRegion) - const parentNode = new Ec2ParentNode(testRegion, testPartition, new Ec2Client(testRegion)) - testNode = new Ec2InstanceNode(parentNode, testClient, testRegion, testPartition, { - InstanceId: 'testId', - LastSeenStatus: 'running', - }) - }) - - after(function () { - sinon.restore() - }) - - it('telemetry', async function () { - const terminalStub = sinon.stub(Ec2Connecter.prototype, 'attemptToOpenEc2Terminal') - await vscode.commands.executeCommand('aws.ec2.openTerminal', testNode) - - assertTelemetry('ec2_connectToInstance', { ec2ConnectionType: 'ssm' }) - terminalStub.restore() - - const stopInstanceStub = sinon.stub(Ec2Client.prototype, 'stopInstanceWithCancel') - await vscode.commands.executeCommand('aws.ec2.stopInstance', testNode) - - assertTelemetry('ec2_changeState', { ec2InstanceState: 'stop' }) - stopInstanceStub.restore() - }) -}) diff --git a/packages/core/src/test/awsService/ec2/commands.test.ts b/packages/core/src/test/awsService/ec2/commands.test.ts deleted file mode 100644 index 67974692bd..0000000000 --- a/packages/core/src/test/awsService/ec2/commands.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { Ec2Selection } from '../../../awsService/ec2/prompter' -import { Ec2ConnecterMap } from '../../../awsService/ec2/connectionManagerMap' - -describe('getConnectionManager', async function () { - let connectionManagers: Ec2ConnecterMap - - beforeEach(function () { - connectionManagers = new Ec2ConnecterMap() - }) - - it('only creates new connection managers once for each region ', async function () { - const fakeSelection: Ec2Selection = { - region: 'region-1', - instanceId: 'fake-id', - } - - const cm = connectionManagers.getOrInit(fakeSelection.region) - assert.strictEqual(connectionManagers.size, 1) - - await cm.addActiveSession('sessionId', 'instanceId') - - const cm2 = connectionManagers.getOrInit(fakeSelection.region) - - assert.strictEqual(cm2.isConnectedTo('instanceId'), true) - }) -}) diff --git a/packages/core/src/test/awsService/ec2/ec2LogDocumentProvider.test.ts b/packages/core/src/test/awsService/ec2/ec2LogDocumentProvider.test.ts deleted file mode 100644 index 179008c753..0000000000 --- a/packages/core/src/test/awsService/ec2/ec2LogDocumentProvider.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { Ec2LogDocumentProvider } from '../../../awsService/ec2/ec2LogDocumentProvider' -import { ec2LogsScheme } from '../../../shared/constants' -import { Ec2Client } from '../../../shared/clients/ec2' - -describe('LogDataDocumentProvider', async function () { - let provider: Ec2LogDocumentProvider - - before(function () { - provider = new Ec2LogDocumentProvider() - }) - - it('throws error on attempt to get content from other schemes', async function () { - const wrongSchemeUri = vscode.Uri.parse(`ec2-not:us-west1:id`) - - await assert.rejects(async () => await provider.provideTextDocumentContent(wrongSchemeUri), { - message: `Invalid EC2 Logs URI: ${wrongSchemeUri.toString()}`, - }) - }) - - it('fetches content for valid ec2 log URI', async function () { - const validUri = vscode.Uri.parse(`${ec2LogsScheme}:us-west1:instance1`) - const expectedContent = 'log content' - sinon.stub(Ec2Client.prototype, 'getConsoleOutput').resolves({ - InstanceId: 'instance1', - Output: expectedContent, - }) - const content = await provider.provideTextDocumentContent(validUri) - assert.strictEqual(content, expectedContent) - sinon.restore() - }) -}) diff --git a/packages/core/src/test/awsService/ec2/explorer/ec2InstanceNode.test.ts b/packages/core/src/test/awsService/ec2/explorer/ec2InstanceNode.test.ts deleted file mode 100644 index 3f97c2d3cd..0000000000 --- a/packages/core/src/test/awsService/ec2/explorer/ec2InstanceNode.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - Ec2InstanceNode, - Ec2InstancePendingContext, - Ec2InstanceRunningContext, - Ec2InstanceStoppedContext, -} from '../../../../awsService/ec2/explorer/ec2InstanceNode' -import { Ec2Client, Ec2Instance, getNameOfInstance } from '../../../../shared/clients/ec2' -import { Ec2ParentNode } from '../../../../awsService/ec2/explorer/ec2ParentNode' -import * as sinon from 'sinon' -import { PollingSet } from '../../../../shared/utilities/pollingSet' -import { InstanceStateName } from '@aws-sdk/client-ec2' - -describe('ec2InstanceNode', function () { - let testNode: Ec2InstanceNode - let testInstance: Ec2Instance - const testRegion = 'testRegion' - const testPartition = 'testPartition' - - before(function () { - testInstance = { - InstanceId: 'testId', - Tags: [ - { - Key: 'Name', - Value: 'testName', - }, - ], - LastSeenStatus: 'running', - } - sinon.stub(Ec2InstanceNode.prototype, 'updateStatus') - // Don't want to be polling here, that is tested in ../ec2ParentNode.test.ts - // disabled here for convenience (avoiding race conditions with timeout) - sinon.stub(PollingSet.prototype, 'add') - const testClient = new Ec2Client('') - const testParentNode = new Ec2ParentNode(testRegion, testPartition, testClient) - testNode = new Ec2InstanceNode(testParentNode, testClient, 'testRegion', 'testPartition', testInstance) - }) - - beforeEach(function () { - testNode.updateInstance(testInstance) - }) - - it('instantiates without issue', async function () { - assert.ok(testNode) - }) - - it('initializes the region code', async function () { - assert.strictEqual(testNode.regionCode, 'testRegion') - }) - - it('initializes the label', async function () { - assert.strictEqual( - testNode.label, - `${getNameOfInstance(testInstance)} (${testInstance.InstanceId}) ${testInstance.LastSeenStatus.toUpperCase()}` - ) - }) - - it('initializes the functionName', async function () { - assert.strictEqual(testNode.name, getNameOfInstance(testInstance)) - }) - - it('has no children', async function () { - const childNodes = await testNode.getChildren() - assert.ok(childNodes) - assert.strictEqual(childNodes.length, 0, 'Expected node to have no children') - }) - - it('has an EC2ParentNode as parent', async function () { - assert.ok(testNode.parent instanceof Ec2ParentNode) - }) - - it('intializes the client', async function () { - assert.ok(testNode.client instanceof Ec2Client) - }) - - it('sets context value based on status', async function () { - const stoppedInstance = { ...testInstance, LastSeenStatus: 'stopped' as InstanceStateName } - testNode.updateInstance(stoppedInstance) - assert.strictEqual(testNode.contextValue, Ec2InstanceStoppedContext) - - const runningInstance = { ...testInstance, LastSeenStatus: 'running' as InstanceStateName } - testNode.updateInstance(runningInstance) - assert.strictEqual(testNode.contextValue, Ec2InstanceRunningContext) - - const pendingInstance = { ...testInstance, LastSeenStatus: 'pending' as InstanceStateName } - testNode.updateInstance(pendingInstance) - assert.strictEqual(testNode.contextValue, Ec2InstancePendingContext) - }) - - it('updates status with new instance', async function () { - const newStatus = 'pending' - const newIdInstance = { ...testInstance, InstanceId: 'testId2', LastSeenStatus: newStatus as InstanceStateName } - testNode.updateInstance(newIdInstance) - assert.strictEqual(testNode.getStatus(), newStatus) - }) -}) diff --git a/packages/core/src/test/awsService/ec2/explorer/ec2ParentNode.test.ts b/packages/core/src/test/awsService/ec2/explorer/ec2ParentNode.test.ts deleted file mode 100644 index 72756722a1..0000000000 --- a/packages/core/src/test/awsService/ec2/explorer/ec2ParentNode.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { Ec2ParentNode } from '../../../../awsService/ec2/explorer/ec2ParentNode' -import { Ec2Client, Ec2Instance } from '../../../../shared/clients/ec2' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../../utilities/explorerNodeAssertions' -import { Ec2InstanceNode } from '../../../../awsService/ec2/explorer/ec2InstanceNode' -import * as FakeTimers from '@sinonjs/fake-timers' -import { installFakeClock } from '../../../testUtil' -import { Filter } from '@aws-sdk/client-ec2' -import { AsyncCollection } from '../../../../shared/utilities/asyncCollection' -import { intoCollection } from '../../../../shared/utilities/collectionUtils' - -export const testInstance = { - InstanceId: 'testId', - Tags: [ - { - Key: 'Name', - Value: 'testName', - }, - ], - LastSeenStatus: 'running', -} satisfies Ec2Instance -export const testClient = new Ec2Client('') -export const testParentNode = new Ec2ParentNode('fake-region', 'testPartition', testClient) - -describe('ec2ParentNode', function () { - let testNode: Ec2ParentNode - let client: Ec2Client - let getInstancesStub: sinon.SinonStub<[filters?: Filter[] | undefined], AsyncCollection> - let clock: FakeTimers.InstalledClock - let refreshStub: sinon.SinonStub<[], Promise> - let statusUpdateStub: sinon.SinonStub<[status: string], Promise> - const testRegion = 'testRegion' - const testPartition = 'testPartition' - - before(function () { - client = new Ec2Client(testRegion) - clock = installFakeClock() - refreshStub = sinon.stub(Ec2InstanceNode.prototype, 'refreshNode') - statusUpdateStub = sinon.stub(Ec2Client.prototype, 'getInstanceStatus') - }) - - beforeEach(function () { - getInstancesStub = sinon.stub(Ec2Client.prototype, 'getInstances') - testNode = new Ec2ParentNode(testRegion, testPartition, client) - refreshStub.resetHistory() - }) - - afterEach(function () { - getInstancesStub.restore() - testNode.pollingSet.clear() - testNode.pollingSet.clearTimer() - }) - - after(function () { - clock.uninstall() - sinon.restore() - }) - - it('returns placeholder node if no children are present', async function () { - getInstancesStub.returns(intoCollection([])) - - const childNodes = await testNode.getChildren() - assertNodeListOnlyHasPlaceholderNode(childNodes) - getInstancesStub.restore() - }) - - it('has instance child nodes', async function () { - const instances = [ - { Name: 'firstOne', InstanceId: '0', LastSeenStatus: 'running' }, - { Name: 'secondOne', InstanceId: '1', LastSeenStatus: 'stopped' }, - ] satisfies Ec2Instance[] - getInstancesStub.returns(intoCollection([instances])) - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, instances.length, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof Ec2InstanceNode, 'Expected child node to be Ec2InstanceNode') - } - - getInstancesStub.restore() - }) - - it('sorts child nodes', async function () { - const sortedText = ['aa', 'ab', 'bb', 'bc', 'cc', 'cd'] - const instances = [ - { Name: 'ab', InstanceId: '0', LastSeenStatus: 'running' }, - { Name: 'bb', InstanceId: '1', LastSeenStatus: 'running' }, - { Name: 'bc', InstanceId: '2', LastSeenStatus: 'running' }, - { Name: 'aa', InstanceId: '3', LastSeenStatus: 'running' }, - { Name: 'cc', InstanceId: '4', LastSeenStatus: 'running' }, - { Name: 'cd', InstanceId: '5', LastSeenStatus: 'running' }, - ] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => (node instanceof Ec2InstanceNode ? node.name : undefined)) - assert.deepStrictEqual(actualChildOrder, sortedText, 'Unexpected child sort order') - getInstancesStub.restore() - }) - - it('has an error node for a child if an error happens during loading', async function () { - getInstancesStub.throws(new Error()) - const node = new Ec2ParentNode(testRegion, testPartition, client) - assertNodeListOnlyHasErrorNode(await node.getChildren()) - getInstancesStub.restore() - }) - - it('is able to handle children with duplicate names', async function () { - const instances = [ - { Name: 'firstOne', InstanceId: '0', LastSeenStatus: 'running' }, - { Name: 'secondOne', InstanceId: '1', LastSeenStatus: 'running' }, - { Name: 'firstOne', InstanceId: '2', LastSeenStatus: 'running' }, - ] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, instances.length, 'Unexpected child count') - getInstancesStub.restore() - }) - - it('adds pending nodes to the polling nodes set', async function () { - const instances = [ - { Name: 'firstOne', InstanceId: '0', LastSeenStatus: 'pending' }, - { Name: 'secondOne', InstanceId: '1', LastSeenStatus: 'stopped' }, - { Name: 'thirdOne', InstanceId: '2', LastSeenStatus: 'running' }, - ] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - assert.strictEqual(testNode.pollingSet.size, 1) - getInstancesStub.restore() - }) - - it('does not refresh explorer when timer goes off if status unchanged', async function () { - statusUpdateStub = statusUpdateStub.resolves('pending') - const instances = [ - { Name: 'firstOne', InstanceId: '0', LastSeenStatus: 'pending' }, - { Name: 'secondOne', InstanceId: '1', LastSeenStatus: 'stopped' }, - { Name: 'thirdOne', InstanceId: '2', LastSeenStatus: 'running' }, - ] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - - await testNode.updateChildren() - await clock.tickAsync(6000) - sinon.assert.notCalled(refreshStub) - getInstancesStub.restore() - }) - - it('does refresh explorer when timer goes and status changed', async function () { - statusUpdateStub = statusUpdateStub.resolves('running') - const instances = [{ Name: 'firstOne', InstanceId: '0', LastSeenStatus: 'pending' }] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - - sinon.assert.notCalled(refreshStub) - await clock.tickAsync(6000) - sinon.assert.called(refreshStub) - }) - - it('returns the node when in the map', async function () { - const instances = [{ Name: 'firstOne', InstanceId: 'node1', LastSeenStatus: 'pending' }] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - const node = testNode.getInstanceNode('node1') - assert.strictEqual(node.InstanceId, instances[0].InstanceId) - getInstancesStub.restore() - }) - - it('throws error when node not in map', async function () { - const instances = [{ Name: 'firstOne', InstanceId: 'node1', LastSeenStatus: 'pending' }] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - assert.throws(() => testNode.getInstanceNode('node2')) - getInstancesStub.restore() - }) - - it('adds node to polling set when asked to track it', async function () { - const instances = [{ Name: 'firstOne', InstanceId: 'node1', LastSeenStatus: 'pending' }] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - testNode.trackPendingNode('node1') - assert.strictEqual(testNode.pollingSet.size, 1) - getInstancesStub.restore() - }) - - it('throws error when asked to track non-child node', async function () { - const instances = [{ Name: 'firstOne', InstanceId: 'node1', LastSeenStatus: 'pending' }] satisfies Ec2Instance[] - - getInstancesStub.returns(intoCollection([instances])) - await testNode.updateChildren() - assert.throws(() => testNode.trackPendingNode('node2')) - getInstancesStub.restore() - }) -}) diff --git a/packages/core/src/test/awsService/ec2/model.test.ts b/packages/core/src/test/awsService/ec2/model.test.ts deleted file mode 100644 index 5497236bbd..0000000000 --- a/packages/core/src/test/awsService/ec2/model.test.ts +++ /dev/null @@ -1,318 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { Ec2Connecter, getRemoveLinesCommand } from '../../../awsService/ec2/model' -import { SsmClient } from '../../../shared/clients/ssm' -import { Ec2Client } from '../../../shared/clients/ec2' -import { Ec2Selection } from '../../../awsService/ec2/prompter' -import { ToolkitError } from '../../../shared/errors' -import { SshKeyPair } from '../../../awsService/ec2/sshKeyPair' -import { IamClient, IamRole } from '../../../shared/clients/iam' -import { assertNoTelemetryMatch, createTestWorkspaceFolder } from '../../testUtil' -import { fs } from '../../../shared' -import path from 'path' -import { ChildProcess } from '../../../shared/utilities/processUtils' -import { isMac, isWin } from '../../../shared/vscode/env' -import { inspect } from '../../../shared/utilities/collectionUtils' -import { assertLogsContain } from '../../globalSetup.test' -import { InstanceStateName } from '@aws-sdk/client-ec2' - -describe('Ec2ConnectClient', function () { - let client: Ec2Connecter - - before(function () { - client = new Ec2Connecter('test-region') - }) - - describe('getAttachedIamRole', async function () { - it('only returns role if receives ARN from instance profile', async function () { - let role: IamRole | undefined - const getInstanceProfileStub = sinon.stub(Ec2Client.prototype, 'getAttachedIamInstanceProfile') - - getInstanceProfileStub.resolves({ Arn: 'thisIsAnArn' }) - sinon - .stub(IamClient.prototype, 'getIAMRoleFromInstanceProfile') - .resolves(createRoleWithArn('ThisIsARoleArn')) - - role = await client.getAttachedIamRole('test-instance') - assert.ok(role) - assert.ok(role.Arn) - - getInstanceProfileStub.resolves({}) - role = await client.getAttachedIamRole('test-instance') - assert.strictEqual(role, undefined) - sinon.restore() - }) - }) - - describe('hasProperPermissions', async function () { - it('throws error when sdk throws error', async function () { - sinon.stub(IamClient.prototype, 'simulatePrincipalPolicy').throws(new ToolkitError('error')) - - try { - await client.hasProperPermissions('') - assert.ok(false) - } catch { - assert.ok(true) - } - - sinon.restore() - }) - }) - - describe('isInstanceRunning', async function () { - it('only returns true with the instance is running', async function () { - sinon - .stub(Ec2Client.prototype, 'getInstanceStatus') - .callsFake(async (input: string) => input.split(':')[0] as InstanceStateName) - - const actualFirstResult = await client.isInstanceRunning('running:instance') - const actualSecondResult = await client.isInstanceRunning('stopped:instance') - - assert.strictEqual(true, actualFirstResult) - assert.strictEqual(false, actualSecondResult) - sinon.restore() - }) - }) - - describe('handleStartSessionError', async function () { - let instanceSelection: Ec2Selection - - before(function () { - instanceSelection = { instanceId: 'testInstance', region: 'testRegion' } - }) - - afterEach(function () { - sinon.restore() - }) - - it('throws EC2SSMStatus error if instance is not running', async function () { - sinon.stub(Ec2Connecter.prototype, 'isInstanceRunning').resolves(false) - - try { - await client.checkForStartSessionError(instanceSelection) - assert.ok(false) - } catch (err) { - assert.strictEqual((err as ToolkitError).code, 'EC2SSMStatus') - } - }) - - it('throws EC2SSMPermission error if instance is running but has no role', async function () { - sinon.stub(Ec2Connecter.prototype, 'isInstanceRunning').resolves(true) - sinon.stub(Ec2Connecter.prototype, 'getAttachedIamRole').resolves(undefined) - - try { - await client.checkForStartSessionError(instanceSelection) - assert.ok(false) - } catch (err) { - assert.strictEqual((err as ToolkitError).code, 'EC2SSMPermission') - } - }) - - it('throws EC2SSMAgent error if instance is running and has IAM Role, but agent is not running', async function () { - sinon.stub(Ec2Connecter.prototype, 'isInstanceRunning').resolves(true) - sinon.stub(Ec2Connecter.prototype, 'getAttachedIamRole').resolves(createRoleWithArn('testRole')) - sinon.stub(Ec2Connecter.prototype, 'hasProperPermissions').resolves(true) - sinon.stub(SsmClient.prototype, 'getInstanceAgentPingStatus').resolves('offline') - - try { - await client.checkForStartSessionError(instanceSelection) - assert.ok(false) - } catch (err) { - assert.strictEqual((err as ToolkitError).code, 'EC2SSMAgentStatus') - } - }) - - it('retries if agent status is not online', async function () { - const instanceAgentStatus = sinon.stub(SsmClient.prototype, 'getInstanceAgentPingStatus') - instanceAgentStatus.onFirstCall().resolves('Offline') - instanceAgentStatus.onSecondCall().resolves('Online') - try { - await client.checkForInstanceSsmError(instanceSelection, { interval: 10, timeout: 100 }) - } catch (err) { - assert.ok(false, `checkForInstanceSsmError failed with error '${err}'`) - } - }) - - it('does not throw an error if all checks pass', async function () { - sinon.stub(Ec2Connecter.prototype, 'isInstanceRunning').resolves(true) - sinon.stub(Ec2Connecter.prototype, 'getAttachedIamRole').resolves(createRoleWithArn('testRole')) - sinon.stub(Ec2Connecter.prototype, 'hasProperPermissions').resolves(true) - sinon.stub(SsmClient.prototype, 'getInstanceAgentPingStatus').resolves('Online') - - assert.doesNotThrow(async () => await client.checkForStartSessionError(instanceSelection)) - }) - }) - - describe('sendSshKeysToInstance', async function () { - it('calls the sdk with the proper parameters', async function () { - const sendCommandStub = sinon.stub(SsmClient.prototype, 'sendCommandAndWait') - - const testSelection = { - instanceId: 'test-id', - region: 'test-region', - } - - const keys = await SshKeyPair.getSshKeyPair('key', 30000) - await client.sendSshKeyToInstance(testSelection, keys, { name: 'test-user', os: 'Amazon Linux' }) - sinon.assert.calledWith(sendCommandStub, testSelection.instanceId, 'AWS-RunShellScript') - sinon.restore() - }) - - it('avoids writing the keys to any telemetry metrics', async function () { - sinon.stub(SsmClient.prototype, 'sendCommandAndWait') - - const testSelection = { - instanceId: 'test-id', - region: 'test-region', - } - const testWorkspaceFolder = await createTestWorkspaceFolder() - const keys = await SshKeyPair.getSshKeyPair('key', 60000) - await client.sendSshKeyToInstance(testSelection, keys, { name: 'test-user', os: 'Amazon Linux' }) - const privKey = await fs.readFileText(keys.getPrivateKeyPath()) - assertNoTelemetryMatch(privKey) - sinon.restore() - - await keys.delete() - await fs.delete(testWorkspaceFolder.uri, { force: true }) - }) - }) - - describe('getRemoteUser', async function () { - let getTargetPlatformNameStub: sinon.SinonStub<[target: string], Promise> - - before(async function () { - getTargetPlatformNameStub = sinon.stub(SsmClient.prototype, 'getTargetPlatformName') - }) - - after(async function () { - sinon.restore() - }) - - it('identifies the user for ubuntu as ubuntu', async function () { - getTargetPlatformNameStub.resolves('Ubuntu') - const remoteUser = await client.getRemoteUser('testInstance') - assert.strictEqual(remoteUser.name, 'ubuntu') - }) - - it('identifies the user for amazon linux as ec2-user', async function () { - getTargetPlatformNameStub.resolves('Amazon Linux') - const remoteUser = await client.getRemoteUser('testInstance') - assert.strictEqual(remoteUser.name, 'ec2-user') - }) - - it('throws error when not given known OS', async function () { - getTargetPlatformNameStub.resolves('ThisIsNotARealOs!') - try { - await client.getRemoteUser('testInstance') - assert.ok(false) - } catch (exception) { - assert.ok(true) - } - }) - }) - - describe('tryCleanKeys', async function () { - it('calls the sdk with the proper parameters', async function () { - const sendCommandStub = sinon.stub(SsmClient.prototype, 'sendCommandAndWait') - - const testSelection = { - instanceId: 'test-id', - region: 'test-region', - } - - await client.tryCleanKeys(testSelection.instanceId, 'hint', 'macOS', 'path/to/keys') - sendCommandStub.calledWith(testSelection.instanceId, 'AWS-RunShellScript', { - commands: [getRemoveLinesCommand('hint', 'macOS', 'path/to/keys')], - }) - sinon.assert.calledWith(sendCommandStub, testSelection.instanceId, 'AWS-RunShellScript') - sinon.restore() - }) - - it('logs warning when sdk call fails', async function () { - const sendCommandStub = sinon - .stub(SsmClient.prototype, 'sendCommandAndWait') - .throws(new ToolkitError('error')) - - const testSelection = { - instanceId: 'test-id', - region: 'test-region', - } - - await client.tryCleanKeys(testSelection.instanceId, 'hint', 'macOS', 'path/to/keys') - sinon.assert.calledWith(sendCommandStub, testSelection.instanceId, 'AWS-RunShellScript', { - commands: [getRemoveLinesCommand('hint', 'macOS', 'path/to/keys')], - }) - sinon.restore() - assertLogsContain('failed to clean keys', false, 'warn') - }) - }) -}) - -describe('getRemoveLinesCommand', async function () { - let tempPath: { uri: { fsPath: string } } - - before(async function () { - tempPath = await createTestWorkspaceFolder() - }) - - after(async function () { - await fs.delete(tempPath.uri.fsPath, { recursive: true, force: true }) - }) - - it('removes lines containing pattern', async function () { - if (isWin()) { - this.skip() - } - // For the test, we only need to distinguish mac and linux - const hostOS = isMac() ? 'macOS' : 'Amazon Linux' - const lines = ['line1', 'line2 pattern', 'line3', 'line4 pattern', 'line5', 'line6 pattern', 'line7'] - const expected = ['line1', 'line3', 'line5', 'line7'] - - const lineToStr = (ls: string[]) => ls.join('\n') + '\n' - - const textFile = path.join(tempPath.uri.fsPath, 'test.txt') - const originalContent = lineToStr(lines) - await fs.writeFile(textFile, originalContent) - const [command, ...args] = getRemoveLinesCommand('pattern', hostOS, textFile).split(' ') - const process = new ChildProcess(command, args, { collect: true }) - const result = await process.run() - assert.strictEqual( - result.exitCode, - 0, - `Ran command '${command} ${args.join(' ')}' and failed with result ${inspect(result)}` - ) - - const newContent = await fs.readFileText(textFile) - assert.notStrictEqual(newContent, originalContent) - assert.strictEqual(newContent, lineToStr(expected)) - }) - - it('includes empty extension on macOS only', async function () { - const macCommand = getRemoveLinesCommand('pattern', 'macOS', 'test.txt') - const alCommand = getRemoveLinesCommand('pattern', 'Amazon Linux', 'test.txt') - const ubuntuCommand = getRemoveLinesCommand('pattern', 'Ubuntu', 'test.txt') - - assert.ok(macCommand.includes("''")) - assert.ok(!alCommand.includes("''")) - assert.strictEqual(ubuntuCommand, alCommand) - }) - - it('throws when given invalid pattern', function () { - assert.throws(() => getRemoveLinesCommand('pat/tern', 'macOS', 'test.txt')) - }) -}) - -function createRoleWithArn(Arn: string) { - return { - RoleName: 'hasArn', - Arn, - Path: 'thisIsAPath', - RoleId: '1', - CreateDate: new Date(), - } -} diff --git a/packages/core/src/test/awsService/ec2/prompter.test.ts b/packages/core/src/test/awsService/ec2/prompter.test.ts deleted file mode 100644 index df73f1f358..0000000000 --- a/packages/core/src/test/awsService/ec2/prompter.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import * as sinon from 'sinon' -import { Ec2Prompter, getSelection } from '../../../awsService/ec2/prompter' -import { Ec2Instance } from '../../../shared/clients/ec2' -import { RegionSubmenuResponse } from '../../../shared/ui/common/regionSubmenu' -import { Ec2Selection } from '../../../awsService/ec2/prompter' -import { Ec2InstanceNode } from '../../../awsService/ec2/explorer/ec2InstanceNode' -import { testClient, testInstance, testParentNode } from './explorer/ec2ParentNode.test' -import { intoCollection } from '../../../shared/utilities/collectionUtils' -import { AsyncCollection } from '../../../shared/utilities/asyncCollection' -import { DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import { getTestWindow } from '../../shared/vscode/window' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' - -describe('Ec2Prompter', async function () { - const defaultInstances: Ec2Instance[][] = [ - [ - { - InstanceId: '1', - Name: 'first', - LastSeenStatus: 'running', - }, - { - InstanceId: '2', - Name: 'second', - LastSeenStatus: 'running', - }, - ], - [ - { - InstanceId: '3', - Name: 'third', - LastSeenStatus: 'running', - }, - ], - ] - - const defaultQuickPickItems: DataQuickPickItem[] = [ - { - label: Ec2Prompter.getLabel(defaultInstances[0][0]), - detail: defaultInstances[0][0].InstanceId, - data: defaultInstances[0][0].InstanceId, - }, - { - label: Ec2Prompter.getLabel(defaultInstances[0][1]), - detail: defaultInstances[0][1].InstanceId, - data: defaultInstances[0][1].InstanceId, - }, - { - label: Ec2Prompter.getLabel(defaultInstances[1][0]), - detail: defaultInstances[1][0].InstanceId, - data: defaultInstances[1][0].InstanceId, - }, - ] - const defaultGetInstances: (regionCode: string) => AsyncCollection = (_) => - intoCollection(defaultInstances) - - it('initializes properly', function () { - const prompter = new Ec2Prompter() - assert.ok(prompter) - }) - - it('does not show items, when no instances returned', async function () { - const prompter = new Ec2Prompter({ getInstancesFromRegion: (_) => intoCollection([]) }) - getTestWindow().onDidShowQuickPick(async (qp) => { - await qp.untilReady() - assert.deepStrictEqual(qp.items.slice(3), []) - qp.hide() - }) - await assert.rejects(async () => await prompter.promptUser(), CancellationError) - }) - - it('shows all items when no filter', async function () { - const prompter = new Ec2Prompter({ getInstancesFromRegion: defaultGetInstances }) - getTestWindow().onDidShowQuickPick(async (qp) => { - await qp.untilReady() - qp.assertContainsItems(...defaultQuickPickItems) - qp.acceptItem(defaultQuickPickItems[0].label) - }) - await prompter.promptUser() - }) - - it('shows filtered items when filter present', async function () { - const prompter = new Ec2Prompter({ - getInstancesFromRegion: defaultGetInstances, - instanceFilter: (i) => parseInt(i.InstanceId) % 2 === 1, - }) - - getTestWindow().onDidShowQuickPick(async (qp) => { - await qp.untilReady() - qp.assertContainsItems(defaultQuickPickItems[0], defaultQuickPickItems[2]) - qp.acceptItem(defaultQuickPickItems[0].label) - }) - await prompter.promptUser() - }) - - describe('asQuickPickItem', async function () { - const testQuickPick = (instance: Ec2Instance) => { - assert.deepStrictEqual(Ec2Prompter.asQuickPickItem(instance), { - label: Ec2Prompter.getLabel(instance), - detail: instance.InstanceId, - data: instance.InstanceId, - }) - } - - it('returns QuickPickItem for named instances', function () { - testQuickPick({ - Name: 'testName', - InstanceId: 'testInstanceId', - LastSeenStatus: 'running', - }) - }) - - it('returns QuickPickItem for non-named instances', function () { - testQuickPick({ - InstanceId: 'testInstanceId', - LastSeenStatus: 'running', - }) - }) - }) - - describe('handleEc2ConnectPrompterResponse', function () { - it('returns correctly formatted Ec2Selection', function () { - const testResponse: RegionSubmenuResponse = { - region: 'test-region', - data: 'testInstance', - } - - const result = Ec2Prompter.getSelectionFromResponse(testResponse) - const expected: Ec2Selection = { - instanceId: testResponse.data, - region: testResponse.region, - } - - assert.deepStrictEqual(result, expected) - }) - }) - - describe('getSelection', async function () { - it('uses node when passed', async function () { - const prompterStub = sinon.stub(Ec2Prompter.prototype, 'promptUser') - const testNode = new Ec2InstanceNode( - testParentNode, - testClient, - 'testRegion', - 'testPartition', - testInstance - ) - const result = await getSelection(testNode) - - assert.strictEqual(result.instanceId, testNode.toSelection().instanceId) - assert.strictEqual(result.region, testNode.toSelection().region) - sinon.assert.notCalled(prompterStub) - prompterStub.restore() - }) - - it('prompts user when no node is passed', async function () { - const prompterStub = sinon.stub(Ec2Prompter.prototype, 'promptUser') - await getSelection() - sinon.assert.calledOnce(prompterStub) - prompterStub.restore() - }) - }) -}) diff --git a/packages/core/src/test/awsService/ec2/remoteSessionManager.test.ts b/packages/core/src/test/awsService/ec2/remoteSessionManager.test.ts deleted file mode 100644 index 48d273e416..0000000000 --- a/packages/core/src/test/awsService/ec2/remoteSessionManager.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { Ec2SessionTracker } from '../../../awsService/ec2/remoteSessionManager' -import { SsmClient } from '../../../shared/clients/ssm' - -describe('Ec2RemoteSessionManager', async function () { - it('maintains connections to instances', async function () { - const envManager = new Ec2SessionTracker('test-region', new SsmClient('test-region')) - await envManager.addSession('test-instance', 'test-env') - await envManager.addSession('test-instance2', 'test-env2') - await envManager.addSession('test-instance3', 'test-env3') - - assert(envManager.isConnectedTo('test-instance')) - assert(envManager.isConnectedTo('test-instance2')) - assert(envManager.isConnectedTo('test-instance3')) - assert(!envManager.isConnectedTo('test-instance4')) - }) - - it('only allows for single connection with any given instance', async function () { - const envManager = new Ec2SessionTracker('test-region', new SsmClient('test-region')) - const terminateStub = sinon.stub(SsmClient.prototype, 'terminateSessionFromId') - - await envManager.addSession('test-instance', 'test-env') - sinon.assert.notCalled(terminateStub) - await envManager.addSession('test-instance', 'test-env2') - - sinon.assert.calledWith(terminateStub, 'test-env') - - assert(envManager.isConnectedTo('test-instance')) - - terminateStub.restore() - }) - - it('closes all active connections', async function () { - const envManager = new Ec2SessionTracker('test-region', new SsmClient('test-region')) - const terminateStub = sinon.stub(SsmClient.prototype, 'terminateSessionFromId') - - await envManager.addSession('test-instance', 'test-env') - await envManager.addSession('test-instance2', 'test-env2') - await envManager.addSession('test-instance3', 'test-env3') - - await envManager.dispose() - - sinon.assert.calledThrice(terminateStub) - assert(!envManager.isConnectedTo('test-instance')) - assert(!envManager.isConnectedTo('test-instance2')) - assert(!envManager.isConnectedTo('test-instance3')) - - terminateStub.restore() - }) -}) diff --git a/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts b/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts deleted file mode 100644 index 144f436e31..0000000000 --- a/packages/core/src/test/awsService/ec2/sshKeyPair.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import nodefs from 'fs' // eslint-disable-line no-restricted-imports -import * as sinon from 'sinon' -import * as os from 'os' -import { SshKeyPair } from '../../../awsService/ec2/sshKeyPair' -import { installFakeClock } from '../../testUtil' -import { InstalledClock } from '@sinonjs/fake-timers' -import { ChildProcess } from '../../../shared/utilities/processUtils' -import { fs, globals } from '../../../shared' - -describe('SshKeyPair', async function () { - let clock: InstalledClock - let keyPair: SshKeyPair - let keyName: string - - before(async function () { - clock = installFakeClock() - }) - - beforeEach(async function () { - keyName = 'testKeyPair' - keyPair = await SshKeyPair.getSshKeyPair(keyName, 30000) - }) - - afterEach(async function () { - await keyPair.delete() - }) - - after(async function () { - clock.uninstall() - sinon.restore() - }) - - it('generates key in target file', async function () { - const contents = await fs.readFileBytes(keyPair.getPrivateKeyPath()) - assert.notStrictEqual(contents.length, 0) - }) - - it('generates unique key each time', async function () { - const keyPair2 = await SshKeyPair.getSshKeyPair(`${keyName}2`, 30000) - const content1 = await fs.readFileBytes(keyPair2.getPrivateKeyPath()) - const content2 = await fs.readFileBytes(keyPair.getPrivateKeyPath()) - assert.notStrictEqual(content1, content2) - await keyPair2.delete() - }) - - it('sets permission of the file to read/write owner', async function () { - if (!globals.isWeb && os.platform() !== 'win32') { - const result = nodefs.statSync(keyPair.getPrivateKeyPath()) - assert.strictEqual(result.mode & 0o777, 0o600) - } - }) - - it('defaults to ed25519 key type', async function () { - const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', keyPair.getPrivateKeyPath()]) - const result = await process.run() - // Check private key header for algorithm name - assert.strictEqual(result.stdout.includes('[ED25519 256]'), true) - }) - - it('falls back on rsa if ed25519 not available', async function () { - await keyPair.delete() - const stub = sinon.stub(SshKeyPair, 'tryKeyGen') - stub.onFirstCall().resolves(false) - stub.callThrough() - const rsaKey = await SshKeyPair.getSshKeyPair('rsa', 30000) - const process = new ChildProcess(`ssh-keygen`, ['-vvv', '-l', '-f', rsaKey.getPrivateKeyPath()]) - const result = await process.run() - // Check private key header for algorithm name - assert.strictEqual(result.stdout.includes('[RSA'), true) - stub.restore() - }) - - it('reads in public ssh key that is non-empty', async function () { - const key = await keyPair.getPublicKey() - assert.notStrictEqual(key.length, 0) - }) - - it('does overwrite existing keys on get call', async function () { - const generateStub = sinon.spy(SshKeyPair, 'generateSshKeyPair') - const keyBefore = await fs.readFileBytes(keyPair.getPrivateKeyPath()) - keyPair = await SshKeyPair.getSshKeyPair(keyName, 30000) - - const keyAfter = await fs.readFileBytes(keyPair.getPrivateKeyPath()) - sinon.assert.calledOnce(generateStub) - - assert.notStrictEqual(keyBefore, keyAfter) - sinon.restore() - }) - - it('deletes key on delete', async function () { - const pubKeyExistsBefore = await fs.existsFile(keyPair.getPublicKeyPath()) - const privateKeyExistsBefore = await fs.existsFile(keyPair.getPrivateKeyPath()) - - await keyPair.delete() - - const pubKeyExistsAfter = await fs.existsFile(keyPair.getPublicKeyPath()) - const privateKeyExistsAfter = await fs.existsFile(keyPair.getPrivateKeyPath()) - - assert.strictEqual(pubKeyExistsBefore && privateKeyExistsBefore, true) - assert.strictEqual(pubKeyExistsAfter && privateKeyExistsAfter, false) - assert(keyPair.isDeleted()) - }) - - it('deletes keys after timeout', async function () { - // Stub methods interacting with file system to avoid flaky test. - sinon.stub(SshKeyPair, 'generateSshKeyPair') - const deleteStub = sinon.stub(SshKeyPair.prototype, 'delete') - - keyPair = await SshKeyPair.getSshKeyPair(keyName, 50) - await clock.tickAsync(10) - sinon.assert.notCalled(deleteStub) - await clock.tickAsync(100) - sinon.assert.calledOnce(deleteStub) - sinon.restore() - }) - - it('determines deleted status based on file system', async function () { - await fs.delete(keyPair.getPrivateKeyPath()) - await fs.delete(keyPair.getPublicKeyPath()) - - assert(keyPair.isDeleted()) - }) - - describe('isDeleted', async function () { - it('returns false if key files exist', async function () { - assert.strictEqual(await keyPair.isDeleted(), false) - }) - - it('returns true if key files do not exist', async function () { - await keyPair.delete() - assert.strictEqual(await keyPair.isDeleted(), true) - }) - - it('returns true if private key remains', async function () { - await fs.delete(keyPair.getPublicKeyPath()) - assert.strictEqual(await keyPair.isDeleted(), true) - }) - - it('returns true if public key remains', async function () { - await fs.delete(keyPair.getPrivateKeyPath()) - assert.strictEqual(await keyPair.isDeleted(), true) - }) - }) -}) diff --git a/packages/core/src/test/awsService/ec2/utils.test.ts b/packages/core/src/test/awsService/ec2/utils.test.ts deleted file mode 100644 index 15e2fb2635..0000000000 --- a/packages/core/src/test/awsService/ec2/utils.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { Ec2Instance } from '../../../shared/clients/ec2' -import { getIconCode } from '../../../awsService/ec2/utils' -import { DefaultAwsContext } from '../../../shared' - -describe('utils', async function () { - before(function () { - sinon.stub(DefaultAwsContext.prototype, 'getCredentialAccountId') - }) - - after(function () { - sinon.restore() - }) - - describe('getIconCode', function () { - it('gives code based on status', function () { - const runningInstance: Ec2Instance = { - InstanceId: 'X', - LastSeenStatus: 'running', - } - const stoppedInstance: Ec2Instance = { - InstanceId: 'XX', - LastSeenStatus: 'stopped', - } - const terminatedInstance: Ec2Instance = { - InstanceId: 'XXX', - LastSeenStatus: 'terminated', - } - - assert.strictEqual(getIconCode(runningInstance), 'pass') - assert.strictEqual(getIconCode(stoppedInstance), 'circle-slash') - assert.strictEqual(getIconCode(terminatedInstance), 'stop') - }) - - it('defaults to loading~spin', function () { - const pendingInstance: Ec2Instance = { - InstanceId: 'X', - LastSeenStatus: 'pending', - } - const stoppingInstance: Ec2Instance = { - InstanceId: 'XX', - LastSeenStatus: 'shutting-down', - } - - assert.strictEqual(getIconCode(pendingInstance), 'loading~spin') - assert.strictEqual(getIconCode(stoppingInstance), 'loading~spin') - }) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/commands/copyRepositoryUri.test.ts b/packages/core/src/test/awsService/ecr/commands/copyRepositoryUri.test.ts deleted file mode 100644 index 36dd78843b..0000000000 --- a/packages/core/src/test/awsService/ecr/commands/copyRepositoryUri.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { EcrRepositoryNode } from '../../../../awsService/ecr/explorer/ecrRepositoryNode' -import { EcrNode } from '../../../../awsService/ecr/explorer/ecrNode' -import { EcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { copyRepositoryUri } from '../../../../awsService/ecr/commands/copyRepositoryUri' -import { getTestWindow } from '../../../shared/vscode/window' -import { FakeClipboard } from '../../../shared/vscode/fakeEnv' - -describe('copyUriCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - it('Copies URI to clipboard and shows in the status bar', async function () { - const node = new EcrRepositoryNode( - {} as EcrNode, - {} as EcrClient, - { repositoryUri: 'www.amazon.com' } as EcrRepository - ) - - await copyRepositoryUri(node) - - assert.strictEqual(await vscode.env.clipboard.readText(), 'www.amazon.com') - assert.deepStrictEqual(getTestWindow().statusBar.messages, ['$(clippy) Copied URI to clipboard']) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/commands/copyTagUri.test.ts b/packages/core/src/test/awsService/ecr/commands/copyTagUri.test.ts deleted file mode 100644 index 5f9326e4a9..0000000000 --- a/packages/core/src/test/awsService/ecr/commands/copyTagUri.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { EcrRepositoryNode } from '../../../../awsService/ecr/explorer/ecrRepositoryNode' -import { EcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { EcrTagNode } from '../../../../awsService/ecr/explorer/ecrTagNode' -import { copyTagUri } from '../../../../awsService/ecr/commands/copyTagUri' -import { getTestWindow } from '../../../shared/vscode/window' -import { FakeClipboard } from '../../../shared/vscode/fakeEnv' - -describe('copyTagUriCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - it('Copies URI to clipboard and shows in the status bar', async function () { - const parentNode = {} as EcrRepositoryNode - const node = new EcrTagNode( - parentNode, - {} as EcrClient, - { repositoryUri: 'www.amazon.com' } as EcrRepository, - 'tag' - ) - - await copyTagUri(node) - - assert.strictEqual(await vscode.env.clipboard.readText(), 'www.amazon.com:tag') - assert.deepStrictEqual(getTestWindow().statusBar.messages, ['$(clippy) Copied URI to clipboard']) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/commands/createRepository.test.ts b/packages/core/src/test/awsService/ecr/commands/createRepository.test.ts deleted file mode 100644 index f0e5b549b3..0000000000 --- a/packages/core/src/test/awsService/ecr/commands/createRepository.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import assert from 'assert' -import { EcrNode } from '../../../../awsService/ecr/explorer/ecrNode' -import { DefaultEcrClient } from '../../../../shared/clients/ecrClient' -import { createRepository } from '../../../../awsService/ecr/commands/createRepository' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('createRepositoryCommand', function () { - const ecr = new DefaultEcrClient('') - let node: EcrNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - node = new EcrNode(ecr) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for repo name, creates repo, shows success, and refreshes node', async function () { - const repoName = 'amazingecrrepo' - - const stub = sandbox.stub(ecr, 'createRepository').callsFake(async (name) => { - assert.strictEqual(name, repoName) - return {} as any - }) - - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter a new repository name') - assert.strictEqual(input.placeholder, 'Repository Name') - input.acceptValue(repoName) - }) - await createRepository(node) - - assert.ok(stub.calledOnce) - getTestWindow().getFirstMessage().assertInfo(`Created repository: ${repoName}`) - assert(spyExecuteCommand.calledWith('aws.refreshAwsExplorerNode', node)) - }) - - it('does nothing when prompt is cancelled', async function () { - getTestWindow().onDidShowInputBox((input) => input.hide()) - const spy = sandbox.spy(ecr, 'createRepository') - - await createRepository(node) - - assert.ok(spy.notCalled) - }) - - it('Shows an error message and refreshes node when repository creation fails', async function () { - sandbox.stub(ecr, 'createRepository').callsFake(async () => { - throw Error('Network busted') - }) - - getTestWindow().onDidShowInputBox((input) => input.acceptValue('input')) - await createRepository(node) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create repository/) - - assert(spyExecuteCommand.calledWith('aws.refreshAwsExplorerNode', node)) - }) - - it('Warns when repository name is invalid', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('404') - assert.strictEqual(input.validationMessage, 'Repository name must start with a lowercase letter') - input.hide() - }) - - await createRepository(node) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/commands/deleteRepository.test.ts b/packages/core/src/test/awsService/ecr/commands/deleteRepository.test.ts deleted file mode 100644 index 51846cec31..0000000000 --- a/packages/core/src/test/awsService/ecr/commands/deleteRepository.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import assert from 'assert' -import { EcrNode } from '../../../../awsService/ecr/explorer/ecrNode' -import { DefaultEcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { EcrRepositoryNode } from '../../../../awsService/ecr/explorer/ecrRepositoryNode' -import { deleteRepository } from '../../../../awsService/ecr/commands/deleteRepository' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('deleteRepositoryCommand', function () { - const repositoryName = 'reponame' - const parentNode: EcrNode = {} as EcrNode - const ecr = new DefaultEcrClient('') - let node: EcrRepositoryNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - node = new EcrRepositoryNode(parentNode, ecr, { repositoryName: repositoryName } as EcrRepository) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('Confirms deletion, deletes repository, shows progress bar, and refreshes parent node', async function () { - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, `Enter ${repositoryName} to confirm deletion`) - assert.strictEqual(input.placeholder, repositoryName) - input.acceptValue(repositoryName) - }) - const stub = sandbox.stub(ecr, 'deleteRepository').callsFake(async (name) => { - assert.strictEqual(name, repositoryName) - }) - - await deleteRepository(node) - - getTestWindow().getFirstMessage().assertInfo(`Deleted repository: ${repositoryName}`) - assert.strictEqual(stub.calledOnce, true) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('Does nothing when deletion is cancelled', async function () { - getTestWindow().onDidShowInputBox((input) => input.hide()) - const spy = sandbox.spy(ecr, 'deleteRepository') - - await deleteRepository(node) - - assert.ok(spy.notCalled) - }) - - it('shows an error message and refreshes node when repository deletion fails', async function () { - sandbox.stub(ecr, 'deleteRepository').callsFake(async () => { - throw Error('Network busted') - }) - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(repositoryName)) - await deleteRepository(node) - - getTestWindow().getFirstMessage().assertError(`Failed to delete repository: ${repositoryName}`) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('Warns when confirmation is invalid', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('something other than the repo name') - assert.strictEqual(input.validationMessage, `Enter ${repositoryName} to confirm deletion`) - input.hide() - }) - - await deleteRepository(node) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/commands/deleteTag.test.ts b/packages/core/src/test/awsService/ecr/commands/deleteTag.test.ts deleted file mode 100644 index 1b6aed0993..0000000000 --- a/packages/core/src/test/awsService/ecr/commands/deleteTag.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import assert from 'assert' -import { EcrTagNode } from '../../../../awsService/ecr/explorer/ecrTagNode' -import { EcrRepositoryNode } from '../../../../awsService/ecr/explorer/ecrRepositoryNode' -import { DefaultEcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { deleteTag } from '../../../../awsService/ecr/commands/deleteTag' -import { assertNoErrorMessages, getTestWindow } from '../../../shared/vscode/window' - -describe('deleteTag', function () { - const repositoryName = 'reponame' - const tagName = 'tag' - const parentNode = {} as EcrRepositoryNode - const ecr = new DefaultEcrClient('') - let node: EcrTagNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - node = new EcrTagNode(parentNode, ecr, { repositoryName: repositoryName } as EcrRepository, tagName) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes file, shows status bar confirmation, and refreshes parent node', async function () { - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - const stub = sandbox.stub(ecr, 'deleteTag').callsFake(async (name, tag) => { - assert.strictEqual(name, repositoryName) - assert.strictEqual(tag, tagName) - }) - - await deleteTag(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to delete tag tag from repository reponame') - - assert.strictEqual(stub.calledOnce, true) - - getTestWindow().getSecondMessage().assertInfo(`Deleted tag ${tagName} from repository ${repositoryName}`) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing when deletion is cancelled', async function () { - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - const spy = sandbox.spy(ecr, 'deleteTag') - - await deleteTag(node) - - assert.strictEqual(spy.notCalled, true) - - assert.deepStrictEqual(getTestWindow().statusBar.messages, []) - assertNoErrorMessages() - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message and refreshes node when file deletion fails', async function () { - sandbox.stub(ecr, 'deleteTag').callsFake(async () => { - throw new Error('network broke') - }) - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - - await deleteTag(node) - - getTestWindow() - .getSecondMessage() - .assertError(new RegExp(`^Failed to delete tag ${tagName}`)) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/explorer/EcrNode.test.ts b/packages/core/src/test/awsService/ecr/explorer/EcrNode.test.ts deleted file mode 100644 index 9d9dd1c103..0000000000 --- a/packages/core/src/test/awsService/ecr/explorer/EcrNode.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import { DefaultEcrClient, EcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { EcrNode } from '../../../../awsService/ecr/explorer/ecrNode' -import { EcrRepositoryNode } from '../../../../awsService/ecr/explorer/ecrRepositoryNode' -import { PlaceholderNode } from '../../../../shared/treeview/nodes/placeholderNode' -import { EcrTagNode } from '../../../../awsService/ecr/explorer/ecrTagNode' -import { assertNodeListOnlyHasErrorNode } from '../../../utilities/explorerNodeAssertions' - -describe('EcrNode', function () { - let ecr: EcrClient - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - ecr = new DefaultEcrClient('') - }) - - afterEach(function () { - sandbox.restore() - }) - - it('Gets children and sorts them by repository name', async function () { - const firstRepo: EcrRepository = { repositoryName: 'name', repositoryArn: 'arn', repositoryUri: 'uri' } - const secondRepo: EcrRepository = { repositoryName: 'uri', repositoryArn: 'arn', repositoryUri: 'uri' } - sinon.stub(ecr, 'describeRepositories').callsFake(async function* () { - yield secondRepo - yield firstRepo - }) - - const [firstNode, secondNode, ...otherNodes] = await new EcrNode(ecr).getChildren() - - // repos are sorted by the top level node, so first repo should be first even though - // it is yielded second - assert.strictEqual((firstNode as EcrRepositoryNode).repository, firstRepo) - assert.strictEqual(firstNode.label, firstRepo.repositoryName) - assert.strictEqual((secondNode as EcrRepositoryNode).repository, secondRepo) - assert.strictEqual(secondNode.label, secondRepo.repositoryName) - - assert.strictEqual(otherNodes.length, 0) - }) - - it('Shows empty node on no children', async function () { - const stub = sinon.stub(ecr, 'describeRepositories').callsFake(async function* () {}) - - const [firstNode, ...otherNodes] = await new EcrNode(ecr).getChildren() - - assert.strictEqual((firstNode as PlaceholderNode).label, '[No repositories found]') - - assert.strictEqual(otherNodes.length, 0) - assert.ok(stub.calledOnce) - }) - - it('Shows error node when getting children fails', async function () { - const stub = sinon.stub(ecr, 'describeRepositories').callsFake(async function* () { - throw Error('network super busted') - // at least one yield is required for async generator even if it is unreachable - yield {} as EcrRepository - }) - - const children = await new EcrNode(ecr).getChildren() - - assertNodeListOnlyHasErrorNode(children) - assert.ok(stub.calledOnce) - }) -}) - -describe('EcrRepositoryNode', function () { - const repository: EcrRepository = { repositoryName: 'name', repositoryUri: 'uri', repositoryArn: 'arn' } - let ecr: EcrClient - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - ecr = new DefaultEcrClient('') - }) - - afterEach(function () { - sandbox.restore() - }) - - it('Gets children and sorts them by tag name', async function () { - sinon.stub(ecr, 'describeTags').callsFake(async function* () { - yield 'ztag' - yield 'atag' - }) - - const [firstNode, secondNode, ...otherNodes] = await new EcrRepositoryNode( - {} as EcrNode, - ecr, - repository - ).getChildren() - - // repos are sorted by the top level node, so first node should be first even though - // it is yielded second - assert.strictEqual((firstNode as EcrTagNode).repository, repository) - assert.strictEqual((firstNode as EcrTagNode).tag, 'atag') - assert.strictEqual(firstNode.label, 'atag') - assert.strictEqual((secondNode as EcrTagNode).repository, repository) - assert.strictEqual((secondNode as EcrTagNode).tag, 'ztag') - assert.strictEqual(secondNode.label, 'ztag') - - assert.strictEqual(otherNodes.length, 0) - }) - - it('Shows empty node on no children', async function () { - const stub = sinon.stub(ecr, 'describeTags').callsFake(async function* () {}) - - const [firstNode, ...otherNodes] = await new EcrRepositoryNode({} as EcrNode, ecr, repository).getChildren() - - assert.strictEqual((firstNode as PlaceholderNode).label, '[No tags found]') - - assert.strictEqual(otherNodes.length, 0) - assert.ok(stub.calledOnce) - }) - - it('Shows error node when getting children fails', async function () { - const stub = sinon.stub(ecr, 'describeTags').callsFake(async function* () { - throw Error('network super busted') - // at least one yield is required for async generator even if it is unreachable - yield 'string' - }) - - const children = await new EcrRepositoryNode({} as EcrNode, ecr, repository).getChildren() - - assertNodeListOnlyHasErrorNode(children) - assert.ok(stub.calledOnce) - }) -}) diff --git a/packages/core/src/test/awsService/ecr/utils.test.ts b/packages/core/src/test/awsService/ecr/utils.test.ts deleted file mode 100644 index 6d0feee0db..0000000000 --- a/packages/core/src/test/awsService/ecr/utils.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { validateRepositoryName } from '../../../awsService/ecr/utils' - -describe('createRepositoryCommand', function () { - it('Validates repository name starts with a lowercase letter', function () { - const message = validateRepositoryName('404') - assert.strictEqual(message, 'Repository name must start with a lowercase letter') - }) - - it('Validates repository name does not contain uppercase characters', function () { - const message = validateRepositoryName('abcDEF') - assert.strictEqual( - message, - 'Repository name must only contain lowercase letters, numbers, hyphens, underscores, and forward slashes' - ) - }) - - it('Validates repository name against large regex the service uses', function () { - for (const item of ['abc--a', 'abc//a', 'abc__a', 'abc-', 'abc/', 'abc_']) { - assert.strictEqual(validateRepositoryName(item), 'Invalid repository name') - } - }) - - it('Allows lowercase names with slashes, underscores, and dashes', function () { - const message = validateRepositoryName('abc1/def2/hij3_klmno-p5') - assert.strictEqual(message, undefined) - }) -}) diff --git a/packages/core/src/test/awsService/ecs/commands.test.ts b/packages/core/src/test/awsService/ecs/commands.test.ts deleted file mode 100644 index cd08f3306c..0000000000 --- a/packages/core/src/test/awsService/ecs/commands.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { DefaultEcsClient } from '../../../shared/clients/ecsClient' -import { TestSettings } from '../../utilities/testSettingsConfiguration' -import { ToolkitPromptSettings } from '../../../shared/settings' -import { Service } from '../../../awsService/ecs/model' -import { stub } from '../../utilities/stubber' -import { EcsRunCommandPrompt, toggleExecuteCommandFlag } from '../../../awsService/ecs/commands' - -describe('toggleExecuteCommandFlag', async function () { - const settings = new ToolkitPromptSettings(new TestSettings()) - - before(async function () { - await settings.disablePrompt(EcsRunCommandPrompt.Enable) - await settings.disablePrompt(EcsRunCommandPrompt.Disable) - }) - - it('attempts to enable service', async function () { - const client = stub(DefaultEcsClient, { regionCode: '' }) - client.updateService.callsFake(async (request) => assert.strictEqual(request.enableExecuteCommand, true)) - const service = new Service(client, {}) - const didChange = new Promise((resolve) => { - service.onDidChangeTreeItem(() => resolve(true)) - setTimeout(() => resolve(false), 1000) - }) - - await toggleExecuteCommandFlag(service, vscode.window, settings) - assert.ok(client.updateService.calledOnce) - assert.ok(await didChange) - }) - - it('attempts to disable service', async function () { - const client = stub(DefaultEcsClient, { regionCode: '' }) - client.updateService.callsFake(async (request) => assert.strictEqual(request.enableExecuteCommand, false)) - const service = new Service(client, { enableExecuteCommand: true }) - const didChange = new Promise((resolve) => { - service.onDidChangeTreeItem(() => resolve(true)) - setTimeout(() => resolve(false), 1000) - }) - - await toggleExecuteCommandFlag(service, vscode.window, settings) - assert.ok(client.updateService.calledOnce) - assert.ok(await didChange) - }) -}) - -describe('openTaskInTerminal', async function () { - const settings = new ToolkitPromptSettings(new TestSettings()) - - before(async function () { - await settings.disablePrompt(EcsRunCommandPrompt.Enable) - await settings.disablePrompt(EcsRunCommandPrompt.Disable) - }) - - it('attempts to enable service', async function () { - const client = stub(DefaultEcsClient, { regionCode: '' }) - client.updateService.callsFake(async (request) => assert.strictEqual(request.enableExecuteCommand, true)) - const service = new Service(client, {}) - const didChange = new Promise((resolve) => { - service.onDidChangeTreeItem(() => resolve(true)) - setTimeout(() => resolve(false), 1000) - }) - - await toggleExecuteCommandFlag(service, vscode.window, settings) - assert.ok(client.updateService.calledOnce) - assert.ok(await didChange) - }) - - it('attempts to disable service', async function () { - const client = stub(DefaultEcsClient, { regionCode: '' }) - client.updateService.callsFake(async (request) => assert.strictEqual(request.enableExecuteCommand, false)) - const service = new Service(client, { enableExecuteCommand: true }) - const didChange = new Promise((resolve) => { - service.onDidChangeTreeItem(() => resolve(true)) - setTimeout(() => resolve(false), 1000) - }) - - await toggleExecuteCommandFlag(service, vscode.window, settings) - assert.ok(client.updateService.calledOnce) - assert.ok(await didChange) - }) -}) diff --git a/packages/core/src/test/awsService/ecs/model.test.ts b/packages/core/src/test/awsService/ecs/model.test.ts deleted file mode 100644 index 5589d3bce9..0000000000 --- a/packages/core/src/test/awsService/ecs/model.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Cluster, Container, Service } from '../../../awsService/ecs/model' -import { DefaultEcsClient } from '../../../shared/clients/ecsClient' -import { assertChildren, assertTreeItem } from '../../shared/treeview/testUtil' -import { createCollectionFromPages } from '../../../shared/utilities/collectionUtils' -import { stub } from '../../utilities/stubber' - -const clusterData = { - clusterArn: 'arn:aws:ecs:us-east-1:012345678910:cluster/my-cluster', - clusterName: 'my-cluster', -} - -const serviceData = { - serviceArn: 'arn:aws:ecs:us-east-1:012345678910:service/my-cluster/my-service', - serviceName: 'my-service', - taskDefinition: 'arn:aws:ecs:us-east-1:012345678910:task-definition/my-task:1', -} - -const containerData = { - name: 'my-container', - clusterArn: clusterData.clusterArn, - taskRoleArn: 'arn:aws:iam::012345678910:role/my-role', -} - -const getClient = () => stub(DefaultEcsClient, { regionCode: 'us-east-1' }) - -describe('Container', function () { - const serviceName = 'my-service' - - it('has a tree item', async function () { - const container = new Container(getClient(), serviceName, containerData) - - await assertTreeItem(container, { - label: containerData.name, - contextValue: 'awsEcsContainerNodeExecDisabled', - }) - }) - - it('has a tree item when "enableExecuteCommand" is set', async function () { - const container = new Container(getClient(), serviceName, { ...containerData, enableExecuteCommand: true }) - - await assertTreeItem(container, { - label: containerData.name, - contextValue: 'awsEcsContainerNodeExecEnabled', - }) - }) -}) - -describe('Service', function () { - it('has a tree item', async function () { - const service = new Service(getClient(), serviceData) - - await assertTreeItem(service, { - label: serviceData.serviceName, - contextValue: 'awsEcsServiceNode.DISABLED', - }) - }) - - it('lists containers', async function () { - const client = getClient() - client.describeTaskDefinition.resolves({ taskDefinition: { containerDefinitions: [containerData] } }) - - const cluster = new Service(client, serviceData) - await assertChildren(cluster, containerData.name) - }) -}) - -describe('Cluster', function () { - it('has a tree item', async function () { - const cluster = new Cluster(getClient(), clusterData) - - await assertTreeItem(cluster, { - label: clusterData.clusterName, - tooltip: clusterData.clusterArn, - contextValue: 'awsEcsClusterNode', - }) - }) - - it('lists services', async function () { - const client = getClient() - client.listServices.returns(createCollectionFromPages([serviceData])) - - const cluster = new Cluster(client, clusterData) - await assertChildren(cluster, serviceData.serviceName) - }) -}) diff --git a/packages/core/src/test/awsService/ecs/util.test.ts b/packages/core/src/test/awsService/ecs/util.test.ts deleted file mode 100644 index 379d5732af..0000000000 --- a/packages/core/src/test/awsService/ecs/util.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { stub } from '../../utilities/stubber' -import { checkPermissionsForSsm } from '../../../awsService/ecs/util' -import { IamClient } from '../../../shared/clients/iam' - -describe('checkPermissionsForSsm', function () { - const getClient = () => stub(IamClient, { regionCode: '' }) - - it('rejects task definitions without a role', async function () { - await assert.rejects(() => checkPermissionsForSsm(getClient(), {}), /must have a task role/i) - }) - - it('rejects roles if any permissions are rejected', async function () { - const client = getClient() - client.getDeniedActions.resolves([{ EvalActionName: 'foo', EvalDecision: 'explicitDeny' }]) - await assert.rejects(() => checkPermissionsForSsm(client, { taskRoleArn: 'bar' }), /insufficient permissions/i) - }) - - it('returns successfully if no permissions are rejected', async function () { - const client = getClient() - client.getDeniedActions.resolves([]) - await checkPermissionsForSsm(client, { taskRoleArn: 'bar' }) - }) -}) diff --git a/packages/core/src/test/awsService/ecs/wizards/executeCommand.test.ts b/packages/core/src/test/awsService/ecs/wizards/executeCommand.test.ts deleted file mode 100644 index d18464ad03..0000000000 --- a/packages/core/src/test/awsService/ecs/wizards/executeCommand.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../../shared/wizards/wizardTestUtils' -import { CommandWizard, CommandWizardState } from '../../../../awsService/ecs/wizards/executeCommand' -import { Container } from '../../../../awsService/ecs/model' -import { stub } from '../../../utilities/stubber' -import { DefaultEcsClient } from '../../../../shared/clients/ecsClient' - -describe('CreateServiceWizard', function () { - let tester: WizardTester - - const createContainer = () => - new Container(stub(DefaultEcsClient, { regionCode: '' }), '', { clusterArn: '', taskRoleArn: '' }) - - beforeEach(async function () { - tester = await createWizardTester(new CommandWizard(createContainer(), false)) - }) - - it('should prompt for tasks and then command', function () { - tester.task.assertShowFirst() - tester.command.assertShowSecond() - }) - - it('should ask for confirmation last', async function () { - tester = await createWizardTester(new CommandWizard(createContainer(), true)) - tester.task.assertShowFirst() - tester.command.assertShowSecond() - tester.confirmation.assertShowThird() - }) - - it('should not ask for confirmation if suppressed', function () { - tester.confirmation.assertDoesNotShow() - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/attachCertificate.test.ts b/packages/core/src/test/awsService/iot/commands/attachCertificate.test.ts deleted file mode 100644 index 840c375a80..0000000000 --- a/packages/core/src/test/awsService/iot/commands/attachCertificate.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import { Certificate } from '@aws-sdk/client-iot' -import { attachCertificateCommand, CertGen } from '../../../../awsService/iot/commands/attachCertificate' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import { DataQuickPickItem } from '../../../../shared/ui/pickerPrompter' -import { PromptResult } from '../../../../shared/ui/prompter' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('attachCertCommand', function () { - const thingName = 'iot-thing' - let iot: IotClient - let certs: Certificate[] - let thingNode: IotThingNode - let selection: number = 0 - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - const prompt: (iot: IotClient, certFetch: CertGen) => Promise> = async ( - iot, - certFetch - ) => { - const iterable = certFetch(iot) - const responses: DataQuickPickItem[] = [] - for await (const response of iterable) { - responses.push(...response) - } - return selection > -1 ? (responses[selection].data as Certificate) : undefined - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - thingNode = new IotThingNode({ name: thingName, arn: 'arn' }, {} as IotThingFolderNode, iot) - certs = [ - { - certificateId: 'cert1', - certificateArn: 'arn1', - status: 'ACTIVE', - creationDate: new globals.clock.Date(), - }, - { - certificateId: 'cert2', - certificateArn: 'arn2', - status: 'INACTIVE', - creationDate: new globals.clock.Date(), - }, - { certificateId: 'cert3', certificateArn: 'arn3', status: 'ACTIVE' }, - ] - }) - - afterEach(function () { - sandbox.restore() - }) - - it('attaches selected certificate', async function () { - // const executeCommand = stubVscodeExecuteCommand() - selection = 0 - const listStub = sinon.stub().resolves({ certificates: certs }) - iot.listCertificates = listStub - const attachStub = sinon.stub() - iot.attachThingPrincipal = attachStub - await attachCertificateCommand(thingNode, prompt) - - assert(attachStub.calledOnceWithExactly({ thingName, principal: 'arn1' })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', thingNode) - }) - - it('shows an error message if certificates are not fetched', async function () { - const listStub = sinon.stub().rejects(new Error('Expected failure')) - iot.listCertificates = listStub - const attachStub = sinon.stub() - iot.attachThingPrincipal = attachStub - - selection = -1 - await attachCertificateCommand(thingNode, prompt) - - assert(attachStub.notCalled) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to retrieve certificate/) - - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message if attaching certificate fails', async function () { - selection = 1 - const listStub = sinon.stub().resolves({ certificates: certs }) - iot.listCertificates = listStub - const attachStub = sinon.stub().rejects(new Error('Expected failure')) - iot.attachThingPrincipal = attachStub - - await attachCertificateCommand(thingNode, prompt) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to attach certificate cert2/) - - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('Does nothing if no certificate is chosen', async function () { - selection = -1 - const listStub = sinon.stub().resolves({ certificates: certs }) - iot.listCertificates = listStub - const attachStub = sinon.stub().rejects(new Error('Expected failure')) - iot.attachThingPrincipal = attachStub - - await attachCertificateCommand(thingNode, prompt) - - assert(attachStub.notCalled) - - sandbox.assert.notCalled(spyExecuteCommand) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/copyEndpoint.test.ts b/packages/core/src/test/awsService/iot/commands/copyEndpoint.test.ts deleted file mode 100644 index 3d25b4843d..0000000000 --- a/packages/core/src/test/awsService/iot/commands/copyEndpoint.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { copyEndpointCommand } from '../../../../awsService/iot/commands/copyEndpoint' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTestWindow } from '../../../shared/vscode/window' -import { FakeClipboard } from '../../../shared/vscode/fakeEnv' - -describe('copyEndpointCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - let iot: IotClient - let node: IotNode - - beforeEach(function () { - iot = {} as any as IotClient - node = new IotNode(iot) - }) - - it('copies endpoint to clipboard', async function () { - const endpointStub = sinon.stub().resolves('endpoint') - iot.getEndpoint = endpointStub - - await copyEndpointCommand(node) - - assert.strictEqual(await vscode.env.clipboard.readText(), 'endpoint') - }) - - it('shows an error message when retrieval fails', async function () { - const endpointStub = sinon.stub().rejects(new Error('Expected failure')) - iot.getEndpoint = endpointStub - - await copyEndpointCommand(node) - - getTestWindow().getFirstMessage().assertError('Failed to retrieve endpoint') - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/createCert.test.ts b/packages/core/src/test/awsService/iot/commands/createCert.test.ts deleted file mode 100644 index 1f28062941..0000000000 --- a/packages/core/src/test/awsService/iot/commands/createCert.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createCertificateCommand } from '../../../../awsService/iot/commands/createCert' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { CreateKeysAndCertificateResponse } from '@aws-sdk/client-iot' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('createCertificateCommand', function () { - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - const certificateId = 'new-certificate' - const certificateArn = 'arn' - const certificatePem = 'certPem' - const keyPair = { PrivateKey: 'private', PublicKey: 'public' } - const certificate: CreateKeysAndCertificateResponse = { certificateId, certificateArn, certificatePem, keyPair } - let iot: IotClient - let node: IotCertsFolderNode - let saveLocation: vscode.Uri | undefined = vscode.Uri.file('/certificate.txt') - let saveSuccess: boolean - const promptFolder: () => Promise = async () => { - return saveLocation - } - const saveFiles: ( - basePath: vscode.Uri, - certId: string, - certPem: string, - privateKey: string, - publicKey: string - ) => Promise = async (basePath, certId, certPem, privateKey, publicKey) => { - return saveSuccess - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - node = new IotCertsFolderNode(iot, new IotNode(iot)) - saveLocation = vscode.Uri.file('/certificate') - saveSuccess = true - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for save location, creates certificate, and saves to filesystem', async function () { - const createStub = sinon.stub().resolves(certificate) - iot.createCertificateAndKeys = createStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - - await createCertificateCommand(node, promptFolder, saveFiles) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created certificate new-certificate/) - - assert(createStub.calledOnceWithExactly({ setAsActive: false })) - assert(deleteStub.notCalled) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - it('does nothing when no save folder is selected', async function () { - saveLocation = undefined - const createStub = sinon.stub().resolves(certificate) - iot.createCertificateAndKeys = createStub - await createCertificateCommand(node, promptFolder, saveFiles) - - assert(createStub.notCalled) - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message if creating certificate fails', async function () { - const createStub = sinon.stub().rejects() - iot.createCertificateAndKeys = createStub - - await createCertificateCommand(node, promptFolder, saveFiles) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create certificate/) - - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message if created certificate is invalid', async function () { - const createStub = sinon.stub().resolves({}) - iot.createCertificateAndKeys = createStub - - await createCertificateCommand(node, promptFolder, saveFiles) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create certificate/) - - assert(createStub.calledOnceWithExactly({ setAsActive: false })) - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('deletes certificate if it cannot be saved', async function () { - const createStub = sinon.stub().resolves(certificate) - iot.createCertificateAndKeys = createStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - saveSuccess = false - - await createCertificateCommand(node, promptFolder, saveFiles) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created certificate new-certificate/) - - assert(createStub.calledOnceWithExactly({ setAsActive: false })) - assert(deleteStub.calledOnceWithExactly({ certificateId })) - }) - - it('shows an error message if certificate cannot be deleted', async function () { - const createStub = sinon.stub().resolves(certificate) - iot.createCertificateAndKeys = createStub - const deleteStub = sinon.stub().rejects() - iot.deleteCertificate = deleteStub - saveSuccess = false - - await createCertificateCommand(node, promptFolder, saveFiles) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created certificate new-certificate/) - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete Certificate new-certificate/) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/createPolicy.test.ts b/packages/core/src/test/awsService/iot/commands/createPolicy.test.ts deleted file mode 100644 index c3bb715983..0000000000 --- a/packages/core/src/test/awsService/iot/commands/createPolicy.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createPolicyCommand } from '../../../../awsService/iot/commands/createPolicy' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('createPolicyCommand', function () { - const policyName = 'test-policy' - let iot: IotClient - let policyObject: any - let policyDocument: string - let node: IotPolicyFolderNode - let returnUndefined: boolean = false - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - const getPolicy: () => Promise = async () => { - if (returnUndefined) { - return undefined - } - return Buffer.from(policyDocument, 'utf-8') - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - node = new IotPolicyFolderNode(iot, new IotNode(iot)) - policyObject = { Version: '2012-10-17', Statement: '' } - policyDocument = JSON.stringify(policyObject) - returnUndefined = false - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for policy name, creates policy, and shows success', async function () { - const policyStub = sinon.stub() - iot.createPolicy = policyStub - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter a new policy name') - assert.strictEqual(input.placeholder, 'Policy Name') - input.acceptValue(policyName) - }) - returnUndefined = false - await createPolicyCommand(node, getPolicy) - getTestWindow() - .getFirstMessage() - .assertInfo(/Created Policy test-policy/) - - assert(policyStub.calledOnceWithExactly({ policyName, policyDocument })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - it('does nothing when prompt is canceled', async function () { - const policyStub = sinon.stub() - iot.createPolicy = policyStub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - assert(policyStub.notCalled) - }) - - it('warns when policy name has invalid length', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('') - assert.strictEqual(input.validationMessage, 'Policy name must be between 1 and 128 characters long') - input.hide() - }) - await createPolicyCommand(node, getPolicy) - }) - - it('warns when policy name has invalid characters', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('illegal/characters') - assert.strictEqual( - input.validationMessage, - 'Policy name must contain only alphanumeric characters and/or the following: +=.,@-' - ) - input.hide() - }) - await createPolicyCommand(node, getPolicy) - }) - - it('does nothing when policy document is not read', async function () { - const policyStub = sinon.stub() - iot.createPolicy = policyStub - returnUndefined = true - getTestWindow().onDidShowInputBox((input) => input.acceptValue(policyName)) - await createPolicyCommand(node, getPolicy) - - assert(policyStub.notCalled) - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message when JSON is invalid', async function () { - const policyStub = sinon.stub() - iot.createPolicy = policyStub - getTestWindow().onDidShowInputBox((input) => input.acceptValue(policyName)) - returnUndefined = false - policyDocument = 'not a JSON' - await createPolicyCommand(node, getPolicy) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create policy test-policy/) - - assert(policyStub.notCalled) - - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message if creating policy fails', async function () { - const policyStub = sinon.stub().rejects() - iot.createPolicy = policyStub - returnUndefined = false - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(policyName)) - await createPolicyCommand(node, getPolicy) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create policy test-policy/) - - sandbox.assert.notCalled(spyExecuteCommand) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/createPolicyVersion.test.ts b/packages/core/src/test/awsService/iot/commands/createPolicyVersion.test.ts deleted file mode 100644 index 12e38293c5..0000000000 --- a/packages/core/src/test/awsService/iot/commands/createPolicyVersion.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createPolicyVersionCommand } from '../../../../awsService/iot/commands/createPolicyVersion' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { getTestWindow } from '../../../shared/vscode/window' -import sinon from 'sinon' -import { assert } from 'console' - -describe('createPolicyVersionCommand', function () { - const policyName = 'test-policy' - - let iot: IotClient - let policyObject: any - let policyDocument: string - let node: IotPolicyWithVersionsNode - let parentNode: IotPolicyFolderNode - let returnUndefined: boolean = false - const getPolicy: () => Promise = async () => { - if (returnUndefined) { - return undefined - } - return Buffer.from(policyDocument, 'utf-8') - } - - beforeEach(function () { - iot = {} as any as IotClient - parentNode = new IotPolicyFolderNode(iot, new IotNode(iot)) - node = new IotPolicyWithVersionsNode({ name: policyName, arn: 'arn' }, parentNode, iot) - policyObject = { Version: '2012-10-17', Statement: '' } - policyDocument = JSON.stringify(policyObject) - returnUndefined = false - }) - - it('creates new policy version and shows success', async function () { - const createStub = sinon.stub() - iot.createPolicyVersion = createStub - returnUndefined = false - await createPolicyVersionCommand(node, getPolicy) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created new version of test-policy/) - - assert(createStub.calledOnceWithExactly({ policyName, policyDocument, setAsDefault: true })) - }) - - it('does nothing when policy document is not read', async function () { - const createStub = sinon.stub() - iot.createPolicyVersion = createStub - returnUndefined = true - await createPolicyVersionCommand(node, getPolicy) - - assert(createStub.notCalled) - }) - - it('shows an error message when JSON is invalid', async function () { - const createStub = sinon.stub() - iot.createPolicyVersion = createStub - returnUndefined = false - policyDocument = 'not a JSON' - await createPolicyVersionCommand(node, getPolicy) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create new version of test-policy/) - - assert(createStub.notCalled) - }) - - it('shows an error message if creating version fails', async function () { - const createStub = sinon.stub().rejects() - iot.createPolicyVersion = createStub - returnUndefined = false - - await createPolicyVersionCommand(node, getPolicy) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create new version of test-policy/) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/createThing.test.ts b/packages/core/src/test/awsService/iot/commands/createThing.test.ts deleted file mode 100644 index 98e125cad8..0000000000 --- a/packages/core/src/test/awsService/iot/commands/createThing.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createThingCommand } from '../../../../awsService/iot/commands/createThing' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('createThingCommand', function () { - const thingName = 'newIotThing' - let iot: IotClient - let node: IotThingFolderNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - node = new IotThingFolderNode(iot, new IotNode(iot)) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for thing name, creates thing, shows success, and refreshes node', async function () { - const createStub = sinon.stub().resolves({ - thingName: thingName, - }) - iot.createThing = createStub - - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter a new Thing name') - assert.strictEqual(input.placeholder, 'Thing Name') - input.acceptValue(thingName) - }) - await createThingCommand(node) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created Thing newIotThing/) - - assert(createStub.calledOnceWithExactly({ thingName })) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - it('does nothing when prompt is cancelled', async function () { - const createStub = sinon.stub() - iot.createThing = createStub - getTestWindow().onDidShowInputBox((input) => input.hide()) - await createThingCommand(node) - - assert(createStub.notCalled) - }) - - it('warns when thing name has invalid length', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('') - assert.strictEqual(input.validationMessage, 'Thing name must be between 1 and 128 characters long') - input.hide() - }) - await createThingCommand(node) - }) - - it('warns when thing name has invalid characters', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('illegal/characters') - assert.strictEqual( - input.validationMessage, - 'Thing name must only contain alphanumeric characters, hyphens, underscores, or colons' - ) - input.hide() - }) - await createThingCommand(node) - }) - - it('shows an error message and refreshes node when thing creation fails', async function () { - const createStub = sinon.stub().rejects() - iot.createThing = createStub - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(thingName)) - await createThingCommand(node) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create Thing/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/deleteCert.test.ts b/packages/core/src/test/awsService/iot/commands/deleteCert.test.ts deleted file mode 100644 index 5a1b78ea85..0000000000 --- a/packages/core/src/test/awsService/iot/commands/deleteCert.test.ts +++ /dev/null @@ -1,175 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { deleteCertCommand } from '../../../../awsService/iot/commands/deleteCert' -import { IotCertWithPoliciesNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('deleteCertCommand', function () { - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - const certificateId = 'test-cert' - const status = 'INACTIVE' - let iot: IotClient - let node: IotCertWithPoliciesNode - let parentNode: IotCertsFolderNode - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - iot = {} as any as IotClient - parentNode = new IotCertsFolderNode(iot, new IotNode(iot)) - node = new IotCertWithPoliciesNode( - { id: certificateId, arn: 'arn', activeStatus: status, creationDate: new globals.clock.Date() }, - parentNode, - iot - ) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes cert, and refreshes node', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const principalStub = sinon.stub().resolves({ policies: [] }) - iot.listPrincipalPolicies = principalStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteCertCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to delete Certificate test-cert?') - - assert(deleteStub.calledOnceWithExactly({ certificateId, forceDelete: false })) - assert(thingsStub.calledOnceWithExactly({ principal: 'arn' })) - assert(principalStub.calledOnceWithExactly({ principal: 'arn' })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing if things are attached', async function () { - const thingsStub = sinon.stub().resolves(['iot-thing']) - iot.listThingsForCert = thingsStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteCertCommand(node) - - getTestWindow() - .getFirstMessage() - .assertError(/Certificate has attached resources: iot-thing/) - - assert(thingsStub.calledOnceWithExactly({ principal: 'arn' })) - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('does nothing when deletion is cancelled', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await deleteCertCommand(node) - - assert(thingsStub.calledOnceWithExactly({ principal: 'arn' })) - assert(deleteStub.notCalled) - }) - - it('does nothing if certificate is active', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - node = new IotCertWithPoliciesNode( - { id: certificateId, arn: 'arn', activeStatus: 'ACTIVE', creationDate: new globals.clock.Date() }, - parentNode, - iot - ) - getTestWindow().onDidShowMessage((m) => m.selectItem('Delete')) - await deleteCertCommand(node) - - assert(deleteStub.notCalled) - }) - - it('shows an error message and refreshes node when certificate deletion fails', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const principalStub = sinon.stub().resolves({ policies: [] }) - iot.listPrincipalPolicies = principalStub - const deleteStub = sinon.stub().rejects() - iot.deleteCertificate = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteCertCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete certificate: test-cert/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('shows an error message if Things are not fetched', async function () { - const thingsStub = sinon.stub().rejects() - iot.listThingsForCert = thingsStub - const deleteStub = sinon.stub().rejects() - iot.deleteCertificate = deleteStub - - await deleteCertCommand(node) - - assert(deleteStub.notCalled) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to retrieve Things attached to certificate/) - - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('confirms force deletion if policies are attached', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const principalStub = sinon.stub().resolves({ policies: [{ policyName: 'policy' }] }) - iot.listPrincipalPolicies = principalStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteCertCommand(node) - - getTestWindow().getSecondMessage().assertWarn('Certificate has attached policies. Delete anyway?') - - assert(deleteStub.calledOnceWithExactly({ certificateId, forceDelete: true })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('shows an error message but refreshes node if policies are not fetched', async function () { - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const principalStub = sinon.stub().rejects() - iot.listPrincipalPolicies = principalStub - const deleteStub = sinon.stub() - iot.deleteCertificate = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteCertCommand(node) - - getTestWindow().getSecondMessage().assertError('Failed to retrieve policies attached to certificate') - assert(deleteStub.calledOnceWithExactly({ certificateId, forceDelete: false })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/deletePolicy.test.ts b/packages/core/src/test/awsService/iot/commands/deletePolicy.test.ts deleted file mode 100644 index 9812b915f3..0000000000 --- a/packages/core/src/test/awsService/iot/commands/deletePolicy.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { PolicyVersion } from '@aws-sdk/client-iot' -import { deletePolicyCommand } from '../../../../awsService/iot/commands/deletePolicy' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { asyncGenerator } from '../../../../shared/utilities/collectionUtils' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('deletePolicyCommand', function () { - const policyName = 'test-policy' - let iot: IotClient - let node: IotPolicyWithVersionsNode - let parentNode: IotPolicyFolderNode - - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - parentNode = new IotPolicyFolderNode(iot, new IotNode(iot)) - node = new IotPolicyWithVersionsNode({ name: policyName, arn: 'arn' }, parentNode, iot) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes policy, and refreshes node', async function () { - const listPolicyStub = sinon.stub().resolves([]) - iot.listPolicyTargets = listPolicyStub - const policyVersions = ['1'] - const listPolicyVersionsStub = sinon.stub().returns( - asyncGenerator( - policyVersions.map((versionId) => { - return { - versionId: versionId, - } - }) - ) - ) - iot.listPolicyVersions = listPolicyVersionsStub - const deleteStub = sinon.stub() - iot.deletePolicy = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to delete Policy test-policy?') - - assert(listPolicyStub.calledOnceWithExactly({ policyName })) - assert(deleteStub.calledOnceWithExactly({ policyName })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing when certificates are attached', async function () { - const listPolicyStub = sinon.stub().resolves(['cert']) - iot.listPolicyTargets = listPolicyStub - const deleteStub = sinon.stub() - iot.deletePolicy = deleteStub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Policy has attached certificates: cert/) - - assert(listPolicyStub.calledOnceWithExactly({ policyName })) - assert(deleteStub.notCalled) - }) - - it('does nothing when multiple versions are present', async function () { - const listPolicyStub = sinon.stub().resolves([]) - iot.listPolicyTargets = listPolicyStub - const policyVersions = ['1', '2'] - const listPolicyVersionsStub = sinon.stub().returns( - asyncGenerator( - policyVersions.map((versionId) => { - return { - versionId: versionId, - } - }) - ) - ) - const deleteStub = sinon.stub() - iot.deletePolicy = deleteStub - iot.listPolicyVersions = listPolicyVersionsStub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyCommand(node) - - assert(listPolicyStub.calledOnceWithExactly({ policyName })) - assert(deleteStub.notCalled) - }) - - it('does nothing when deletion is cancelled', async function () { - const deleteStub = sinon.stub() - iot.deletePolicy = deleteStub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await deletePolicyCommand(node) - - assert(deleteStub.notCalled) - }) - - it('shows an error message and refreshes node when policy deletion fails', async function () { - const deleteStub = sinon.stub().rejects() - iot.deletePolicy = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete Policy: test-policy/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/deletePolicyVersion.test.ts b/packages/core/src/test/awsService/iot/commands/deletePolicyVersion.test.ts deleted file mode 100644 index dff62c306c..0000000000 --- a/packages/core/src/test/awsService/iot/commands/deletePolicyVersion.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { deletePolicyVersionCommand } from '../../../../awsService/iot/commands/deletePolicyVersion' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyVersionNode } from '../../../../awsService/iot/explorer/iotPolicyVersionNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('deletePolicyVersionCommand', function () { - const policyName = 'test-policy' - let iot: IotClient - let node: IotPolicyVersionNode - let parentNode: IotPolicyWithVersionsNode - - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - parentNode = new IotPolicyWithVersionsNode({ name: policyName, arn: 'arn' }, {} as IotPolicyFolderNode, iot) - node = new IotPolicyVersionNode( - { name: policyName, arn: 'arn' }, - { versionId: 'V1', isDefaultVersion: false }, - false, - parentNode, - iot - ) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes policy, and refreshes node', async function () { - const deleteStub = sinon.stub() - iot.deletePolicyVersion = deleteStub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyVersionCommand(node) - - getTestWindow() - .getFirstMessage() - .assertWarn('Are you sure you want to delete Version V1 of Policy test-policy?') - - assert(deleteStub.calledOnceWithExactly({ policyName, policyVersionId: 'V1' })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode') - }) - - it('does nothing when deletion is cancelled', async function () { - const deleteStub = sinon.stub() - iot.deletePolicyVersion = deleteStub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await deletePolicyVersionCommand(node) - - assert(deleteStub.notCalled) - }) - - it('shows an error message and refreshes node when deletion fails', async function () { - const deleteStub = sinon.stub().rejects() - iot.deletePolicyVersion = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deletePolicyVersionCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete Version V1 of Policy test-policy/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode') - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/deleteThing.test.ts b/packages/core/src/test/awsService/iot/commands/deleteThing.test.ts deleted file mode 100644 index 6f7d71cb90..0000000000 --- a/packages/core/src/test/awsService/iot/commands/deleteThing.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { deleteThingCommand } from '../../../../awsService/iot/commands/deleteThing' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('deleteThingCommand', function () { - const thingName = 'iot-thing' - let iot: IotClient - let node: IotThingNode - let parentNode: IotThingFolderNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - parentNode = new IotThingFolderNode(iot, new IotNode(iot)) - node = new IotThingNode({ name: thingName, arn: 'arn' }, parentNode, iot) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes thing, and refreshes node', async function () { - const listStub = sinon.stub().resolves({ principals: [] }) - iot.listThingPrincipals = listStub - const deleteStub = sinon.stub() - iot.deleteThing = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteThingCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to delete Thing iot-thing?') - - assert(listStub.calledOnceWithExactly({ thingName })) - assert(deleteStub.calledOnceWithExactly({ thingName })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing if thing principals are attached', async function () { - const listStub = sinon.stub().resolves({ principals: ['string'] }) - iot.listThingPrincipals = listStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteThingCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Cannot delete Thing iot-thing/) - - assert(listStub.calledOnceWithExactly({ thingName })) - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('does nothing when deletion is cancelled', async function () { - const deleteStub = sinon.stub() - iot.deleteThing = deleteStub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await deleteThingCommand(node) - - assert(deleteStub.notCalled) - }) - - it('shows an error message and refreshes node when thing deletion fails', async function () { - const deleteStub = sinon.stub().rejects() - iot.deleteThing = deleteStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteThingCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete Thing: iot-thing/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('shows an error message and refreshes node if principals are not fetched', async function () { - const listStub = sinon.stub().rejects() - iot.listThingPrincipals = listStub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteThingCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete Thing: iot-thing/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/detachCert.test.ts b/packages/core/src/test/awsService/iot/commands/detachCert.test.ts deleted file mode 100644 index 06790d26e4..0000000000 --- a/packages/core/src/test/awsService/iot/commands/detachCert.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { detachThingCertCommand } from '../../../../awsService/iot/commands/detachCert' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotThingCertNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('detachThingCertCommand', function () { - const certificateId = 'test-certificate' - const principal = 'cert:arn' - const thingName = 'iot-thing' - let iot: IotClient - let node: IotThingCertNode - let parentNode: IotThingNode - - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - parentNode = new IotThingNode({ name: thingName, arn: 'arn' }, {} as IotThingFolderNode, iot) - node = new IotThingCertNode( - { id: certificateId, arn: principal, activeStatus: 'ACTIVE', creationDate: new globals.clock.Date() }, - parentNode, - iot - ) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms detach, detaches certificate, and refreshes node', async function () { - const stub = sinon.stub() - iot.detachThingPrincipal = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Detach')?.select()) - await detachThingCertCommand(node) - - getTestWindow() - .getFirstMessage() - .assertWarn('Are you sure you want to detach certificate from Thing iot-thing?') - - assert(stub.calledOnceWithExactly({ thingName, principal })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing when cancelled', async function () { - const stub = sinon.stub() - iot.detachThingPrincipal = stub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await detachThingCertCommand(node) - - assert(stub.notCalled) - }) - - it('shows an error message and refreshes node when thing detachment fails', async function () { - const stub = sinon.stub().rejects() - iot.detachThingPrincipal = stub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Detach')?.select()) - await detachThingCertCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to detach: test-certificate/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/detachPolicy.test.ts b/packages/core/src/test/awsService/iot/commands/detachPolicy.test.ts deleted file mode 100644 index 2e4574cbb5..0000000000 --- a/packages/core/src/test/awsService/iot/commands/detachPolicy.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { detachPolicyCommand } from '../../../../awsService/iot/commands/detachPolicy' -import { IotCertWithPoliciesNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { IotPolicyCertNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import sinon from 'sinon' -import assert from 'assert' - -describe('detachPolicyCommand', function () { - const policyName = 'test-policy' - const target = 'cert:arn' - let iot: IotClient - let node: IotPolicyCertNode - let parentNode: IotCertWithPoliciesNode - - beforeEach(function () { - iot = {} as any as IotClient - parentNode = new IotCertWithPoliciesNode( - { id: 'id', arn: target, activeStatus: 'ACTIVE', creationDate: new globals.clock.Date() }, - new IotCertsFolderNode(iot, new IotNode(iot)), - iot - ) - node = new IotPolicyCertNode({ name: policyName, arn: 'arn' }, parentNode, iot) - }) - - it('confirms detach, detaches policy, and refreshes node', async function () { - const stub = sinon.stub() - iot.detachPolicy = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Detach')?.select()) - await detachPolicyCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to detach policy test-policy?') - - assert(stub.calledOnceWithExactly({ policyName, target })) - }) - - it('does nothing when cancelled', async function () { - const stub = sinon.stub() - iot.detachPolicy = stub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await detachPolicyCommand(node) - - assert(stub.notCalled) - }) - - it('shows an error message and refreshes node when thing detachment fails', async function () { - const stub = sinon.stub().rejects() - iot.detachPolicy = stub - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Detach')?.select()) - await detachPolicyCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to detach: test-policy/) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/setDefaultPolicy.test.ts b/packages/core/src/test/awsService/iot/commands/setDefaultPolicy.test.ts deleted file mode 100644 index ee2f0eedea..0000000000 --- a/packages/core/src/test/awsService/iot/commands/setDefaultPolicy.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { setDefaultPolicy } from '../../../../awsService/iot/commands/setDefaultPolicy' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyVersionNode } from '../../../../awsService/iot/explorer/iotPolicyVersionNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTestWindow } from '../../../shared/vscode/window' -import assert from 'assert' - -describe('setDefaultPolicy', function () { - const policyName = 'test-policy' - let iot: IotClient - let node: IotPolicyVersionNode - let parentNode: IotPolicyWithVersionsNode - let parentParentNode: IotPolicyFolderNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - iot = {} as any as IotClient - parentParentNode = new IotPolicyFolderNode(iot, new IotNode(iot)) - parentNode = new IotPolicyWithVersionsNode({ name: policyName, arn: 'arn' }, parentParentNode, iot) - node = new IotPolicyVersionNode( - { name: policyName, arn: 'arn' }, - { versionId: 'V1', isDefaultVersion: false }, - false, - parentNode, - iot - ) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('sets default version and refreshes node', async function () { - const stub = sinon.stub() - iot.setDefaultPolicyVersion = stub - await setDefaultPolicy(node) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Set V1 as default version of test-policy/) - assert(stub.calledOnceWithExactly({ policyName, policyVersionId: 'V1' })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode') - }) - - it('shows an error message and refreshes node when deletion fails', async function () { - const stub = sinon.stub().rejects() - iot.setDefaultPolicyVersion = stub - await setDefaultPolicy(node) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to set default policy version/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode') - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/updateCert.test.ts b/packages/core/src/test/awsService/iot/commands/updateCert.test.ts deleted file mode 100644 index 27c94f562e..0000000000 --- a/packages/core/src/test/awsService/iot/commands/updateCert.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - deactivateCertificateCommand, - activateCertificateCommand, - revokeCertificateCommand, -} from '../../../../awsService/iot/commands/updateCert' -import { IotCertWithPoliciesNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { IotThingCertNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import sinon from 'sinon' -import assert from 'assert' - -describe('updateCertificate', function () { - const certificateId = 'test-cert' - let iot: IotClient - let node: IotCertWithPoliciesNode | IotThingCertNode - let parentNode: IotCertsFolderNode - let thingParentNode: IotThingNode - - beforeEach(function () { - iot = {} as any as IotClient - }) - - describe('deactivateCommand', function () { - this.beforeEach(function () { - parentNode = new IotCertsFolderNode(iot, new IotNode(iot)) - node = new IotCertWithPoliciesNode( - { id: certificateId, arn: 'arn', activeStatus: 'ACTIVE', creationDate: new globals.clock.Date() }, - parentNode, - iot - ) - }) - - it('confirms deactivation, deactivates cert, and refreshes tree', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Deactivate')?.select()) - await deactivateCertificateCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to deactivate certificate test-cert?') - getTestWindow().getSecondMessage().assertInfo('Deactivated: test-cert') - - assert(stub.calledOnceWithExactly({ certificateId, newStatus: 'INACTIVE' })) - }) - - it('does nothing when deactivation is cancelled', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await deactivateCertificateCommand(node) - - assert(stub.notCalled) - }) - - it('shows an error message if deactivating the certificate fails', async function () { - const stub = sinon.stub().rejects() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Deactivate')?.select()) - await deactivateCertificateCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to deactivate: test-cert/) - }) - }) - - describe('activateCommand', function () { - this.beforeEach(function () { - thingParentNode = new IotThingNode( - { name: 'iot-thing', arn: 'thingArn' }, - new IotThingFolderNode(iot, new IotNode(iot)), - iot - ) - node = new IotThingCertNode( - { id: certificateId, arn: 'arn', activeStatus: 'INACTIVE', creationDate: new globals.clock.Date() }, - thingParentNode, - iot - ) - }) - - it('confirms activation, activates cert, and refreshes tree', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Activate')?.select()) - await activateCertificateCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to activate certificate test-cert?') - getTestWindow() - .getSecondMessage() - .assertInfo(/Activated: test-cert/) - - assert(stub.calledOnceWithExactly({ certificateId, newStatus: 'ACTIVE' })) - }) - - it('does nothing when activation is cancelled', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await activateCertificateCommand(node) - - assert(stub.notCalled) - }) - - it('shows an error message if deactivating the certificate fails', async function () { - const stub = sinon.stub().rejects() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Activate')?.select()) - await activateCertificateCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to activate: test-cert/) - }) - }) - - describe('revoke', function () { - this.beforeEach(function () { - parentNode = new IotCertsFolderNode(iot, new IotNode(iot)) - node = new IotCertWithPoliciesNode( - { id: certificateId, arn: 'arn', activeStatus: 'ACTIVE', creationDate: new globals.clock.Date() }, - parentNode, - iot - ) - }) - - it('confirms revocation, revokes cert, and refreshes tree', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Revoke')?.select()) - await revokeCertificateCommand(node) - - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to revoke certificate test-cert?') - getTestWindow() - .getSecondMessage() - .assertInfo(/Revoked: test-cert/) - - assert(stub.calledOnceWithExactly({ certificateId, newStatus: 'REVOKED' })) - }) - - it('does nothing when revocation is cancelled', async function () { - const stub = sinon.stub() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - await revokeCertificateCommand(node) - - assert(stub.notCalled) - }) - - it('shows an error message if revoking the certificate fails', async function () { - const stub = sinon.stub().rejects() - iot.updateCertificate = stub - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Revoke')?.select()) - await revokeCertificateCommand(node) - - getTestWindow() - .getSecondMessage() - .assertError(/Failed to revoke: test-cert/) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/commands/viewPolicyVersion.test.ts b/packages/core/src/test/awsService/iot/commands/viewPolicyVersion.test.ts deleted file mode 100644 index 5a5b675d7f..0000000000 --- a/packages/core/src/test/awsService/iot/commands/viewPolicyVersion.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { viewPolicyVersionCommand } from '../../../../awsService/iot/commands/viewPolicyVersion' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyVersionNode } from '../../../../awsService/iot/explorer/iotPolicyVersionNode' -import { IotClient } from '../../../../shared/clients/iotClient' -import { getTabSizeSetting } from '../../../../shared/utilities/editorUtilities' - -const awsIotExamplePolicy = - '{ "Version": "2012-10-17", "Statement": [ {"Effect": "Allow", "Action": "*", "Resource": "*" } ] }' - -const expectedPolicy = JSON.stringify(JSON.parse(awsIotExamplePolicy), undefined, getTabSizeSetting()) - -describe('viewPolicyVersionCommand', function () { - const policyName = 'test-policy' - let iot: IotClient - let node: IotPolicyVersionNode - let parentNode: IotPolicyWithVersionsNode - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - iot = {} as any as IotClient - parentNode = new IotPolicyWithVersionsNode({ name: policyName, arn: 'arn' }, {} as IotPolicyFolderNode, iot) - node = new IotPolicyVersionNode( - { name: policyName, arn: 'arn' }, - { versionId: 'V1', isDefaultVersion: false }, - false, - parentNode, - iot - ) - sandbox = sinon.createSandbox() - }) - - this.afterEach(function () { - sandbox.restore() - }) - - it('inserts policy document into editor', async function () { - const textEditor = {} as vscode.TextDocument - const openTextDocumentStub = sandbox - .stub(vscode.workspace, 'openTextDocument') - .returns(Promise.resolve(textEditor)) - const showTextDocumentStub = sandbox.stub(vscode.window, 'showTextDocument').resolves({} as vscode.TextEditor) - const stub = sinon.stub().resolves({ - policyDocument: awsIotExamplePolicy, - }) - iot.getPolicyVersion = stub - - await viewPolicyVersionCommand(node) - - assert.strictEqual(openTextDocumentStub.calledOnce, true, 'should be called once') - assert.deepStrictEqual( - openTextDocumentStub.getCalls()[0].args[0], - { - language: 'json', - content: expectedPolicy, - }, - 'should open with correct content and language' - ) - - assert.strictEqual(showTextDocumentStub.calledOnce, true, 'should be called once') - assert.strictEqual(showTextDocumentStub.getCall(0).args[0], textEditor) - }) - - it('does nothing when policy retrieval fails', async function () { - const openTextDocumentStub = sandbox - .stub(vscode.workspace, 'openTextDocument') - .returns(Promise.resolve({} as vscode.TextDocument)) - const stub = sinon.stub().rejects() - iot.getPolicyVersion = stub - - await viewPolicyVersionCommand(node) - - assert.strictEqual(openTextDocumentStub.notCalled, true, 'should not be called') - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotCertFolderNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotCertFolderNode.test.ts deleted file mode 100644 index 7247b1df63..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotCertFolderNode.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotCertificate, IotClient } from '../../../../shared/clients/iotClient' -import { Certificate } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { IotCertWithPoliciesNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotCertFolderNode', function () { - const nextMarker = 'nextToken' - const pageSize = 250 - - let iot: IotClient - let config: TestSettings - - const cert: Certificate = { - certificateId: 'cert', - certificateArn: 'arn', - status: 'ACTIVE', - creationDate: new Date(0), - } - const expectedCert: IotCertificate = { - id: 'cert', - arn: 'arn', - activeStatus: 'ACTIVE', - creationDate: new Date(0), - } - - function assertCertNode(node: AWSTreeNodeBase, expectedCert: IotCertificate): void { - assert.ok(node instanceof IotCertWithPoliciesNode, `Node ${node} should be a Certificate Node`) - assert.deepStrictEqual((node as IotCertWithPoliciesNode).certificate, expectedCert) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const listStub = sinon.stub().resolves({ - certificates: [cert], - nextMarker: undefined, - }) - iot.listCertificates = listStub - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - - const node = new IotCertsFolderNode(iot, new IotNode(iot), config) - const [certNode, ...otherNodes] = await node.getChildren() - - assert(listStub.calledOnceWithExactly({ marker: undefined, pageSize })) - assert(thingsStub.calledOnceWithExactly({ principal: 'arn' })) - assertCertNode(certNode, expectedCert) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const listStub = sinon.stub().resolves({ - certificates: [cert], - nextMarker, - }) - iot.listCertificates = listStub - const thingsStub = sinon.stub().resolves([]) - iot.listThingsForCert = thingsStub - const targetsStub = sinon.stub().resolves([]) - iot.listPolicyTargets = targetsStub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - const node = new IotCertsFolderNode(iot, new IotNode(iot), config) - const [certNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(listStub.calledOnceWithExactly({ marker: undefined, pageSize })) - assertCertNode(certNode, expectedCert) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotCertificateNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotCertificateNode.test.ts deleted file mode 100644 index 3afdcb6181..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotCertificateNode.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { IotClient, IotPolicy } from '../../../../shared/clients/iotClient' -import { Policy } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { IotPolicyCertNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotCertWithPoliciesNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotCertsFolderNode } from '../../../../awsService/iot/explorer/iotCertFolderNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotCertificateNode', function () { - const nextMarker = 'nextMarker' - const pageSize = 250 - - let iot: IotClient - let config: TestSettings - const certArn = 'certArn' - const cert = { id: 'cert', arn: certArn, activeStatus: 'ACTIVE', creationDate: new Date(0) } - const policy: Policy = { policyName: 'policy', policyArn: 'arn' } - const expectedPolicy: IotPolicy = { name: 'policy', arn: 'arn' } - - function assertPolicyNode(node: AWSTreeNodeBase, expectedPolicy: IotPolicy): void { - assert.ok(node instanceof IotPolicyCertNode, `Node ${node} should be a Policy Node`) - assert.deepStrictEqual((node as IotPolicyCertNode).policy, expectedPolicy) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const stub = sinon.stub().resolves({ - policies: [policy], - nextMarker: undefined, - }) - iot.listPrincipalPolicies = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - const node = new IotCertWithPoliciesNode(cert, {} as IotCertsFolderNode, iot, undefined, config) - const [policyNode, ...otherNodes] = await node.getChildren() - - assertPolicyNode(policyNode, expectedPolicy) - assert(stub.calledOnceWithExactly({ principal: certArn, marker: undefined, pageSize })) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const stub = sinon.stub().resolves({ - policies: [policy], - nextMarker, - }) - iot.listPrincipalPolicies = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - const node = new IotCertWithPoliciesNode(cert, {} as IotCertsFolderNode, iot, undefined, config) - const [policyNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ principal: certArn, marker: undefined, pageSize })) - assertPolicyNode(policyNode, expectedPolicy) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotNodes.test.ts b/packages/core/src/test/awsService/iot/explorer/iotNodes.test.ts deleted file mode 100644 index ce7dda7321..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotNodes.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient } from '../../../../shared/clients/iotClient' - -describe('IotNode', function () { - let iot: IotClient - - beforeEach(function () { - iot = {} as any as IotClient - }) - - it('gets children', async function () { - const node = new IotNode(iot) - const [thingFolder, certFolder, policyFolder] = await node.getChildren() - - assert.strictEqual(thingFolder, node.thingFolderNode) - assert.strictEqual(certFolder, node.certFolderNode) - assert.strictEqual(policyFolder, node.policyFolderNode) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotPolicyFolderNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotPolicyFolderNode.test.ts deleted file mode 100644 index 18e91a0253..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotPolicyFolderNode.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotClient, IotPolicy } from '../../../../shared/clients/iotClient' -import { Policy } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotPolicyFolderNode', function () { - const nextMarker = 'nextMarker' - const pageSize = 250 - - let iot: IotClient - let config: TestSettings - const policy: Policy = { policyName: 'policy', policyArn: 'arn' } - const expectedPolicy: IotPolicy = { name: 'policy', arn: 'arn' } - - function assertPolicyNode(node: AWSTreeNodeBase, expectedPolicy: IotPolicy): void { - assert.ok(node instanceof IotPolicyWithVersionsNode, `Node ${node} should be a Policy Node`) - assert.deepStrictEqual((node as IotPolicyWithVersionsNode).policy, expectedPolicy) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const policiesStub = sinon.stub().resolves({ - policies: [policy], - nextMarker: undefined, - }) - iot.listPolicies = policiesStub - const targetsStub = sinon.stub().resolves([]) - iot.listPolicyTargets = targetsStub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - const node = new IotPolicyFolderNode(iot, new IotNode(iot), config) - const [policyNode, ...otherNodes] = await node.getChildren() - - assert(policiesStub.calledOnceWithExactly({ marker: undefined, pageSize })) - assert(targetsStub.calledOnceWithExactly({ policyName: 'policy' })) - assertPolicyNode(policyNode, expectedPolicy) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const policiesStub = sinon.stub().resolves({ - policies: [policy], - nextMarker, - }) - iot.listPolicies = policiesStub - const targetsStub = sinon.stub().resolves([]) - iot.listPolicyTargets = targetsStub - - await config.getSection('aws').update('iot.maxItemsPerPage', pageSize) - const node = new IotPolicyFolderNode(iot, new IotNode(iot), config) - const [policyNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(policiesStub.calledOnceWithExactly({ marker: undefined, pageSize })) - assert(targetsStub.calledOnceWithExactly({ policyName: 'policy' })) - assertPolicyNode(policyNode, expectedPolicy) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotPolicyNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotPolicyNode.test.ts deleted file mode 100644 index 88e55b929d..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotPolicyNode.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { IotClient, IotPolicy } from '../../../../shared/clients/iotClient' -import { PolicyVersion } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { asyncGenerator } from '../../../../shared/utilities/collectionUtils' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyVersionNode } from '../../../../awsService/iot/explorer/iotPolicyVersionNode' -import { IotPolicyFolderNode } from '../../../../awsService/iot/explorer/iotPolicyFolderNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotPolicyNode', function () { - let iot: IotClient - let config: TestSettings - const policyName = 'policy' - const expectedPolicy: IotPolicy = { name: policyName, arn: 'arn' } - const policyVersion: PolicyVersion = { versionId: 'V1', isDefaultVersion: true } - - function assertPolicyVersionNode( - node: AWSTreeNodeBase, - expectedPolicy: IotPolicy, - expectedVersion: PolicyVersion - ): void { - assert.ok(node instanceof IotPolicyVersionNode, `Node ${node} should be a Policy Version Node`) - assert.deepStrictEqual((node as IotPolicyVersionNode).version, expectedVersion) - assert.deepStrictEqual((node as IotPolicyVersionNode).policy, expectedPolicy) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const versions = [{ versionId: 'V1', isDefaultVersion: true }] - const stub = sinon.stub().returns(asyncGenerator(versions)) - iot.listPolicyVersions = stub - const node = new IotPolicyWithVersionsNode( - expectedPolicy, - {} as IotPolicyFolderNode, - iot, - undefined, - config - ) - const [policyVersionNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ policyName })) - assertPolicyVersionNode(policyVersionNode, expectedPolicy, policyVersion) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotPolicyVersionNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotPolicyVersionNode.test.ts deleted file mode 100644 index 4faf56e743..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotPolicyVersionNode.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { IotClient, IotPolicy } from '../../../../shared/clients/iotClient' -import { PolicyVersion } from '@aws-sdk/client-iot' -import { IotPolicyWithVersionsNode } from '../../../../awsService/iot/explorer/iotPolicyNode' -import { IotPolicyVersionNode } from '../../../../awsService/iot/explorer/iotPolicyVersionNode' -import { stringOrProp } from '../../../../shared/utilities/tsUtils' -import { formatLocalized } from '../../../../shared/datetime' - -describe('IotPolicyVersionNode', function () { - const policyName = 'policy' - const expectedPolicy: IotPolicy = { name: policyName, arn: 'arn' } - const createDate = new Date(Date.UTC(2021, 1, 1)) // Feb 1 UTC = Jan 31 PDT - const createDateFormatted = formatLocalized(createDate) - const policyVersion: PolicyVersion = { versionId: 'V1', isDefaultVersion: true, createDate } - const nonDefaultVersion: PolicyVersion = { versionId: 'V2', isDefaultVersion: false, createDate } - - it('creates an IoT Policy Version Node for default version', async function () { - const node = new IotPolicyVersionNode( - expectedPolicy, - policyVersion, - policyVersion.isDefaultVersion!, - {} as IotPolicyWithVersionsNode, - {} as IotClient - ) - - assert.ok( - stringOrProp(node.tooltip, 'tooltip').startsWith( - `Policy: ${policyName}\nVersion: V1\nDEFAULT\nCreated: ${createDateFormatted}` - ) - ) - assert.strictEqual(node.label, 'Version V1*') - }) - - it('creates an IoT Policy Version Node for non-default version', async function () { - const node = new IotPolicyVersionNode( - expectedPolicy, - nonDefaultVersion, - nonDefaultVersion.isDefaultVersion!, - {} as IotPolicyWithVersionsNode, - {} as IotClient - ) - - assert.ok( - stringOrProp(node.tooltip, 'tooltip').startsWith( - `Policy: ${policyName}\nVersion: V2\nCreated: ${createDateFormatted}` - ) - ) - assert.strictEqual(node.label, 'Version V2') - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotThingFolderNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotThingFolderNode.test.ts deleted file mode 100644 index 667b39974b..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotThingFolderNode.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { IotNode } from '../../../../awsService/iot/explorer/iotNodes' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotClient, IotThing } from '../../../../shared/clients/iotClient' -import { ThingAttribute } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotThingFolderNode', function () { - const nextToken = 'nextToken' - const maxResults = 150 - - let iot: IotClient - let config: TestSettings - const thing: ThingAttribute = { thingName: 'thing', thingArn: 'arn' } - const expectedThing: IotThing = { name: 'thing', arn: 'arn' } - - function assertThingNode(node: AWSTreeNodeBase, expectedThing: IotThing): void { - assert.ok(node instanceof IotThingNode, `Node ${node} should be a Thing Node`) - assert.deepStrictEqual((node as IotThingNode).thing, expectedThing) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const stub = sinon.stub().resolves({ - things: [thing], - nextToken: undefined, - }) - iot.listThings = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', maxResults) - const node = new IotThingFolderNode(iot, new IotNode(iot), config) - const [thingNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ nextToken: undefined, maxResults })) - assertThingNode(thingNode, expectedThing) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const stub = sinon.stub().resolves({ - things: [thing], - nextToken, - }) - iot.listThings = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', maxResults) - const node = new IotThingFolderNode(iot, new IotNode(iot), config) - const [thingNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ nextToken: undefined, maxResults })) - assertThingNode(thingNode, expectedThing) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/iot/explorer/iotThingNode.test.ts b/packages/core/src/test/awsService/iot/explorer/iotThingNode.test.ts deleted file mode 100644 index 663860ef96..0000000000 --- a/packages/core/src/test/awsService/iot/explorer/iotThingNode.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { IotCertificate, IotClient } from '../../../../shared/clients/iotClient' -import { Certificate } from '@aws-sdk/client-iot' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { IotThingCertNode } from '../../../../awsService/iot/explorer/iotCertificateNode' -import { IotThingNode } from '../../../../awsService/iot/explorer/iotThingNode' -import { IotThingFolderNode } from '../../../../awsService/iot/explorer/iotThingFolderNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('IotThingNode', function () { - const nextToken = 'nextToken' - const maxResults = 250 - - let iot: IotClient - let config: TestSettings - const thingName = 'thing' - const thing = { name: thingName, arn: 'thingArn' } - const cert: Certificate = { - certificateId: 'cert', - certificateArn: 'arn', - status: 'ACTIVE', - creationDate: new Date(0), - } - const expectedCert: IotCertificate = { - id: 'cert', - arn: 'arn', - activeStatus: 'ACTIVE', - creationDate: new Date(0), - } - - function assertCertNode(node: AWSTreeNodeBase, expectedCert: IotCertificate): void { - assert.ok(node instanceof IotThingCertNode, `Node ${node} should be a Certificate Node`) - assert.deepStrictEqual((node as IotThingCertNode).certificate, expectedCert) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - } - - beforeEach(function () { - iot = {} as any as IotClient - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const stub = sinon.stub().resolves({ - certificates: [cert], - nextToken: undefined, - }) - iot.listThingCertificates = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', maxResults) - const node = new IotThingNode(thing, {} as IotThingFolderNode, iot, config) - const [certNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ thingName, nextToken: undefined, maxResults })) - assertCertNode(certNode, expectedCert) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const stub = sinon.stub().resolves({ - certificates: [cert], - nextToken, - }) - iot.listThingCertificates = stub - - await config.getSection('aws').update('iot.maxItemsPerPage', maxResults) - const node = new IotThingNode(thing, {} as IotThingFolderNode, iot, config) - const [certNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ thingName, nextToken: undefined, maxResults })) - assertCertNode(certNode, expectedCert) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/explorer/redshiftDatabaseNode.test.ts b/packages/core/src/test/awsService/redshift/explorer/redshiftDatabaseNode.test.ts deleted file mode 100644 index f291b926c0..0000000000 --- a/packages/core/src/test/awsService/redshift/explorer/redshiftDatabaseNode.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { mockClient } from 'aws-sdk-client-mock' -import { RedshiftDatabaseNode } from '../../../../awsService/redshift/explorer/redshiftDatabaseNode' -import { RedshiftDataClient, ListSchemasCommand } from '@aws-sdk/client-redshift-data' -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../../../../awsService/redshift/models/models' -import assert = require('assert') -import { RedshiftSchemaNode } from '../../../../awsService/redshift/explorer/redshiftSchemaNode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' - -describe('RedshiftDatabaseNode', function () { - const mockRedshiftData = mockClient(RedshiftDataClient) - const redshiftClient = new DefaultRedshiftClient('us-east-1', () => mockRedshiftData as any, undefined, undefined) - const connectionParams = new ConnectionParams( - ConnectionType.TempCreds, - 'testDb1', - 'warehouseId', - RedshiftWarehouseType.PROVISIONED - ) - - describe('getChildren', function () { - afterEach(function () { - mockRedshiftData.reset() - }) - - it('loads schemas successfully', async () => { - const node = new RedshiftDatabaseNode('testDB1', redshiftClient, connectionParams) - mockRedshiftData.on(ListSchemasCommand).resolves({ Schemas: ['schema1'] }) - const childNodes = await node.getChildren() - verifyChildNodes(childNodes, false) - }) - - it('loads schemas and shows load more node when there are more schemas', async () => { - const node = new RedshiftDatabaseNode('testDB1', redshiftClient, connectionParams) - mockRedshiftData.on(ListSchemasCommand).resolves({ Schemas: ['schema1'], NextToken: 'next' }) - const childNodes = await node.getChildren() - verifyChildNodes(childNodes, true) - }) - - it('shows error node when listSchema fails', async () => { - const node = new RedshiftDatabaseNode('testDB1', redshiftClient, connectionParams) - mockRedshiftData.on(ListSchemasCommand).rejects('Failed') - const childNodes = await node.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0].contextValue, 'awsErrorNode') - }) - }) -}) - -function verifyChildNodes(childNodes: AWSTreeNodeBase[], withLoadMore: boolean) { - assert.strictEqual(childNodes.length, withLoadMore ? 2 : 1) - assert.ok(childNodes[0] instanceof RedshiftSchemaNode) - assert.strictEqual((childNodes[0] as RedshiftSchemaNode).schemaName, 'schema1') - if (withLoadMore) { - assert.ok(childNodes[1] instanceof MoreResultsNode) - } -} diff --git a/packages/core/src/test/awsService/redshift/explorer/redshiftNode.test.ts b/packages/core/src/test/awsService/redshift/explorer/redshiftNode.test.ts deleted file mode 100644 index 26df4f5abf..0000000000 --- a/packages/core/src/test/awsService/redshift/explorer/redshiftNode.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*! - * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -// eslint-disable-next-line header/header -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock' -import { RedshiftNode } from '../../../../awsService/redshift/explorer/redshiftNode' -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import assert = require('assert') -import { RedshiftWarehouseNode } from '../../../../awsService/redshift/explorer/redshiftWarehouseNode' -import { Cluster, ClustersMessage, RedshiftClient, DescribeClustersCommand } from '@aws-sdk/client-redshift' -import { - ListWorkgroupsResponse, - Workgroup, - RedshiftServerlessClient, - ListWorkgroupsCommand, -} from '@aws-sdk/client-redshift-serverless' -import { RedshiftWarehouseType } from '../../../../awsService/redshift/models/models' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' - -function getExpectedProvisionedResponse(withNextToken: boolean): ClustersMessage { - const response = { - Clusters: [ - { ClusterNamespaceArn: 'testArn', ClusterIdentifier: 'testId', ClusterAvailabilityStatus: 'available' }, - ] as Cluster[], - } as ClustersMessage - if (withNextToken) { - response.Marker = 'next' - } - return response -} - -function getExpectedServerlessResponse(withNextToken: boolean): ListWorkgroupsResponse { - const response = { - workgroups: [{ workgroupArn: 'testArn', workgroupName: 'testWorkgroup', status: 'AVAILABLE' }] as Workgroup[], - } as ListWorkgroupsResponse - if (withNextToken) { - response.nextToken = 'next' - } - return response -} - -function verifyChildNodeCounts( - childNodes: AWSTreeNodeBase[], - expectedProvisionedNodeCount: number, - expectedServerlessNodeCount: number, - expectedLoadMoreNodeCount: number -) { - const provisionedNodes = childNodes.filter( - (childNode) => (childNode as RedshiftWarehouseNode).warehouseType === RedshiftWarehouseType.PROVISIONED - ) - const serverlessNodes = childNodes.filter( - (childNode) => (childNode as RedshiftWarehouseNode).warehouseType === RedshiftWarehouseType.SERVERLESS - ) - const loadMoreNodes = childNodes.filter((childNode) => childNode instanceof MoreResultsNode) - assert.strictEqual( - childNodes.length, - expectedProvisionedNodeCount + expectedServerlessNodeCount + expectedLoadMoreNodeCount, - 'Total node count mismatch' - ) - assert.strictEqual(provisionedNodes.length, expectedProvisionedNodeCount, 'ProvisionedNode count mismatch') - assert.strictEqual(serverlessNodes.length, expectedServerlessNodeCount, 'ServerlessNode count mismatch') - assert.strictEqual(loadMoreNodes.length, expectedLoadMoreNodeCount, 'LoadMoreNode count mismatch') -} - -describe('redshiftNode', function () { - describe('getChildren', function () { - let node: RedshiftNode - let redshiftClient: DefaultRedshiftClient - let mockRedshift: AwsClientStub - let mockRedshiftServerless: AwsClientStub - - beforeEach(function () { - mockRedshift = mockClient(RedshiftClient) - mockRedshiftServerless = mockClient(RedshiftServerlessClient) - redshiftClient = new DefaultRedshiftClient( - 'us-east-1', - undefined, - // @ts-expect-error - () => mockRedshift, - () => mockRedshiftServerless - ) - node = new RedshiftNode(redshiftClient) - }) - - afterEach(function () { - mockRedshift.reset() - mockRedshiftServerless.reset() - }) - - it('gets both provisioned and serverless warehouses when no results have been loaded', async () => { - mockRedshift.on(DescribeClustersCommand).resolves(getExpectedProvisionedResponse(false)) - mockRedshiftServerless.on(ListWorkgroupsCommand).resolves(getExpectedServerlessResponse(false)) - const childNodes = await node.getChildren() - verifyChildNodeCounts(childNodes, 1, 1, 0) - }) - - it('gets both provisioned and serverless warehouses if results have been loaded but there are more results', async () => { - mockRedshift.on(DescribeClustersCommand).resolves(getExpectedProvisionedResponse(true)) - mockRedshiftServerless.on(ListWorkgroupsCommand).resolves(getExpectedServerlessResponse(true)) - const childNodes = await node.getChildren() - verifyChildNodeCounts(childNodes, 1, 1, 1) - }) - - it('gets only provisioned warehouses if results have been loaded and there are only more provisioned warehouses', async () => { - mockRedshift.on(DescribeClustersCommand).resolves(getExpectedProvisionedResponse(true)) - mockRedshiftServerless.on(ListWorkgroupsCommand).resolves(getExpectedServerlessResponse(false)) - const childNodes = await node.getChildren() - verifyChildNodeCounts(childNodes, 1, 1, 1) - await node.loadMoreChildren() - const newChildNodes = await node.getChildren() - verifyChildNodeCounts(newChildNodes, 2, 1, 1) - }) - - it('gets only serverless warehouses if results have been loaded and there are only more serverless warehouses', async () => { - mockRedshift.on(DescribeClustersCommand).resolves(getExpectedProvisionedResponse(false)) - mockRedshiftServerless.on(ListWorkgroupsCommand).resolves(getExpectedServerlessResponse(true)) - const childNodes = await node.getChildren() - verifyChildNodeCounts(childNodes, 1, 1, 1) - await node.loadMoreChildren() - const newChildNodes = await node.getChildren() - verifyChildNodeCounts(newChildNodes, 1, 2, 1) - }) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/explorer/redshiftSchemaNode.test.ts b/packages/core/src/test/awsService/redshift/explorer/redshiftSchemaNode.test.ts deleted file mode 100644 index 68c2d792cf..0000000000 --- a/packages/core/src/test/awsService/redshift/explorer/redshiftSchemaNode.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock' -import * as assert from 'assert' -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import { RedshiftDataClient, ListTablesCommand, ListTablesResponse } from '@aws-sdk/client-redshift-data' -import { RedshiftSchemaNode } from '../../../../awsService/redshift/explorer/redshiftSchemaNode' -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../../../../awsService/redshift/models/models' -import { RedshiftTableNode } from '../../../../awsService/redshift/explorer/redshiftTableNode' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' - -describe('RedshiftSchemaNode', function () { - const mockRedshiftData: AwsClientStub = mockClient(RedshiftDataClient) - const redshiftClient: DefaultRedshiftClient = new DefaultRedshiftClient( - 'us-east-1', - // @ts-expect-error - () => mockRedshiftData, - undefined, - undefined - ) - const connectionParams = new ConnectionParams( - ConnectionType.TempCreds, - 'testDb1', - 'warehouseId', - RedshiftWarehouseType.PROVISIONED - ) - - describe('getChildren', function () { - afterEach(function () { - mockRedshiftData.reset() - }) - - it('gets table nodes and filters out tables with pkey', async () => { - mockRedshiftData - .on(ListTablesCommand) - .resolves({ Tables: [{ name: 'test' }, { name: 'test_pkey' }] } as ListTablesResponse) - const node = new RedshiftSchemaNode('testSchema', redshiftClient, connectionParams) - const childNodes = await node.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual((childNodes[0] as RedshiftTableNode).tableName, 'test') - }) - - it('gets table nodes & adds load more node if there are more nodes to be loaded', async () => { - mockRedshiftData.on(ListTablesCommand).resolves({ - Tables: [{ name: 'test' }, { name: 'test_pkey' }], - NextToken: 'next', - } as ListTablesResponse) - const node = new RedshiftSchemaNode('testSchema', redshiftClient, connectionParams) - const childNodes = await node.getChildren() - assert.strictEqual(childNodes.length, 2) - assert.strictEqual((childNodes[0] as RedshiftTableNode).tableName, 'test') - assert.ok(childNodes[1] instanceof MoreResultsNode) - }) - - it('shows error node when list table API errors out', async () => { - mockRedshiftData.on(ListTablesCommand).rejects('failed') - const node = new RedshiftSchemaNode('testSchema', redshiftClient, connectionParams) - const childNodes = await node.getChildren() - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0].contextValue, 'awsErrorNode') - }) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/explorer/redshiftWarehouseNode.test.ts b/packages/core/src/test/awsService/redshift/explorer/redshiftWarehouseNode.test.ts deleted file mode 100644 index b82e9c972f..0000000000 --- a/packages/core/src/test/awsService/redshift/explorer/redshiftWarehouseNode.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon = require('sinon') -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import { ListDatabasesResponse, RedshiftDataClient, ListDatabasesCommand } from '@aws-sdk/client-redshift-data' -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../../../../awsService/redshift/models/models' -import { - RedshiftWarehouseNode, - CreateNotebookNode, -} from '../../../../awsService/redshift/explorer/redshiftWarehouseNode' -import { RedshiftNode } from '../../../../awsService/redshift/explorer/redshiftNode' -import { AWSResourceNode } from '../../../../shared/treeview/nodes/awsResourceNode' -import * as assert from 'assert' -import { RedshiftDatabaseNode } from '../../../../awsService/redshift/explorer/redshiftDatabaseNode' -import { AWSCommandTreeNode } from '../../../../shared/treeview/nodes/awsCommandTreeNode' -import { RedshiftNodeConnectionWizard } from '../../../../awsService/redshift/wizards/connectionWizard' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock' - -function verifyChildNodes(childNodes: AWSTreeNodeBase[], databaseNodeCount: number, shouldHaveLoadMore: boolean) { - assert.strictEqual(childNodes.length, databaseNodeCount + (shouldHaveLoadMore ? 1 : 0) + 1) - const startButtonNodes = childNodes.filter((node) => node instanceof CreateNotebookNode) - assert.strictEqual(startButtonNodes.length, 1) - assert.ok(childNodes[0] instanceof CreateNotebookNode) - const databaseNodes = childNodes.filter((node) => node instanceof RedshiftDatabaseNode) - assert.strictEqual(databaseNodes.length, databaseNodeCount) - const loadMoreNodes = childNodes.filter((node) => node instanceof MoreResultsNode) - assert.strictEqual(loadMoreNodes.length, shouldHaveLoadMore ? 1 : 0) -} - -function verifyRetryNode(childNodes: AWSTreeNodeBase[]) { - assert.strictEqual(childNodes.length, 1) - assert.ok(childNodes[0] instanceof AWSCommandTreeNode) - const cmdTreeNode = childNodes[0] as AWSCommandTreeNode - assert.strictEqual(cmdTreeNode.command?.command, 'aws.refreshAwsExplorerNode') -} - -describe('redshiftWarehouseNode', function () { - describe('getChildren', function () { - const expectedResponse = { Databases: ['testDb1'] } as ListDatabasesResponse - const expectedResponseWithNextToken = { Databases: ['testDb1'], NextToken: 'next' } as ListDatabasesResponse - const connectionParams = new ConnectionParams( - ConnectionType.TempCreds, - 'testDb1', - 'warehouseId', - RedshiftWarehouseType.PROVISIONED - ) - const resourceNode = { arn: 'testARN', name: 'warehouseId' } as AWSResourceNode - let mockRedshiftData: AwsClientStub - let redshiftClient: DefaultRedshiftClient - let redshiftNode: RedshiftNode - let warehouseNode: RedshiftWarehouseNode - let connectionWizardStub: sinon.SinonStub - - beforeEach(function () { - mockRedshiftData = mockClient(RedshiftDataClient) - redshiftClient = new DefaultRedshiftClient( - 'us-east-1', - // @ts-expect-error - () => mockRedshiftData, - undefined, - undefined - ) - redshiftNode = new RedshiftNode(redshiftClient) - }) - - afterEach(function () { - mockRedshiftData.reset() - connectionWizardStub.restore() - }) - it('gets databases for a warehouse and adds a start button', async () => { - connectionWizardStub = sinon.stub(RedshiftNodeConnectionWizard.prototype, 'run').resolves(connectionParams) - warehouseNode = new RedshiftWarehouseNode(redshiftNode, resourceNode, RedshiftWarehouseType.PROVISIONED) - mockRedshiftData.on(ListDatabasesCommand).resolves(expectedResponse) - - const childNodes = await warehouseNode.getChildren() - - verifyChildNodes(childNodes, 1, false) - }) - - it('gets databases for a warehouse, adds a start button and a load more button if there are more results', async () => { - connectionWizardStub = sinon.stub(RedshiftNodeConnectionWizard.prototype, 'run').resolves(connectionParams) - warehouseNode = new RedshiftWarehouseNode(redshiftNode, resourceNode, RedshiftWarehouseType.PROVISIONED) - mockRedshiftData.on(ListDatabasesCommand).resolves(expectedResponseWithNextToken) - - const childNodes = await warehouseNode.getChildren() - - verifyChildNodes(childNodes, 1, true) - }) - - it('shows a node with retry if user exits wizard', async () => { - connectionWizardStub = sinon.stub(RedshiftNodeConnectionWizard.prototype, 'run').resolves(undefined) - warehouseNode = new RedshiftWarehouseNode(redshiftNode, resourceNode, RedshiftWarehouseType.PROVISIONED) - const childNodes = await warehouseNode.getChildren() - verifyRetryNode(childNodes) - }) - - it('shows a node with retry if there is error fetching databases', async () => { - connectionWizardStub = sinon.stub(RedshiftNodeConnectionWizard.prototype, 'run').resolves(connectionParams) - warehouseNode = new RedshiftWarehouseNode(redshiftNode, resourceNode, RedshiftWarehouseType.PROVISIONED) - mockRedshiftData.on(ListDatabasesCommand).rejects('Failed') - const childNodes = await warehouseNode.getChildren() - verifyRetryNode(childNodes) - }) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/notebook/cellStatusBarItemProvider.test.ts b/packages/core/src/test/awsService/redshift/notebook/cellStatusBarItemProvider.test.ts deleted file mode 100644 index eb22459659..0000000000 --- a/packages/core/src/test/awsService/redshift/notebook/cellStatusBarItemProvider.test.ts +++ /dev/null @@ -1,63 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon = require('sinon') -import assert = require('assert') -import { CellStatusBarItemProvider } from '../../../../awsService/redshift/notebook/cellStatusBarItemProvider' -import * as vscode from 'vscode' - -describe('CellStatusBarItemProvider', function () { - let cell: any - let token: any - let cellStatusBarItemProvider: any - - beforeEach(() => { - cell = { - notebook: { - metadata: { - connectionParams: { - warehouseIdentifier: 'TestWarehouse', - }, - }, - }, - } - token = new vscode.CancellationTokenSource().token - cellStatusBarItemProvider = new CellStatusBarItemProvider() - }) - - this.afterEach(() => { - sinon.restore() - }) - - it('provides "Connect" status bar item when cell has no connectionParams', () => { - const cell = { notebook: { metadata: { connectionParams: undefined } } } - const expectedText = '$(notebook-state-error) Connect' - const result = cellStatusBarItemProvider.provideCellStatusBarItems(cell, undefined) - assert(Array.isArray(result)) - assert.strictEqual(result.length, 1) - assert.strictEqual(result[0].text, expectedText) - }) - - it('provides status bar with success-icon and connection information', () => { - const result = cellStatusBarItemProvider.provideCellStatusBarItems(cell, token) - const expectedText = '$(notebook-state-success) Connected to TestWarehouse' - const expectedCommand = { - command: 'aws.redshift.notebookConnectClicked', - arguments: [cell, cellStatusBarItemProvider.refreshCellStatusBar.bind(cellStatusBarItemProvider)], - } - assert.strictEqual(result.length, 1) - assert.strictEqual(result[0].text, expectedText) - assert.deepStrictEqual(result[0].command.command, expectedCommand.command) - }) - - it('fires onDidChangeCellStatusBarItems when refreshCellStatusBar is called', () => { - const onDidChangeCellStatusBarItemsSpy = sinon.spy( - cellStatusBarItemProvider._onDidChangeCellStatusBarItems, - 'fire' - ) - cellStatusBarItemProvider.refreshCellStatusBar() - assert.strictEqual(onDidChangeCellStatusBarItemsSpy.name, 'fire') - }) -}) diff --git a/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookController.test.ts b/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookController.test.ts deleted file mode 100644 index 8b9bfd5b54..0000000000 --- a/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookController.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { RedshiftNotebookController } from '../../../../awsService/redshift/notebook/redshiftNotebookController' -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock' -import assert = require('assert') -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import { RedshiftDataClient } from '@aws-sdk/client-redshift-data' -import sinon = require('sinon') - -describe('RedshiftNotebookController', () => { - const mockRedshiftData: AwsClientStub = mockClient(RedshiftDataClient) - // @ts-expect-error - const redshiftClient = new DefaultRedshiftClient('us-east-1', () => mockRedshiftData, undefined, undefined) - let notebookController: any - let createNotebookControllerStub: any - beforeEach(() => { - createNotebookControllerStub = sinon.stub(vscode.notebooks, 'createNotebookController') - const controllerInstanceValue = { - supportedLanguages: ['sql'], - supportsExecutionOrder: false, - executeHandler: undefined, - dispose: sinon.stub, - } - createNotebookControllerStub.returns(controllerInstanceValue) - notebookController = new RedshiftNotebookController(redshiftClient) - }) - afterEach(() => { - mockRedshiftData.reset() - sinon.restore() - }) - it('validating parameters of a notebook controller instance', () => { - assert.strictEqual(notebookController.id, 'aws-redshift-sql-notebook') - assert.strictEqual(notebookController.label, 'Redshift SQL notebook') - assert.deepStrictEqual(notebookController.supportedLanguages, ['sql']) - assert.strictEqual(notebookController._executionOrder, 0) - }) - - it('should execute all cells', () => { - const executeAllStub = sinon.stub(notebookController, '_doExecution') - const mockcell = { - document: { - getText: () => 'select * from table;', - }, - } - const cells = [mockcell] - notebookController._executeAll(cells, undefined, undefined) - assert.strictEqual(executeAllStub.callCount, cells.length) - }) - it('should execute a cell successfully', async () => { - const executeCellStub = sinon.stub(notebookController, 'executeCell') - executeCellStub.resolves(vscode.NotebookCellOutput) - const cellMock: any = { - metadata: { - connectionParams: { - warehouseIdentifier: 'TestWarehouse', - }, - }, - document: { - getText: sinon.stub().returns('SELECT * FROM my_table'), - }, - } - const cellOutput = await executeCellStub(cellMock) - assert.strictEqual(cellOutput.isNotebookCellOutput.length, 1) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookSerializer.test.ts b/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookSerializer.test.ts deleted file mode 100644 index 91607675b1..0000000000 --- a/packages/core/src/test/awsService/redshift/notebook/redshiftNotebookSerializer.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RedshiftNotebookSerializer } from '../../../../awsService/redshift/notebook/redshiftNotebookSerializer' -import * as vscode from 'vscode' -import assert = require('assert') - -describe('RedshiftNotebookSerializer', () => { - let serializer: RedshiftNotebookSerializer - - beforeEach(() => { - serializer = new RedshiftNotebookSerializer() - }) - - it('should correctly deserialize an empty notebook cell', async () => { - const contents = new TextEncoder().encode(JSON.stringify({ cells: [] })) - const token = new vscode.CancellationTokenSource().token - const notebookData = await serializer.deserializeNotebook(contents, token) - assert.deepStrictEqual(notebookData.cells, []) - }) - - it('should deserialize NotebookData correctly', async () => { - const serializer = new RedshiftNotebookSerializer() - const expectedMetadata = { Test: 'Meta' } - const rawNotebookData = { - cells: [ - { - kind: vscode.NotebookCellKind.Code, - language: 'sql', - value: 'select * from table', - metadata: expectedMetadata, - }, - ], - } - const rawData = new TextEncoder().encode(JSON.stringify(rawNotebookData)) - const token = new vscode.CancellationTokenSource().token - const result = await serializer.deserializeNotebook(rawData, token) - assert.strictEqual(result.cells.length, 1) - const actualResultCopy = JSON.parse(JSON.stringify(result.cells[0])) - assert.deepStrictEqual(actualResultCopy, { - kind: vscode.NotebookCellKind.Code, - value: 'select * from table', - languageId: 'sql', - outputs: [], - }) - assert.deepStrictEqual(result.metadata, expectedMetadata) - }) - - it('should serialize NotebookData correctly', async () => { - const serializer = new RedshiftNotebookSerializer() - const notebookData = new vscode.NotebookData([ - new vscode.NotebookCellData(vscode.NotebookCellKind.Code, 'select * from table;', 'SQL'), - ]) - notebookData.metadata = { Test: 'Meta' } - const token = new vscode.CancellationTokenSource().token - const result = await serializer.serializeNotebook(notebookData, token) - const decodedResult = new TextDecoder().decode(result) - const expectedSerializedData = - '{"cells":[{"kind":2,"language":"SQL","value":"select * from table;","metadata":{"Test":"Meta"}}]}' - assert.strictEqual(decodedResult, expectedSerializedData) - }) - - it('should correctly handle invalid JSOn during deserialization', async () => { - const invalidContents = new TextEncoder().encode('data:{xyz:onk}') - const token = new vscode.CancellationTokenSource().token - const notebookData = await serializer.deserializeNotebook(invalidContents, token) - assert.deepStrictEqual(Array.from(notebookData.cells), []) - }) -}) diff --git a/packages/core/src/test/awsService/redshift/wizards/connectionWizard.test.ts b/packages/core/src/test/awsService/redshift/wizards/connectionWizard.test.ts deleted file mode 100644 index f8df01a738..0000000000 --- a/packages/core/src/test/awsService/redshift/wizards/connectionWizard.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { RedshiftNode } from '../../../../awsService/redshift/explorer/redshiftNode' -import { DefaultRedshiftClient } from '../../../../shared/clients/redshiftClient' -import { createWizardTester } from '../../../shared/wizards/wizardTestUtils' -import { RedshiftWarehouseType, ConnectionType } from '../../../../awsService/redshift/models/models' -import { RedshiftWarehouseNode } from '../../../../awsService/redshift/explorer/redshiftWarehouseNode' -import { AWSResourceNode } from '../../../../shared/treeview/nodes/awsResourceNode' -import { - NotebookConnectionWizard, - RedshiftNodeConnectionWizard, -} from '../../../../awsService/redshift/wizards/connectionWizard' -import { RegionProvider } from '../../../../shared/regions/regionProvider' - -describe('redshiftNodeConnectionWizard', async function () { - let mockRedshiftClient: DefaultRedshiftClient - let provisionedNode: RedshiftWarehouseNode - let serverlessNode: RedshiftWarehouseNode - const clusterName = 'testCluster' - const workgroupName = 'testWorkgroup' - const clusterArn = 'testClusterARN' - const workgroupArn = 'testWorkgroupARN' - - beforeEach(function () { - mockRedshiftClient = {} - const redshiftNode = new RedshiftNode(mockRedshiftClient) - provisionedNode = new RedshiftWarehouseNode( - redshiftNode, - { arn: clusterArn, name: clusterName } as AWSResourceNode, - RedshiftWarehouseType.PROVISIONED - ) - serverlessNode = new RedshiftWarehouseNode( - redshiftNode, - { arn: workgroupArn, name: workgroupName } as AWSResourceNode, - RedshiftWarehouseType.SERVERLESS - ) - }) - - it('shows all steps for provisionedNode and database username connection type', async function () { - const testWizard = await createWizardTester(new RedshiftNodeConnectionWizard(provisionedNode)) - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.TempCreds) - testWizard.database.assertShowFirst() - testWizard.database.applyInput('testDB') - testWizard.username.assertShowFirst() - }) - - it('shows only database as input for serverlessNode and database username connection type', async function () { - const testWizard = await createWizardTester(new RedshiftNodeConnectionWizard(serverlessNode)) - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.TempCreds) - testWizard.database.assertShowFirst() - testWizard.username.assertDoesNotShow() - }) - - it('shows all steps for provisionedNode and secrets manager connection type', async function () { - const testWizard = await createWizardTester(new RedshiftNodeConnectionWizard(provisionedNode)) - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.SecretsManager) - testWizard.database.assertShowFirst() - testWizard.database.applyInput('testDB') - testWizard.secret.assertShowFirst() - testWizard.secret.applyInput('SecretTest') - }) - - it('shows all steps for serverlessNode and secrets manager connection type', async function () { - const testWizard = await createWizardTester(new RedshiftNodeConnectionWizard(serverlessNode)) - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.SecretsManager) - testWizard.database.assertShowFirst() - testWizard.database.applyInput('testDB') - testWizard.secret.assertShowFirst() - testWizard.secret.applyInput('SecretTest') - }) -}) - -describe('NotebookConnectionWizard', async () => { - const mockRegionProvider: RegionProvider = {} - - it('shows all steps for database username connection type', async function () { - const testWizard = await createWizardTester(new NotebookConnectionWizard(mockRegionProvider)) - testWizard.region.assertShowFirst() - testWizard.region.applyInput({ id: 'US East - 1', name: 'us-east-1' }) - testWizard.warehouseIdentifier.assertShowFirst() - testWizard.warehouseIdentifier.applyInput('testCluster') - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.TempCreds) - testWizard.database.assertShowFirst() - testWizard.database.applyInput('testDB') - testWizard.username.assertShowFirst() - testWizard.username.applyInput('testUser') - testWizard.warehouseType.assertDoesNotShow() - }) - - it('shows all steps for secrets manager connection type', async function () { - const testWizard = await createWizardTester(new NotebookConnectionWizard(mockRegionProvider)) - testWizard.region.assertShowFirst() - testWizard.region.applyInput({ id: 'US East - 1', name: 'us-east-1' }) - testWizard.warehouseIdentifier.assertShowFirst() - testWizard.warehouseIdentifier.applyInput('testCluster') - testWizard.connectionType.assertShowFirst() - testWizard.connectionType.applyInput(ConnectionType.SecretsManager) - testWizard.database.assertShowFirst() - testWizard.database.applyInput('testDB') - testWizard.secret.applyInput('SecretTest') - testWizard.warehouseType.assertDoesNotShow() - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/copyPath.test.ts b/packages/core/src/test/awsService/s3/commands/copyPath.test.ts deleted file mode 100644 index 1e67299c7a..0000000000 --- a/packages/core/src/test/awsService/s3/commands/copyPath.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { copyPathCommand } from '../../../../awsService/s3/commands/copyPath' -import { S3FolderNode } from '../../../../awsService/s3/explorer/s3FolderNode' -import { S3Client } from '../../../../shared/clients/s3' -import { FakeClipboard } from '../../../shared/vscode/fakeEnv' - -describe('copyPathCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - it('copies path to clipboard and shows status bar confirmation', async function () { - const node = createS3FolderNode() - - await copyPathCommand(node) - - assert.strictEqual(await vscode.env.clipboard.readText(), 'path') - }) -}) - -function createS3FolderNode(): S3FolderNode { - return new S3FolderNode( - { Name: 'name', BucketRegion: 'region', Arn: 'arn' }, - { name: 'name', path: 'path', arn: 'arn ' }, - {} as S3Client - ) -} diff --git a/packages/core/src/test/awsService/s3/commands/createBucket.test.ts b/packages/core/src/test/awsService/s3/commands/createBucket.test.ts deleted file mode 100644 index 62c33905a1..0000000000 --- a/packages/core/src/test/awsService/s3/commands/createBucket.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createBucketCommand } from '../../../../awsService/s3/commands/createBucket' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Client } from '../../../../shared/clients/s3' -import { CancellationError } from '../../../../shared/utilities/timeoutUtils' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('createBucketCommand', function () { - const bucketName = 'buc.ket-n4.m3' - let s3: S3Client - let node: S3Node - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - s3 = {} as any as S3Client - node = new S3Node(s3) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for bucket name, creates bucket, shows success, and refreshes node', async function () { - const stub = sinon.stub().resolves({ - bucket: { name: bucketName, region: 'region', arn: 'arn' }, - }) - s3.createBucket = stub - - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter a new bucket name') - assert.strictEqual(input.placeholder, 'Bucket Name') - input.acceptValue(bucketName) - }) - await createBucketCommand(node) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created bucket: buc.ket-n4.m3/) - - assert(stub.calledOnceWithExactly({ bucketName })) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - it('does nothing when prompt is cancelled', async function () { - const stub = sinon.stub() - s3.createBucket = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - await assert.rejects(() => createBucketCommand(node), CancellationError) - - assert(stub.notCalled) - }) - - it('throws an error and refreshes node when bucket creation fails', async function () { - const stub = sinon.stub().rejects() - s3.createBucket = stub - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(bucketName)) - await assert.rejects(() => createBucketCommand(node), /Failed to create bucket/) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/createFolder.test.ts b/packages/core/src/test/awsService/s3/commands/createFolder.test.ts deleted file mode 100644 index 9ad31412fe..0000000000 --- a/packages/core/src/test/awsService/s3/commands/createFolder.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createFolderCommand } from '../../../../awsService/s3/commands/createFolder' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Client } from '../../../../shared/clients/s3' -import { getTestWindow } from '../../../shared/vscode/window' - -describe('createFolderCommand', function () { - const invalidFolderNames: { folderName: string; error: string }[] = [ - { folderName: 'contains/delimiter', error: `Folder name must not contain '/'` }, - { folderName: '', error: 'Folder name must not be empty' }, - ] - - const folderName = 'foo' - const folderPath = 'foo/' - const bucketName = 'bucket-name' - - let s3: S3Client - let node: S3BucketNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - s3 = {} as any as S3Client - node = new S3BucketNode({ Name: bucketName, BucketRegion: 'region', Arn: 'arn' }, new S3Node(s3), s3) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('prompts for folder name, creates folder, shows success, and refreshes node', async function () { - const stub = sinon.stub().resolves({ - folder: { name: folderName, path: folderPath, arn: 'arn' }, - }) - s3.createFolder = stub - - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter a folder to create in s3://bucket-name') - assert.strictEqual(input.placeholder, 'Folder Name') - input.acceptValue(folderName) - }) - await createFolderCommand(node) - - getTestWindow() - .getFirstMessage() - .assertInfo(/Created folder: foo/) - - assert(stub.calledOnceWithExactly({ path: folderPath, bucketName })) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - it('does nothing when prompt is cancelled', async function () { - getTestWindow().onDidShowInputBox((input) => input.hide()) - await assert.rejects(() => createFolderCommand(node), /cancelled/i) - }) - - it('shows an error message and refreshes node when folder creation fails', async function () { - const stub = sinon.stub().rejects(new Error('Expected failure')) - s3.createFolder = stub - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(folderName)) - await assert.rejects(() => createFolderCommand(node), /failed to create folder/i) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - }) - - for (const invalid of invalidFolderNames) { - it(`warns '${invalid.error}' when folder name is '${invalid.folderName}'`, async () => { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(invalid.folderName) - assert.strictEqual(input.validationMessage, invalid.error) - input.hide() - }) - await assert.rejects(() => createFolderCommand(node)) - }) - } -}) diff --git a/packages/core/src/test/awsService/s3/commands/deleteBucket.test.ts b/packages/core/src/test/awsService/s3/commands/deleteBucket.test.ts deleted file mode 100644 index 5bca81235a..0000000000 --- a/packages/core/src/test/awsService/s3/commands/deleteBucket.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { deleteBucketCommand } from '../../../../awsService/s3/commands/deleteBucket' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Client } from '../../../../shared/clients/s3' -import { assertNoErrorMessages, getTestWindow } from '../../../shared/vscode/window' - -describe('deleteBucketCommand', function () { - const bucketName = 'bucket-name' - - let s3: S3Client - let parentNode: S3Node - let node: S3BucketNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - s3 = {} as any as S3Client - parentNode = new S3Node(s3) - node = new S3BucketNode({ Name: bucketName, BucketRegion: 'region', Arn: 'arn' }, parentNode, s3) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes bucket, shows progress bar, and refreshes parent node', async function () { - const stub = sinon.stub() - s3.deleteBucket = stub - - getTestWindow().onDidShowInputBox((input) => { - assert.strictEqual(input.prompt, 'Enter bucket-name to confirm deletion') - assert.strictEqual(input.placeholder, bucketName) - input.acceptValue(bucketName) - }) - await deleteBucketCommand(node) - - assert(stub.calledOnceWithExactly({ bucketName })) - - getTestWindow().getFirstMessage().assertProgress('Deleting bucket-name...') - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing when deletion is cancelled', async function () { - const stub = sinon.stub() - s3.deleteBucket = stub - - getTestWindow().onDidShowInputBox((input) => input.hide()) - await assert.rejects(() => deleteBucketCommand(node), /cancelled/i) - - assert(stub.notCalled) - - assertNoErrorMessages() - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message and refreshes node when bucket deletion fails', async function () { - const stub = sinon.stub().rejects(new Error('Expected failure')) - s3.deleteBucket = stub - - getTestWindow().onDidShowInputBox((input) => input.acceptValue(bucketName)) - await assert.rejects(() => deleteBucketCommand(node), /failed to delete bucket bucket-name/i) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('warns when confirmation is invalid', async function () { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('something other than the bucket name') - assert.strictEqual(input.validationMessage, 'Enter bucket-name to confirm deletion') - input.hide() - }) - await assert.rejects(() => deleteBucketCommand(node)) - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/deleteFile.test.ts b/packages/core/src/test/awsService/s3/commands/deleteFile.test.ts deleted file mode 100644 index ce6a5996c6..0000000000 --- a/packages/core/src/test/awsService/s3/commands/deleteFile.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { deleteFileCommand } from '../../../../awsService/s3/commands/deleteFile' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Bucket, S3Client } from '../../../../shared/clients/s3' -import { assertNoErrorMessages, getTestWindow } from '../../../shared/vscode/window' - -describe('deleteFileCommand', function () { - const key = 'foo/bar.jpg' - const name = 'bar.jpg' - const bucketName = 'bucket-name' - const bucket: S3Bucket = { Name: bucketName, BucketRegion: 'region', Arn: 'arn' } - - let s3: S3Client - let parentNode: S3BucketNode - let node: S3FileNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - s3 = {} as any as S3Client - parentNode = new S3BucketNode(bucket, {} as S3Node, s3) - node = new S3FileNode(bucket, { name, key, arn: 'arn' }, parentNode, s3) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes file, shows status bar confirmation, and refreshes parent node', async function () { - getTestWindow().onDidShowMessage((m) => m.selectItem('Delete')) - const stub = sinon.stub() - s3.deleteObject = stub - await deleteFileCommand(node) - - getTestWindow() - .getFirstMessage() - .assertWarn('Are you sure you want to delete file s3://bucket-name/foo/bar.jpg?') - - assert(stub.calledOnceWithExactly({ bucketName, key })) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) - - it('does nothing when deletion is cancelled', async function () { - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - const stub = sinon.stub() - s3.deleteObject = stub - await assert.rejects(() => deleteFileCommand(node), /cancelled/i) - - assert(stub.notCalled) - assert.deepStrictEqual(getTestWindow().statusBar.messages, []) - assertNoErrorMessages() - sandbox.assert.notCalled(spyExecuteCommand) - }) - - it('shows an error message and refreshes node when file deletion fails', async function () { - const stub = sinon.stub().rejects(new Error('Expected failure')) - s3.deleteObject = stub - - getTestWindow().onDidShowMessage((m) => m.selectItem('Delete')) - await assert.rejects(() => deleteFileCommand(node), /failed to delete file bar.jpg/i) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/downloadFileAs.test.ts b/packages/core/src/test/awsService/s3/commands/downloadFileAs.test.ts deleted file mode 100644 index 24db19a411..0000000000 --- a/packages/core/src/test/awsService/s3/commands/downloadFileAs.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as path from 'path' -import { downloadFileAsCommand } from '../../../../awsService/s3/commands/downloadFileAs' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Bucket, S3Client } from '../../../../shared/clients/s3' -import { bufferToStream } from '../../../../shared/utilities/streamUtilities' -import { MockOutputChannel } from '../../../mockOutputChannel' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import globals from '../../../../shared/extensionGlobals' -import { getTestWindow } from '../../../shared/vscode/window' -import sinon from 'sinon' - -describe('downloadFileAsCommand', function () { - const bucketName = 'bucket-name' - const key = 'path/to/file.jpg' - const fileName = 'file.jpg' - const lastModified = new Date(2020, 5, 4) - const sizeBytes = 16 - - let s3: S3Client - let temp: string - let saveLocation: vscode.Uri - let bucketNode: S3BucketNode - let node: S3FileNode - - before(async function () { - // TODO: write separate test code for the progress report behavior - temp = await makeTemporaryToolkitFolder() - saveLocation = vscode.Uri.file(path.join(temp, 'file.jpg')) - }) - - beforeEach(function () { - s3 = {} as any as S3Client - - const bucket: S3Bucket = { Name: bucketName, BucketRegion: 'region', Arn: 'arn' } - bucketNode = new S3BucketNode(bucket, {} as S3Node, s3) - node = new S3FileNode(bucket, { name: fileName, key: key, arn: 'arn', lastModified, sizeBytes }, bucketNode, s3) - }) - - it('prompts for save location, downloads file with progress, and shows output channel', async function () { - getTestWindow().onDidShowDialog((dialog) => { - assert.deepStrictEqual(dialog.filters, { 'All Files': ['*'], '*.jpg': ['jpg'] }) - assert.strictEqual(dialog.acceptButtonLabel, 'Download') - dialog.accept() - }) - const outputChannel = new MockOutputChannel() - await globals.globalState.update('aws.downloadPath', temp) - - s3.downloadFileStream = sinon.stub().resolves(bufferToStream(Buffer.alloc(16))) - - await downloadFileAsCommand(node, outputChannel) - - assert.deepStrictEqual(outputChannel.lines, [ - `Downloading "s3://bucket-name/path/to/file.jpg" to: ${saveLocation}`, - `Downloaded: ${saveLocation}`, - ]) - assert.strictEqual(outputChannel.isShown, true) - assert.strictEqual(outputChannel.isFocused, false) - }) - - it('does nothing when prompt is cancelled', async function () { - const stub = sinon.stub() - s3.downloadFileStream = stub - getTestWindow().onDidShowDialog((d) => d.close()) - await assert.rejects(() => downloadFileAsCommand(node), /cancelled/i) - - assert(stub.notCalled) - }) - - it('throws when download fails', async function () { - getTestWindow().onDidShowDialog((d) => d.selectItem(saveLocation)) - s3.downloadFileStream = sinon.stub().rejects(new Error('Expected failure')) - - await assert.rejects(() => downloadFileAsCommand(node, new MockOutputChannel()), /Failed to download/) - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/presignedURL.test.ts b/packages/core/src/test/awsService/s3/commands/presignedURL.test.ts deleted file mode 100644 index 96e9f4ebe8..0000000000 --- a/packages/core/src/test/awsService/s3/commands/presignedURL.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import {} from '../../../../shared/vscode/env' -import { copyToClipboard } from '../../../../shared/utilities/messages' -import { presignedURLCommand } from '../../../../awsService/s3/commands/presignedURL' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Bucket, S3Client } from '../../../../shared/clients/s3' -import { getTestWindow } from '../../../shared/vscode/window' -import { FakeClipboard } from '../../../shared/vscode/fakeEnv' - -describe('presignedURLCommand', function () { - const bucketName = 'bucket-name' - const key = 'file.jpg' - const testUrl = 'https://presigned-url.com' - - let node: S3FileNode - let s3: S3Client - let bucketNode: S3BucketNode - - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - s3 = {} as any as S3Client - const bucket: S3Bucket = { Name: bucketName, BucketRegion: 'region', Arn: 'arn' } - bucketNode = new S3BucketNode(bucket, {} as S3Node, s3) - node = new S3FileNode(bucket, { name: key, key: key, arn: 'arn' }, bucketNode, s3) - }) - - it('calls S3 to get the URL', async function () { - getTestWindow().onDidShowInputBox((input) => input.acceptValue('20')) - s3.getSignedUrlForObject = sinon.stub().resolves(testUrl) - - await presignedURLCommand(node) - - assert.deepStrictEqual(await vscode.env.clipboard.readText(), testUrl) - }) - - it('copies a given URL to the clipboard', async function () { - await copyToClipboard(testUrl, 'URL') - assert.deepStrictEqual(await vscode.env.clipboard.readText(), testUrl) - }) -}) diff --git a/packages/core/src/test/awsService/s3/commands/uploadFile.test.ts b/packages/core/src/test/awsService/s3/commands/uploadFile.test.ts deleted file mode 100644 index 9dba5e3234..0000000000 --- a/packages/core/src/test/awsService/s3/commands/uploadFile.test.ts +++ /dev/null @@ -1,341 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { - FileSizeBytes, - getFilesToUpload, - promptUserForBucket, - uploadFileCommand, - BucketQuickPickItem, -} from '../../../../awsService/s3/commands/uploadFile' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3Client } from '../../../../shared/clients/s3' -import { MockOutputChannel } from '../../../mockOutputChannel' -import { getTestWindow } from '../../../shared/vscode/window' -import sinon from 'sinon' -import { Upload } from '@aws-sdk/lib-storage' -import { Bucket } from '@aws-sdk/client-s3' - -describe('uploadFileCommand', function () { - const bucketName = 'bucket-name' - const key = 'file.jpg' - const sizeBytes = 16 - const fileLocation = vscode.Uri.file('/file.jpg') - const statFile: FileSizeBytes = (_file) => sizeBytes - const bucketResponse = { label: 'label', bucket: { Name: bucketName } } - const folderResponse = { - label: 'label', - bucket: { Name: bucketName }, - folder: { name: 'folderA', path: 'folderA/', arn: 'arn' }, - } - const getFolder: (s3client: S3Client) => Promise = (s3Client) => { - return new Promise((resolve, reject) => { - resolve(folderResponse) - }) - } - let outputChannel: MockOutputChannel - let s3: S3Client - let bucketNode: S3BucketNode - let getBucket: (s3client: S3Client) => Promise - let getFile: (document?: vscode.Uri) => Promise - let mockedUpload: Upload - - const setDefaultsForFileAndBucket = () => { - getFile = (document) => { - return new Promise((resolve, reject) => { - resolve([fileLocation]) - }) - } - - getBucket = (s3Client) => { - return new Promise((resolve, reject) => { - resolve(bucketResponse) - }) - } - } - - beforeEach(function () { - mockedUpload = {} as any as Upload - s3 = {} as any as S3Client - bucketNode = new S3BucketNode({ Name: bucketName, BucketRegion: 'region', Arn: 'arn' }, new S3Node(s3), s3) - outputChannel = new MockOutputChannel() - }) - - describe('with node parameter', async function () { - this.beforeEach(function () { - s3 = {} as any as S3Client - bucketNode = new S3BucketNode({ Name: bucketName, BucketRegion: 'region', Arn: 'arn' }, new S3Node(s3), s3) - outputChannel = new MockOutputChannel() - }) - - it('uploads successfully', async function () { - const uploadStub = sinon.stub().resolves(mockedUpload) - s3.uploadFile = uploadStub - mockedUpload.done = sinon.stub().resolves() - - getFile = (document) => { - return new Promise((resolve, reject) => { - resolve([fileLocation]) - }) - } - - await uploadFileCommand(s3, bucketNode, statFile, undefined, getFile, outputChannel) - - const [uploadFileRequest] = uploadStub.lastCall.args - - assert.strictEqual(uploadFileRequest.bucketName, bucketName) - assert.strictEqual(uploadFileRequest.key, key) - assert.strictEqual(uploadFileRequest.content, fileLocation) - - assert.deepStrictEqual(outputChannel.lines, [ - 'Uploading file file.jpg to s3://bucket-name/file.jpg', - 'Uploaded 1/1 files', - ]) - }) - - it('cancels and displays a message if a user does not select a file', async function () { - getFile = (document) => { - return new Promise((resolve, reject) => { - resolve(undefined) - }) - } - - await uploadFileCommand(s3, bucketNode, statFile, undefined, getFile, outputChannel) - assert.deepStrictEqual(outputChannel.lines, ['No file selected, cancelling upload']) - }) - }) - - describe('without node parameter', async function () { - beforeEach(function () { - s3 = {} as any as S3Client - outputChannel = new MockOutputChannel() - setDefaultsForFileAndBucket() - }) - - it('uploads if user provides file and bucket', async function () { - const uploadStub = sinon.stub().resolves(mockedUpload) - s3.uploadFile = uploadStub - mockedUpload.done = sinon.stub().resolves() - - await uploadFileCommand(s3, undefined, statFile, getBucket, getFile, outputChannel) - - assert.deepStrictEqual(outputChannel.lines, [ - 'Uploading file file.jpg to s3://bucket-name/file.jpg', - 'Uploaded 1/1 files', - ]) - }) - - it('cancels if user does not provide bucket', async function () { - getBucket = (s3Client) => { - return new Promise((resolve, reject) => { - resolve('cancel') - }) - } - - await uploadFileCommand(s3, undefined, statFile, getBucket, getFile, outputChannel) - assert.deepStrictEqual(outputChannel.lines, ['No bucket selected, cancelling upload']) - }) - - it('cancels if user does not select file', async function () { - getFile = (document) => { - return new Promise((resolve, reject) => { - resolve(undefined) - }) - } - - await uploadFileCommand(s3, undefined, statFile, getBucket, getFile, outputChannel) - assert.deepStrictEqual(outputChannel.lines, ['No file selected, cancelling upload']) - }) - }) - - setDefaultsForFileAndBucket() - - it('successfully upload file or folder', async function () { - const uploadStub = sinon.stub().resolves(mockedUpload) - s3.uploadFile = uploadStub - - mockedUpload.done = sinon.stub().resolves() - getTestWindow().onDidShowDialog((d) => d.selectItem(fileLocation)) - - // Upload to bucket. - await uploadFileCommand(s3, fileLocation, statFile, getBucket, getFile, outputChannel) - // Upload to folder. - await uploadFileCommand(s3, fileLocation, statFile, getFolder, getFile, outputChannel) - - assert.deepStrictEqual(outputChannel.lines, [ - 'Uploading file file.jpg to s3://bucket-name/file.jpg', - `Uploaded 1/1 files`, - 'Uploading file file.jpg to s3://bucket-name/folderA/file.jpg', - `Uploaded 1/1 files`, - ]) - }) - - it('errors when s3 call fails', async function () { - const uploadStub = sinon.stub().rejects(new Error('Expected failure')) - s3.uploadFile = uploadStub - getTestWindow().onDidShowDialog((d) => d.selectItem(fileLocation)) - getTestWindow().onDidShowMessage((m) => m.close()) - - outputChannel = new MockOutputChannel() - await uploadFileCommand(s3, fileLocation, statFile, getBucket, getFile, outputChannel) - - assert.deepStrictEqual(outputChannel.lines, [ - 'Uploading file file.jpg to s3://bucket-name/file.jpg', - `Failed to upload file file.jpg: Expected failure`, - 'Uploaded 0/1 files', - 'Failed uploads:', - `${key}`, - ]) - }) -}) - -describe('getFileToUpload', function () { - const fileLocation = vscode.Uri.file('/file.jpg') - - const selection: any = { label: 'Browse for more files...' } - const prompt: (opts: { - picker: vscode.QuickPick - onDidTriggerButton?( - button: vscode.QuickInputButton, - resolve: (value: T[] | PromiseLike | undefined) => void, - reject: (reason?: any) => void - ): void - }) => Promise = () => { - return new Promise((resolve, reject) => { - resolve([selection]) - }) - } - - it('directly asks user for file if no active editor', async function () { - getTestWindow().onDidShowDialog((d) => d.selectItem(fileLocation)) - const response = await getFilesToUpload(undefined, prompt) - assert.deepStrictEqual(response, [fileLocation]) - }) - - it('Returns undefined if no file is selected on first prompt', async function () { - getTestWindow().onDidShowDialog((d) => d.close()) - const response = await getFilesToUpload(undefined, prompt) - assert.strictEqual(response, undefined) - }) - - it('opens the current file if a user selects it from the prompt', async function () { - const alreadyOpenedUri = vscode.Uri.file('/alreadyOpened.txt') - selection.label = alreadyOpenedUri.fsPath - - const response = await getFilesToUpload(alreadyOpenedUri, prompt) - assert.deepStrictEqual(response, [alreadyOpenedUri]) - }) - - it('opens the file prompt if a user selects to browse for more files', async function () { - selection.label = 'Browse for more files...' - getTestWindow().onDidShowDialog((d) => d.selectItem(fileLocation)) - - const response = await getFilesToUpload(fileLocation, prompt) - assert.deepStrictEqual(response, [fileLocation]) - }) - - it('returns undefined if the user does not select a file through the file browser', async function () { - selection.label = 'Browse for more files...' - getTestWindow().onDidShowDialog((d) => d.close()) - - const response = await getFilesToUpload(fileLocation, prompt) - - assert.strictEqual(response, undefined) - }) -}) - -describe('promptUserForBucket', async function () { - const fileLocation = vscode.Uri.file('/file.jpg') - - let s3: S3Client - let buckets: Bucket[] - - const promptUndef: (opts: { - picker: vscode.QuickPick - onDidTriggerButton?( - button: vscode.QuickInputButton, - resolve: (value: T[] | PromiseLike | undefined) => void, - reject: (reason?: any) => void - ): void - }) => Promise = () => { - return new Promise((resolve, reject) => { - resolve(undefined) - }) - } - const selection: any = { - label: 'bucket selected', - bucket: { Name: 'bucket 1' }, - } - const promptSelect: (opts: { - picker: vscode.QuickPick - onDidTriggerButton?( - button: vscode.QuickInputButton, - resolve: (value: T[] | PromiseLike | undefined) => void, - reject: (reason?: any) => void - ): void - }) => Promise = () => { - return new Promise((resolve, reject) => { - resolve([selection]) - }) - } - - beforeEach(function () { - s3 = {} as any as S3Client - buckets = [{ Name: 'bucket 1' }, { Name: 'bucket 2' }, { Name: 'bucket 3' }] - getTestWindow().onDidShowDialog((d) => d.selectItem(fileLocation)) - }) - - it('Returns selected bucket', async function () { - const stub = sinon.stub().resolves({ buckets }) - s3.listBuckets = stub - - const response = await promptUserForBucket(s3, promptSelect) - assert.deepStrictEqual(response, selection) - }) - - it('Returns "back" when selected', async function () { - const stub = sinon.stub().resolves({ buckets }) - s3.listBuckets = stub - - selection.label = 'back' - selection.bucket = undefined - - const response = await promptUserForBucket(s3, promptSelect) - assert.strictEqual(response, 'back') - }) - - it('Lets the user create a new bucket', async function () { - const stub = sinon.stub().resolves({ buckets }) - s3.listBuckets = stub - - selection.label = 'Create new bucket' - selection.bucket = undefined - - const createBucket: (node?: S3Node) => Promise = () => { - throw new Error('Error expected') - } - await assert.rejects(() => promptUserForBucket(s3, promptSelect, createBucket)) - }) - - it('Returns "cancel" when user doesn\'t select a bucket', async function () { - const stub = sinon.stub().resolves({ buckets }) - s3.listBuckets = stub - - const response = await promptUserForBucket(s3, promptUndef) - assert.strictEqual(response, 'cancel') - }) - - it('Throws error when it is not possible to list buckets from client', async function () { - const stub = sinon.stub().rejects(new Error('Expected failure')) - s3.listBuckets = stub - await assert.rejects(() => promptUserForBucket(s3)) - getTestWindow() - .getFirstMessage() - .assertError(/Failed to list buckets from client/) - }) -}) diff --git a/packages/core/src/test/awsService/s3/explorer/s3BucketNode.test.ts b/packages/core/src/test/awsService/s3/explorer/s3BucketNode.test.ts deleted file mode 100644 index 7a7a19dfae..0000000000 --- a/packages/core/src/test/awsService/s3/explorer/s3BucketNode.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3FolderNode } from '../../../../awsService/s3/explorer/s3FolderNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Client, File, Folder, S3Bucket } from '../../../../shared/clients/s3' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../../shared/treeview/nodes/loadMoreNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('S3BucketNode', function () { - const name = 'bucket-name' - const continuationToken = 'continuationToken' - const bucket: S3Bucket = { Name: name, BucketRegion: 'region', Arn: 'arn' } - const file: File = { name: 'name', key: 'key', arn: 'arn' } - const folder: Folder = { name: 'folder', path: 'path', arn: 'arn' } - const maxResults = 200 - let s3: S3Client - let config: TestSettings - - function assertBucketNode(node: LoadMoreNode): void { - assert.ok(node instanceof S3BucketNode, `Node ${String(node)} should be a Bucket Node`) - assert.deepStrictEqual((node as S3BucketNode).bucket, bucket) - } - - function assertFolderNode(node: AWSTreeNodeBase, expectedFolder: Folder): void { - assert.ok(node instanceof S3FolderNode, `Node ${node} should be a Folder Node`) - assert.deepStrictEqual((node as S3FolderNode).bucket, bucket) - assert.deepStrictEqual((node as S3FolderNode).folder, expectedFolder) - } - - function assertFileNode(node: AWSTreeNodeBase, expectedFile: File): void { - assert.ok(node instanceof S3FileNode, `Node ${node} should be a File Node`) - assert.deepStrictEqual((node as S3FileNode).bucket, bucket) - assert.deepStrictEqual((node as S3FileNode).file, expectedFile) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - assertBucketNode((node as MoreResultsNode).parent) - } - - beforeEach(function () { - s3 = {} as any as S3Client - config = new TestSettings() - }) - - describe('getChildren', function () { - it('gets children', async function () { - const stub = sinon.stub().resolves({ - folders: [folder], - files: [file], - continuationToken: undefined, - }) - s3.listFiles = stub - - await config.getSection('aws').update('s3.maxItemsPerPage', maxResults) - const node = new S3BucketNode(bucket, new S3Node(s3), s3, config) - const [folderNode, fileNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ bucketName: name, continuationToken: undefined, maxResults })) - assertFolderNode(folderNode, folder) - assertFileNode(fileNode, file) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const stub = sinon.stub().resolves({ - folders: [folder], - files: [file], - continuationToken, - }) - s3.listFiles = stub - - await config.getSection('aws').update('s3.maxItemsPerPage', maxResults) - const node = new S3BucketNode(bucket, new S3Node(s3), s3, config) - const [folderNode, fileNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert(stub.calledOnceWithExactly({ bucketName: name, continuationToken: undefined, maxResults })) - assertFolderNode(folderNode, folder) - assertFileNode(fileNode, file) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/s3/explorer/s3FileNode.test.ts b/packages/core/src/test/awsService/s3/explorer/s3FileNode.test.ts deleted file mode 100644 index c54a022c3c..0000000000 --- a/packages/core/src/test/awsService/s3/explorer/s3FileNode.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { stringOrProp } from '../../../../shared/utilities/tsUtils' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3Client } from '../../../../shared/clients/s3' -import { formatLocalized } from '../../../../shared/datetime' - -describe('S3FileNode', function () { - const arn = 'arn' - const name = 'file.jpg' - const key = 'path/to/file.jpg' - const sizeBytes = 1024 - const lastModified = new Date(Date.UTC(2020, 5, 4, 3, 2, 1)) - const now = new Date(Date.UTC(2020, 6, 4)) - const lastModifiedReadable = formatLocalized(lastModified) - - it('creates an S3 File Node', async function () { - const node = new S3FileNode( - { Name: 'bucket-name', BucketRegion: 'region', Arn: 'arn' }, - { name, key, arn, sizeBytes, lastModified }, - {} as S3BucketNode, - {} as S3Client, - now - ) - - assert.ok( - stringOrProp(node.tooltip, 'tooltip').startsWith( - `path/to/file.jpg\nSize: 1 KB\nLast Modified: ${lastModifiedReadable}` - ) - ) - assert.ok((node.description as string).startsWith('1 KB, last month')) - }) -}) diff --git a/packages/core/src/test/awsService/s3/explorer/s3FolderNode.test.ts b/packages/core/src/test/awsService/s3/explorer/s3FolderNode.test.ts deleted file mode 100644 index b7823a087e..0000000000 --- a/packages/core/src/test/awsService/s3/explorer/s3FolderNode.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { MoreResultsNode } from '../../../../awsexplorer/moreResultsNode' -import { S3FileNode } from '../../../../awsService/s3/explorer/s3FileNode' -import { S3FolderNode } from '../../../../awsService/s3/explorer/s3FolderNode' -import { S3Client, File, Folder, S3Bucket } from '../../../../shared/clients/s3' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import { LoadMoreNode } from '../../../../shared/treeview/nodes/loadMoreNode' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import sinon from 'sinon' - -describe('S3FolderNode', function () { - const bucketName = 'bucket-name' - const path = 'folder/path' - const continuationToken = 'continuationToken' - const bucket: S3Bucket = { Name: bucketName, BucketRegion: 'region', Arn: 'arn' } - const file: File = { name: 'name', key: 'key', arn: 'arn' } - const folder: Folder = { name: 'folder', path, arn: 'arn' } - const subFolder: Folder = { name: 'subFolder', path: 'subPath', arn: 'subArn' } - const maxResults = 200 - - let s3: S3Client - let config: TestSettings - - function assertFolderNode(node: AWSTreeNodeBase | LoadMoreNode, expectedFolder: Folder): void { - assert.ok(node instanceof S3FolderNode, `Node ${String(node)} should be a Folder Node`) - assert.deepStrictEqual((node as S3FolderNode).bucket, bucket) - assert.deepStrictEqual((node as S3FolderNode).folder, expectedFolder) - } - - function assertFileNode(node: AWSTreeNodeBase, expectedFile: File): void { - assert.ok(node instanceof S3FileNode, `Node ${node} should be a File Node`) - assert.deepStrictEqual((node as S3FileNode).bucket, bucket) - assert.deepStrictEqual((node as S3FileNode).file, expectedFile) - } - - function assertMoreResultsNode(node: AWSTreeNodeBase): void { - assert.ok(node instanceof MoreResultsNode, `Node ${node} should be a More Results Node`) - assertFolderNode((node as MoreResultsNode).parent, folder) - } - - beforeEach(async () => { - s3 = {} as any as S3Client - config = new TestSettings() - await config.getSection('aws').update('s3.maxItemsPerPage', maxResults) - }) - - describe('getChildren', function () { - it('gets children', async function () { - const stub = sinon.stub().resolves({ - folders: [subFolder], - files: [file], - continuationToken: undefined, - }) - s3.listFiles = stub - - const node = new S3FolderNode(bucket, folder, s3, config) - const [subFolderNode, fileNode, ...otherNodes] = await node.getChildren() - - assert( - stub.calledOnceWithExactly({ bucketName, folderPath: path, continuationToken: undefined, maxResults }) - ) - assertFolderNode(subFolderNode, subFolder) - assertFileNode(fileNode, file) - assert.strictEqual(otherNodes.length, 0) - }) - - it('gets children with node for loading more results', async function () { - const stub = sinon.stub().resolves({ - folders: [subFolder], - files: [file], - continuationToken, - }) - s3.listFiles = stub - - const node = new S3FolderNode(bucket, folder, s3, config) - const [subFolderNode, fileNode, moreResultsNode, ...otherNodes] = await node.getChildren() - - assert( - stub.calledOnceWithExactly({ bucketName, folderPath: path, continuationToken: undefined, maxResults }) - ) - assertFolderNode(subFolderNode, subFolder) - assertFileNode(fileNode, file) - assertMoreResultsNode(moreResultsNode) - assert.strictEqual(otherNodes.length, 0) - }) - }) -}) diff --git a/packages/core/src/test/awsService/s3/explorer/s3Nodes.test.ts b/packages/core/src/test/awsService/s3/explorer/s3Nodes.test.ts deleted file mode 100644 index ee25061d61..0000000000 --- a/packages/core/src/test/awsService/s3/explorer/s3Nodes.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { S3BucketNode } from '../../../../awsService/s3/explorer/s3BucketNode' -import { S3Node } from '../../../../awsService/s3/explorer/s3Nodes' -import { S3Client, S3Bucket } from '../../../../shared/clients/s3' -import { AWSTreeNodeBase } from '../../../../shared/treeview/nodes/awsTreeNodeBase' -import sinon from 'sinon' - -describe('S3Node', function () { - const firstBucket: S3Bucket = { Name: 'first-bucket-name', BucketRegion: 'firstRegion', Arn: 'firstArn' } - const secondBucket: S3Bucket = { Name: 'second-bucket-name', BucketRegion: 'secondRegion', Arn: 'secondArn' } - - let s3: S3Client - - function assertBucketNode(node: AWSTreeNodeBase, expectedBucket: S3Bucket): void { - assert.ok(node instanceof S3BucketNode, `Node ${node} should be a Bucket Node`) - assert.deepStrictEqual((node as S3BucketNode).bucket, expectedBucket) - } - - beforeEach(function () { - s3 = {} as any as S3Client - }) - - it('gets children', async function () { - const stub = sinon.stub().resolves({ - buckets: [firstBucket, secondBucket], - }) - s3.listBuckets = stub - - const node = new S3Node(s3) - const [firstBucketNode, secondBucketNode, ...otherNodes] = await node.getChildren() - - assertBucketNode(firstBucketNode, firstBucket) - assertBucketNode(secondBucketNode, secondBucket) - assert.strictEqual(otherNodes.length, 0) - }) -}) diff --git a/packages/core/src/test/awsService/s3/util/fileViewerManager.test.ts b/packages/core/src/test/awsService/s3/util/fileViewerManager.test.ts deleted file mode 100644 index 1168ae9dbd..0000000000 --- a/packages/core/src/test/awsService/s3/util/fileViewerManager.test.ts +++ /dev/null @@ -1,334 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { S3FileProvider, S3FileViewerManager } from '../../../../awsService/s3/fileViewerManager' -import { S3Client, File, toFile, toBucket } from '../../../../shared/clients/s3' -import globals from '../../../../shared/extensionGlobals' -import { VirtualFileSystem } from '../../../../shared/virtualFilesystem' -import { bufferToStream } from '../../../../shared/utilities/streamUtilities' -import { MockOutputChannel } from '../../../mockOutputChannel' -import { SeverityLevel } from '../../../shared/vscode/message' -import { assertTelemetry, assertTextEditorContains } from '../../../testUtil' -import { ToolkitPromptSettings } from '../../../../shared/settings' -import { stub } from '../../../utilities/stubber' -import { assertHasProps } from '../../../../shared/utilities/tsUtils' -import { ToolkitError } from '../../../../shared/errors' -import { getTestWindow } from '../../../shared/vscode/window' -import { Upload } from '@aws-sdk/lib-storage' - -const bucket = toBucket('bucket-name', 'us-west-2', 'aws') - -const bigImage = toFile(bucket, { - ETag: '12345', - Key: 'big-image.jpg', - Size: 5 * Math.pow(10, 6), -}) - -const computeTag = (content: Buffer) => content.toString() -const makeFile = (key: string, content: Buffer) => { - return Object.assign( - toFile(bucket, { - Key: key, - ETag: computeTag(content), - Size: content.byteLength, - LastModified: new Date(), - }), - { content } - ) -} - -type DataFile = File & { readonly content: Buffer } -function createS3() { - const files = new Map() - const client = stub(S3Client, { regionCode: bucket.BucketRegion }) - client.downloadFileStream.callsFake(async (_, key) => bufferToStream(getFile(key).content)) - client.headObject.callsFake(async (req) => getFile(req.key)) - client.uploadFile.callsFake(async (req) => { - if (req.content instanceof vscode.Uri) { - throw new TypeError('Did not expect a URI, expected a Buffer') - } - const newFile = { - ...makeFile(req.key, Buffer.from(req.content)), - ContentType: req.contentType, - } - assertHasProps(newFile, 'Key', 'ETag') - files.set(newFile.key, newFile) - - const upload = stub(Upload) - upload.done.resolves({ - ...newFile, - Bucket: bucket.Name, - Location: newFile.key, - $metadata: {}, - }) - - return upload as any as Upload - }) - - function getFile(key: string) { - const file = files.get(key) - assert.ok(file, `No file found for key "${key}"`) - - return file - } - - function addFile(file: DataFile) { - assert.strictEqual(files.get(file.key), undefined, `File "${file.key}" already exists`) - files.set(file.key, file) - } - - return { client, addFile, getFile } -} - -describe('S3FileProvider', function () { - const textFile = makeFile('file.txt', Buffer.from('content', 'utf-8')) - let provider: S3FileProvider - let s3: ReturnType - - function createProvider(file: DataFile) { - return new S3FileProvider(s3.client, { ...file, bucket }) - } - - before(function () { - // TODO: fix this dependency - globals.outputChannel ??= new MockOutputChannel() - }) - - beforeEach(function () { - s3 = createS3() - s3.addFile(textFile) - provider = createProvider(textFile) - }) - - it('can read contents from s3', async function () { - const content = await provider.read() - assert.deepStrictEqual(content, s3.getFile(textFile.key).content) - }) - - it('can upload to s3', async function () { - const newContent = Buffer.from('new content', 'utf-8') - await provider.write(newContent) - assert.deepStrictEqual(await provider.read(), newContent) - }) - - it('can use `stat`', async function () { - const stats = await provider.stat() - assert.deepStrictEqual(stats, { - ctime: 0, - size: textFile.sizeBytes, - mtime: textFile.lastModified?.getTime(), - }) - }) - - it('fires an event when the eTag changes', async function () { - const changed = new Promise((resolve, reject) => { - provider.onDidChange(resolve) - setTimeout(() => reject(new Error('Event did not fire')), 1000) - }) - await provider.write(Buffer.from('some text')) - await changed - }) - - it('uses mime type if no content type exists', async function () { - await provider.refresh() - await provider.write(Buffer.from('some text')) - assert.strictEqual(s3.getFile(textFile.key).ContentType, 'text/plain; charset=utf-8') - }) - - it('preserves content types if present', async function () { - const jsonFile = { - ...makeFile('json-blob', Buffer.from('{}')), - ContentType: 'application/json', - } - s3.addFile(jsonFile) - const newData = Buffer.from(JSON.stringify({ foo: 'bar' })) - const provider = createProvider(jsonFile) - await provider.refresh() - await provider.write(newData) - - const result = s3.getFile(jsonFile.key) - assert.deepStrictEqual(result.content, newData) - assert.deepStrictEqual(result.ContentType, jsonFile.ContentType) - }) - - it('emits telemetry when downloading', async function () { - await provider.read() - assertTelemetry('s3_downloadObject', { - component: 'viewer', - result: 'Succeeded', - }) - }) - - it('emits telemetry when uploading', async function () { - await provider.write(Buffer.from('111', 'utf-8')) - assertTelemetry('s3_uploadObject', { - component: 'viewer', - result: 'Succeeded', - }) - }) -}) - -describe('FileViewerManager', function () { - const readScheme = 's3-read-test' - const editScheme = 's3-edit-test' - let s3: ReturnType - let fs: VirtualFileSystem - let fileViewerManager: S3FileViewerManager - let disposables: vscode.Disposable[] - - function findEditors(documentName: string, window = vscode.window) { - return window.visibleTextEditors.filter((e) => e.document.fileName.endsWith(documentName)) - } - - function registerFileSystemProviders(): vscode.Disposable[] { - return [ - vscode.workspace.registerFileSystemProvider(editScheme, fs, { isCaseSensitive: true }), - vscode.workspace.registerFileSystemProvider(readScheme, fs, { isReadonly: true, isCaseSensitive: true }), - ] - } - - before(function () { - s3 = createS3() - fs = new VirtualFileSystem() - - fileViewerManager = new S3FileViewerManager(() => s3.client, fs, new ToolkitPromptSettings(), { - read: readScheme, - edit: editScheme, - }) - - disposables = registerFileSystemProviders() - - const bigImageFile = makeFile('big-image.jpg', Buffer.from('fake image', 'utf-8')) - s3.addFile(bigImageFile) - }) - - afterEach(async function () { - await fileViewerManager.closeEditors() - }) - - after(async function () { - await vscode.Disposable.from(...disposables).dispose() - await fileViewerManager.dispose() - }) - - it('prompts if file size is greater than 4MB', async function () { - // User can "Continue". - const didOpen = fileViewerManager.openInReadMode({ ...bigImage, bucket }) - await getTestWindow() - .waitForMessage(/File size is more than 4MB/) - .then((message) => message.selectItem(/Continue/)) - await (await didOpen)?.dispose() - }) - - it('throws if the user cancels a download', async function () { - // User can "Cancel". - const didOpen = fileViewerManager.openInReadMode({ ...bigImage, bucket }) - await getTestWindow() - .waitForMessage(/File size is more than 4MB/) - .then((message) => message.selectItem(/Cancel/)) - await assert.rejects(didOpen) - }) - - describe('opens text files', function () { - const textFile1Contents = Buffer.from('test1 contents', 'utf-8') - const textFile1 = makeFile('test1.txt', textFile1Contents) - const textFile2Contents = Buffer.from('test2 contents', 'utf-8') - const textFile2 = makeFile('test2.txt', textFile2Contents) - - before(function () { - s3.addFile(textFile1) - s3.addFile(textFile2) - }) - - it('opens a new editor if no document exists', async function () { - await fileViewerManager.openInReadMode({ ...textFile1, bucket }) - await assertTextEditorContains(textFile1.content.toString()) - }) - - it('closes the read-only tab when opening in edit mode', async function () { - await fileViewerManager.openInReadMode({ ...textFile1, bucket }) - await fileViewerManager.openInEditMode({ ...textFile1, bucket }) - const editors = findEditors(textFile1.name) - assert.strictEqual(editors.length, 1) - assert.strictEqual(findEditors(textFile1.name)[0]?.document.uri.scheme, editScheme) - }) - - it('re-uses tabs in edit mode when opening as read-only', async function () { - await fileViewerManager.openInEditMode({ ...textFile1, bucket }) - await fileViewerManager.openInReadMode({ ...textFile1, bucket }) - const editors = findEditors(textFile1.name) - assert.strictEqual(editors.length, 1) - assert.strictEqual(findEditors(textFile1.name)[0]?.document.uri.scheme, editScheme) - }) - - it('can open in edit mode, showing a warning with two options', async function () { - const shownMessage = getTestWindow() - .waitForMessage(/You are now editing an S3 file./) - .then((message) => { - message.assertSeverity(SeverityLevel.Warning) - assert.strictEqual(message.items.length, 2) - return message - }) - - await fileViewerManager.openInEditMode({ ...textFile1, bucket }) - - await assertTextEditorContains(textFile1.content.toString()) - await shownMessage - }) - - it('re-uses an editor if already opened, focusing it', async function () { - await fileViewerManager.openInReadMode({ ...textFile1, bucket }) - await fileViewerManager.openInReadMode({ ...textFile2, bucket }) - - await assertTextEditorContains(textFile2.content.toString()) - - await fileViewerManager.openInReadMode({ ...textFile1, bucket }) - await assertTextEditorContains(textFile1.content.toString()) - assert.strictEqual(findEditors(textFile1.name).length, 1) - }) - - it('can open an S3 file with reserved URI characters', async function () { - const contents = Buffer.from('text', 'utf-8') - const file = makeFile('us-west-2:3cff280c/file.txt', contents) - s3.addFile(file) - - await fileViewerManager.openInReadMode({ ...file, bucket }) - await assertTextEditorContains(contents.toString()) - }) - - it('can open files with no file extension', async function () { - const contents = Buffer.from('text', 'utf-8') - const file = makeFile('us-west-2:3cff280c/file', contents) - s3.addFile(file) - - await fileViewerManager.openInReadMode({ ...file, bucket }) - await assertTextEditorContains(contents.toString()) - }) - - it('rejects if the file does not exist', async function () { - const file = makeFile('foo.txt', Buffer.from('0', 'utf-8')) - const err = await fileViewerManager.openInReadMode({ ...file, bucket }).catch((e) => e) - assert.ok(err instanceof ToolkitError) - }) - - it('is case-sensitive for file names', async function () { - // Create and assert content of file with lowercase name - const lowerCaseFileContent = 'lowercaseContent' - const lowerCaseFile = makeFile('myFile.txt', Buffer.from(lowerCaseFileContent, 'utf-8')) - s3.addFile(lowerCaseFile) - await fileViewerManager.openInReadMode({ ...lowerCaseFile, bucket }) - await assertTextEditorContains(lowerCaseFileContent) - - // Create similarily named file, but with different uppercase in name - const upperCaseFileContent = 'uppercaseContent' - const upperCaseFile = makeFile('MyFile.txt', Buffer.from(upperCaseFileContent, 'utf-8')) - s3.addFile(upperCaseFile) - await fileViewerManager.openInReadMode({ ...upperCaseFile, bucket }) - await assertTextEditorContains(upperCaseFileContent) - }) - }) -}) diff --git a/packages/core/src/test/awsService/s3/util/progressReporter.test.ts b/packages/core/src/test/awsService/s3/util/progressReporter.test.ts deleted file mode 100644 index 67670dba30..0000000000 --- a/packages/core/src/test/awsService/s3/util/progressReporter.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { progressReporter } from '../../../../awsService/s3/progressReporter' -import sinon from 'sinon' - -describe('progressReporter', function () { - let progress: vscode.Progress<{ message?: string; increment?: number }> - - beforeEach(function () { - progress = {} as any as vscode.Progress<{ message?: string; increment?: number }> - }) - - it('does not round', function () { - const stub = sinon.stub() - progress.report = stub - const reporter = progressReporter(progress, { totalBytes: 3, minIntervalMillis: 0 }) - - reporter(1) - assert(stub.firstCall.calledWithExactly({ message: undefined, increment: (1 / 3) * 100 })) - - reporter(2) - assert(stub.secondCall.calledWithExactly({ message: undefined, increment: (2 / 3) * 100 })) - }) - - it('reports incremental percentage when total is provided', function () { - const stub = sinon.stub() - progress.report = stub - const reporter = progressReporter(progress, { totalBytes: 16, minIntervalMillis: 0 }) - - reporter(4) - assert(stub.firstCall.calledWithExactly({ message: undefined, increment: 25 })) - - reporter(8) - assert(stub.secondCall.calledWithExactly({ message: undefined, increment: 50 })) - }) - - it('reports a formatted message if `reportMessage` is set', function () { - const stub = sinon.stub() - progress.report = stub - const reporter = progressReporter(progress, { - totalBytes: 16, - minIntervalMillis: 0, - reportMessage: true, - }) - - reporter(4) - assert(stub.firstCall.calledWithExactly({ message: `4 B / 16 B`, increment: 25 })) - - reporter(8) - assert(stub.secondCall.calledWithExactly({ message: `12 B / 16 B`, increment: 50 })) - }) - - it('throttles progress updates when update frequency exceeds throttle interval', function () { - const stub = sinon.stub() - progress.report = stub - const reporter = progressReporter(progress, { - totalBytes: 16, - minIntervalMillis: 99999, - }) - - reporter(4) // should fire as leading edge of interval (4/16 - 0/16 = 4/16 = +25%) - assert( - stub.firstCall.calledWithExactly({ - message: undefined, - increment: 25, - }) - ) - - reporter(4) // shouldn't fire - reporter(8) // should fire, since this puts progress at 100% - - assert( - stub.secondCall.calledWithExactly({ - message: undefined, - increment: 75, - }) - ) - }) - - it('reports no incremental percentage when total is not provided', function () { - const stub = sinon.stub() - progress.report = stub - const reporter = progressReporter(progress, { minIntervalMillis: 0 }) - - reporter(4) - reporter(8) - - assert(stub.calledTwice) - assert(stub.firstCall.notCalledWith(sinon.match.number)) - assert(stub.secondCall.notCalledWith(sinon.match.number)) - }) - - it('throws an error if total bytes is not an integer', function () { - assert.throws(() => progressReporter(progress, { totalBytes: 1.5, minIntervalMillis: 0 }), /must be an integer/) - }) - - it('throws an error if total bytes is negative', function () { - assert.throws(() => progressReporter(progress, { totalBytes: -5, minIntervalMillis: 0 }), /cannot be negative/) - }) - - it('throws an error if updated with non integer bytes', function () { - const reporter = progressReporter(progress, { totalBytes: 5, minIntervalMillis: 0 }) - - assert.throws(() => reporter(1.5), /must be an integer/) - }) - - it('throws an error if updated with negative bytes', function () { - const reporter = progressReporter(progress, { totalBytes: 5, minIntervalMillis: 0 }) - - assert.throws(() => reporter(-5), /cannot be negative/) - }) - - it('throws an error if updated with bytes greater than total bytes', function () { - const reporter = progressReporter(progress, { totalBytes: 2, minIntervalMillis: 0 }) - - assert.throws(() => reporter(5), /cannot be greater than totalBytes/) - }) -}) diff --git a/packages/core/src/test/awsService/s3/util/util.test.ts b/packages/core/src/test/awsService/s3/util/util.test.ts deleted file mode 100644 index 3c4d1d92e9..0000000000 --- a/packages/core/src/test/awsService/s3/util/util.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { readablePath } from '../../../../awsService/s3/util' - -describe('messages', function () { - describe('readablePath', function () { - const bucketName = 'bucket-name' - const bucketPath = '' - const objectPath = 'path/to/object' - - it('creates a readable path for an S3 bucket', function () { - const path = readablePath({ bucket: { Name: bucketName }, path: bucketPath }) - - assert.strictEqual(path, 's3://bucket-name') - }) - - it('creates a readable path for an object in an S3 bucket', function () { - const path = readablePath({ bucket: { Name: bucketName }, path: objectPath }) - - assert.strictEqual(path, 's3://bucket-name/path/to/object') - }) - }) -}) diff --git a/packages/core/src/test/awsService/s3/util/validateBucketName.test.ts b/packages/core/src/test/awsService/s3/util/validateBucketName.test.ts deleted file mode 100644 index f1331f0663..0000000000 --- a/packages/core/src/test/awsService/s3/util/validateBucketName.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { validateBucketName } from '../../../../awsService/s3/util' - -describe('validateBucketName', function () { - const invalidErrors: { bucketNames: string[]; error: string }[] = [ - { bucketNames: ['a', 'aa', 'a'.repeat(64)], error: 'Bucket name must be between 3 and 63 characters long' }, - { bucketNames: ['-bucket'], error: 'Bucket name must start with a lowercase letter or number' }, - { bucketNames: ['bucket-'], error: 'Bucket name must end with a lowercase letter or number' }, - { - bucketNames: ['til~de', 'under_score', 'quo"te', 'paren(theses', 'sla/sh'], - error: 'Bucket name must only contain lowercase letters, numbers, hyphens, and periods', - }, - { - bucketNames: ['buck..et', 'buck.-et', 'buck-.et'], - error: 'Periods in bucket name must be surrounded by a lowercase letter or number', - }, - { bucketNames: ['127.0.0.1'], error: 'Bucket name must not resemble an IP address' }, - ] - - it('returns undefined for a valid bucket name', function () { - assert.strictEqual(validateBucketName('v.4.l1d.buc.ket-n4.m3'), undefined) - }) - - for (const invalid of invalidErrors) { - describe(invalid.error, () => { - for (const bucketName of invalid.bucketNames) { - it(bucketName, () => { - assert.strictEqual(validateBucketName(bucketName), invalid.error) - }) - } - }) - } -}) diff --git a/packages/core/src/test/awsService/sagemaker/commands.test.ts b/packages/core/src/test/awsService/sagemaker/commands.test.ts deleted file mode 100644 index 8b7a445b1b..0000000000 --- a/packages/core/src/test/awsService/sagemaker/commands.test.ts +++ /dev/null @@ -1,403 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as sinon from 'sinon' -import assert from 'assert' -import { SagemakerClient } from '../../../shared/clients/sagemaker' -import { getTestWindow } from '../../shared/vscode/window' -import { - RemoteAccessRequiredMessage, - InstanceTypeInsufficientMemoryMessage, -} from '../../../awsService/sagemaker/constants' - -// Import types only, actual functions will be dynamically imported -import type { openRemoteConnect as openRemoteConnectStatic } from '../../../awsService/sagemaker/commands' - -describe('SageMaker Commands', () => { - let sandbox: sinon.SinonSandbox - let mockClient: any - let mockNode: any - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockClient = sandbox.createStubInstance(SagemakerClient) - mockNode = { - regionCode: 'us-east-1', - spaceApp: { - DomainId: 'domain-123', - SpaceName: 'test-space', - }, - } - }) - - afterEach(() => { - sandbox.restore() - getTestWindow().dispose() - - for (const key of Object.keys(require.cache)) { - if (key.includes('awsService/sagemaker/commands')) { - delete require.cache[key] - } - } - }) - - describe('openRemoteConnect handler integration tests', () => { - let mockTryRefreshNode: sinon.SinonStub - let mockTryRemoteConnection: sinon.SinonStub - let mockIsRemoteWorkspace: sinon.SinonStub - let openRemoteConnect: typeof openRemoteConnectStatic - - beforeEach(() => { - mockNode = { - regionCode: 'us-east-1', - spaceApp: { - DomainId: 'domain-123', - SpaceName: 'test-space', - App: { - AppType: 'JupyterLab', - AppName: 'default', - }, - SpaceSettingsSummary: { - AppType: 'JupyterLab', - RemoteAccess: 'DISABLED', - }, - }, - getStatus: sandbox.stub().returns('Running'), - } - - // Mock helper functions - mockTryRefreshNode = sandbox.stub().resolves() - mockTryRemoteConnection = sandbox.stub().resolves() - mockIsRemoteWorkspace = sandbox.stub().returns(false) - - sandbox.replace( - require('../../../awsService/sagemaker/explorer/sagemakerSpaceNode'), - 'tryRefreshNode', - mockTryRefreshNode - ) - sandbox.replace( - require('../../../awsService/sagemaker/model'), - 'tryRemoteConnection', - mockTryRemoteConnection - ) - sandbox.replace(require('../../../shared/vscode/env'), 'isRemoteWorkspace', mockIsRemoteWorkspace) - - const freshModule = require('../../../awsService/sagemaker/commands') - openRemoteConnect = freshModule.openRemoteConnect - }) - - describe('handleRunningSpaceWithDisabledAccess', () => { - beforeEach(() => { - mockNode.getStatus.returns('Running') - mockNode.spaceApp.SpaceSettingsSummary.RemoteAccess = 'DISABLED' - }) - - /** - * Test 1: Shows confirmation dialog mentioning "remote access" when instance type is sufficient - * - * Scenario: User tries to connect to a running space that doesn't have remote access enabled, - * but the instance type (ml.t3.large) has sufficient memory for remote access. - * - * Expected behavior: - * - System checks instance type via describeSpace - * - Shows confirmation dialog mentioning only "remote access" (no instance upgrade needed) - * - User confirms, then space is restarted with remote access enabled - * - Connection is established - */ - it('shows confirmation dialog with remote access message when no upgrade needed', async () => { - mockClient.describeSpace.resolves({ - $metadata: {}, - SpaceSettings: { - AppType: 'JupyterLab', - JupyterLabAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', // Sufficient memory - }, - }, - }, - }) - mockClient.deleteApp.resolves() - mockClient.startSpace.resolves() - mockClient.waitForAppInService.resolves() - - // Setup test window to handle confirmation dialog - getTestWindow().onDidShowMessage((message) => { - if (message.message.includes(RemoteAccessRequiredMessage)) { - message.selectItem('Restart Space and Connect') - } - }) - - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify describeSpace was called to check instance type - assert(mockClient.describeSpace.calledOnce) - assert( - mockClient.describeSpace.calledWith({ - DomainId: 'domain-123', - SpaceName: 'test-space', - }) - ) - - // Verify confirmation dialog was shown - const messages = getTestWindow().shownMessages - assert(messages.length > 0) - const confirmMessage = messages.find((m) => m.message.includes('remote access')) - assert(confirmMessage, 'Should show remote access message') - assert(!confirmMessage.message.includes('ml.t3'), 'Should not mention instance type upgrade') - }) - - /** - * Test 2: Shows confirmation dialog mentioning instance upgrade when needed - * - * Scenario: User tries to connect to a running space with an instance type (ml.t3.medium) - * that has insufficient memory for remote access. - * - * Expected behavior: - * - System checks instance type via describeSpace - * - Detects ml.t3.medium is insufficient (needs upgrade to ml.t3.large) - * - Dialog includes current type (ml.t3.medium) and target type (ml.t3.large) - * - User confirms, then space is restarted with upgraded instance and remote access - */ - it('shows confirmation dialog with instance upgrade message when upgrade needed', async () => { - mockClient.describeSpace.resolves({ - $metadata: {}, - SpaceSettings: { - AppType: 'JupyterLab', - JupyterLabAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.medium', // Insufficient memory - }, - }, - }, - }) - mockClient.deleteApp.resolves() - mockClient.startSpace.resolves() - mockClient.waitForAppInService.resolves() - - // Setup test window to handle confirmation dialog - getTestWindow().onDidShowMessage((message) => { - if ( - message.message.includes( - InstanceTypeInsufficientMemoryMessage('test-space', 'ml.t3.medium', 'ml.t3.large') - ) - ) { - message.selectItem('Restart Space and Connect') - } - }) - - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify describeSpace was called to check instance type - assert(mockClient.describeSpace.calledOnce) - - // Verify confirmation dialog includes instance type upgrade info - const messages = getTestWindow().shownMessages - const expectedMessage = InstanceTypeInsufficientMemoryMessage( - 'test-space', - 'ml.t3.medium', - 'ml.t3.large' - ) - const confirmMessage = messages.find((m) => m.message.includes(expectedMessage)) - assert(confirmMessage, 'Should show instance upgrade message') - }) - - /** - * Test 3: Verifies the full workflow when user confirms - * - * Scenario: User confirms the restart dialog for a running space with disabled remote access. - * - * Expected behavior (in order): - * 1. tryRefreshNode() - Refresh node state before starting - * 2. describeSpace() - Check instance type requirements - * 3. Show confirmation dialog - * 4. User confirms - * 5. deleteApp() - Stop the running space - * 6. startSpace() - Restart with remote access enabled (3rd param = true) - * 7. tryRefreshNode() - Refresh node state after restart - * 8. waitForAppInService() - Wait for space to be ready - * 9. tryRemoteConnection() - Establish the remote connection - */ - it('performs space restart and connection when user confirms', async () => { - mockClient.describeSpace.resolves({ - $metadata: {}, - SpaceSettings: { - AppType: 'JupyterLab', - JupyterLabAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', - }, - }, - }, - }) - mockClient.deleteApp.resolves() - mockClient.startSpace.resolves() - mockClient.waitForAppInService.resolves() - - // Setup test window to confirm - getTestWindow().onDidShowMessage((message) => { - if (message.items.some((item) => item.title === 'Restart Space and Connect')) { - message.selectItem('Restart Space and Connect') - } - }) - - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify tryRefreshNode was called at the start of openRemoteConnect - assert(mockTryRefreshNode.calledBefore(mockClient.deleteApp)) - - // Verify space operations were performed in correct order - assert(mockClient.deleteApp.calledOnce) - assert( - mockClient.deleteApp.calledWith({ - DomainId: 'domain-123', - SpaceName: 'test-space', - AppType: 'JupyterLab', - AppName: 'default', - }) - ) - assert(mockClient.startSpace.calledOnce) - assert(mockClient.startSpace.calledWith('test-space', 'domain-123', true)) // Remote access enabled - - // Verify tryRefreshNode was called after startSpace - assert(mockTryRefreshNode.calledAfter(mockClient.startSpace)) - - assert(mockClient.waitForAppInService.calledOnce) - assert(mockClient.waitForAppInService.calledWith('domain-123', 'test-space', 'JupyterLab')) - assert(mockTryRemoteConnection.calledOnce) - }) - - /** - * Test 4: Verifies nothing happens when user cancels - * - * Scenario: User is shown the confirmation dialog but clicks "Cancel" instead of confirming. - * - * Expected behavior: - * - tryRefreshNode() is called (happens before showing dialog) - * - describeSpace() is called (to check instance type) - * - Confirmation dialog is shown - * - User cancels - * - NO space operations are performed (no deleteApp, startSpace, or connection attempts) - */ - it('does not perform operations when user cancels', async () => { - mockClient.describeSpace.resolves({ - $metadata: {}, - SpaceSettings: { - AppType: 'JupyterLab', - JupyterLabAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', - }, - }, - }, - }) - - // Setup test window to cancel - getTestWindow().onDidShowMessage((message) => { - message.selectItem('Cancel') - }) - - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify tryRefreshNode was called (happens before confirmation) - assert(mockTryRefreshNode.calledOnce) - // Verify no space operations were performed after cancellation - assert(mockClient.deleteApp.notCalled) - assert(mockClient.startSpace.notCalled) - assert(mockTryRemoteConnection.notCalled) - }) - }) - - describe('handleStoppedSpace', () => { - beforeEach(() => { - mockNode.getStatus.returns('Stopped') - }) - - /** - * Test: Starts space and connects without showing confirmation dialog - * - * Scenario: User tries to connect to a stopped space. - * - * Expected behavior: - * - NO confirmation dialog is shown - * - tryRefreshNode() is called at the start - * - startSpace() is called WITHOUT remote access flag (2 params only) - * - tryRefreshNode() is called again after starting - * - waitForAppInService() waits for space to be ready - * - tryRemoteConnection() establishes the connection - * - * Key difference from running space: No confirmation needed because starting - * a stopped space is non-destructive - */ - it('starts space and connects without confirmation', async () => { - mockClient.startSpace.resolves() - mockClient.waitForAppInService.resolves() - - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify no confirmation dialog shown for stopped space - const confirmMessages = getTestWindow().shownMessages.filter((m) => - m.message.includes('Restart Space and Connect') - ) - assert.strictEqual(confirmMessages.length, 0, 'Should not show confirmation for stopped space') - - // Verify tryRefreshNode was called at start of openRemoteConnect - assert(mockTryRefreshNode.calledBefore(mockClient.startSpace)) - - // Verify space operations - startSpace is called before withProgress - assert(mockClient.startSpace.calledOnce) - assert(mockClient.startSpace.calledWith('test-space', 'domain-123')) // No remote access flag - - // Verify tryRefreshNode was called after startSpace (before progress) - assert(mockTryRefreshNode.calledAfter(mockClient.startSpace)) - assert.strictEqual(mockTryRefreshNode.callCount, 2) // Once at start, once after startSpace - - // Verify operations inside progress callback - assert(mockClient.waitForAppInService.calledOnce) - assert(mockClient.waitForAppInService.calledWith('domain-123', 'test-space', 'JupyterLab')) - assert(mockTryRemoteConnection.calledOnce) - }) - }) - - describe('handleRunningSpaceWithEnabledAccess', () => { - beforeEach(() => { - mockNode.getStatus.returns('Running') - mockNode.spaceApp.SpaceSettingsSummary.RemoteAccess = 'ENABLED' - }) - - /** - * Test: Connects directly without any space operations - * - * Scenario: User tries to connect to a running space that already has remote access enabled. - * - * Expected behavior: - * - tryRefreshNode() is called once at the start - * - NO confirmation dialog is shown (space is already configured correctly) - * - NO space operations are performed: - * - No deleteApp() (no need to stop) - * - No startSpace() (already running) - * - No waitForAppInService() (already ready) - * - ONLY tryRemoteConnection() is called to establish the connection - * - * This is the "happy path" - space is ready, just connect directly. - */ - it('connects directly without any space operations', async () => { - await openRemoteConnect(mockNode, {} as any, mockClient) - - // Verify tryRefreshNode was called at start - assert(mockTryRefreshNode.calledOnce) - // Verify no confirmation needed - const confirmMessages = getTestWindow().shownMessages.filter((m) => - m.message.includes('Restart Space and Connect') - ) - assert.strictEqual(confirmMessages.length, 0) - // Verify no space operations performed - assert(mockClient.deleteApp.notCalled) - assert(mockClient.startSpace.notCalled) - assert(mockClient.waitForAppInService.notCalled) - // Only remote connection should be attempted - assert(mockTryRemoteConnection.calledOnce) - }) - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/credentialMapping.test.ts b/packages/core/src/test/awsService/sagemaker/credentialMapping.test.ts deleted file mode 100644 index 6b699dbaab..0000000000 --- a/packages/core/src/test/awsService/sagemaker/credentialMapping.test.ts +++ /dev/null @@ -1,518 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as assert from 'assert' -import { - persistLocalCredentials, - persistSSMConnection, - persistSmusProjectCreds, - loadMappings, - saveMappings, - setSpaceIamProfile, - setSpaceSsoProfile, - setSmusSpaceProfile, - setSpaceCredentials, -} from '../../../awsService/sagemaker/credentialMapping' -import { Auth } from '../../../auth' -import { DevSettings, fs } from '../../../shared' -import globals from '../../../shared/extensionGlobals' -import { SagemakerUnifiedStudioSpaceNode } from '../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' - -describe('credentialMapping', () => { - describe('persistLocalCredentials', () => { - const appArn = 'arn:aws:sagemaker:us-west-2:123456789012:space/d-f0lwireyzpjp/test-space' - - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('writes IAM profile to mappings', async () => { - sandbox.stub(Auth.instance, 'getCurrentProfileId').returns('profile:my-iam-profile') - sandbox.stub(fs, 'existsFile').resolves(false) // simulate no existing mapping file - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await persistLocalCredentials(appArn) - - assert.ok(writeStub.calledOnce) - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - assert.deepStrictEqual(data.localCredential?.[appArn], { - type: 'iam', - profileName: 'profile:my-iam-profile', - }) - }) - - it('writes SSO credentials to mappings', async () => { - sandbox.stub(Auth.instance, 'getCurrentProfileId').returns('sso:my-sso-profile') - sandbox.stub(globals.loginManager.store, 'credentialsCache').value({ - 'sso:my-sso-profile': { - credentials: { - accessKeyId: 'AKIA123', - secretAccessKey: 'SECRET', - sessionToken: 'TOKEN', - }, - }, - }) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await persistLocalCredentials(appArn) - - assert.ok(writeStub.calledOnce) - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.localCredential?.[appArn], { - type: 'sso', - accessKey: 'AKIA123', - secret: 'SECRET', - token: 'TOKEN', - }) - }) - - it('throws if no current profile ID is available', async () => { - sandbox.stub(Auth.instance, 'getCurrentProfileId').returns(undefined) - - await assert.rejects(() => persistLocalCredentials(appArn), { - message: 'No current profile ID available for saving space credentials.', - }) - }) - }) - - describe('persistSSMConnection', () => { - const appArn = 'arn:aws:sagemaker:us-west-2:123456789012:space/d-f0lwireyzpjp/test-space' - const domain = 'd-f0lwireyzpjp' - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - function assertRefreshUrlMatches(writtenUrl: string, expectedSubdomain: string) { - assert.ok( - writtenUrl.startsWith(`https://studio-${domain}.${expectedSubdomain}`), - `Expected refresh URL to start with https://studio-${domain}.${expectedSubdomain}, got ${writtenUrl}` - ) - } - - it('uses default (studio) endpoint if no custom endpoint is set', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({}) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - // Stub the AWS API call - const mockDescribeSpace = sandbox.stub().resolves({ - SpaceSettings: { - AppType: 'JupyterLab', - }, - }) - sandbox.stub(require('../../../shared/clients/sagemaker'), 'SagemakerClient').returns({ - describeSpace: mockDescribeSpace, - }) - - await persistSSMConnection(appArn, domain) - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - assertRefreshUrlMatches(data.deepLink?.[appArn]?.refreshUrl, 'studio.us-west-2.sagemaker.aws') - assert.deepStrictEqual(data.deepLink?.[appArn]?.requests['initial-connection'], { - sessionId: '-', - url: '-', - token: '-', - status: 'fresh', - }) - }) - - it('uses devo subdomain for beta endpoint', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({ sagemaker: 'https://beta.whatever' }) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - // Stub the AWS API call - const mockDescribeSpace = sandbox.stub().resolves({ - SpaceSettings: { - AppType: 'JupyterLab', - }, - }) - sandbox.stub(require('../../../shared/clients/sagemaker'), 'SagemakerClient').returns({ - describeSpace: mockDescribeSpace, - }) - - await persistSSMConnection(appArn, domain, 'sess', 'wss://ws', 'token') - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - assertRefreshUrlMatches(data.deepLink?.[appArn]?.refreshUrl, 'devo.studio.us-west-2.asfiovnxocqpcry.com') - assert.deepStrictEqual(data.deepLink?.[appArn]?.requests['initial-connection'], { - sessionId: 'sess', - url: 'wss://ws', - token: 'token', - status: 'fresh', - }) - }) - - it('uses loadtest subdomain for gamma endpoint', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({ sagemaker: 'https://gamma.example' }) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - // Stub the AWS API call - const mockDescribeSpace = sandbox.stub().resolves({ - SpaceSettings: { - AppType: 'JupyterLab', - }, - }) - sandbox.stub(require('../../../shared/clients/sagemaker'), 'SagemakerClient').returns({ - describeSpace: mockDescribeSpace, - }) - - await persistSSMConnection(appArn, domain) - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - assertRefreshUrlMatches( - data.deepLink?.[appArn]?.refreshUrl, - 'loadtest.studio.us-west-2.asfiovnxocqpcry.com' - ) - }) - - // TODO: Skipped due to hardcoded appSubDomain. Currently hardcoded to 'jupyterlab' due to - // a bug in Studio that only supports refreshing the token for both CodeEditor and JupyterLab - // Apps in the jupyterlab subdomain. This will be fixed shortly after NYSummit launch to - // support refresh URL in CodeEditor subdomain. Additionally, appType will be determined by - // the deeplink URI rather than the describeSpace call from the toolkit. - it.skip('throws error when app type is unsupported', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({}) - sandbox.stub(fs, 'existsFile').resolves(false) - - // Stub the AWS API call to return an unsupported app type - const mockDescribeSpace = sandbox.stub().resolves({ - SpaceSettings: { - AppType: 'UnsupportedApp', - }, - }) - sandbox.stub(require('../../../shared/clients/sagemaker'), 'SagemakerClient').returns({ - describeSpace: mockDescribeSpace, - }) - - await assert.rejects(() => persistSSMConnection(appArn, domain), { - name: 'Error', - message: - 'Unsupported or missing app type for space. Expected JupyterLab or CodeEditor, got: UnsupportedApp', - }) - }) - - it('stores undefined refreshUrl when isSMUS=true', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({}) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await persistSSMConnection(appArn, domain, 'sess-123', 'wss://smus-ws', 'token-xyz', 'jupyterlab', true) - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - // Verify refreshUrl is undefined for SMUS connections - assert.strictEqual(data.deepLink?.[appArn]?.refreshUrl, undefined) - - // Verify SSM connection info is stored correctly - assert.deepStrictEqual(data.deepLink?.[appArn]?.requests['initial-connection'], { - sessionId: 'sess-123', - url: 'wss://smus-ws', - token: 'token-xyz', - status: 'fresh', - }) - }) - - it('stores valid refreshUrl when isSMUS=false (SageMaker AI behavior)', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({}) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await persistSSMConnection(appArn, domain, 'sess-456', 'wss://sm-ws', 'token-abc', 'jupyterlab', false) - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - // Verify refreshUrl is present for SageMaker AI connections - assert.ok(data.deepLink?.[appArn]?.refreshUrl) - assertRefreshUrlMatches(data.deepLink?.[appArn]?.refreshUrl, 'studio.us-west-2.sagemaker.aws') - - // Verify SSM connection info is stored correctly - assert.deepStrictEqual(data.deepLink?.[appArn]?.requests['initial-connection'], { - sessionId: 'sess-456', - url: 'wss://sm-ws', - token: 'token-abc', - status: 'fresh', - }) - }) - - it('stores valid refreshUrl when isSMUS is undefined (default SageMaker AI behavior)', async () => { - sandbox.stub(DevSettings.instance, 'get').returns({}) - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - // Call without isSMUS parameter (should default to SageMaker AI behavior) - await persistSSMConnection(appArn, domain, 'sess-789', 'wss://default-ws', 'token-def', 'jupyterlab') - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - - // Verify refreshUrl is present when isSMUS is not specified - assert.ok(data.deepLink?.[appArn]?.refreshUrl) - assertRefreshUrlMatches(data.deepLink?.[appArn]?.refreshUrl, 'studio.us-west-2.sagemaker.aws') - - // Verify SSM connection info is stored correctly - assert.deepStrictEqual(data.deepLink?.[appArn]?.requests['initial-connection'], { - sessionId: 'sess-789', - url: 'wss://default-ws', - token: 'token-def', - status: 'fresh', - }) - }) - }) - - describe('persistSmusProjectCreds', () => { - const appArn = 'arn:aws:sagemaker:us-west-2:123456789012:space/d-f0lwireyzpjp/test-space' - const projectId = 'test-project-id' - let sandbox: sinon.SinonSandbox - let mockNode: sinon.SinonStubbedInstance - let mockParent: sinon.SinonStubbedInstance - - beforeEach(() => { - sandbox = sinon.createSandbox() - mockNode = sandbox.createStubInstance(SagemakerUnifiedStudioSpaceNode) - mockParent = sandbox.createStubInstance(SageMakerUnifiedStudioSpacesParentNode) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('persists SMUS project credentials', async () => { - const mockCredentialProvider = { - getCredentials: sandbox.stub().resolves(), - startProactiveCredentialRefresh: sandbox.stub(), - } - - const mockAuthProvider = { - getProjectCredentialProvider: sandbox.stub().resolves(mockCredentialProvider), - } - - mockNode.getParent.returns(mockParent as any) - mockParent.getAuthProvider.returns(mockAuthProvider as any) - mockParent.getProjectId.returns(projectId) - sandbox.stub(require('../../../sagemakerunifiedstudio/auth/model'), 'isSmusSsoConnection').returns(true) - - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await persistSmusProjectCreds(appArn, mockNode as any) - - assert.ok(writeStub.calledOnce) - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.localCredential?.[appArn], { - type: 'sso', - smusProjectId: projectId, - }) - - // Verify the correct methods were called - assert.ok(mockAuthProvider.getProjectCredentialProvider.calledWith(projectId)) - assert.ok(mockCredentialProvider.getCredentials.calledOnce) - assert.ok(mockCredentialProvider.startProactiveCredentialRefresh.calledOnce) - }) - }) - - describe('loadMappings', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('returns empty object when file does not exist', async () => { - sandbox.stub(fs, 'existsFile').resolves(false) - - const result = await loadMappings() - - assert.deepStrictEqual(result, {}) - }) - - it('loads and parses existing mappings', async () => { - const mockData = { localCredential: { 'test-arn': { type: 'iam' as const, profileName: 'test' } } } - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockData)) - - const result = await loadMappings() - - assert.deepStrictEqual(result, mockData) - }) - - it('returns empty object on parse error', async () => { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves('invalid json') - - const result = await loadMappings() - - assert.deepStrictEqual(result, {}) - }) - }) - - describe('saveMappings', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('saves mappings to file', async () => { - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - const testData = { localCredential: { 'test-arn': { type: 'iam' as const, profileName: 'test' } } } - - await saveMappings(testData) - - assert.ok(writeStub.calledOnce) - const [, content, options] = writeStub.firstCall.args - assert.strictEqual(content, JSON.stringify(testData, undefined, 2)) - assert.deepStrictEqual(options, { mode: 0o600, atomic: true }) - }) - }) - - describe('setSpaceIamProfile', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('sets IAM profile for space', async () => { - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await setSpaceIamProfile('test-space', 'test-profile') - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.localCredential?.['test-space'], { - type: 'iam', - profileName: 'test-profile', - }) - }) - }) - - describe('setSpaceSsoProfile', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('sets SSO profile for space', async () => { - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await setSpaceSsoProfile('test-space', 'access-key', 'secret', 'token') - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.localCredential?.['test-space'], { - type: 'sso', - accessKey: 'access-key', - secret: 'secret', - token: 'token', - }) - }) - }) - - describe('setSmusSpaceProfile', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('sets SMUS SSO profile for space', async () => { - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await setSmusSpaceProfile('test-space', 'project-id', 'sso') - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.localCredential?.['test-space'], { - type: 'sso', - smusProjectId: 'project-id', - }) - }) - }) - - describe('setSpaceCredentials', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('sets space credentials with refresh URL', async () => { - sandbox.stub(fs, 'existsFile').resolves(false) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - const credentials = { sessionId: 'sess', url: 'ws://test', token: 'token' } - - await setSpaceCredentials('test-space', 'https://refresh.url', credentials) - - const raw = writeStub.firstCall.args[1] - const data = JSON.parse(typeof raw === 'string' ? raw : raw.toString()) - assert.deepStrictEqual(data.deepLink?.['test-space'], { - refreshUrl: 'https://refresh.url', - requests: { - 'initial-connection': { - ...credentials, - status: 'fresh', - }, - }, - }) - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/credentials.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/credentials.test.ts deleted file mode 100644 index 3db189f839..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/credentials.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as utils from '../../../../awsService/sagemaker/detached-server/utils' -import { resolveCredentialsFor } from '../../../../awsService/sagemaker/detached-server/credentials' - -const connectionId = 'arn:aws:sagemaker:region:acct:space/name' - -describe('resolveCredentialsFor', () => { - afterEach(() => sinon.restore()) - - it('throws if no profile is found', async () => { - sinon.stub(utils, 'readMapping').resolves({ localCredential: {} }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: `No profile found for "${connectionId}"`, - }) - }) - - it('throws if IAM profile name is malformed', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'iam', - profileName: 'dev-profile', // no colon - }, - }, - }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: `Invalid IAM profile name for "${connectionId}"`, - }) - }) - - it('resolves SSO credentials correctly', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'sso', - accessKey: 'key', - secret: 'sec', - token: 'tok', - }, - }, - }) - - const creds = await resolveCredentialsFor(connectionId) - assert.deepStrictEqual(creds, { - accessKeyId: 'key', - secretAccessKey: 'sec', - sessionToken: 'tok', - }) - }) - - it('throws if SSO credentials are incomplete', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'sso', - accessKey: 'key', - secret: 'sec', - token: '', // token is required but intentionally left empty for this test - }, - }, - }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: `Missing SSO credentials for "${connectionId}"`, - }) - }) - - it('resolves SSO credentials with SMUS project ID', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'sso', - smusProjectId: 'project123', - }, - }, - smusProjects: { - project123: { - accessKey: 'smus-key', - secret: 'smus-secret', - token: 'smus-token', - }, - }, - }) - - const creds = await resolveCredentialsFor(connectionId) - assert.deepStrictEqual(creds, { - accessKeyId: 'smus-key', - secretAccessKey: 'smus-secret', - sessionToken: 'smus-token', - }) - }) - - it('throws if SMUS project credentials are missing', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'sso', - smusProjectId: 'project123', - }, - }, - smusProjects: { - project123: { - accessKey: '', - secret: 'smus-secret', - token: 'smus-token', - }, - }, - }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: `Missing ProjectRole credentials for SMUS Space "${connectionId}"`, - }) - }) - - it('throws if SMUS project is not found', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'sso', - smusProjectId: 'nonexistent', - }, - }, - smusProjects: {}, - }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: `Missing ProjectRole credentials for SMUS Space "${connectionId}"`, - }) - }) - - it('throws for unsupported profile types', async () => { - sinon.stub(utils, 'readMapping').resolves({ - localCredential: { - [connectionId]: { - type: 'unknown', - } as any, - }, - }) - - await assert.rejects(() => resolveCredentialsFor(connectionId), { - message: /Unsupported profile type/, // don't hard-code full value since object might be serialized - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSession.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSession.test.ts deleted file mode 100644 index 5b3f176a29..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSession.test.ts +++ /dev/null @@ -1,102 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as http from 'http' -import * as sinon from 'sinon' -import assert from 'assert' -import { handleGetSession } from '../../../../../awsService/sagemaker/detached-server/routes/getSession' -import * as credentials from '../../../../../awsService/sagemaker/detached-server/credentials' -import * as utils from '../../../../../awsService/sagemaker/detached-server/utils' -import * as errorPage from '../../../../../awsService/sagemaker/detached-server/errorPage' - -describe('handleGetSession', () => { - let req: Partial - let res: Partial - let resWriteHead: sinon.SinonSpy - let resEnd: sinon.SinonSpy - - beforeEach(() => { - resWriteHead = sinon.spy() - resEnd = sinon.spy() - - res = { - writeHead: resWriteHead, - end: resEnd, - } - sinon.stub(errorPage, 'openErrorPage') - }) - - it('responds with 400 if connection_identifier is missing', async () => { - req = { url: '/session' } - await handleGetSession(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(400)) - assert(resEnd.calledWithMatch(/Missing required query parameter/)) - }) - - it('responds with 500 if resolveCredentialsFor throws', async () => { - req = { url: '/session?connection_identifier=arn:aws:sagemaker:region:acc:space/domain/name' } - sinon.stub(credentials, 'resolveCredentialsFor').rejects(new Error('creds error')) - sinon.stub(utils, 'parseArn').returns({ - region: 'us-west-2', - accountId: '123456789012', - spaceName: 'space-name', - }) - - await handleGetSession(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(500)) - assert(resEnd.calledWith('creds error')) - }) - - it('responds with 500 if startSagemakerSession throws', async () => { - req = { url: '/session?connection_identifier=arn:aws:sagemaker:region:acc:space/domain/name' } - sinon.stub(credentials, 'resolveCredentialsFor').resolves({}) - sinon.stub(utils, 'parseArn').returns({ - region: 'us-west-2', - accountId: '123456789012', - spaceName: 'space-name', - }) - sinon.stub(utils, 'startSagemakerSession').rejects(new Error('session error')) - - await handleGetSession(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(500)) - assert(resEnd.calledWith('Failed to start SageMaker session')) - }) - - it('responds with 200 and session data on success', async () => { - req = { url: '/session?connection_identifier=arn:aws:sagemaker:region:acc:space/domain/name' } - sinon.stub(credentials, 'resolveCredentialsFor').resolves({}) - sinon.stub(utils, 'parseArn').returns({ - region: 'us-west-2', - accountId: '123456789012', - spaceName: 'space-name', - }) - sinon.stub(utils, 'startSagemakerSession').resolves({ - SessionId: 'abc123', - StreamUrl: 'https://stream', - TokenValue: 'token123', - $metadata: { httpStatusCode: 200 }, - }) - - await handleGetSession(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(200)) - assert( - resEnd.calledWithMatch( - JSON.stringify({ - SessionId: 'abc123', - StreamUrl: 'https://stream', - TokenValue: 'token123', - }) - ) - ) - }) - - afterEach(() => { - sinon.restore() - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSessionAsync.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSessionAsync.test.ts deleted file mode 100644 index 9b3ecb2f2c..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/routes/getSessionAsync.test.ts +++ /dev/null @@ -1,195 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as http from 'http' -import * as sinon from 'sinon' -import assert from 'assert' -import { SessionStore } from '../../../../../awsService/sagemaker/detached-server/sessionStore' -import { handleGetSessionAsync } from '../../../../../awsService/sagemaker/detached-server/routes/getSessionAsync' -import * as utils from '../../../../../awsService/sagemaker/detached-server/utils' -import * as errorPage from '../../../../../awsService/sagemaker/detached-server/errorPage' -import { SmusDeeplinkSessionExpiredError } from '../../../../../awsService/sagemaker/constants' - -describe('handleGetSessionAsync', () => { - let req: Partial - let res: Partial - let resWriteHead: sinon.SinonSpy - let resEnd: sinon.SinonSpy - let storeStub: sinon.SinonStubbedInstance - - beforeEach(() => { - resWriteHead = sinon.spy() - resEnd = sinon.spy() - res = { writeHead: resWriteHead, end: resEnd } - - storeStub = sinon.createStubInstance(SessionStore) - sinon.stub(SessionStore.prototype, 'getFreshEntry').callsFake(storeStub.getFreshEntry) - sinon.stub(SessionStore.prototype, 'getStatus').callsFake(storeStub.getStatus) - sinon.stub(SessionStore.prototype, 'getRefreshUrl').callsFake(storeStub.getRefreshUrl) - sinon.stub(SessionStore.prototype, 'markPending').callsFake(storeStub.markPending) - sinon.stub(SessionStore.prototype, 'cleanupExpiredConnection').callsFake(storeStub.cleanupExpiredConnection) - }) - - it('responds with 400 if required query parameters are missing', async () => { - req = { url: '/session_async?connection_identifier=abc' } // missing request_id - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(400)) - assert(resEnd.calledWithMatch(/Missing required query parameters/)) - }) - - it('responds with 200 and session data if freshEntry exists', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - storeStub.getFreshEntry.returns(Promise.resolve({ sessionId: 'sid', token: 'tok', url: 'wss://test' })) - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(200)) - const actualJson = JSON.parse(resEnd.firstCall.args[0]) - assert.deepStrictEqual(actualJson, { - SessionId: 'sid', - TokenValue: 'tok', - StreamUrl: 'wss://test', - }) - }) - - it('responds with 204 if session is pending', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('pending')) - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(204)) - assert(resEnd.calledOnce) - }) - - it('responds with 202 if status is not-started and opens browser', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('not-started')) - storeStub.getRefreshUrl.returns(Promise.resolve('https://example.com/refresh')) - storeStub.markPending.returns(Promise.resolve()) - - sinon.stub(utils, 'readServerInfo').resolves({ pid: 1234, port: 4567 }) - sinon - .stub(utils, 'parseArn') - .returns({ region: 'us-east-1', accountId: '123456789012', spaceName: 'test-space' }) - sinon.stub(utils, 'open').resolves() - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(202)) - assert(resEnd.calledWithMatch(/Session is not ready yet/)) - assert(storeStub.markPending.calledWith('abc', 'req123')) - }) - - it('responds with 500 if unexpected error occurs', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - storeStub.getFreshEntry.throws(new Error('fail')) - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(500)) - assert(resEnd.calledWith('Unexpected error')) - }) - - describe('SMUS session expiration handling', () => { - let openErrorPageStub: sinon.SinonStub - - beforeEach(() => { - // Stub the openErrorPage function to prevent actual browser opening - openErrorPageStub = sinon.stub(errorPage, 'openErrorPage').resolves() - }) - - it('handles SMUS session expiration when refreshUrl is undefined', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('not-started')) - storeStub.getRefreshUrl.returns(Promise.resolve(undefined)) // SMUS case: no refreshUrl - storeStub.cleanupExpiredConnection.resolves() - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - // Verify HTTP 400 response with correct error structure - assert(resWriteHead.calledWith(400)) - const actualJson = JSON.parse(resEnd.firstCall.args[0]) - assert.strictEqual(actualJson.error, SmusDeeplinkSessionExpiredError.code) - assert.strictEqual(actualJson.message, SmusDeeplinkSessionExpiredError.shortMessage) - - // Verify cleanup was called - assert(storeStub.cleanupExpiredConnection.calledOnce) - assert(storeStub.cleanupExpiredConnection.calledWith('abc')) - - // Verify error page was opened with correct message - assert(openErrorPageStub.calledOnce) - assert.strictEqual(openErrorPageStub.firstCall.args[0], SmusDeeplinkSessionExpiredError.title) - assert.strictEqual(openErrorPageStub.firstCall.args[1], SmusDeeplinkSessionExpiredError.message) - }) - - it('responds with 400 even if cleanup fails', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('not-started')) - storeStub.getRefreshUrl.returns(Promise.resolve(undefined)) - storeStub.cleanupExpiredConnection.rejects(new Error('cleanup failed')) - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(400)) - const actualJson = JSON.parse(resEnd.firstCall.args[0]) - assert.strictEqual(actualJson.error, SmusDeeplinkSessionExpiredError.code) - }) - - it('responds with 202 when refreshUrl is valid (existing SageMaker AI flow)', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('not-started')) - storeStub.getRefreshUrl.returns(Promise.resolve('https://example.com/refresh')) // Valid refreshUrl - storeStub.markPending.returns(Promise.resolve()) - - sinon.stub(utils, 'readServerInfo').resolves({ pid: 1234, port: 4567 }) - sinon - .stub(utils, 'parseArn') - .returns({ region: 'us-east-1', accountId: '123456789012', spaceName: 'test-space' }) - sinon.stub(utils, 'open').resolves() - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - // Verify SageMaker AI flow still works correctly - assert(resWriteHead.calledWith(202)) - assert(resEnd.calledWithMatch(/Session is not ready yet/)) - assert(storeStub.markPending.calledWith('abc', 'req123')) - }) - - it('does not call cleanupExpiredConnection for SageMaker AI connections', async () => { - req = { url: '/session_async?connection_identifier=abc&request_id=req123' } - - storeStub.getFreshEntry.returns(Promise.resolve(undefined)) - storeStub.getStatus.returns(Promise.resolve('not-started')) - storeStub.getRefreshUrl.returns(Promise.resolve('https://example.com/refresh')) - storeStub.markPending.returns(Promise.resolve()) - storeStub.cleanupExpiredConnection.resolves() - - sinon.stub(utils, 'readServerInfo').resolves({ pid: 1234, port: 4567 }) - sinon - .stub(utils, 'parseArn') - .returns({ region: 'us-east-1', accountId: '123456789012', spaceName: 'test-space' }) - sinon.stub(utils, 'open').resolves() - - await handleGetSessionAsync(req as http.IncomingMessage, res as http.ServerResponse) - - // Verify cleanup was NOT called - assert(storeStub.cleanupExpiredConnection.notCalled) - }) - }) - - afterEach(() => { - sinon.restore() - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/routes/refreshToken.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/routes/refreshToken.test.ts deleted file mode 100644 index 2fe6b3c648..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/routes/refreshToken.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as http from 'http' -import * as sinon from 'sinon' -import assert from 'assert' -import { SessionStore } from '../../../../../awsService/sagemaker/detached-server/sessionStore' -import { handleRefreshToken } from '../../../../../awsService/sagemaker/detached-server/routes/refreshToken' - -describe('handleRefreshToken', () => { - let req: Partial - let res: Partial - let resWriteHead: sinon.SinonSpy - let resEnd: sinon.SinonSpy - let storeStub: sinon.SinonStubbedInstance - - beforeEach(() => { - resWriteHead = sinon.spy() - resEnd = sinon.spy() - - res = { - writeHead: resWriteHead, - end: resEnd, - } - - storeStub = sinon.createStubInstance(SessionStore) - sinon.stub(SessionStore.prototype, 'setSession').callsFake(storeStub.setSession) - }) - - it('responds with 400 if any required query parameter is missing', async () => { - req = { url: '/refresh?connection_identifier=abc&request_id=req123' } // missing others - - await handleRefreshToken(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(400)) - assert(resEnd.calledWithMatch(/Missing required parameters/)) - }) - - it('responds with 500 if setSession throws', async () => { - req = { - url: '/refresh?connection_identifier=abc&request_id=req123&ws_url=wss://abc&token=tok123&session=sess123', - } - storeStub.setSession.throws(new Error('store error')) - - await handleRefreshToken(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(500)) - assert(resEnd.calledWith('Failed to save session token')) - }) - - it('responds with 200 if session is saved successfully', async () => { - req = { - url: '/refresh?connection_identifier=abc&request_id=req123&ws_url=wss://abc&token=tok123&session=sess123', - } - - await handleRefreshToken(req as http.IncomingMessage, res as http.ServerResponse) - - assert(resWriteHead.calledWith(200)) - assert(resEnd.calledWith('Session token refreshed successfully')) - assert( - storeStub.setSession.calledWith('abc', 'req123', { - sessionId: 'sess123', - token: 'tok123', - url: 'wss://abc', - }) - ) - }) - - afterEach(() => { - sinon.restore() - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/sessionStore.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/sessionStore.test.ts deleted file mode 100644 index 468b92faa1..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/sessionStore.test.ts +++ /dev/null @@ -1,214 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import * as utils from '../../../../awsService/sagemaker/detached-server/utils' -import { SessionStore } from '../../../../awsService/sagemaker/detached-server/sessionStore' -import { SsmConnectionInfo } from '../../../../awsService/sagemaker/types' - -describe('SessionStore', () => { - let readMappingStub: sinon.SinonStub - let writeMappingStub: sinon.SinonStub - const connectionId = 'abc' - const requestId = 'req123' - - const baseMapping = { - deepLink: { - [connectionId]: { - refreshUrl: 'https://refresh.url', - requests: { - [requestId]: { sessionId: 's1', token: 't1', url: 'u1', status: 'fresh' }, - 'initial-connection': { sessionId: 's0', token: 't0', url: 'u0', status: 'fresh' }, - }, - }, - }, - } - - beforeEach(() => { - readMappingStub = sinon.stub(utils, 'readMapping').returns(JSON.parse(JSON.stringify(baseMapping))) - writeMappingStub = sinon.stub(utils, 'writeMapping') - }) - - afterEach(() => sinon.restore()) - - it('gets refreshUrl', async () => { - const store = new SessionStore() - const result = await store.getRefreshUrl(connectionId) - assert.strictEqual(result, 'https://refresh.url') - }) - - it('returns undefined for SMUS connections (no refreshUrl)', async () => { - const store = new SessionStore() - readMappingStub.returns({ - deepLink: { - [connectionId]: { - refreshUrl: undefined, - requests: { - 'initial-connection': { sessionId: 's0', token: 't0', url: 'u0', status: 'fresh' }, - }, - }, - }, - }) - const result = await store.getRefreshUrl(connectionId) - assert.strictEqual(result, undefined) - }) - - it('returns valid URL for SageMaker AI connections (existing behavior)', async () => { - const store = new SessionStore() - const result = await store.getRefreshUrl(connectionId) - assert.strictEqual(result, 'https://refresh.url') - }) - - it('throws if no mapping exists for connectionId', async () => { - const store = new SessionStore() - readMappingStub.returns({ deepLink: {} }) - - await assert.rejects(() => store.getRefreshUrl('missing'), /No mapping found/) - }) - - it('throws if no deepLink mapping exists', async () => { - const store = new SessionStore() - readMappingStub.returns({}) - - await assert.rejects(() => store.getRefreshUrl(connectionId), /No deepLink mapping found/) - }) - - it('returns fresh entry and marks consumed', async () => { - const store = new SessionStore() - const result = await store.getFreshEntry(connectionId, requestId) - assert.deepStrictEqual(result, { - sessionId: 's0', - token: 't0', - url: 'u0', - status: 'consumed', - }) - assert(writeMappingStub.calledOnce) - }) - - it('returns async fresh entry and deletes it', async () => { - const store = new SessionStore() - // Disable initial-connection freshness - readMappingStub.returns({ - deepLink: { - [connectionId]: { - refreshUrl: 'url', - requests: { - 'initial-connection': { status: 'consumed' }, - [requestId]: { sessionId: 'a', token: 'b', url: 'c', status: 'fresh' }, - }, - }, - }, - }) - const result = await store.getFreshEntry(connectionId, requestId) - assert.ok(result, 'Expected result to be defined') - assert.strictEqual(result.sessionId, 'a') - assert(writeMappingStub.calledOnce) - - // Verify the entry was deleted from the mapping - const updated = writeMappingStub.firstCall.args[0] - assert.strictEqual(updated.deepLink[connectionId].requests[requestId], undefined) - }) - - it('returns undefined if no fresh entries exist', async () => { - const store = new SessionStore() - readMappingStub.returns({ - deepLink: { - [connectionId]: { - refreshUrl: 'url', - requests: { - 'initial-connection': { status: 'consumed' }, - [requestId]: { status: 'pending' }, - }, - }, - }, - }) - const result = await store.getFreshEntry(connectionId, requestId) - assert.strictEqual(result, undefined) - }) - - it('gets status of known entry', async () => { - const store = new SessionStore() - const result = await store.getStatus(connectionId, requestId) - assert.strictEqual(result, 'fresh') - }) - - it('returns not-started if request not found', async () => { - const store = new SessionStore() - const result = await store.getStatus(connectionId, 'unknown') - assert.strictEqual(result, 'not-started') - }) - - it('marks entry as consumed', async () => { - const store = new SessionStore() - await store.markConsumed(connectionId, requestId) - const updated = writeMappingStub.firstCall.args[0] - assert.strictEqual(updated.deepLink[connectionId].requests[requestId].status, 'consumed') - }) - - it('marks request as pending', async () => { - const store = new SessionStore() - await store.markPending(connectionId, 'newReq') - const updated = writeMappingStub.firstCall.args[0] - assert.strictEqual(updated.deepLink[connectionId].requests['newReq'].status, 'pending') - }) - - it('sets session entry with default fresh status', async () => { - const store = new SessionStore() - const info: SsmConnectionInfo = { - sessionId: 's99', - token: 't99', - url: 'u99', - } - await store.setSession(connectionId, 'r99', info) - const written = writeMappingStub.firstCall.args[0] - assert.deepStrictEqual(written.deepLink[connectionId].requests['r99'], { - sessionId: 's99', - token: 't99', - url: 'u99', - status: 'fresh', - }) - }) - - it('cleans up expired connection', async () => { - const store = new SessionStore() - await store.cleanupExpiredConnection(connectionId) - const updated = writeMappingStub.firstCall.args[0] - assert.strictEqual(updated.deepLink[connectionId], undefined) - }) - - it('does not throw when cleaning up non-existent connection', async () => { - const store = new SessionStore() - await store.cleanupExpiredConnection('non-existent-connection') - assert(writeMappingStub.notCalled) - }) - - it('cleans up only the specified connection without affecting other connections', async () => { - const store = new SessionStore() - const otherConnectionId = 'other-connection' - readMappingStub.returns({ - deepLink: { - [connectionId]: { - refreshUrl: undefined, - requests: { - 'initial-connection': { sessionId: 's1', token: 't1', url: 'u1', status: 'fresh' }, - }, - }, - [otherConnectionId]: { - refreshUrl: 'https://refresh.url', - requests: { - 'initial-connection': { sessionId: 's2', token: 't2', url: 'u2', status: 'fresh' }, - }, - }, - }, - }) - - await store.cleanupExpiredConnection(connectionId) - const updated = writeMappingStub.firstCall.args[0] - assert.strictEqual(updated.deepLink[connectionId], undefined) - assert.ok(updated.deepLink[otherConnectionId]) - assert.ok(updated.deepLink[otherConnectionId].requests['initial-connection']) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/detached-server/utils.test.ts b/packages/core/src/test/awsService/sagemaker/detached-server/utils.test.ts deleted file mode 100644 index bc8d0a8867..0000000000 --- a/packages/core/src/test/awsService/sagemaker/detached-server/utils.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -/* eslint-disable no-restricted-imports */ -import * as assert from 'assert' -import { parseArn, writeMapping, readMapping } from '../../../../awsService/sagemaker/detached-server/utils' -import { promises as fs } from 'fs' -import * as path from 'path' -import * as os from 'os' -import { SpaceMappings } from '../../../../awsService/sagemaker/types' - -describe('parseArn', () => { - it('parses a standard SageMaker ARN with forward slash', () => { - const arn = 'arn:aws:sagemaker:us-west-2:123456789012:space/domain-name/my-space-name' - const result = parseArn(arn) - assert.deepStrictEqual(result, { - region: 'us-west-2', - accountId: '123456789012', - spaceName: 'my-space-name', - }) - }) - - it('parses an ARN prefixed with sagemaker-user@', () => { - const arn = 'sagemaker-user@arn:aws:sagemaker:ap-southeast-1:123456789012:space/foo/my-space-name' - const result = parseArn(arn) - assert.deepStrictEqual(result, { - region: 'ap-southeast-1', - accountId: '123456789012', - spaceName: 'my-space-name', - }) - }) - - it('throws on malformed ARN', () => { - const invalidArn = 'arn:aws:invalid:format' - assert.throws(() => parseArn(invalidArn), /Invalid SageMaker ARN format/) - }) - - it('throws when missing region/account', () => { - const invalidArn = 'arn:aws:sagemaker:::space/xyz' - assert.throws(() => parseArn(invalidArn), /Invalid SageMaker ARN format/) - }) -}) - -describe('writeMapping', () => { - let testDir: string - - beforeEach(async () => { - testDir = await fs.mkdtemp(path.join(os.tmpdir(), 'sagemaker-test-')) - }) - - afterEach(async () => { - await fs.rmdir(testDir, { recursive: true }) - }) - - it('handles concurrent writes without race conditions', async () => { - const mapping1: SpaceMappings = { - localCredential: { - 'space-1': { type: 'iam', profileName: 'profile1' }, - }, - } - const mapping2: SpaceMappings = { - localCredential: { - 'space-2': { type: 'iam', profileName: 'profile2' }, - }, - } - const mapping3: SpaceMappings = { - deepLink: { - 'space-3': { - requests: { - req1: { - sessionId: 'session-456', - url: 'wss://example3.com', - token: 'token-456', - }, - }, - refreshUrl: 'https://example3.com/refresh', - }, - }, - } - - const writePromises = [writeMapping(mapping1), writeMapping(mapping2), writeMapping(mapping3)] - - await Promise.all(writePromises) - - const finalContent = await readMapping() - const possibleResults = [mapping1, mapping2, mapping3] - const isValidResult = possibleResults.some( - (expected) => JSON.stringify(finalContent) === JSON.stringify(expected) - ) - assert.strictEqual(isValidResult, true, 'Final content should match one of the written mappings') - }) - - it('queues multiple writes and processes them sequentially', async () => { - const mappings = Array.from({ length: 5 }, (_, i) => ({ - localCredential: { - [`space-${i}`]: { type: 'iam' as const, profileName: `profile-${i}` }, - }, - })) - - const writePromises = mappings.map((mapping) => writeMapping(mapping)) - - await Promise.all(writePromises) - - const finalContent = await readMapping() - assert.strictEqual(typeof finalContent, 'object', 'Final content should be a valid object') - - const isValidResult = mappings.some((mapping) => JSON.stringify(finalContent) === JSON.stringify(mapping)) - assert.strictEqual(isValidResult, true, 'Final content should match one of the written mappings') - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/explorer/sagemakerSpaceNode.test.ts b/packages/core/src/test/awsService/sagemaker/explorer/sagemakerSpaceNode.test.ts deleted file mode 100644 index 5289dea266..0000000000 --- a/packages/core/src/test/awsService/sagemaker/explorer/sagemakerSpaceNode.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import assert from 'assert' -import { AppType } from '@aws-sdk/client-sagemaker' -import { SagemakerClient, SagemakerSpaceApp } from '../../../../shared/clients/sagemaker' -import { SagemakerSpaceNode } from '../../../../awsService/sagemaker/explorer/sagemakerSpaceNode' -import { SagemakerStudioNode } from '../../../../awsService/sagemaker/explorer/sagemakerStudioNode' -import { PollingSet } from '../../../../shared/utilities/pollingSet' - -describe('SagemakerSpaceNode', function () { - const testRegion = 'testRegion' - let client: SagemakerClient - let testParent: SagemakerStudioNode - let testSpaceApp: SagemakerSpaceApp - let describeAppStub: sinon.SinonStub - let testSpaceAppNode: SagemakerSpaceNode - - beforeEach(function () { - testSpaceApp = { - SpaceName: 'TestSpace', - DomainId: 'd-12345', - App: { AppName: 'TestApp', Status: 'InService' }, - SpaceSettingsSummary: { AppType: AppType.JupyterLab }, - OwnershipSettingsSummary: { OwnerUserProfileName: 'test-user' }, - SpaceSharingSettingsSummary: { SharingType: 'Private' }, - Status: 'InService', - DomainSpaceKey: '123', - } - - sinon.stub(PollingSet.prototype, 'add') - client = new SagemakerClient(testRegion) - testParent = new SagemakerStudioNode(testRegion, client) - - describeAppStub = sinon.stub(SagemakerClient.prototype, 'describeApp') - testSpaceAppNode = new SagemakerSpaceNode(testParent, client, testRegion, testSpaceApp) - }) - - afterEach(function () { - sinon.restore() - }) - - it('initializes with correct label, description, and tooltip', function () { - const node = new SagemakerSpaceNode(testParent, client, testRegion, testSpaceApp) - - assert.strictEqual(node.label, 'TestSpace (Running)') - assert.strictEqual(node.description, 'Private space') - assert.ok(node.tooltip instanceof vscode.MarkdownString) - assert.ok((node.tooltip as vscode.MarkdownString).value.includes('**Space:** TestSpace')) - }) - - it('falls back to defaults if optional fields are missing', function () { - const partialApp: SagemakerSpaceApp = { - SpaceName: undefined, - DomainId: 'domainId', - Status: 'Failed', - DomainSpaceKey: '123', - } - - const node = new SagemakerSpaceNode(testParent, client, testRegion, partialApp) - - assert.strictEqual(node.label, '(no name) (Failed)') - assert.strictEqual(node.description, 'Unknown space') - assert.ok((node.tooltip as vscode.MarkdownString).value.includes('**Space:** -')) - }) - - it('returns ARN from describeApp', async function () { - describeAppStub.resolves({ AppArn: 'arn:aws:sagemaker:1234:app/TestApp', $metadata: {} }) - - const arn = await testSpaceAppNode.getAppArn() - - assert.strictEqual(arn, 'arn:aws:sagemaker:1234:app/TestApp') - sinon.assert.calledOnce(describeAppStub) - sinon.assert.calledWithExactly(describeAppStub, { - DomainId: 'd-12345', - AppName: 'TestApp', - AppType: AppType.JupyterLab, - SpaceName: 'TestSpace', - }) - }) - - it('returns space ARN from describeSpace', async function () { - const describeSpaceStub = sinon.stub(SagemakerClient.prototype, 'describeSpace') - describeSpaceStub.resolves({ SpaceArn: 'arn:aws:sagemaker:1234:space/TestSpace', $metadata: {} }) - - const arn = await testSpaceAppNode.getSpaceArn() - - assert.strictEqual(arn, 'arn:aws:sagemaker:1234:space/TestSpace') - sinon.assert.calledOnce(describeSpaceStub) - }) - - it('updates status with new spaceApp', function () { - const newSpaceApp = { ...testSpaceApp, App: { AppName: 'TestApp', Status: 'Pending' } } as SagemakerSpaceApp - testSpaceAppNode.updateSpace(newSpaceApp) - assert.strictEqual(testSpaceAppNode.getStatus(), 'Starting') - }) - - it('delegates to SagemakerSpace for properties', function () { - const node = new SagemakerSpaceNode(testParent, client, testRegion, testSpaceApp) - - // Verify that properties are managed by SagemakerSpace - assert.strictEqual(node.name, 'TestSpace') - assert.strictEqual(node.label, 'TestSpace (Running)') - assert.strictEqual(node.description, 'Private space') - assert.ok(node.tooltip instanceof vscode.MarkdownString) - }) - - it('updates space app status', async function () { - const describeSpaceStub = sinon.stub(SagemakerClient.prototype, 'describeSpace') - describeSpaceStub.resolves({ SpaceName: 'TestSpace', Status: 'InService', $metadata: {} }) - - const listAppForSpaceStub = sinon.stub(SagemakerClient.prototype, 'listAppForSpace') - listAppForSpaceStub.resolves({ AppName: 'TestApp', Status: 'InService' }) - - await testSpaceAppNode.updateSpaceAppStatus() - - sinon.assert.calledOnce(describeSpaceStub) - sinon.assert.calledOnce(listAppForSpaceStub) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/explorer/sagemakerStudioNode.test.ts b/packages/core/src/test/awsService/sagemaker/explorer/sagemakerStudioNode.test.ts deleted file mode 100644 index 6eabd96135..0000000000 --- a/packages/core/src/test/awsService/sagemaker/explorer/sagemakerStudioNode.test.ts +++ /dev/null @@ -1,344 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { DescribeDomainResponse } from '@amzn/sagemaker-client' -import { GetCallerIdentityResponse } from 'aws-sdk/clients/sts' -import { SagemakerClient, SagemakerSpaceApp } from '../../../../shared/clients/sagemaker' -import { SagemakerConstants } from '../../../../awsService/sagemaker/explorer/constants' -import { - SagemakerStudioNode, - SelectedDomainUsers, - SelectedDomainUsersByRegion, -} from '../../../../awsService/sagemaker/explorer/sagemakerStudioNode' -import { globals } from '../../../../shared' -import { DefaultStsClient } from '../../../../shared/clients/stsClient' -import { assertNodeListOnlyHasPlaceholderNode } from '../../../utilities/explorerNodeAssertions' -import assert from 'assert' - -describe('sagemakerStudioNode', function () { - let testNode: SagemakerStudioNode - let client: SagemakerClient - let fetchSpaceAppsAndDomainsStub: sinon.SinonStub< - [domainId?: string | undefined, filterSmusDomains?: boolean | undefined], - Promise<[Map, Map]> - > - let getCallerIdentityStub: sinon.SinonStub<[], Promise> - const testRegion = 'testRegion' - const domainsMap: Map = new Map([ - ['domain1', { DomainId: 'domain1', DomainName: 'domainName1' }], - ['domain2', { DomainId: 'domain2', DomainName: 'domainName2' }], - ]) - const spaceAppsMap: Map = new Map([ - [ - 'domain1__name1', - { - SpaceName: 'name1', - DomainId: 'domain1', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user1-abcd' }, - Status: 'InService', - DomainSpaceKey: 'domain1__name1', - }, - ], - [ - 'domain2__name2', - { - SpaceName: 'name2', - DomainId: 'domain2', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user2-efgh' }, - Status: 'InService', - DomainSpaceKey: 'domain2__name2', - }, - ], - ]) - const spaceAppsMapPending: Map = new Map([ - [ - 'domain1__name3', - { - SpaceName: 'name3', - DomainId: 'domain1', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user1-abcd' }, - Status: 'InService', - DomainSpaceKey: 'domain1__name3', - App: { - Status: 'InService', - }, - }, - ], - [ - 'domain2__name4', - { - SpaceName: 'name4', - DomainId: 'domain2', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user2-efgh' }, - Status: 'InService', - DomainSpaceKey: 'domain2__name4', - App: { - Status: 'Pending', - }, - }, - ], - ]) - const iamUser = { - UserId: 'test-userId', - Account: '123456789012', - Arn: 'arn:aws:iam::123456789012:user/user2', - } - const assumedRoleUser = { - UserId: 'test-userId', - Account: '123456789012', - Arn: 'arn:aws:sts::123456789012:assumed-role/UserRole/user2', - } - const ssoUser = { - UserId: 'test-userId', - Account: '123456789012', - Arn: 'arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_MyPermissionSet_abcd1234/user2', - } - const getConfigTrue = { - get: () => true, - } - const getConfigFalse = { - get: () => false, - } - - before(function () { - client = new SagemakerClient(testRegion) - }) - - beforeEach(function () { - fetchSpaceAppsAndDomainsStub = sinon.stub(SagemakerClient.prototype, 'fetchSpaceAppsAndDomains') - getCallerIdentityStub = sinon.stub(DefaultStsClient.prototype, 'getCallerIdentity') - testNode = new SagemakerStudioNode(testRegion, client) - }) - - afterEach(function () { - fetchSpaceAppsAndDomainsStub.restore() - getCallerIdentityStub.restore() - testNode.pollingSet.clear() - testNode.pollingSet.clearTimer() - sinon.restore() - }) - - it('returns placeholder node if no children are present', async function () { - fetchSpaceAppsAndDomainsStub.returns( - Promise.resolve([new Map(), new Map()]) - ) - getCallerIdentityStub.returns(Promise.resolve(iamUser)) - - const childNodes = await testNode.getChildren() - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has child nodes', async function () { - fetchSpaceAppsAndDomainsStub.returns(Promise.resolve([spaceAppsMap, domainsMap])) - getCallerIdentityStub.returns(Promise.resolve(iamUser)) - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigFalse as unknown as vscode.WorkspaceConfiguration) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, spaceAppsMap.size, 'Unexpected child count') - assert.strictEqual(childNodes[0].label, 'name1 (Stopped)', 'Unexpected node label') - assert.strictEqual(childNodes[1].label, 'name2 (Stopped)', 'Unexpected node label') - }) - - it('adds pending nodes to polling nodes set', async function () { - fetchSpaceAppsAndDomainsStub.returns(Promise.resolve([spaceAppsMapPending, domainsMap])) - getCallerIdentityStub.returns(Promise.resolve(iamUser)) - - await testNode.updateChildren() - assert.strictEqual(testNode.pollingSet.size, 1) - fetchSpaceAppsAndDomainsStub.restore() - }) - - it('filters spaces owned by user profiles that match the IAM user', async function () { - fetchSpaceAppsAndDomainsStub.returns(Promise.resolve([spaceAppsMap, domainsMap])) - getCallerIdentityStub.returns(Promise.resolve(iamUser)) - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigTrue as unknown as vscode.WorkspaceConfiguration) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, 1, 'Unexpected child count') - assert.strictEqual(childNodes[0].label, 'name2 (Stopped)', 'Unexpected node label') - }) - - it('filters spaces owned by user profiles that match the IAM assumed-role session name', async function () { - fetchSpaceAppsAndDomainsStub.returns(Promise.resolve([spaceAppsMap, domainsMap])) - getCallerIdentityStub.returns(Promise.resolve(assumedRoleUser)) - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigTrue as unknown as vscode.WorkspaceConfiguration) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, 1, 'Unexpected child count') - assert.strictEqual(childNodes[0].label, 'name2 (Stopped)', 'Unexpected node label') - }) - - it('filters spaces owned by user profiles that match the Identity Center user', async function () { - fetchSpaceAppsAndDomainsStub.returns(Promise.resolve([spaceAppsMap, domainsMap])) - getCallerIdentityStub.returns(Promise.resolve(ssoUser)) - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigFalse as unknown as vscode.WorkspaceConfiguration) - - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, 1, 'Unexpected child count') - assert.strictEqual(childNodes[0].label, 'name2 (Stopped)', 'Unexpected node label') - }) - - describe('getSelectedDomainUsers', function () { - let originalState: Map - - beforeEach(async function () { - testNode = new SagemakerStudioNode(testRegion, client) - originalState = new Map( - globals.globalState.get(SagemakerConstants.SelectedDomainUsersState, []) - ) - }) - - afterEach(async function () { - await globals.globalState.update(SagemakerConstants.SelectedDomainUsersState, [...originalState]) - }) - - it('gets cached selectedDomainUsers for a given region', async function () { - await globals.globalState.update(SagemakerConstants.SelectedDomainUsersState, [ - [testRegion, [['arn:aws:iam::123456789012:user/user2', ['domain2__user-cached']]]], - ]) - testNode.callerIdentity = iamUser - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigTrue as unknown as vscode.WorkspaceConfiguration) - - const result = await testNode.getSelectedDomainUsers() - assert.deepStrictEqual( - [...result], - ['domain2__user-cached'], - 'Should match only cached selected domain user' - ) - }) - - it('gets default selectedDomainUsers', async function () { - await globals.globalState.update(SagemakerConstants.SelectedDomainUsersState, []) - testNode.spaceApps = spaceAppsMap - testNode.callerIdentity = iamUser - sinon - .stub(vscode.workspace, 'getConfiguration') - .returns(getConfigTrue as unknown as vscode.WorkspaceConfiguration) - - const result = await testNode.getSelectedDomainUsers() - assert.deepStrictEqual( - [...result], - ['domain2__user2-efgh'], - 'Should match only default selected domain user' - ) - }) - }) - - describe('saveSelectedDomainUsers', function () { - let originalState: Map - - beforeEach(async function () { - testNode = new SagemakerStudioNode(testRegion, client) - originalState = new Map( - globals.globalState.get(SagemakerConstants.SelectedDomainUsersState, []) - ) - }) - - afterEach(async function () { - await globals.globalState.update(SagemakerConstants.SelectedDomainUsersState, [...originalState]) - }) - - it('saves selectedDomainUsers for a given region', async function () { - testNode.callerIdentity = iamUser - testNode.saveSelectedDomainUsers(['domain1__user-1', 'domain2__user-2']) - - const selectedDomainUsersByRegionMap = new Map( - globals.globalState.get(SagemakerConstants.SelectedDomainUsersState, []) - ) - const selectedDomainUsers = new Map(selectedDomainUsersByRegionMap.get(testRegion)) - - assert.deepStrictEqual(selectedDomainUsers.get(iamUser.Arn), ['domain1__user-1', 'domain2__user-2']) - }) - }) - - describe('getLocalSelectedDomainUsers', function () { - const createSpaceApp = (ownerName: string): SagemakerSpaceApp => ({ - SpaceName: 'space1', - DomainId: 'domain1', - Status: 'InService', - OwnershipSettingsSummary: { - OwnerUserProfileName: ownerName, - }, - DomainSpaceKey: 'domain1__name1', - }) - - beforeEach(function () { - testNode = new SagemakerStudioNode(testRegion, client) - }) - - it('matches IAM user ARN when filtering is enabled', async function () { - testNode.callerIdentity = { - Arn: 'arn:aws:iam::123456789012:user/user1', - } - - testNode.spaceApps = new Map([ - ['domain1__space1', createSpaceApp('user1-abc')], - ['domain1__space2', createSpaceApp('user2-xyz')], - ]) - - sinon.stub(vscode.workspace, 'getConfiguration').returns(getConfigTrue as any) - - const result = await testNode.getLocalSelectedDomainUsers() - assert.deepStrictEqual(result, ['domain1__user1-abc'], 'Should match only user1-prefixed space') - }) - - it('matches IAM assumed-role ARN when filtering is enabled', async function () { - testNode.callerIdentity = { - Arn: 'arn:aws:sts::123456789012:assumed-role/SomeRole/user2', - } - - testNode.spaceApps = new Map([ - ['domain1__space1', createSpaceApp('user2-xyz')], - ['domain1__space2', createSpaceApp('user3-def')], - ]) - - sinon.stub(vscode.workspace, 'getConfiguration').returns(getConfigTrue as any) - - const result = await testNode.getLocalSelectedDomainUsers() - assert.deepStrictEqual(result, ['domain1__user2-xyz'], 'Should match only user2-prefixed space') - }) - - it('matches Identity Center ARN when IAM filtering is disabled', async function () { - testNode.callerIdentity = { - Arn: 'arn:aws:sts::123456789012:assumed-role/AWSReservedSSO_PermissionSet_abcd/user3', - } - - testNode.spaceApps = new Map([ - ['domain1__space1', createSpaceApp('user3-aaa')], - ['domain1__space2', createSpaceApp('other-user')], - ]) - - sinon.stub(vscode.workspace, 'getConfiguration').returns(getConfigFalse as any) - - const result = await testNode.getLocalSelectedDomainUsers() - assert.deepStrictEqual(result, ['domain1__user3-aaa'], 'Should match only user3-prefixed space') - }) - - it('returns empty array if no match is found', async function () { - testNode.callerIdentity = { - Arn: 'arn:aws:iam::123456789012:user/no-match', - } - - testNode.spaceApps = new Map([['domain1__space1', createSpaceApp('someone-else')]]) - - sinon.stub(vscode.workspace, 'getConfiguration').returns(getConfigTrue as any) - - const result = await testNode.getLocalSelectedDomainUsers() - assert.deepStrictEqual(result, [], 'Should return empty list when no prefix matches') - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/model.test.ts b/packages/core/src/test/awsService/sagemaker/model.test.ts deleted file mode 100644 index 7570e1bfe3..0000000000 --- a/packages/core/src/test/awsService/sagemaker/model.test.ts +++ /dev/null @@ -1,304 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import * as os from 'os' -import * as path from 'path' -import { DevSettings, fs, ToolkitError } from '../../../shared' -import { removeKnownHost, startLocalServer, stopLocalServer } from '../../../awsService/sagemaker/model' -import { assertLogsContain } from '../../globalSetup.test' -import assert from 'assert' - -describe('SageMaker Model', () => { - describe('startLocalServer', function () { - const ctx = { - globalStorageUri: vscode.Uri.file(path.join(os.tmpdir(), 'test-storage')), - extensionPath: path.join(os.tmpdir(), 'extension'), - asAbsolutePath: (relPath: string) => path.join(path.join(os.tmpdir(), 'extension'), relPath), - } as vscode.ExtensionContext - - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('waits for info file and starts server', async function () { - // Simulate the file doesn't exist initially, then appears on 3rd check - const existsStub = sandbox.stub(fs, 'existsFile') - existsStub.onCall(0).resolves(false) - existsStub.onCall(1).resolves(false) - existsStub.onCall(2).resolves(true) - - sandbox.stub(require('fs'), 'openSync').returns(42) - - const stopStub = sandbox.stub().resolves() - sandbox.replace(require('../../../awsService/sagemaker/model'), 'stopLocalServer', stopStub) - - const spawnStub = sandbox.stub().returns({ unref: sandbox.stub() }) - sandbox.replace(require('../../../awsService/sagemaker/utils'), 'spawnDetachedServer', spawnStub) - - sandbox.stub(DevSettings.instance, 'get').returns({ sagemaker: 'https://fake-endpoint' }) - - await startLocalServer(ctx) - - sinon.assert.called(spawnStub) - sinon.assert.calledWith( - spawnStub, - process.execPath, - [ctx.asAbsolutePath('dist/src/awsService/sagemaker/detached-server/server.js')], - sinon.match.any - ) - - assert.ok(existsStub.callCount >= 3, 'should have retried for file existence') - }) - - it('throws ToolkitError when info file never appears', async function () { - sandbox.stub(fs, 'existsFile').resolves(false) - sandbox.stub(require('fs'), 'openSync').returns(42) - sandbox.replace( - require('../../../awsService/sagemaker/model'), - 'stopLocalServer', - sandbox.stub().resolves() - ) - sandbox.replace( - require('../../../awsService/sagemaker/utils'), - 'spawnDetachedServer', - sandbox.stub().returns({ unref: sandbox.stub() }) - ) - sandbox.stub(DevSettings.instance, 'get').returns({}) - - try { - await startLocalServer(ctx) - assert.ok(false, 'Expected error not thrown') - } catch (err) { - assert.ok(err instanceof ToolkitError) - assert.ok(err.message.includes('Timed out waiting for local server info file')) - } - }) - }) - - describe('stopLocalServer', function () { - const ctx = { - globalStorageUri: vscode.Uri.file(path.join(os.tmpdir(), 'test-storage')), - } as vscode.ExtensionContext - - const infoFilePath = path.join(ctx.globalStorageUri.fsPath, 'sagemaker-local-server-info.json') - const validPid = 12345 - const validJson = JSON.stringify({ pid: validPid }) - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('logs debug when successfully stops server and deletes file', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(validJson) - const killStub = sandbox.stub(process, 'kill').returns(true) - const deleteStub = sandbox.stub(fs, 'delete').resolves() - - await stopLocalServer(ctx) - - sinon.assert.calledWith(killStub, validPid) - sinon.assert.calledWith(deleteStub, infoFilePath) - assertLogsContain(`stopped local server with PID ${validPid}`, false, 'debug') - assertLogsContain('removed server info file.', false, 'debug') - }) - - it('throws ToolkitError when info file is invalid JSON', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves('invalid json') - - try { - await stopLocalServer(ctx) - assert.ok(false, 'Expected error not thrown') - } catch (err) { - assert.ok(err instanceof ToolkitError) - assert.strictEqual(err.message, 'failed to parse server info file') - } - }) - - it('logs warning when process not found (ESRCH)', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(validJson) - sandbox.stub(fs, 'delete').resolves() - sandbox.stub(process, 'kill').throws({ code: 'ESRCH', message: 'no such process' }) - - await stopLocalServer(ctx) - - assertLogsContain(`no process found with PID ${validPid}. It may have already exited.`, false, 'warn') - }) - - it('throws ToolkitError when killing process fails for another reason', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(validJson) - sandbox.stub(fs, 'delete').resolves() - sandbox.stub(process, 'kill').throws({ code: 'EPERM', message: 'permission denied' }) - - try { - await stopLocalServer(ctx) - assert.ok(false) - } catch (err) { - assert.ok(err instanceof ToolkitError) - assert.strictEqual(err.message, 'failed to stop local server') - } - }) - - it('logs warning when PID is invalid', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify({ pid: 'invalid' })) - sandbox.stub(fs, 'delete').resolves() - - await stopLocalServer(ctx) - - assertLogsContain('no valid PID found in info file.', false, 'warn') - }) - - it('logs warning when file deletion fails', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(validJson) - sandbox.stub(process, 'kill').returns(true) - sandbox.stub(fs, 'delete').rejects(new Error('delete failed')) - - await stopLocalServer(ctx) - - assertLogsContain('could not delete info file: delete failed', false, 'warn') - }) - }) - - describe('removeKnownHost', function () { - const knownHostsPath = path.join(os.homedir(), '.ssh', 'known_hosts') - const hostname = 'test.host.com' - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('removes line with hostname and writes updated file', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - - const inputContent = `${hostname} ssh-rsa AAAA\nsome.other.com ssh-rsa BBBB` - const expectedOutput = `some.other.com ssh-rsa BBBB` - - sandbox.stub(fs, 'readFileText').resolves(inputContent) - - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - await removeKnownHost(hostname) - - sinon.assert.calledWith( - writeStub, - knownHostsPath, - sinon.match((value: string) => value.trim() === expectedOutput), - { atomic: true } - ) - assertLogsContain(`Removed '${hostname}' from known_hosts`, false, 'debug') - }) - - it('removes case-sensitive hostname when entry in known_hosts is lowercase', async function () { - const mixedCaseHostname = 'Test.Host.Com' - - sandbox.stub(fs, 'existsFile').resolves(true) - - const inputContent = `test.host.com ssh-rsa AAAA\nsome.other.com ssh-rsa BBBB` - const expectedOutput = `some.other.com ssh-rsa BBBB` - - sandbox.stub(fs, 'readFileText').resolves(inputContent) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await removeKnownHost(mixedCaseHostname) - - sinon.assert.calledWith( - writeStub, - path.join(os.homedir(), '.ssh', 'known_hosts'), - sinon.match((value: string) => value.trim() === expectedOutput), - { atomic: true } - ) - assertLogsContain(`Removed '${mixedCaseHostname}' from known_hosts`, false, 'debug') - }) - - it('handles hostname in comma-separated list', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - - const inputContent = `host1,${hostname},host2 ssh-rsa AAAA\nother.host ssh-rsa BBBB` - const expectedOutput = `other.host ssh-rsa BBBB` - - sandbox.stub(fs, 'readFileText').resolves(inputContent) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await removeKnownHost(hostname) - - sinon.assert.calledWith( - writeStub, - knownHostsPath, - sinon.match((value: string) => value.trim() === expectedOutput), - { atomic: true } - ) - }) - - it('does not write file when hostname not found', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - - const inputContent = `other.host ssh-rsa AAAA\nsome.other.com ssh-rsa BBBB` - sandbox.stub(fs, 'readFileText').resolves(inputContent) - const writeStub = sandbox.stub(fs, 'writeFile').resolves() - - await removeKnownHost(hostname) - - sinon.assert.notCalled(writeStub) - }) - - it('logs warning when known_hosts does not exist', async function () { - sandbox.stub(fs, 'existsFile').resolves(false) - - await removeKnownHost('test.host.com') - - assertLogsContain(`known_hosts not found at`, false, 'warn') - }) - - it('throws ToolkitError when reading known_hosts fails', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').rejects(new Error('read failed')) - - try { - await removeKnownHost(hostname) - assert.ok(false, 'Expected error was not thrown') - } catch (err) { - assert.ok(err instanceof ToolkitError) - assert.strictEqual(err.message, 'Failed to read known_hosts file') - assert.strictEqual((err as ToolkitError).cause?.message, 'read failed') - } - }) - - it('throws ToolkitError when writing known_hosts fails', async function () { - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(`${hostname} ssh-rsa key\nsomehost ssh-rsa key`) - sandbox.stub(fs, 'writeFile').rejects(new Error('write failed')) - - try { - await removeKnownHost(hostname) - assert.ok(false, 'Expected error was not thrown') - } catch (err) { - assert.ok(err instanceof ToolkitError) - assert.strictEqual(err.message, 'Failed to write updated known_hosts file') - assert.strictEqual((err as ToolkitError).cause?.message, 'write failed') - } - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/remoteUtils.test.ts b/packages/core/src/test/awsService/sagemaker/remoteUtils.test.ts deleted file mode 100644 index 4168dfdeee..0000000000 --- a/packages/core/src/test/awsService/sagemaker/remoteUtils.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as assert from 'assert' -import { getRemoteAppMetadata } from '../../../awsService/sagemaker/remoteUtils' -import { fs } from '../../../shared/fs/fs' -import { SagemakerClient } from '../../../shared/clients/sagemaker' - -describe('getRemoteAppMetadata', function () { - let sandbox: sinon.SinonSandbox - let fsStub: sinon.SinonStub - let parseRegionStub: sinon.SinonStub - let describeSpaceStub: sinon.SinonStub - let loggerStub: sinon.SinonStub - - const mockMetadata = { - AppType: 'JupyterLab', - DomainId: 'd-f0lwireyzpjp', - SpaceName: 'test-ae-3', - ExecutionRoleArn: 'arn:aws:iam::177118115371:role/service-role/AmazonSageMaker-ExecutionRole-20250415T091941', - ResourceArn: 'arn:aws:sagemaker:us-west-2:177118115371:app/d-f0lwireyzpjp/test-ae-3/JupyterLab/default', - ResourceName: 'default', - AppImageVersion: '', - ResourceArnCaseSensitive: - 'arn:aws:sagemaker:us-west-2:177118115371:app/d-f0lwireyzpjp/test-ae-3/JupyterLab/default', - IpAddressType: 'ipv4', - } - - const mockSpaceDetails = { - OwnershipSettings: { - OwnerUserProfileName: 'test-user-profile', - }, - } - - beforeEach(() => { - sandbox = sinon.createSandbox() - fsStub = sandbox.stub(fs, 'readFileText') - parseRegionStub = sandbox - .stub() - .returns({ region: 'us-west-2', accountId: '123456789012', spaceName: 'test-space' }) - sandbox.replace(require('../../../awsService/sagemaker/detached-server/utils'), 'parseArn', parseRegionStub) - - describeSpaceStub = sandbox.stub().resolves(mockSpaceDetails) - sandbox.stub(SagemakerClient.prototype, 'describeSpace').callsFake(describeSpaceStub) - - loggerStub = sandbox.stub().returns({ - error: sandbox.stub(), - }) - sandbox.replace(require('../../../shared/logger/logger'), 'getLogger', loggerStub) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('successfully reads metadata file and returns remote app metadata', async function () { - fsStub.resolves(JSON.stringify(mockMetadata)) - - const result = await getRemoteAppMetadata() - - assert.deepStrictEqual(result, { - DomainId: 'd-f0lwireyzpjp', - UserProfileName: 'test-user-profile', - }) - - sinon.assert.calledWith(fsStub, '/opt/ml/metadata/resource-metadata.json') - sinon.assert.calledWith(parseRegionStub, mockMetadata.ResourceArn) - sinon.assert.calledWith(describeSpaceStub, { - DomainId: 'd-f0lwireyzpjp', - SpaceName: 'test-ae-3', - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/sagemakerSpace.test.ts b/packages/core/src/test/awsService/sagemaker/sagemakerSpace.test.ts deleted file mode 100644 index 12db8b9c6f..0000000000 --- a/packages/core/src/test/awsService/sagemaker/sagemakerSpace.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SagemakerSpace } from '../../../awsService/sagemaker/sagemakerSpace' -import { SagemakerClient, SagemakerSpaceApp } from '../../../shared/clients/sagemaker' -import sinon from 'sinon' - -describe('SagemakerSpace', function () { - let mockClient: sinon.SinonStubbedInstance - let mockSpaceApp: SagemakerSpaceApp - - beforeEach(function () { - mockClient = sinon.createStubInstance(SagemakerClient) - mockSpaceApp = { - SpaceName: 'test-space', - Status: 'InService', - DomainId: 'test-domain', - DomainSpaceKey: 'test-key', - SpaceSettingsSummary: { - AppType: 'JupyterLab', - RemoteAccess: 'ENABLED', - }, - } - }) - - afterEach(function () { - sinon.restore() - }) - - describe('updateSpaceAppStatus', function () { - it('should correctly map DescribeSpace API response to SagemakerSpaceApp type', async function () { - // Mock DescribeSpace response (uses full property names) - const mockDescribeSpaceResponse = { - SpaceName: 'updated-space', - Status: 'InService', - DomainId: 'test-domain', - SpaceSettings: { - // Note: 'SpaceSettings' not 'SpaceSettingsSummary' - AppType: 'CodeEditor', - RemoteAccess: 'DISABLED', - }, - OwnershipSettings: { - OwnerUserProfileName: 'test-user', - }, - SpaceSharingSettings: { - SharingType: 'Private', - }, - $metadata: { requestId: 'test-request-id' }, - } - - // Mock DescribeApp response - const mockDescribeAppResponse = { - AppName: 'test-app', - Status: 'InService', - ResourceSpec: { - InstanceType: 'ml.t3.medium', - }, - $metadata: { requestId: 'test-request-id' }, - } - - mockClient.describeSpace.resolves(mockDescribeSpaceResponse) - mockClient.describeApp.resolves(mockDescribeAppResponse) - mockClient.listAppForSpace.resolves(mockDescribeAppResponse) - - const space = new SagemakerSpace(mockClient as any, 'us-east-1', mockSpaceApp) - const updateSpaceSpy = sinon.spy(space, 'updateSpace') - - await space.updateSpaceAppStatus() - - // Verify updateSpace was called with correctly mapped properties - assert.ok(updateSpaceSpy.calledOnce) - const updateSpaceArgs = updateSpaceSpy.getCall(0).args[0] - - // Verify property name mapping from DescribeSpace to SagemakerSpaceApp - assert.strictEqual(updateSpaceArgs.SpaceSettingsSummary?.AppType, 'CodeEditor') - assert.strictEqual(updateSpaceArgs.SpaceSettingsSummary?.RemoteAccess, 'DISABLED') - assert.strictEqual(updateSpaceArgs.OwnershipSettingsSummary?.OwnerUserProfileName, 'test-user') - assert.strictEqual(updateSpaceArgs.SpaceSharingSettingsSummary?.SharingType, 'Private') - - // Verify other properties are preserved - assert.strictEqual(updateSpaceArgs.SpaceName, 'updated-space') - assert.strictEqual(updateSpaceArgs.Status, 'InService') - assert.strictEqual(updateSpaceArgs.DomainId, 'test-domain') - assert.strictEqual(updateSpaceArgs.App, mockDescribeAppResponse) - assert.strictEqual(updateSpaceArgs.DomainSpaceKey, 'test-key') - - // Verify original API property names are not present - assert.ok(!('SpaceSettings' in updateSpaceArgs)) - assert.ok(!('OwnershipSettings' in updateSpaceArgs)) - assert.ok(!('SpaceSharingSettings' in updateSpaceArgs)) - }) - - it('should handle missing optional properties gracefully', async function () { - // Mock minimal DescribeSpace response - const mockDescribeSpaceResponse = { - SpaceName: 'minimal-space', - Status: 'InService', - DomainId: 'test-domain', - $metadata: { requestId: 'test-request-id' }, - // No SpaceSettings, OwnershipSettings, or SpaceSharingSettings - } - - const mockDescribeAppResponse = { - AppName: 'test-app', - Status: 'InService', - $metadata: { requestId: 'test-request-id' }, - } - mockClient.listAppForSpace.resolves(mockDescribeAppResponse) - mockClient.describeSpace.resolves(mockDescribeSpaceResponse) - - const space = new SagemakerSpace(mockClient as any, 'us-east-1', mockSpaceApp) - const updateSpaceSpy = sinon.spy(space, 'updateSpace') - - await space.updateSpaceAppStatus() - - // Should not throw and should handle undefined properties - assert.ok(updateSpaceSpy.calledOnce) - const updateSpaceArgs = updateSpaceSpy.getCall(0).args[0] - - assert.strictEqual(updateSpaceArgs.SpaceName, 'minimal-space') - assert.strictEqual(updateSpaceArgs.SpaceSettingsSummary, undefined) - assert.strictEqual(updateSpaceArgs.OwnershipSettingsSummary, undefined) - assert.strictEqual(updateSpaceArgs.SpaceSharingSettingsSummary, undefined) - }) - - it('should update app status using listAppForSpace', async function () { - const mockDescribeSpaceResponse = { - SpaceName: 'test-space', - Status: 'InService', - DomainId: 'test-domain', - $metadata: { requestId: 'test-request-id' }, - } - - const mockAppFromList = { - AppName: 'listed-app', - Status: 'InService', - $metadata: { requestId: 'test-request-id' }, - } - - mockClient.describeSpace.resolves(mockDescribeSpaceResponse) - mockClient.listAppForSpace.resolves(mockAppFromList) - - // Create space without App.AppName - const spaceWithoutAppName: SagemakerSpaceApp = { - ...mockSpaceApp, - App: undefined, - } - - const space = new SagemakerSpace(mockClient as any, 'us-east-1', spaceWithoutAppName) - await space.updateSpaceAppStatus() - - // Verify listAppForSpace was called instead of describeApp - assert.ok(mockClient.listAppForSpace.calledOnce) - assert.ok(mockClient.listAppForSpace.calledWith('test-domain', 'test-space')) - assert.ok(mockClient.describeApp.notCalled) - }) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/uriHandlers.test.ts b/packages/core/src/test/awsService/sagemaker/uriHandlers.test.ts deleted file mode 100644 index f27df1fcb1..0000000000 --- a/packages/core/src/test/awsService/sagemaker/uriHandlers.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import assert from 'assert' -import { UriHandler } from '../../../shared/vscode/uriHandler' -import { VSCODE_EXTENSION_ID } from '../../../shared/extensions' -import { register } from '../../../awsService/sagemaker/uriHandlers' - -function createConnectUri(params: { [key: string]: string }): vscode.Uri { - const query = Object.entries(params) - .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) - .join('&') - return vscode.Uri.parse(`vscode://${VSCODE_EXTENSION_ID.awstoolkit}/connect/sagemaker?${query}`) -} - -describe('SageMaker URI handler', function () { - let handler: UriHandler - let deeplinkConnectStub: sinon.SinonStub - - beforeEach(function () { - handler = new UriHandler() - deeplinkConnectStub = sinon.stub().resolves() - sinon.replace(require('../../../awsService/sagemaker/commands'), 'deeplinkConnect', deeplinkConnectStub) - - register({ - uriHandler: handler, - } as any) - }) - - afterEach(function () { - sinon.restore() - }) - - it('calls deeplinkConnect with all expected params', async function () { - const params = { - connection_identifier: 'abc123', - domain: 'my-domain', - user_profile: 'me', - session: 'sess-xyz', - ws_url: 'wss://example.com', - 'cell-number': '4', - token: 'my-token', - app_type: 'jupyterlab', - } - - const uri = createConnectUri(params) - await handler.handleUri(uri) - - assert.ok(deeplinkConnectStub.calledOnce) - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[1], 'abc123') - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[2], 'sess-xyz') - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[3], 'wss://example.com&cell-number=4') - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[4], 'my-token') - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[5], 'my-domain') - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[6], 'jupyterlab') - }) - - it('calls deeplinkConnect with undefined app_type when not provided', async function () { - const params = { - connection_identifier: 'abc123', - domain: 'my-domain', - user_profile: 'me', - session: 'sess-xyz', - ws_url: 'wss://example.com', - 'cell-number': '4', - token: 'my-token', - } - - const uri = createConnectUri(params) - await handler.handleUri(uri) - - assert.ok(deeplinkConnectStub.calledOnce) - assert.deepStrictEqual(deeplinkConnectStub.firstCall.args[6], undefined) - }) -}) diff --git a/packages/core/src/test/awsService/sagemaker/utils.test.ts b/packages/core/src/test/awsService/sagemaker/utils.test.ts deleted file mode 100644 index c73fd6968f..0000000000 --- a/packages/core/src/test/awsService/sagemaker/utils.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { AppStatus, SpaceStatus } from '@aws-sdk/client-sagemaker' -import { generateSpaceStatus, ActivityCheckInterval } from '../../../awsService/sagemaker/utils' -import * as assert from 'assert' -import * as sinon from 'sinon' -import { fs } from '../../../shared/fs/fs' -import * as utils from '../../../awsService/sagemaker/utils' - -describe('generateSpaceStatus', function () { - it('returns Failed if space status is Failed', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Failed, AppStatus.InService), 'Failed') - }) - - it('returns Failed if space status is Delete_Failed', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Delete_Failed, AppStatus.InService), 'Failed') - }) - - it('returns Failed if space status is Update_Failed', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Update_Failed, AppStatus.InService), 'Failed') - }) - - it('returns Failed if app status is Failed and space status is not Updating', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Deleting, AppStatus.Failed), 'Failed') - }) - - it('does not return Failed if app status is Failed but space status is Updating', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Updating, AppStatus.Failed), 'Updating') - }) - - it('returns Running if both statuses are InService', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.InService, AppStatus.InService), 'Running') - }) - - it('returns Starting if app is Pending and space is InService', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.InService, AppStatus.Pending), 'Starting') - }) - - it('returns Updating if space status is Updating', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Updating, AppStatus.Deleting), 'Updating') - }) - - it('returns Stopping if app is Deleting and space is InService', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.InService, AppStatus.Deleting), 'Stopping') - }) - - it('returns Stopped if app is Deleted and space is InService', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.InService, AppStatus.Deleted), 'Stopped') - }) - - it('returns Stopped if app status is undefined and space is InService', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.InService, undefined), 'Stopped') - }) - - it('returns Deleting if space is Deleting', function () { - assert.strictEqual(generateSpaceStatus(SpaceStatus.Deleting, AppStatus.InService), 'Deleting') - }) - - it('returns Unknown if none of the above match', function () { - assert.strictEqual(generateSpaceStatus(undefined, undefined), 'Unknown') - assert.strictEqual( - generateSpaceStatus('SomeOtherStatus' as SpaceStatus, 'RandomAppStatus' as AppStatus), - 'Unknown' - ) - }) -}) - -describe('checkTerminalActivity', function () { - let sandbox: sinon.SinonSandbox - let fsReaddirStub: sinon.SinonStub - let fsStatStub: sinon.SinonStub - let fsWriteFileStub: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - fsReaddirStub = sandbox.stub(fs, 'readdir') - fsStatStub = sandbox.stub(fs, 'stat') - fsWriteFileStub = sandbox.stub(fs, 'writeFile') - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should write to idle file when recent terminal activity is detected', async function () { - const idleFilePath = '/tmp/test-idle-file' - const recentTime = Date.now() - ActivityCheckInterval / 2 // Recent activity - - fsReaddirStub.resolves([ - ['pts1', 1], - ['pts2', 1], - ]) // Mock file entries - fsStatStub.onFirstCall().resolves({ mtime: new Date(recentTime) }) - fsWriteFileStub.resolves() - - await utils.checkTerminalActivity(idleFilePath) - - // Verify that fs.writeFile was called (which means updateIdleFile was called) - assert.strictEqual(fsWriteFileStub.callCount, 1) - assert.strictEqual(fsWriteFileStub.firstCall.args[0], idleFilePath) - - // Verify the timestamp is a valid ISO string - const timestamp = fsWriteFileStub.firstCall.args[1] - assert.strictEqual(typeof timestamp, 'string') - assert.ok(!isNaN(Date.parse(timestamp))) - }) - - it('should stop checking once activity is detected', async function () { - const idleFilePath = '/tmp/test-idle-file' - const recentTime = Date.now() - ActivityCheckInterval / 2 - - fsReaddirStub.resolves([ - ['pts1', 1], - ['pts2', 1], - ['pts3', 1], - ]) - fsStatStub.onFirstCall().resolves({ mtime: new Date(recentTime) }) // First file has activity - fsWriteFileStub.resolves() - - await utils.checkTerminalActivity(idleFilePath) - - // Should only call stat once since activity was detected on first file - assert.strictEqual(fsStatStub.callCount, 1) - // Should write to file once - assert.strictEqual(fsWriteFileStub.callCount, 1) - }) - - it('should handle stat error gracefully and continue checking other files', async function () { - const idleFilePath = '/tmp/test-idle-file' - const recentTime = Date.now() - ActivityCheckInterval / 2 - const statError = new Error('File not found') - - fsReaddirStub.resolves([ - ['pts1', 1], - ['pts2', 1], - ]) - fsStatStub.onFirstCall().rejects(statError) // First file fails - fsStatStub.onSecondCall().resolves({ mtime: new Date(recentTime) }) // Second file succeeds - fsWriteFileStub.resolves() - - await utils.checkTerminalActivity(idleFilePath) - - // Should continue and find activity on second file - assert.strictEqual(fsStatStub.callCount, 2) - assert.strictEqual(fsWriteFileStub.callCount, 1) - }) -}) diff --git a/packages/core/src/test/codecatalyst/auth.test.ts b/packages/core/src/test/codecatalyst/auth.test.ts deleted file mode 100644 index fc915e6e23..0000000000 --- a/packages/core/src/test/codecatalyst/auth.test.ts +++ /dev/null @@ -1,216 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { CodeCatalystAuthStorage, CodeCatalystAuthenticationProvider, defaultScopes } from '../../codecatalyst/auth' -import { getTestWindow } from '../shared/vscode/window' -import { FakeSecretStorage } from '../fakeExtensionContext' -import { createBuilderIdProfile, createSsoProfile, createTestAuth } from '../credentials/testUtil' -import Sinon from 'sinon' -import { isAnySsoConnection } from '../../auth/connection' -import globals from '../../shared/extensionGlobals' - -const enterpriseSsoStartUrl = 'https://enterprise.awsapps.com/start' - -describe('CodeCatalystAuthenticationProvider', async function () { - let auth: ReturnType - let codecatalystAuth: CodeCatalystAuthenticationProvider - - beforeEach(async function () { - auth = createTestAuth(globals.globalState) - codecatalystAuth = new CodeCatalystAuthenticationProvider( - new CodeCatalystAuthStorage(new FakeSecretStorage()), - auth - ) - }) - - afterEach(async function () { - await auth.logout() - }) - - describe('connectToAwsBuilderId()', () => { - it('should create a new connection', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.connectToAwsBuilderId() - const conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'AWS Builder ID') - }) - - it('should add scopes to existing Builder ID connection', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - getTestWindow().onDidShowMessage(async (message) => { - assert.ok(message.modal) - message.selectItem('Proceed') - }) - const otherScope = 'my:other:scope' - const ssoConn = await auth.createInvalidSsoConnection(createBuilderIdProfile({ scopes: [otherScope] })) - - // Method under test - await codecatalystAuth.connectToEnterpriseSso(ssoConn.startUrl, 'us-east-1') - - const conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'AWS Builder ID') - assert.deepStrictEqual(conn.scopes, [otherScope, ...defaultScopes]) - }) - - it('does not prompt to sign out of duplicate builder ID connections', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - await codecatalystAuth.connectToAwsBuilderId() - await codecatalystAuth.connectToAwsBuilderId() - assert.ok(codecatalystAuth.isConnected()) - - const ssoConnectionIds = new Set( - auth.activeConnectionEvents.emits.filter(isAnySsoConnection).map((c) => c.id) - ) - assert.strictEqual(ssoConnectionIds.size, 1, 'Expected exactly 1 unique SSO connection id') - assert.strictEqual((await auth.listConnections()).filter(isAnySsoConnection).length, 1) - }) - }) - - describe('connectToEnterpriseSso()', () => { - it('should create a new connection', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1') - const conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') - }) - - it('should add scopes to existing IAM Identity Center connection', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - getTestWindow().onDidShowMessage(async (message) => { - assert.ok(message.modal) - message.selectItem('Proceed') - }) - const otherScope = 'my:other:scope' - const ssoConn = await auth.createInvalidSsoConnection( - createSsoProfile({ startUrl: enterpriseSsoStartUrl, scopes: [otherScope] }) - ) - - // Method under test - await codecatalystAuth.connectToEnterpriseSso(ssoConn.startUrl, 'us-east-1') - - const conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') - assert.deepStrictEqual(conn.scopes, [otherScope, ...defaultScopes]) - }) - }) - - describe('tryConnectTo', async () => { - it('should do nothing if connection is already active', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso') - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1') - let conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') - - await codecatalystAuth.tryConnectTo({ startUrl: enterpriseSsoStartUrl, region: 'us-east-1' }) - conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') - - assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected no extra calls on active connection') - }) - - it('should switch to IAM Identity Center', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso') - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.connectToEnterpriseSso(enterpriseSsoStartUrl, 'us-east-1') - let conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (enterprise)') - assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected one call to connectToEnterpriseSso') - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.tryConnectTo({ - startUrl: 'https://other-enterprise.awsapps.com/start', - region: 'us-east-1', - }) - conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (other-enterprise)') - assert.strictEqual(conn.startUrl, 'https://other-enterprise.awsapps.com/start') - - assert.strictEqual( - connectToEnterpriseSso.callCount, - 2, - 'Expected two calls to complete switch for connectToEnterpriseSso' - ) - }) - - it('should switch to Builder ID', async function () { - Sinon.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - const connectToAwsBuilderId = Sinon.spy(codecatalystAuth, 'connectToAwsBuilderId') - const connectToEnterpriseSso = Sinon.spy(codecatalystAuth, 'connectToEnterpriseSso') - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.tryConnectTo({ - startUrl: 'https://other-enterprise.awsapps.com/start', - region: 'us-east-1', - }) - let conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'IAM Identity Center (other-enterprise)') - assert.strictEqual(conn.startUrl, 'https://other-enterprise.awsapps.com/start') - - assert.strictEqual(connectToEnterpriseSso.callCount, 1, 'Expected one call to connectToEnterpriseSso') - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - - await codecatalystAuth.connectToAwsBuilderId() - conn = codecatalystAuth.activeConnection - assert.strictEqual(conn?.type, 'sso') - assert.strictEqual(conn.label, 'AWS Builder ID') - assert.strictEqual(connectToAwsBuilderId.callCount, 1, 'Expected one call to connectToAwsBuilderId') - assert.strictEqual( - connectToEnterpriseSso.callCount, - 1, - 'Expected no additional calls to connectToEnterpriseSso' - ) - }) - }) -}) diff --git a/packages/core/src/test/codecatalyst/codecatalystClient.test.ts b/packages/core/src/test/codecatalyst/codecatalystClient.test.ts deleted file mode 100644 index d885d6bec1..0000000000 --- a/packages/core/src/test/codecatalyst/codecatalystClient.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon = require('sinon') -import { toCodeCatalystUrl } from '../../codecatalyst/utils' -import * as codecatalyst from '../../shared/clients/codecatalystClient' -import { ListSourceRepositoriesItem } from '@aws-sdk/client-codecatalyst' -import { RequiredProps } from '../../shared/utilities/tsUtils' - -describe('codeCatalystClient', function () { - it('toCodeCatalystUrl()', async function () { - const org: codecatalyst.CodeCatalystOrg = { - type: 'org', - name: 'org1', - regionName: 'region', - } - const project: codecatalyst.CodeCatalystProject = { - type: 'project', - org: org, - name: 'project1', - } - const repo: codecatalyst.CodeCatalystRepo = { - type: 'repo', - org: org, - project: project, - id: 'repoid1', - name: 'repo1', - lastUpdatedTime: new Date(), - createdTime: new Date(), - } - const prefix = `https://${codecatalyst.getCodeCatalystConfig().hostname}/spaces` - assert.deepStrictEqual(toCodeCatalystUrl(org), `${prefix}/org1/view`) - assert.deepStrictEqual(toCodeCatalystUrl(project), `${prefix}/org1/projects/project1/view`) - assert.deepStrictEqual( - toCodeCatalystUrl(repo), - `${prefix}/org1/projects/project1/source-repositories/repo1/view` - ) - }) -}) - -describe('getFirstPartyRepos()', function () { - let sandbox: sinon.SinonSandbox - let codeCatalystClient: codecatalyst.CodeCatalystClient - let getRepoCloneUrlStub: sinon.SinonStub - - before(function () { - sandbox = sinon.createSandbox() - }) - - beforeEach(function () { - codeCatalystClient = {} - - getRepoCloneUrlStub = sandbox.stub() - codeCatalystClient.getRepoCloneUrl = getRepoCloneUrlStub - }) - - it('removes third party repos', async function () { - getRepoCloneUrlStub.onCall(0).resolves('https://github.com/aws/not-code-catalyst-1.git') - getRepoCloneUrlStub.onCall(1).resolves('https://codecatalyst.aws/code-catalyst-1') - getRepoCloneUrlStub.onCall(2).resolves('https://github.com/aws/not-code-catalyst-2.git') - getRepoCloneUrlStub.onCall(3).resolves('https://codecatalyst.aws/code-catalyst-1') - - const allRepos = await codecatalyst.excludeThirdPartyRepos(codeCatalystClient, '', '', [ - { name: 'not-code-catalyst-1' }, - { name: 'code-catalyst-1' }, - { name: 'not-code-catalyst-2' }, - { name: 'code-catalyst-2' }, - ] as RequiredProps[]) - - assert.deepStrictEqual(allRepos, [{ name: 'code-catalyst-1' }, { name: 'code-catalyst-2' }]) - }) - - it('returns empty array if no first party repos', async function () { - getRepoCloneUrlStub.onCall(0).resolves('https://github.com/aws/aws-cdk.git') - getRepoCloneUrlStub.onCall(1).resolves('https://github.com/aws/vscode-toolkit.git') - - const allRepos = await codecatalyst.excludeThirdPartyRepos(codeCatalystClient, '', '', [ - { name: 'aws-cdk' }, - { name: 'vscode-toolkit' }, - ] as RequiredProps[]) - - assert.deepStrictEqual(allRepos, []) - }) -}) diff --git a/packages/core/src/test/codecatalyst/devfileLocation.test.ts b/packages/core/src/test/codecatalyst/devfileLocation.test.ts deleted file mode 100644 index e4751d47d7..0000000000 --- a/packages/core/src/test/codecatalyst/devfileLocation.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { getDevfileLocation } from '../../codecatalyst/model' -import { DevEnvClient } from '../../shared/clients/devenvClient' -import * as sinon from 'sinon' -import * as fileSystemUtils from '../../shared/filesystemUtilities' - -describe('getDevfileLocation', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - function mockClient(location: string | undefined): DevEnvClient { - const devEnvClient = new DevEnvClient() - if (!location) { - sandbox.stub(devEnvClient, 'getStatus').resolves({}) - } else { - sandbox.stub(devEnvClient, 'getStatus').resolves({ - location: location, - }) - } - return devEnvClient - } - - it('devfile found at root', async function () { - const client = mockClient('devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects')) - assert.strictEqual(location.toString(), 'file:///projects/devfile.yaml') - }) - - it('devfile with repo found in subfolder', async function () { - const client = mockClient('WebApplication/devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects')) - assert.strictEqual(location.toString(), 'file:///projects/WebApplication/devfile.yaml') - }) - - it('devfile without repo found in workspace root', async function () { - const devfilePath = vscode.Uri.parse('/projects/WebApplication/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient('devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - assert.strictEqual(location.toString(), 'file:///projects/WebApplication/devfile.yaml') - }) - - it('devfile found in subfolder with repo', async function () { - const devfilePath = vscode.Uri.parse('/projects/WebApplication/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient('WebApplication/devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - assert.strictEqual(location.toString(), 'file:///projects/WebApplication/devfile.yaml') - }) - - it('throws when devfile is not found', async function () { - sandbox.stub(fileSystemUtils, 'fileExists').resolves(false) - const client = mockClient('test/devfile.yaml') - const location = getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - await assert.rejects(location, new Error('Devfile location was not found')) - }) - - it('falls back to default projects location when devfile cannot be located', async function () { - const devfilePath = vscode.Uri.parse('/projects/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient('WebApplication/devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - assert.strictEqual(location.toString(), 'file:///projects/devfile.yaml') - }) - - it('falls back to default workspace location when devfile cannot be located', async function () { - const devfilePath = vscode.Uri.parse('/projects/WebApplication/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient('devfile.yaml') - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - assert.strictEqual(location.toString(), 'file:///projects/WebApplication/devfile.yaml') - }) - - it('checks project root for devfile when location isnt specified', async function () { - const devfilePath = vscode.Uri.parse('/projects/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient(undefined) - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects')) - assert.strictEqual(location.toString(), 'file:///projects/devfile.yaml') - }) - - it('checks workspace root for devfile when location isnt specified', async function () { - const devfilePath = vscode.Uri.parse('/projects/WebApplication/devfile.yaml').fsPath - sandbox.stub(fileSystemUtils, 'fileExists').callsFake(async function (p: string) { - return p === devfilePath - }) - const client = mockClient(undefined) - const location = await getDevfileLocation(client, vscode.Uri.parse('/projects/WebApplication')) - assert.strictEqual(location.toString(), 'file:///projects/WebApplication/devfile.yaml') - }) -}) diff --git a/packages/core/src/test/codecatalyst/uriHandlers.test.ts b/packages/core/src/test/codecatalyst/uriHandlers.test.ts deleted file mode 100644 index 8e5ed9adf3..0000000000 --- a/packages/core/src/test/codecatalyst/uriHandlers.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import assert from 'assert' -import { register } from '../../codecatalyst/uriHandlers' -import { UriHandler } from '../../shared/vscode/uriHandler' -import { VSCODE_EXTENSION_ID } from '../../shared/extensions' -import { CodeCatalystClient } from '../../shared/clients/codecatalystClient' -import { SeverityLevel } from '../shared/vscode/message' -import { getTestWindow } from '../shared/vscode/window' -import { defaultSsoRegion } from '../../auth/connection' -import { builderIdStartUrl } from '../../auth/sso/constants' - -type Stub any> = sinon.SinonStub, ReturnType> - -function createCloneUri(target: string): vscode.Uri { - return vscode.Uri.parse(`vscode://${VSCODE_EXTENSION_ID.awstoolkit}/clone?url=${encodeURIComponent(target)}`) -} - -function createConnectUri(params: { [key: string]: any }): vscode.Uri { - const encoded = Object.entries(params) - .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) - .join('&') - return vscode.Uri.parse(`vscode://${VSCODE_EXTENSION_ID.awstoolkit}/connect/codecatalyst?${encoded}`) -} - -// Tests involving `UriHandler` should _not_ couple the URI paths to the implementation. -// The path is apart of our public API! They should not be easy to change. - -describe('CodeCatalyst handlers', function () { - let commandStub: Stub - const client = {} as any as CodeCatalystClient - - beforeEach(function () { - commandStub = sinon.stub(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sinon.restore() - }) - - describe('clone', function () { - let handler: UriHandler - - beforeEach(function () { - handler = new UriHandler() - register(handler, { - cloneRepo: { - execute: async () => undefined, - } as any, - } as any) - }) - - it('registers for "/clone"', function () { - assert.throws(() => handler.onPath('/clone', () => {})) - }) - - it('ignores requests without a url', async function () { - await handler.handleUri(createCloneUri('').with({ query: '' })) - assert.strictEqual(commandStub.called, false) - }) - - it('does a normal git clone if not a CodeCatalyst URL', async function () { - const target = 'https://github.com/antlr/grammars-v4.git' - await handler.handleUri(createCloneUri(target)) - assert.ok(commandStub.calledWith('git.clone', target)) - }) - }) - - describe('connect', function () { - const devenvId = { - id: 'somefoo', - org: { name: 'org' }, - project: { name: 'project' }, - } - - const params = { - devEnvironmentId: devenvId.id, - spaceName: devenvId.org.name, - projectName: devenvId.project.name, - } - - let openDevEnvMock: sinon.SinonExpectation - let handler: UriHandler - - beforeEach(function () { - handler = new UriHandler() - openDevEnvMock = sinon.mock() - register(handler, { - openDevEnv: { - execute: openDevEnvMock, - } as any, - } as any) - }) - - it('returns builder ID SSO if IdC params are not present', async function () { - await handler.handleUri(createConnectUri(params)) - assert.ok( - openDevEnvMock.calledWith(sinon.match.any, devenvId, undefined, { - startUrl: builderIdStartUrl, - region: defaultSsoRegion, - }) - ) - }) - - it('returns provided IdC params', async function () { - const ssoStartUrl = 'https://my-url' - const ssoRegion = 'us-west-2' - await handler.handleUri(createConnectUri({ ...params, sso_start_url: ssoStartUrl, sso_region: ssoRegion })) - assert.ok( - openDevEnvMock.calledWith(sinon.match.any, devenvId, undefined, { - startUrl: ssoStartUrl, - region: ssoRegion, - }) - ) - }) - - it('checks that the environment exists', async function () { - // This test is not accurate anymore because dependencies are checked prior to API calls - // Unit tests are ran without other extensions activated, so this fails on the SSH extension check - this.skip() - - const getDevEnvStub = sinon.stub().rejects(new Error('No dev environment found')) - client.getDevEnvironment = getDevEnvStub - const errorMessage = getTestWindow() - .waitForMessage(/Failed to handle/) - .then((message) => { - message.assertSeverity(SeverityLevel.Error) - }) - - await handler.handleUri(createConnectUri(devenvId)) - await errorMessage - }) - }) -}) diff --git a/packages/core/src/test/codecatalyst/wizards/selectResource.test.ts b/packages/core/src/test/codecatalyst/wizards/selectResource.test.ts deleted file mode 100644 index 273dcacce7..0000000000 --- a/packages/core/src/test/codecatalyst/wizards/selectResource.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon from 'sinon' -import { createOrgPrompter, createProjectPrompter } from '../../../codecatalyst/wizards/selectResource' -import { CodeCatalystOrg, CodeCatalystClient, CodeCatalystProject } from '../../../shared/clients/codecatalystClient' -import { intoCollection } from '../../../shared/utilities/collectionUtils' -import { createQuickPickPrompterTester } from '../../shared/ui/testUtils' - -describe('Prompts', function () { - let orgs: CodeCatalystOrg[] - let projects: CodeCatalystProject[] - - beforeEach(function () { - orgs = [{ type: 'org', name: 'MyOrg', description: 'My Description', regionName: 'region' }] - projects = orgs.map((org) => ({ - name: 'MyProject', - type: 'project', - org, - })) - }) - - function mockClient(): CodeCatalystClient { - const client = { - listSpaces: sinon.stub().returns(intoCollection([orgs])), - listResources: sinon.stub().callsFake((arg: string) => { - if (arg === 'project') { - return intoCollection([projects]) - } - }), - } as any as CodeCatalystClient - - return client - } - - it('can list spaces (organizations)', async function () { - const prompt = createOrgPrompter(mockClient()) - const tester = createQuickPickPrompterTester(prompt) - - tester.assertItems([{ label: 'MyOrg', detail: 'My Description', data: orgs[0] }]) - tester.acceptItem('MyOrg') - - await tester.result() - }) - - it('can refresh spaces (organizations)', async function () { - const prompt = createOrgPrompter(mockClient()) - const tester = createQuickPickPrompterTester(prompt) - const newOrg = { type: 'org', name: 'AnotherOrg', description: 'More Text', regionName: 'region' } as const - - tester.assertItems([{ label: 'MyOrg', detail: 'My Description', data: orgs[0] }]) - tester.addCallback(() => orgs.push(newOrg)) - tester.pressButton('Refresh') - tester.assertItems(['MyOrg', 'AnotherOrg']) - tester.acceptItem('AnotherOrg') - - await tester.result(newOrg) - }) - - it('can refresh projects', async function () { - const prompt = createProjectPrompter(mockClient()) - const tester = createQuickPickPrompterTester(prompt) - const newProj = { type: 'project', name: 'AnotherProject', org: orgs[0] } as const - - tester.assertItems(['MyOrg / MyProject']) - tester.addCallback(() => projects.push(newProj)) - tester.pressButton('Refresh') - tester.assertItems(['MyOrg / MyProject', 'MyOrg / AnotherProject']) - tester.acceptItem('MyOrg / AnotherProject') - - await tester.result(newProj) - }) -}) diff --git a/packages/core/src/test/credentials/utils.test.ts b/packages/core/src/test/credentials/utils.test.ts index ff4dc1046b..7d83c562aa 100644 --- a/packages/core/src/test/credentials/utils.test.ts +++ b/packages/core/src/test/credentials/utils.test.ts @@ -4,7 +4,7 @@ */ import assert from 'assert' import { BuilderIdKind, ExtensionUse, SsoKind, hasBuilderId, hasIamCredentials, hasSso } from '../../auth/utils' -import { Connection, SsoConnection, scopesCodeCatalyst } from '../../auth/connection' +import { Connection, SsoConnection } from '../../auth/connection' import { builderIdConnection, iamConnection, ssoConnection } from './testUtil' import { amazonQScopes } from '../../codewhisperer/util/authUtil' import globals from '../../shared/extensionGlobals' @@ -73,18 +73,7 @@ describe('connection exists funcs', function () { scopes: amazonQScopes, label: 'codeWhispererBuilderId', } - const ccBuilderIdConnection: SsoConnection = { - ...builderIdConnection, - scopes: scopesCodeCatalyst, - label: 'codeCatalystBuilderId', - } - const ssoConnections: Connection[] = [ - ssoConnection, - builderIdConnection, - cwIdcConnection, - cwBuilderIdConnection, - ccBuilderIdConnection, - ] + const ssoConnections: Connection[] = [ssoConnection, builderIdConnection, cwIdcConnection, cwBuilderIdConnection] const allConnections = [iamConnection, ...ssoConnections] describe('ssoExists()', function () { @@ -125,16 +114,7 @@ describe('connection exists funcs', function () { return { ...c, kind: 'codewhisperer' } }) - const ccBuilderIdCases: BuilderIdTestCase[] = [ - { connections: [ccBuilderIdConnection], expected: true }, - { connections: allConnections, expected: true }, - { connections: [], expected: false }, - { connections: allConnections.filter((c) => c !== ccBuilderIdConnection), expected: false }, - ].map((c) => { - return { ...c, kind: 'codecatalyst' } - }) - - const allCases = [...cwBuilderIdCases, ...ccBuilderIdCases] + const allCases = [...cwBuilderIdCases] for (const args of allCases) { it(`builderIdExists() returns '${args.expected}' when kind '${args.kind}' given [${args.connections diff --git a/packages/core/src/test/docdb/commands/addRegion.test.ts b/packages/core/src/test/docdb/commands/addRegion.test.ts deleted file mode 100644 index 5a5e503080..0000000000 --- a/packages/core/src/test/docdb/commands/addRegion.test.ts +++ /dev/null @@ -1,250 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { assertTelemetry } from '../../testUtil' -import { getTestWindow } from '../../shared/vscode/window' -import { globals } from '../../../shared' -import { DBStorageType, DefaultDocumentDBClient, DocumentDBClient } from '../../../shared/clients/docdbClient' -import { addRegion } from '../../../docdb/commands/addRegion' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBGlobalClusterNode } from '../../../docdb/explorer/dbGlobalClusterNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { GlobalCluster } from '@aws-sdk/client-docdb' - -describe('addRegionCommand', function () { - const globalClusterName = 'docdb-global' - const clusterName = 'docdb-1234' - const cluster = { - DBClusterArn: 'arn:docdb-1234', - Status: 'available', - DBClusterMembers: [{ IsClusterWriter: true }], - } - let docdb: DocumentDBClient - let testNode: DBClusterNode | DBGlobalClusterNode - let sinonSandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sinonSandbox = sinon.createSandbox() - spyExecuteCommand = sinonSandbox.spy(vscode.commands, 'executeCommand') - sinonSandbox.stub(globals.regionProvider, 'isServiceInRegion').returns(true) - sinonSandbox.stub(globals.regionProvider, 'getRegions').returns([ - { id: 'us-test-1', name: 'Test Region 1' }, - { id: 'us-test-2', name: 'Test Region 2' }, - ]) - - docdb = { regionCode: 'us-test-1' } as DocumentDBClient - docdb.listEngineVersions = sinon.stub().resolves([{ EngineVersion: 'test-version' }]) - docdb.listInstanceClassOptions = sinon - .stub() - .resolves([{ DBInstanceClass: 'db.r5.large', StorageType: DBStorageType.Standard }]) - - sinonSandbox.stub(DefaultDocumentDBClient, 'create').returns(docdb) - - cluster.DBClusterMembers = [{ IsClusterWriter: true }] - const parentNode = new DocumentDBNode(docdb) - - testNode = new DBClusterNode(parentNode, cluster, docdb) - }) - afterEach(function () { - sinonSandbox.restore() - getTestWindow().dispose() - }) - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - let val: string - - if (input.prompt?.includes('global')) { - val = globalClusterName - } else if (input.prompt?.includes('cluster name')) { - val = clusterName - } else { - val = '' - } - input.acceptValue(val) - }) - - getTestWindow().onDidShowQuickPick(async (testQuickPicker) => { - await testQuickPicker.untilReady() - testQuickPicker.acceptItem(testQuickPicker.items[0]) - }) - } - - it('prompts for new region and cluster params, creates global cluster, creates secondary cluster, and refreshes node', async function () { - // arrange - const createGlobalClusterStub = sinon.stub().resolves({ - GlobalClusterIdentifier: globalClusterName, - }) - const createClusterStub = sinon.stub().resolves({ - DBClusterIdentifier: clusterName, - }) - const createInstanceSinonStub = sinon.stub().resolves() - docdb.createGlobalCluster = createGlobalClusterStub - docdb.createCluster = createClusterStub - docdb.createInstance = createInstanceSinonStub - - setupWizard() - - // act - await addRegion(testNode) - - // assert - getTestWindow().getFirstMessage().assertInfo('Region added') - - assert( - createGlobalClusterStub.calledOnceWithExactly({ - GlobalClusterIdentifier: globalClusterName, - SourceDBClusterIdentifier: cluster.DBClusterArn, - }) - ) - - assert( - createClusterStub.calledOnceWith( - sinon.match({ - DBClusterIdentifier: clusterName, - GlobalClusterIdentifier: globalClusterName, - }) - ) - ) - assert( - createInstanceSinonStub.calledOnceWith( - sinon.match({ - Engine: 'docdb', - DBClusterIdentifier: clusterName, - DBInstanceIdentifier: clusterName, - DBInstanceClass: 'db.r5.large', - }) - ) - ) - - sinonSandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', testNode.parent) - - assertTelemetry('docdb_addRegion', { result: 'Succeeded' }) - }) - - it('creates a secondary cluster, and refreshes node when adding to a global cluster', async function () { - // arrange - const createGlobalClusterStub = sinon.stub().resolves({ - GlobalClusterIdentifier: globalClusterName, - }) - const createClusterSinonStub = sinon.stub().resolves({ - DBClusterIdentifier: clusterName, - }) - const createInstanceStub = sinon.stub().resolves() - docdb.createGlobalCluster = createGlobalClusterStub - docdb.createCluster = createClusterSinonStub - docdb.createInstance = createInstanceStub - setupWizard() - - const globalCluster: GlobalCluster = { - GlobalClusterIdentifier: globalClusterName, - GlobalClusterMembers: [], - Status: 'available', - } - testNode = new DBGlobalClusterNode(new DocumentDBNode(docdb), globalCluster, new Map(), docdb) - - // act - await addRegion(testNode) - - // assert - getTestWindow().getFirstMessage().assertInfo('Region added') - - assert(createGlobalClusterStub.notCalled) - - assert( - createClusterSinonStub.calledOnceWith( - sinon.match({ - DBClusterIdentifier: clusterName, - GlobalClusterIdentifier: globalClusterName, - }) - ) - ) - - assert( - createInstanceStub.calledOnceWith( - sinon.match({ - Engine: 'docdb', - DBClusterIdentifier: clusterName, - DBInstanceIdentifier: clusterName, - DBInstanceClass: 'db.r5.large', - }) - ) - ) - sinonSandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', testNode) - - assertTelemetry('docdb_addRegion', { result: 'Succeeded' }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.createGlobalCluster = stub - getTestWindow().onDidShowQuickPick((input) => input.hide()) - - // act - await assert.rejects(addRegion(testNode)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_addRegion', { result: 'Cancelled' }) - }) - - it('shows an error when cluster creation fails', async function () { - // arrange - docdb.createGlobalCluster = sinon.stub().resolves({ - GlobalClusterIdentifier: globalClusterName, - }) - docdb.createCluster = sinon.stub().rejects() - setupWizard() - - // act - await assert.rejects(addRegion(testNode)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create cluster: docdb-1234/) - - assertTelemetry('docdb_addRegion', { result: 'Failed' }) - }) - - it('shows a warning when the cluster has no instances', async function () { - // arrange - const clusterNode = testNode as DBClusterNode - clusterNode.cluster.DBClusterMembers = [] - setupWizard() - - // act - await assert.rejects(addRegion(testNode)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Cluster must have at least one instance to add a region/) - - assertTelemetry('docdb_addRegion', { result: 'Cancelled' }) - }) - - it('shows a warning when the cluster has an unsupported instance class', async function () { - // arrange - const clusterNode = testNode as DBClusterNode - clusterNode.instances = [{ DBInstanceClass: 'db.t3.medium' }] - setupWizard() - - // act - await assert.rejects(addRegion(testNode)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Instance class db.t3.medium not supported/) - - assertTelemetry('docdb_addRegion', { result: 'Cancelled' }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/createCluster.test.ts b/packages/core/src/test/docdb/commands/createCluster.test.ts deleted file mode 100644 index 90d31ad148..0000000000 --- a/packages/core/src/test/docdb/commands/createCluster.test.ts +++ /dev/null @@ -1,167 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { assertTelemetry } from '../../testUtil' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { DBStorageType, DocumentDBClient } from '../../../shared/clients/docdbClient' -import { createCluster } from '../../../docdb/commands/createCluster' - -describe('createClusterCommand', function () { - const clusterName = 'docdb-1234' - const testUser = 'testuser' - const testPassword = 'test-password' - let docdb: DocumentDBClient - let node: DocumentDBNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - docdb.listEngineVersions = sinon.stub().resolves([{ EngineVersion: 'test-version' }]) - docdb.listInstanceClassOptions = sinon - .stub() - .resolves([{ DBInstanceClass: 'db.t3.medium', StorageType: DBStorageType.Standard }]) - - node = new DocumentDBNode(docdb) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - let value: string - if (input.prompt?.includes('username')) { - value = testUser - } else if (input.prompt?.includes('password')) { - value = testPassword - } else if (input.prompt?.includes('cluster name')) { - value = clusterName - } else { - value = '' - } - input.acceptValue(value) - }) - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[0]) - }) - } - - it('prompts for cluster params, creates cluster, shows success, and refreshes node', async function () { - // arrange - const createClusterStub = sinon.stub().resolves({ - DBClusterIdentifier: clusterName, - }) - const createInstanceStub = sinon.stub().resolves({ - DBInstanceIdentifier: clusterName, - }) - docdb.createCluster = createClusterStub - docdb.createInstance = createInstanceStub - setupWizard() - - // act - await createCluster(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Created cluster: docdb-1234/) - - assert( - createClusterStub.calledOnceWithExactly({ - Engine: 'docdb', - EngineVersion: 'test-version', - DBClusterIdentifier: clusterName, - MasterUsername: testUser, - MasterUserPassword: testPassword, - StorageEncrypted: true, - DBInstanceCount: 1, - DBInstanceClass: 'db.t3.medium', - }) - ) - - assert( - createInstanceStub.calledOnceWithExactly({ - Engine: 'docdb', - DBClusterIdentifier: clusterName, - DBInstanceIdentifier: clusterName, - DBInstanceClass: 'db.t3.medium', - }) - ) - - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - - assertTelemetry('docdb_createCluster', { - awsRegion: docdb.regionCode, - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.createCluster = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - getTestWindow().onDidShowQuickPick((input) => input.hide()) - - // act - await assert.rejects(createCluster(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_createCluster', { - result: 'Cancelled', - }) - }) - - it('shows an error when cluster creation fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.createCluster = stub - setupWizard() - - // act - await assert.rejects(createCluster(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create cluster: docdb-1234/) - - assertTelemetry('docdb_createCluster', { - result: 'Failed', - }) - }) - - it('shows an error when instance creation fails', async function () { - // arrange - docdb.createCluster = sinon.stub().resolves({ DBClusterIdentifier: clusterName }) - docdb.createInstance = sinon.stub().rejects() - setupWizard() - - // act - await assert.rejects(createCluster(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create cluster: docdb-1234/) - - assertTelemetry('docdb_createCluster', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/createInstance.test.ts b/packages/core/src/test/docdb/commands/createInstance.test.ts deleted file mode 100644 index ca58671ba6..0000000000 --- a/packages/core/src/test/docdb/commands/createInstance.test.ts +++ /dev/null @@ -1,145 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient, DBStorageType } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { createInstance } from '../../../docdb/commands/createInstance' -import { CreateDBInstanceMessage, DBCluster } from '@aws-sdk/client-docdb' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { assertTelemetry } from '../../testUtil' - -describe('createInstanceCommand', function () { - const clusterName = 'docdb-1234' - const instanceName = 'test-instance' - let docdb: DocumentDBClient - let cluster: DBCluster - let node: DBClusterNode - let spyExecuteCommand: sinon.SinonSpy - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - - docdb.listInstances = sinon.stub().resolves([]) - - docdb.listInstanceClassOptions = sinon - .stub() - .resolves([{ DBInstanceClass: 'db.t3.medium', StorageType: DBStorageType.Standard }]) - - cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - const parentNode = new DocumentDBNode(docdb) - node = new DBClusterNode(parentNode, cluster, docdb) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(instanceName) - }) - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[0]) - }) - } - - it('prompts for instance name and instance class, creates instance, shows success, and refreshes node', async function () { - // arrange - const stub = sinon.stub().resolves({ - DBInstanceIdentifier: instanceName, - }) - docdb.createInstance = stub - setupWizard() - - // act - await createInstance(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Creating instance: test-instance/) - - const expectedArgs: CreateDBInstanceMessage = { - Engine: 'docdb', - DBClusterIdentifier: clusterName, - DBInstanceIdentifier: instanceName, - DBInstanceClass: 'db.t3.medium', - } - - assert(stub.calledOnceWithExactly(expectedArgs)) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - assert((docdb.listInstances as sinon.SinonSpy).calledOnce) - - assertTelemetry('docdb_createInstance', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.createInstance = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(createInstance(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_createInstance', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the cluster has the max number of instances', async function () { - // arrange - docdb.listInstances = sinon.stub().resolves(new Array(16)) - const stub = sinon.stub() - docdb.createInstance = stub - setupWizard() - - // act - await assert.rejects(createInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Max instances in use/) - - assertTelemetry('docdb_createInstance', { - result: 'Failed', - }) - }) - - it('shows an error when instance creation fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.createInstance = stub - setupWizard() - - // act - await assert.rejects(createInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to create instance: test-instance/) - - assertTelemetry('docdb_createInstance', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/deleteCluster.test.ts b/packages/core/src/test/docdb/commands/deleteCluster.test.ts deleted file mode 100644 index 5c86a7b6ae..0000000000 --- a/packages/core/src/test/docdb/commands/deleteCluster.test.ts +++ /dev/null @@ -1,239 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { assertTelemetry } from '../../testUtil' -import { getTestWindow } from '../../shared/vscode/window' -import { DBCluster, DBInstance } from '@aws-sdk/client-docdb' -import { DocumentDBClient } from '../../../shared/clients/docdbClient' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { DBElasticClusterNode } from '../../../docdb/explorer/dbElasticClusterNode' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { deleteCluster } from '../../../docdb/commands/deleteCluster' - -describe('deleteClusterCommand', function () { - const clusterName = 'test-cluster' - let docdb: DocumentDBClient - let sinonSandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sinonSandbox = sinon.createSandbox() - spyExecuteCommand = sinonSandbox.spy(vscode.commands, 'executeCommand') - }) - afterEach(function () { - sinonSandbox.restore() - getTestWindow().dispose() - }) - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(input.placeholder!) - }) - - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[0]) - }) - } - - describe('regional cluster', function () { - let cluster: DBCluster - let instances: DBInstance[] - let node: DBClusterNode - let deleteInstanceStub: sinon.SinonStub - - beforeEach(function () { - cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - instances = [ - { DBClusterIdentifier: clusterName, DBInstanceIdentifier: 'instance-1' }, - { DBClusterIdentifier: clusterName, DBInstanceIdentifier: 'instance-2' }, - ] - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - docdb.listClusters = sinon.stub().resolves([cluster]) - docdb.listInstances = sinon.stub().resolves(instances) - deleteInstanceStub = sinon.stub().onFirstCall().resolves(instances[0]).onSecondCall().resolves(instances[1]) - docdb.deleteInstance = deleteInstanceStub - - const parentNode = new DocumentDBNode(docdb) - node = new DBClusterNode(parentNode, cluster, docdb) - node.waitUntilStatusChanged = sinon.stub().resolves(true) - }) - - it('prompts for snapshot and confirmation, deletes all instances, deletes cluster, and refreshes node', async function () { - // arrange - const deleteClusterStub = sinon.stub().resolves({ - DBClusterIdentifier: clusterName, - Status: 'backing up', - }) - docdb.deleteCluster = deleteClusterStub - setupWizard() - - // act - await deleteCluster(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Deleting cluster: test-cluster/) - - assert(deleteClusterStub.calledOnceWithExactly(sinon.match({ DBClusterIdentifier: clusterName }))) - assert(deleteInstanceStub.calledTwice) - sinonSandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node.parent) - - assertTelemetry('docdb_deleteCluster', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - const deleteClusterStub = sinon.stub() - docdb.deleteCluster = deleteClusterStub - getTestWindow().onDidShowQuickPick((picker) => picker.hide()) - getTestWindow().onDidShowInputBox((input) => input.hide()) - - await assert.rejects(deleteCluster(node)) - assert(deleteClusterStub.notCalled) - assertTelemetry('docdb_deleteCluster', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the cluster is stopped', async function () { - cluster.Status = 'stopped' - const deleteClusterStub = sinon.stub() - docdb.deleteCluster = deleteClusterStub - setupWizard() - - await assert.rejects(deleteCluster(node)) - getTestWindow() - .getFirstMessage() - .assertMessage(/Cluster must be running/) - - assertTelemetry('docdb_deleteCluster', { - result: 'Cancelled', - }) - }) - - it('shows an error when cluster deletion fails', async function () { - const deleteClusterStub = sinon.stub().rejects() - docdb.deleteCluster = deleteClusterStub - setupWizard() - - await assert.rejects(deleteCluster(node)) - - getTestWindow() - .getFirstMessage() - .assertError(/Failed to delete cluster: test-cluster/) - - assertTelemetry('docdb_deleteCluster', { - result: 'Failed', - }) - }) - }) - - describe('elastic cluster', function () { - let cluster: any - let node: DBElasticClusterNode - - beforeEach(function () { - cluster = { clusterName, clusterArn: 'arn:test-cluster', status: 'ACTIVE' } - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - docdb.listElasticClusters = sinon.stub().resolves([cluster]) - - const parentNode = new DocumentDBNode(docdb) - node = new DBElasticClusterNode(parentNode, cluster, docdb) - node.waitUntilStatusChanged = sinon.stub().resolves(true) - }) - - it('prompts for snapshot and confirmation, creates snapshot, deletes cluster, and refreshes node', async function () { - // arrange - const createSnapshotStub = sinon.stub().resolves() - const deleteClusterStub = sinon.stub().resolves() - docdb.createClusterSnapshot = createSnapshotStub - docdb.deleteElasticCluster = deleteClusterStub - setupWizard() - - // act - await deleteCluster(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Taking snapshot of cluster: test-cluster/) - - getTestWindow() - .getSecondMessage() - .assertInfo(/Deleting cluster: test-cluster/) - - assert(createSnapshotStub.calledOnceWithExactly(sinon.match({ clusterArn: cluster.clusterArn }))) - assert(deleteClusterStub.calledOnceWithExactly(cluster.clusterArn)) - sinonSandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node.parent) - - assertTelemetry('docdb_deleteCluster', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const deleteClusterStub = sinon.stub() - docdb.deleteElasticCluster = deleteClusterStub - getTestWindow().onDidShowQuickPick((picker) => picker.hide()) - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(deleteCluster(node)) - - // assert - assert(deleteClusterStub.notCalled) - - assertTelemetry('docdb_deleteCluster', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the cluster is stopped', async function () { - // arrange - cluster.status = 'STOPPED' - const deleteClusterStub = sinon.stub() - docdb.deleteElasticCluster = deleteClusterStub - setupWizard() - - // act - await assert.rejects(deleteCluster(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Cluster must be running/) - - assertTelemetry('docdb_deleteCluster', { - result: 'Cancelled', - }) - }) - - it('shows an error when cluster deletion fails', async function () { - // arrange - docdb.createClusterSnapshot = sinon.stub().resolves() - docdb.deleteElasticCluster = sinon.stub().rejects() - setupWizard() - - // act - await assert.rejects(deleteCluster(node)) - - // assert - getTestWindow() - .getSecondMessage() - .assertError(/Failed to delete cluster: test-cluster/) - - assertTelemetry('docdb_deleteCluster', { - result: 'Failed', - }) - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/deleteInstance.test.ts b/packages/core/src/test/docdb/commands/deleteInstance.test.ts deleted file mode 100644 index 8ba1601cb9..0000000000 --- a/packages/core/src/test/docdb/commands/deleteInstance.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { deleteInstance } from '../../../docdb/commands/deleteInstance' -import { DBCluster, DBInstance } from '@aws-sdk/client-docdb' -import { assertTelemetry } from '../../testUtil' - -describe('deleteInstanceCommand', function () { - const instanceName = 'test-instance' - let docdb: DocumentDBClient - let cluster: DBCluster - let instance: DBInstance - let parentNode: DBClusterNode - let node: DBInstanceNode - let spyExecuteCommand: sinon.SinonSpy - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - cluster = { Status: 'available' } - instance = { - DBInstanceIdentifier: instanceName, - DBClusterIdentifier: 'test-cluster', - DBInstanceStatus: 'available', - } - parentNode = new DBClusterNode(undefined!, cluster, docdb) - node = new DBInstanceNode(parentNode, instance) - }) - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(instanceName) - }) - } - - it('prompts for instance name, deletes instance, shows success, and refreshes node', async function () { - // arrange - const stub = sinon.stub().resolves({ - DBInstanceIdentifier: instanceName, - }) - docdb.deleteInstance = stub - setupWizard() - - // act - await deleteInstance(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Deleting instance: test-instance/) - - assert(stub.calledOnceWithExactly({ DBInstanceIdentifier: instanceName })) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - - assertTelemetry('docdb_deleteInstance', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.deleteInstance = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(deleteInstance(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_deleteInstance', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the cluster is stopped', async function () { - // arrange - cluster.Status = 'stopped' - const stub = sinon.stub() - docdb.deleteInstance = stub - setupWizard() - - // act - await assert.rejects(deleteInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Cluster must be started to delete instances/) - - assertTelemetry('docdb_deleteInstance', { - result: 'Cancelled', - }) - }) - - it('shows an error when instance deletion fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.deleteInstance = stub - setupWizard() - - // act - await assert.rejects(deleteInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to delete instance: test-instance/) - - assertTelemetry('docdb_deleteInstance', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/modifyInstance.test.ts b/packages/core/src/test/docdb/commands/modifyInstance.test.ts deleted file mode 100644 index 77872b411b..0000000000 --- a/packages/core/src/test/docdb/commands/modifyInstance.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient, DBInstance, DBStorageType } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { modifyInstance } from '../../../docdb/commands/modifyInstance' -import { ModifyDBInstanceMessage, DBCluster } from '@aws-sdk/client-docdb' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { assertTelemetry } from '../../testUtil' - -describe('modifyInstanceCommand', function () { - const clusterName = 'docdb-1234' - const instanceName = 'test-instance' - let docdb: DocumentDBClient - let cluster: DBCluster - let instance: DBInstance - let node: DBInstanceNode - let spyExecuteCommand: sinon.SinonSpy - let sandbox: sinon.SinonSandbox - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - docdb.listInstanceClassOptions = sinon - .stub() - .resolves([{ DBInstanceClass: 'db.t3.large', StorageType: DBStorageType.Standard }]) - - cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - instance = { - DBInstanceIdentifier: instanceName, - DBClusterIdentifier: clusterName, - DBInstanceStatus: 'available', - } - const parentNode = new DBClusterNode(new DocumentDBNode(docdb), cluster, docdb) - node = new DBInstanceNode(parentNode, instance) - node.waitUntilStatusChanged = sinon.stub().resolves(true) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - function setupWizard() { - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[0]) - }) - } - - it('prompts for instance class, modifies instance, shows success, and refreshes node', async function () { - // arrange - const stub = sinon.stub().resolves({ - DBInstanceIdentifier: instanceName, - }) - docdb.modifyInstance = stub - setupWizard() - - // act - await modifyInstance(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Modified instance: test-instance/) - - const expectedArgs: ModifyDBInstanceMessage = { - DBInstanceIdentifier: instanceName, - DBInstanceClass: 'db.t3.large', - ApplyImmediately: true, - } - - assert(stub.calledOnceWithExactly(expectedArgs)) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node.parent) - - assertTelemetry('docdb_resizeInstance', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.modifyInstance = stub - getTestWindow().onDidShowQuickPick((picker) => picker.hide()) - - // act - await assert.rejects(modifyInstance(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_resizeInstance', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the instance is not available', async function () { - // arrange - instance.DBInstanceStatus = 'stopped' - const stub = sinon.stub() - docdb.modifyInstance = stub - setupWizard() - - // act - await assert.rejects(modifyInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Instance must be running/) - - assertTelemetry('docdb_resizeInstance', { - result: 'Cancelled', - }) - }) - - it('shows an error when instance creation fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.modifyInstance = stub - setupWizard() - - // act - await assert.rejects(modifyInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to modify instance: test-instance/) - - assertTelemetry('docdb_resizeInstance', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/rebootInstance.test.ts b/packages/core/src/test/docdb/commands/rebootInstance.test.ts deleted file mode 100644 index 4006ba52fc..0000000000 --- a/packages/core/src/test/docdb/commands/rebootInstance.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient, DBInstance } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { rebootInstance } from '../../../docdb/commands/rebootInstance' -import { assertTelemetry } from '../../testUtil' - -describe('rebootInstanceCommand', function () { - const instanceName = 'test-instance' - let docdb: DocumentDBClient - let instance: DBInstance - let node: DBInstanceNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Yes')?.select()) - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - const clusterName = 'docdb-1234' - const cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - const parentNode = new DBClusterNode(new DocumentDBNode(docdb), cluster, docdb) - instance = { - DBInstanceIdentifier: instanceName, - DBClusterIdentifier: clusterName, - DBInstanceStatus: 'available', - } - node = new DBInstanceNode(parentNode, instance) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - it('reboots instance, and refreshes parent node', async function () { - // arrange - const stub = sinon.stub().resolves(true) - docdb.rebootInstance = stub - - // act - await rebootInstance(node) - - // assert - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to reboot instance test-instance?') - - getTestWindow() - .getSecondMessage() - .assertInfo(/Rebooting instance: test-instance/) - - assert(stub.calledOnceWithExactly(instanceName)) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node.parent) - - assertTelemetry('docdb_rebootInstance', { - result: 'Succeeded', - }) - }) - - it('shows an error when api returns failure', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.rebootInstance = stub - - // act - await assert.rejects(rebootInstance(node)) - - // assert - getTestWindow() - .getSecondMessage() - .assertError(/Failed to reboot instance: test-instance/) - - assertTelemetry('docdb_rebootInstance', { - result: 'Failed', - }) - }) - - it('shows a warning when the instance is not available', async function () { - // arrange - instance.DBInstanceStatus = 'stopped' - const stub = sinon.stub() - docdb.rebootInstance = stub - - // act - await assert.rejects(rebootInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Instance must be running/) - - assertTelemetry('docdb_rebootInstance', { - result: 'Cancelled', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/renameCluster.test.ts b/packages/core/src/test/docdb/commands/renameCluster.test.ts deleted file mode 100644 index 9f31659878..0000000000 --- a/packages/core/src/test/docdb/commands/renameCluster.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { renameCluster } from '../../../docdb/commands/renameCluster' -import { DBCluster } from '@aws-sdk/client-docdb' -import { assertTelemetry } from '../../testUtil' - -describe('renameClusterCommand', function () { - const clusterName = 'test-cluster' - const newClusterName = 'new-cluster-name' - let docdb: DocumentDBClient - let spyExecuteCommand: sinon.SinonSpy - let cluster: DBCluster - let node: DBClusterNode - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - node = new DBClusterNode(new DocumentDBNode(docdb), cluster, docdb) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(newClusterName) - }) - } - - it('prompts for new cluster name, modifies cluster, shows success, and refreshes parent node', async function () { - // arrange - const stub = sinon.stub().resolves({ - DBClusterIdentifier: clusterName, - }) - docdb.modifyCluster = stub - setupWizard() - - // act - await renameCluster(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Updated cluster: test-cluster/) - - const expectedArgs = { - DBClusterIdentifier: clusterName, - NewDBClusterIdentifier: newClusterName, - ApplyImmediately: true, - } - - assert(stub.calledOnceWithExactly(expectedArgs)) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node.parent) - - assertTelemetry('docdb_renameCluster', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.modifyCluster = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(renameCluster(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_renameCluster', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the cluster is not available', async function () { - // arrange - cluster.Status = 'stopped' - const stub = sinon.stub() - docdb.modifyCluster = stub - setupWizard() - - // act - await assert.rejects(renameCluster(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Cluster must be running/) - - assertTelemetry('docdb_renameCluster', { - result: 'Cancelled', - }) - }) - - it('shows an error when cluster creation fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.modifyCluster = stub - setupWizard() - - // act - await assert.rejects(renameCluster(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to rename cluster: test-cluster/) - - assertTelemetry('docdb_renameCluster', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/renameInstance.test.ts b/packages/core/src/test/docdb/commands/renameInstance.test.ts deleted file mode 100644 index 097e01d7ba..0000000000 --- a/packages/core/src/test/docdb/commands/renameInstance.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { getTestWindow } from '../../shared/vscode/window' -import { DBCluster } from '@aws-sdk/client-docdb' -import { DocumentDBClient, DBInstance } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { renameInstance } from '../../../docdb/commands/renameInstance' -import { assertTelemetry } from '../../testUtil' - -describe('renameInstanceCommand', function () { - const clusterName = 'docdb-1234' - const instanceName = 'test-instance' - const newInstanceName = 'new-instance-name' - let docdb: DocumentDBClient - let cluster: DBCluster - let instance: DBInstance - let node: DBInstanceNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - instance = { - DBInstanceIdentifier: instanceName, - DBClusterIdentifier: clusterName, - DBInstanceStatus: 'available', - } - const parentNode = new DBClusterNode(new DocumentDBNode(docdb), cluster, docdb) - node = new DBInstanceNode(parentNode, instance) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - function setupWizard() { - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue(newInstanceName) - }) - } - - it('prompts for new instance name, modifies instance, shows success, and refreshes node', async function () { - // arrange - const stub = sinon.stub().resolves({ - DBInstanceIdentifier: instanceName, - }) - docdb.modifyInstance = stub - setupWizard() - - // act - await renameInstance(node) - - // assert - getTestWindow() - .getFirstMessage() - .assertInfo(/Updated instance: test-instance/) - - const expectedArgs = { - DBInstanceIdentifier: instanceName, - NewDBInstanceIdentifier: newInstanceName, - ApplyImmediately: true, - } - - assert(stub.calledOnceWithExactly(expectedArgs)) - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', node) - - assertTelemetry('docdb_renameInstance', { - result: 'Succeeded', - }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub() - docdb.modifyInstance = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(renameInstance(node)) - - // assert - assert(stub.notCalled) - - assertTelemetry('docdb_renameInstance', { - result: 'Cancelled', - }) - }) - - it('shows a warning when the instance is not available', async function () { - // arrange - instance.DBInstanceStatus = 'stopped' - const stub = sinon.stub() - docdb.modifyInstance = stub - setupWizard() - - // act - await assert.rejects(renameInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertMessage(/Instance must be running/) - - assertTelemetry('docdb_renameInstance', { - result: 'Cancelled', - }) - }) - - it('shows an error when instance creation fails', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.modifyInstance = stub - setupWizard() - - // act - await assert.rejects(renameInstance(node)) - - // assert - getTestWindow() - .getFirstMessage() - .assertError(/Failed to rename instance: test-instance/) - - assertTelemetry('docdb_renameInstance', { - result: 'Failed', - }) - }) -}) diff --git a/packages/core/src/test/docdb/commands/tagCommands.test.ts b/packages/core/src/test/docdb/commands/tagCommands.test.ts deleted file mode 100644 index de3c12bb77..0000000000 --- a/packages/core/src/test/docdb/commands/tagCommands.test.ts +++ /dev/null @@ -1,166 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { assertTelemetry } from '../../testUtil' -import { getTestWindow } from '../../shared/vscode/window' -import { DocumentDBClient, DBInstance } from '../../../shared/clients/docdbClient' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { addTag, listTags, removeTag } from '../../../docdb/commands/tagCommands' - -describe('Tags', function () { - const instanceName = 'test-instance' - let docdb: DocumentDBClient - let instance: DBInstance - let node: DBInstanceNode - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - - docdb = { regionCode: 'us-east-1' } as DocumentDBClient - const clusterName = 'docdb-1234' - const cluster = { DBClusterIdentifier: clusterName, Status: 'available' } - const parentNode = new DBClusterNode(new DocumentDBNode(docdb), cluster, docdb) - instance = { - DBInstanceIdentifier: instanceName, - DBInstanceArn: 'arn:' + instanceName, - DBClusterIdentifier: clusterName, - } - node = new DBInstanceNode(parentNode, instance) - }) - - afterEach(function () { - sandbox.restore() - getTestWindow().dispose() - }) - - describe('listTagsCommand', function () { - it('displays a list of resource tags', async function () { - // arrange - const stub = sinon.stub().resolves({}) - docdb.listResourceTags = stub - getTestWindow().onDidShowMessage((message) => { - assert.equal(message.message, 'Tags for test-instance:') - message.close() - }) - - // act - await listTags(node) - - // assert - assert(stub.calledOnceWithExactly(node.arn)) - assertTelemetry('docdb_listTags', { result: 'Succeeded' }) - }) - - it('shows an error when api returns failure', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.listResourceTags = stub - - // act - await assert.rejects(listTags(node)) - - // assert - assertTelemetry('docdb_listTags', { result: 'Failed' }) - }) - }) - - describe('addTagCommand', function () { - it('prompts for a new tag key/value and adds the tag', async function () { - // arrange - const stub = sinon.stub().resolves() - docdb.addResourceTags = stub - getTestWindow().onDidShowInputBox((input) => input.acceptValue('test-value')) - - // act - await addTag(node) - - // assert - assert(stub.calledOnce) - getTestWindow().getFirstMessage().assertInfo('Tag added') - assertTelemetry('docdb_addTag', { result: 'Succeeded' }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const stub = sinon.stub().resolves() - docdb.addResourceTags = stub - getTestWindow().onDidShowInputBox((input) => input.hide()) - - // act - await assert.rejects(addTag(node)) - - // assert - assert(stub.notCalled) - assertTelemetry('docdb_addTag', { result: 'Cancelled' }) - }) - - it('shows an error when api returns failure', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.addResourceTags = stub - getTestWindow().onDidShowInputBox((input) => input.acceptValue('test-value')) - - // act - await assert.rejects(addTag(node)) - - // assert - assertTelemetry('docdb_addTag', { result: 'Failed' }) - }) - }) - - describe('removeTagCommand', function () { - it('prompts with a list of tags and removes the selected tag', async function () { - // arrange - const tag = { ['test-key']: 'test-value ' } - docdb.listResourceTags = sinon.stub().resolves(tag) - const stub = sinon.stub().resolves() - docdb.removeResourceTags = stub - getTestWindow().onDidShowQuickPick((picker) => picker.acceptItem(picker.items[0])) - - // act - await removeTag(node) - - // assert - assert(stub.calledOnce) - getTestWindow().getFirstMessage().assertInfo('Tag removed') - assertTelemetry('docdb_removeTag', { result: 'Succeeded' }) - }) - - it('does nothing when prompt is cancelled', async function () { - // arrange - const tag = { ['test-key']: 'test-value ' } - docdb.listResourceTags = sinon.stub().resolves(tag) - const stub = sinon.stub().resolves() - docdb.removeResourceTags = stub - getTestWindow().onDidShowQuickPick((picker) => picker.hide()) - - // act - await assert.rejects(removeTag(node)) - - // assert - assert(stub.notCalled) - assertTelemetry('docdb_removeTag', { result: 'Cancelled' }) - }) - - it('shows an error when api returns failure', async function () { - // arrange - const stub = sinon.stub().rejects() - docdb.listResourceTags = stub - docdb.removeResourceTags = stub - getTestWindow().onDidShowQuickPick((picker) => picker.acceptItem(picker.items[0])) - - // act - await assert.rejects(removeTag(node)) - - // assert - assertTelemetry('docdb_removeTag', { result: 'Failed' }) - }) - }) -}) diff --git a/packages/core/src/test/docdb/explorer/dbClusterNode.test.ts b/packages/core/src/test/docdb/explorer/dbClusterNode.test.ts deleted file mode 100644 index 0fdd5be3f1..0000000000 --- a/packages/core/src/test/docdb/explorer/dbClusterNode.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { DBCluster } from '@aws-sdk/client-docdb' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DBInstanceNode } from '../../../docdb/explorer/dbInstanceNode' -import { DBInstance, DocumentDBClient } from '../../../shared/clients/docdbClient' - -describe('DBClusterNode', function () { - let mockClient: DocumentDBClient - let parentNode: AWSTreeNodeBase - - beforeEach(() => { - parentNode = { - refresh: sinon.stub(), - } as unknown as AWSTreeNodeBase - - mockClient = { - listClusters: sinon.stub().resolves([{ DBClusterIdentifier: 'Cluster-1', Status: 'available' }]), - listInstances: sinon.stub().resolves([]), - } as Partial as DocumentDBClient - - DBClusterNode['globalPollingArns'].clear() - }) - - afterEach(() => { - DBClusterNode['globalPollingArns'].clear() - }) - - const cluster: DBCluster = { DBClusterIdentifier: 'Cluster-1' } - const instanceA: DBInstance = { DBInstanceIdentifier: 'Instance-A' } - const instanceB: DBInstance = { DBInstanceIdentifier: 'Instance-B' } - - function assertInstanceNode(node: AWSTreeNodeBase, expectedInstance: DBInstance): void { - assert.ok(node instanceof DBInstanceNode, `Node ${node} should be an Instance Node`) - assert.deepStrictEqual((node as DBInstanceNode).instance, expectedInstance) - } - - it('gets children', async function () { - ;(mockClient.listInstances as sinon.SinonStub).resolves([instanceA, instanceB]) - const node = new DBClusterNode(parentNode, cluster, mockClient) - const [firstInstanceNode, secondInstanceNode, ...otherNodes] = await node.getChildren() - - assertInstanceNode(firstInstanceNode, instanceA) - assertInstanceNode(secondInstanceNode, instanceB) - assert.strictEqual(otherNodes.length, 0) - }) - - it('returns false for available status', function () { - const clusterStatus = { ...cluster, Status: 'available' } - const node = new DBClusterNode(parentNode, clusterStatus, mockClient) - const requiresPolling = node.isStatusRequiringPolling() - - assert.strictEqual(requiresPolling, false, 'isStatusRequiringPolling should return false for available status') - }) - - it('returns true for creating status', function () { - const clusterStatus = { ...cluster, Status: 'creating' } - const node = new DBClusterNode(parentNode, clusterStatus, mockClient) - const requiresPolling = node.isStatusRequiringPolling() - - assert.strictEqual(requiresPolling, true, 'isStatusRequiringPolling should return true for creating status') - }) - - it('starts tracking changes when status requires polling', function () { - const clusterStatus = { ...cluster, Status: 'creating' } - const trackChangesSpy = sinon.spy(DBClusterNode.prototype, 'trackChanges') - const node = new DBClusterNode(parentNode, clusterStatus, mockClient) - const requiresPolling = node.isStatusRequiringPolling() - - assert.strictEqual(requiresPolling, true, 'isStatusRequiringPolling should return true for creating status') - assert.ok(trackChangesSpy.calledOnce, 'trackChanges should be called when polling is required') - assert.strictEqual(node.isPolling, true, 'Node should be in polling state') - - trackChangesSpy.restore() - }) - - function assertNodeDoesNotPoll(clusterStatus: any, message: string) { - const trackChangesSpy = sinon.spy(DBClusterNode.prototype, 'trackChanges') - const node = new DBClusterNode(parentNode, clusterStatus, mockClient) - const requiresPolling = node.isStatusRequiringPolling() - - assert.strictEqual( - requiresPolling, - false, - `${message}: isStatusRequiringPolling should return false for available status` - ) - assert.ok( - trackChangesSpy.notCalled, - `${message}: trackChanges should not be called when polling is not required` - ) - assert.strictEqual(node.isPolling, false, `${message}: Node should not be in polling state`) - - trackChangesSpy.restore() - } - - it('does not start tracking changes when status does not require polling', function () { - const clusterStatus = { ...cluster, Status: 'available' } - assertNodeDoesNotPoll(clusterStatus, 'does not start tracking changes') - }) - - it('does not poll when status is available', function () { - const clusterStatus = { ...cluster, Status: 'available' } - assertNodeDoesNotPoll(clusterStatus, 'does not poll') - }) - - it('has isPolling set to false and getStatus returns available when status is available', async function () { - const clusterStatus = { DBClusterIdentifier: 'Cluster-1', Status: 'available' } - ;(mockClient.listClusters as sinon.SinonStub).resolves([clusterStatus]) - - const node = new DBClusterNode(parentNode, clusterStatus, mockClient) - - assert.strictEqual(node.isPolling, false, 'Node should not be in polling state') - assert.strictEqual(node.status, 'available', 'getStatus should return available for the node') - }) - - it('handles missing clusters gracefully in getStatus', async function () { - ;(mockClient.listClusters as sinon.SinonStub).resolves([]) - const node = new DBClusterNode(parentNode, cluster, mockClient) - - const status = await node.getStatus() - assert.strictEqual(status, undefined, 'getStatus should return undefined when no cluster is found') - }) -}) diff --git a/packages/core/src/test/docdb/explorer/docdbNode.test.ts b/packages/core/src/test/docdb/explorer/docdbNode.test.ts deleted file mode 100644 index 0ff929f51f..0000000000 --- a/packages/core/src/test/docdb/explorer/docdbNode.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon from 'sinon' -import assert from 'assert' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { DocumentDBClient } from '../../../shared/clients/docdbClient' -import { DBCluster } from '@aws-sdk/client-docdb' -import { DBClusterNode } from '../../../docdb/explorer/dbClusterNode' -import { DocumentDBNode } from '../../../docdb/explorer/docdbNode' -import { DBElasticClusterNode } from '../../../docdb/explorer/dbElasticClusterNode' - -describe('DocumentDBNode', function () { - const firstCluster: DBCluster = { DBClusterIdentifier: 'Cluster-1' } - const secondCluster: DBCluster = { DBClusterIdentifier: 'Cluster-2' } - const thirdCluster = { clusterName: 'Cluster-3', clusterArn: '', status: 'ACTIVE' } - - let client: DocumentDBClient - - function assertClusterNode(node: AWSTreeNodeBase, expectedCluster: DBCluster): void { - assert.ok(node instanceof DBClusterNode, `Node ${node} should be a Cluster Node`) - assert.deepStrictEqual((node as DBClusterNode).cluster, expectedCluster) - } - - beforeEach(function () { - client = {} as any as DocumentDBClient - }) - - it('gets children', async function () { - client.listClusters = sinon.stub().resolves([firstCluster, secondCluster]) - client.listElasticClusters = sinon.stub().resolves([thirdCluster]) - client.listGlobalClusters = sinon.stub().resolves([]) - - const node = new DocumentDBNode(client) - const [firstClusterNode, secondClusterNode, thirdClusterNode, ...otherNodes] = await node.getChildren() - - assertClusterNode(firstClusterNode, firstCluster) - assertClusterNode(secondClusterNode, secondCluster) - assert.ok( - thirdClusterNode instanceof DBElasticClusterNode, - `Node ${thirdClusterNode} should be an Elastic Cluster Node` - ) - assert.deepStrictEqual((thirdClusterNode as DBElasticClusterNode).cluster, thirdCluster) - assert.strictEqual(otherNodes.length, 0) - }) -}) diff --git a/packages/core/src/test/docdb/utils.test.ts b/packages/core/src/test/docdb/utils.test.ts deleted file mode 100644 index 3a81d3a401..0000000000 --- a/packages/core/src/test/docdb/utils.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { validateClusterName, validateInstanceName, validatePassword, validateUsername } from '../../docdb/utils' - -describe('validateClusterName', function () { - it('validates', function () { - assert.strictEqual(validateClusterName(''), 'Cluster name must be between 1 and 63 characters long') - assert.strictEqual(validateClusterName('c'.repeat(64)), 'Cluster name must be between 1 and 63 characters long') - assert.strictEqual(validateClusterName('404'), 'Cluster name must start with a lowercase letter') - assert.strictEqual( - validateClusterName('abcDEF'), - 'Cluster name must only contain lowercase letters, numbers, and hyphens' - ) - assert.strictEqual( - validateClusterName('abc-'), - 'Cluster name cannot end with a hyphen or contain 2 consecutive hyphens' - ) - assert.strictEqual( - validateClusterName('a--b'), - 'Cluster name cannot end with a hyphen or contain 2 consecutive hyphens' - ) - assert.strictEqual(validateClusterName('a-2'), undefined) - }) -}) - -describe('validateUsername', function () { - it('validates', function () { - assert.strictEqual(validateUsername(''), 'Username name must be between 1 and 63 characters long') - assert.strictEqual(validateUsername('x'.repeat(64)), 'Username name must be between 1 and 63 characters long') - assert.strictEqual(validateUsername('123'), 'Username must start with a letter') - assert.strictEqual(validateUsername('a2Z'), undefined) - }) -}) - -describe('validatePassword', function () { - it('validates', function () { - assert.strictEqual(validatePassword('passw0rd |~'), undefined) - assert.strictEqual(validatePassword('1234567'), 'Password must be between 8 and 100 characters long') - assert.strictEqual(validatePassword('x'.repeat(101)), 'Password must be between 8 and 100 characters long') - - for (const item of ['pass/word', 'p@ssword', '"password"']) { - assert.strictEqual( - validatePassword(item), - 'Password must only contain printable ASCII characters (except for slash, double quotes and @ symbol)' - ) - } - - for (const item of ['password\x19', 'password\u{fe0f}']) { - assert.strictEqual( - validatePassword(item), - 'Password must only contain printable ASCII characters (except for slash, double quotes and @ symbol)' - ) - } - }) -}) - -describe('validateInstanceName', function () { - it('validates', function () { - assert.strictEqual(validateInstanceName(''), 'Instance name must be between 1 and 63 characters long') - assert.strictEqual( - validateInstanceName('c'.repeat(64)), - 'Instance name must be between 1 and 63 characters long' - ) - assert.strictEqual(validateInstanceName('404'), 'Instance name must start with a lowercase letter') - assert.strictEqual( - validateInstanceName('abcDEF'), - 'Instance name must only contain lowercase letters, numbers, and hyphens' - ) - assert.strictEqual( - validateInstanceName('abc-'), - 'Instance name cannot end with a hyphen or contain 2 consecutive hyphens' - ) - assert.strictEqual( - validateInstanceName('a--b'), - 'Instance name cannot end with a hyphen or contain 2 consecutive hyphens' - ) - assert.strictEqual(validateInstanceName('a-2'), undefined) - }) -}) diff --git a/packages/core/src/test/docdb/wizards/createClusterWizard.test.ts b/packages/core/src/test/docdb/wizards/createClusterWizard.test.ts deleted file mode 100644 index 9824ed1c52..0000000000 --- a/packages/core/src/test/docdb/wizards/createClusterWizard.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { CreateClusterState, CreateClusterWizard } from '../../../docdb/wizards/createClusterWizard' - -describe('CreateClusterWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const wizard = new CreateClusterWizard({} as any) - tester = await createWizardTester(wizard) - }) - - it('prompts for cluster type first', function () { - tester.ClusterType.assertShowFirst() - }) - - it('prompts for regional cluster when selected', function () { - tester.ClusterType.applyInput('regional') - tester.RegionalCluster.assertShowAny() - tester.ElasticCluster.assertDoesNotShowAny() - }) - - it('prompts for elastic cluster when selected', function () { - tester.ClusterType.applyInput('elastic') - tester.RegionalCluster.assertDoesNotShowAny() - tester.ElasticCluster.assertShowAny() - }) -}) diff --git a/packages/core/src/test/docdb/wizards/createGlobalClusterWizard.test.ts b/packages/core/src/test/docdb/wizards/createGlobalClusterWizard.test.ts deleted file mode 100644 index 5ca5358fcb..0000000000 --- a/packages/core/src/test/docdb/wizards/createGlobalClusterWizard.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { CreateGlobalClusterState, CreateGlobalClusterWizard } from '../../../docdb/wizards/createGlobalClusterWizard' - -describe('CreateGlobalClusterWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const engineVersion = '5.0.0' - const wizard = new CreateGlobalClusterWizard('region', engineVersion, {} as any) - tester = await createWizardTester(wizard) - }) - - it('prompts for secondary region and global cluster name', function () { - tester.RegionCode.assertShowFirst() - tester.GlobalClusterName.assertShowSecond() - tester.Cluster.assertShowAny() - }) -}) diff --git a/packages/core/src/test/docdb/wizards/elasticClusterWizard.test.ts b/packages/core/src/test/docdb/wizards/elasticClusterWizard.test.ts deleted file mode 100644 index 9bb54bf833..0000000000 --- a/packages/core/src/test/docdb/wizards/elasticClusterWizard.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { ElasticClusterConfiguration, ElasticClusterWizard } from '../../../docdb/wizards/elasticClusterWizard' - -describe('ElasticClusterWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const wizard = new ElasticClusterWizard({} as any, 'title') - tester = await createWizardTester(wizard) - }) - - it('prompts for properties in the correct order', function () { - tester.clusterName.assertShow(1) - tester.adminUserName.assertShow(2) - tester.adminUserPassword.assertShow(3) - tester.shardCount.assertShow(4) - tester.shardInstanceCount.assertShow(5) - tester.shardCapacity.assertShow(6) - tester.authType.assertDoesNotShow() - }) -}) diff --git a/packages/core/src/test/docdb/wizards/regionalClusterWizard.test.ts b/packages/core/src/test/docdb/wizards/regionalClusterWizard.test.ts deleted file mode 100644 index 576eeeaa02..0000000000 --- a/packages/core/src/test/docdb/wizards/regionalClusterWizard.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { RegionalClusterConfiguration, RegionalClusterWizard } from '../../../docdb/wizards/regionalClusterWizard' - -describe('RegionalClusterWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const wizard = new RegionalClusterWizard({} as any, 'title') - tester = await createWizardTester(wizard) - }) - - it('prompts for properties in the correct order', function () { - tester.DBClusterIdentifier.assertShow(1) - tester.Engine.assertDoesNotShow() - tester.EngineVersion.assertShow(2) - tester.MasterUsername.assertShow(3) - tester.MasterUserPassword.assertShow(4) - tester.StorageEncrypted.assertShow(5) - tester.DBInstanceCount.assertShow(6) - tester.DBInstanceClass.assertShow(7) - }) -}) diff --git a/packages/core/src/test/dynamicResources/awsResourceManager.test.ts b/packages/core/src/test/dynamicResources/awsResourceManager.test.ts deleted file mode 100644 index b2e52d7dd4..0000000000 --- a/packages/core/src/test/dynamicResources/awsResourceManager.test.ts +++ /dev/null @@ -1,246 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as testutil from '../testUtil' -import * as sinon from 'sinon' -import * as path from 'path' -import * as vscode from 'vscode' -import { ResourcesNode } from '../../dynamicResources/explorer/nodes/resourcesNode' -import { ResourceNode } from '../../dynamicResources/explorer/nodes/resourceNode' -import { ResourceTypeNode } from '../../dynamicResources/explorer/nodes/resourceTypeNode' -import { formatResourceModel, AwsResourceManager } from '../../dynamicResources/awsResourceManager' -import { CloudControlClient } from '../../shared/clients/cloudControl' -import { CloudFormationClient } from '../../shared/clients/cloudFormation' -import { makeTemporaryToolkitFolder, readFileAsString } from '../../shared/filesystemUtilities' -import { FakeExtensionContext } from '../fakeExtensionContext' -import { existsSync } from 'fs' // eslint-disable-line no-restricted-imports -import { ResourceTypeMetadata } from '../../dynamicResources/model/resources' -import globals from '../../shared/extensionGlobals' -import { Stub, stub } from '../utilities/stubber' -import * as CloudControl from '@aws-sdk/client-cloudcontrol' -import { fs } from '../../shared' -import { DescribeTypeOutput } from '@aws-sdk/client-cloudformation' - -describe('ResourceManager', function () { - let sandbox: sinon.SinonSandbox - let cloudFormation: Stub - let cloudControl: Stub - let resourceNode: ResourceNode - let resourceTypeNode: ResourceTypeNode - let resourceManager: AwsResourceManager - let tempFolder: string - let registerMappingSpy: sinon.SinonSpy - - const fakeTypeName = 'sometype' - const fakeIdentifier = 'someidentifier' - const fakeRegion = 'someregion' - - const fakeResourceDescription = { - Role: 'arn:aws:iam::1234:role/service-role/fooResource', - Name: 'fooResource', - ANestedType: { - Type: 'string', - Value: 'FooBar', - }, - ArrayType: ['foo', 'bar', 'baz'], - } - - beforeEach(async function () { - cloudControl = stub(CloudControlClient, { - regionCode: '', - }) - cloudFormation = stub(CloudFormationClient, { - regionCode: '', - }) - sandbox = sinon.createSandbox() - mockClients() - tempFolder = await makeTemporaryToolkitFolder() - - const rootNode = new ResourcesNode(fakeRegion, cloudFormation, cloudControl) - resourceTypeNode = new ResourceTypeNode(rootNode, fakeTypeName, cloudControl, {} as ResourceTypeMetadata) - resourceNode = new ResourceNode(resourceTypeNode, fakeIdentifier) - const fakeContext = await FakeExtensionContext.create() - fakeContext.globalStorageUri = vscode.Uri.file(tempFolder) - resourceManager = new AwsResourceManager(fakeContext) - registerMappingSpy = sinon.spy(globals.schemaService, 'registerMapping') - }) - - afterEach(async function () { - registerMappingSpy.restore() - sandbox.restore() - await resourceManager.dispose() - await fs.delete(tempFolder, { recursive: true }) - }) - - it('opens resources in preview mode', async function () { - const mockTextDocument = {} as vscode.TextDocument - const mockTextEditor = {} as vscode.TextEditor - const openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockTextDocument) - const showTextDocumentStub = sandbox.stub(vscode.window, 'showTextDocument').resolves(mockTextEditor) - - const editor = await resourceManager.open(resourceNode, true) - - const capturedUri = openTextDocumentStub.getCall(0).args[0] as vscode.Uri - - assert.strictEqual(capturedUri.scheme, 'awsResource') - assert.strictEqual(capturedUri.fsPath, `${fakeIdentifier}.${fakeTypeName}.preview.json`) - assert.strictEqual(capturedUri.query, formatResourceModel(JSON.stringify(fakeResourceDescription))) - - assert.strictEqual(showTextDocumentStub.getCall(0).args[0], mockTextDocument) - assert.deepStrictEqual(showTextDocumentStub.getCall(0).args[1], { preview: true }) - - assert.strictEqual(editor, mockTextEditor) - }) - - it('opens resources in edit mode', async function () { - const mockTextDocument = {} as vscode.TextDocument - const mockTextEditor = {} as vscode.TextEditor - const openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockTextDocument) - const showTextDocumentStub = sandbox.stub(vscode.window, 'showTextDocument').resolves(mockTextEditor) - - const editor = await resourceManager.open(resourceNode, false) - - const capturedUri = openTextDocumentStub.getCall(0).args[0] as vscode.Uri - - assert.strictEqual(capturedUri.scheme, 'file') - assert.strictEqual(capturedUri.fsPath.endsWith(`${fakeIdentifier}.${fakeTypeName}.awsResource.json`), true) - - const fileContents = JSON.parse(await readFileAsString(capturedUri.fsPath)) - assert.deepStrictEqual(fileContents, fakeResourceDescription) - - assert.strictEqual(showTextDocumentStub.getCall(0).args[0], mockTextDocument) - assert.deepStrictEqual(showTextDocumentStub.getCall(0).args[1], { preview: false }) - - assert.strictEqual(editor, mockTextEditor) - }) - - it('closes existing document when reopening resource', async function () { - const mockTextDocument = {} as vscode.TextDocument - const mockTextEditor = { - document: mockTextDocument, - } as vscode.TextEditor - const openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockTextDocument) - sandbox.stub(vscode.window, 'showTextDocument').resolves(mockTextEditor) - - await resourceManager.open(resourceNode, true) - const capturedUri = openTextDocumentStub.getCall(0).args[0] as vscode.Uri - assert.strictEqual(resourceManager.fromUri(capturedUri), resourceNode) - - await resourceManager.open(resourceNode, false) - assert.strictEqual(resourceManager.fromUri(capturedUri), undefined) - }) - - it('creates new resource documents', async function () { - const mockTextLine = { - range: new vscode.Range(0, 0, 0, 0), - } - const mockTextDocument: vscode.TextDocument = { - lineAt: () => mockTextLine, - } as any as vscode.TextDocument - const mockTextEditor = { - document: mockTextDocument, - } as vscode.TextEditor - const openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockTextDocument) - const showTextDocumentStub = sandbox.stub(vscode.window, 'showTextDocument').resolves(mockTextEditor) - - const editor = await resourceManager.new(resourceTypeNode) - - const capturedUri = openTextDocumentStub.getCall(0).args[0] as vscode.Uri - - assert.strictEqual(capturedUri.scheme, 'file') - assert.strictEqual(capturedUri.fsPath.endsWith(`new.${fakeTypeName}.awsResource.json`), true) - - const fileContents = JSON.parse(await readFileAsString(capturedUri.fsPath)) - assert.deepStrictEqual(fileContents, {}) - - assert.strictEqual(showTextDocumentStub.getCall(0).args[0], mockTextDocument) - assert.deepStrictEqual(showTextDocumentStub.getCall(0).args[1], { preview: false }) - - assert.strictEqual(editor, mockTextEditor) - }) - - it('returns existing resource from uri', async function () { - const editor = await resourceManager.open(resourceNode, false) - assert.strictEqual(resourceManager.fromUri(editor.document.uri), resourceNode) - }) - - it('returns undefined for non-existent resource from uri', function () { - assert.strictEqual(resourceManager.fromUri(vscode.Uri.parse('foo')), undefined) - }) - - it('returns existing resource uri from resource node', async function () { - const editor = await resourceManager.open(resourceNode, false) - assert.strictEqual( - resourceManager.toUri(resourceNode)?.fsPath.toLowerCase(), - editor.document.uri.fsPath.toLowerCase() - ) - }) - - it('returns undefined for non-existent resource from resource node', function () { - assert.strictEqual(resourceManager.toUri(resourceNode), undefined) - }) - - it('registers schema mappings when opening in edit', async function () { - const editor = await resourceManager.open(resourceNode, false) - assert(registerMappingSpy.calledOnce) - - const [mapping] = registerMappingSpy.args[registerMappingSpy.args.length - 1] - - const expectedSchemaLocation = path.join(tempFolder, 'sometype.schema.json') - assert.ok(existsSync(expectedSchemaLocation)) - assert.strictEqual(mapping.type, 'json') - testutil.assertEqualPaths(mapping.uri.fsPath, editor.document.uri.fsPath) - const schema = mapping.schema as vscode.Uri - assert.strictEqual(schema.fsPath.toLowerCase(), expectedSchemaLocation.toLowerCase()) - }) - - it('does not register schemas when opening in preview', async function () { - const mockTextDocument = {} as vscode.TextDocument - const mockTextEditor = {} as vscode.TextEditor - sandbox.stub(vscode.workspace, 'openTextDocument').resolves(mockTextDocument) - sandbox.stub(vscode.window, 'showTextDocument').resolves(mockTextEditor) - - await resourceManager.open(resourceNode, true) - assert(registerMappingSpy.notCalled) - assert(cloudFormation.describeType.notCalled) - }) - - it('deletes resource mapping on file close', async function () { - const editor = await resourceManager.open(resourceNode, false) - await resourceManager.close(editor.document.uri) - assert(registerMappingSpy.firstCall.calledWith(sinon.match.any, true)) - assert(registerMappingSpy.secondCall.calledWith(sinon.match.any)) - - const [mapping] = registerMappingSpy.args[registerMappingSpy.args.length - 1] - - assert.strictEqual(mapping.type, 'json') - testutil.assertEqualPaths(mapping.uri.fsPath, editor.document.uri.fsPath) - assert.strictEqual(mapping.schema, undefined) - }) - - function mockClients(): void { - cloudControl.getResource.callsFake(async (request: CloudControl.GetResourceInput) => { - assert.deepStrictEqual(request, { - TypeName: fakeTypeName, - Identifier: fakeIdentifier, - }) - - return { - TypeName: 'foo', - ResourceDescription: { - Identifier: 'bar', - Properties: JSON.stringify(fakeResourceDescription), - }, - } - }) - cloudFormation.describeType.callsFake(async (name: string) => { - if (name === fakeTypeName) { - return { Schema: '{}' } as any as DescribeTypeOutput - } - throw new Error() - }) - } -}) diff --git a/packages/core/src/test/dynamicResources/commands/configure.test.ts b/packages/core/src/test/dynamicResources/commands/configure.test.ts deleted file mode 100644 index aa5c8f2fd5..0000000000 --- a/packages/core/src/test/dynamicResources/commands/configure.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { configureResources, ResourcesSettings } from '../../../dynamicResources/commands/configure' -import { memoizedGetResourceTypes } from '../../../dynamicResources/model/resources' -import { Settings } from '../../../shared/settings' -import { getTestWindow } from '../../shared/vscode/window' - -describe('configureCommand', function () { - let settings: ResourcesSettings - - beforeEach(async function () { - const workspaceSettings = new Settings(vscode.ConfigurationTarget.Workspace) - settings = new ResourcesSettings(workspaceSettings) - await settings.reset() - }) - - it('maps selected services to configuration', async function () { - const testItems = Array.from(memoizedGetResourceTypes().keys()) - getTestWindow().onDidShowQuickPick((picker) => picker.acceptItems(...testItems)) - await configureResources(settings) - - assert.deepStrictEqual(settings.get('enabledResources'), testItems) - }) -}) diff --git a/packages/core/src/test/dynamicResources/commands/copyIdentifier.test.ts b/packages/core/src/test/dynamicResources/commands/copyIdentifier.test.ts deleted file mode 100644 index 6105adbc5c..0000000000 --- a/packages/core/src/test/dynamicResources/commands/copyIdentifier.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { copyIdentifier } from '../../../dynamicResources/commands/copyIdentifier' -import { assertNoErrorMessages } from '../../shared/vscode/window' -import { FakeClipboard } from '../../shared/vscode/fakeEnv' - -describe('copyIdentifierCommand', function () { - beforeEach(function () { - const fakeClipboard = new FakeClipboard() - sinon.stub(vscode.env, 'clipboard').value(fakeClipboard) - }) - - it('copies identifier to clipboard and shows status bar confirmation', async function () { - const fakeIdentifier = 'resource1' - - await copyIdentifier('fakeResourceType', fakeIdentifier) - - assert.strictEqual(await vscode.env.clipboard.readText(), fakeIdentifier) - assertNoErrorMessages() - }) -}) diff --git a/packages/core/src/test/dynamicResources/commands/deleteResource.test.ts b/packages/core/src/test/dynamicResources/commands/deleteResource.test.ts deleted file mode 100644 index 099bf4acda..0000000000 --- a/packages/core/src/test/dynamicResources/commands/deleteResource.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import assert from 'assert' -import { deleteResource } from '../../../dynamicResources/commands/deleteResource' -import { CloudControlClient } from '../../../shared/clients/cloudControl' -import { assertNoErrorMessages, getTestWindow } from '../../shared/vscode/window' - -describe('deleteResource', function () { - const fakeType = 'fakeType' - const fakeIdentifier = 'fakeIdentifier' - const cloudControl = new CloudControlClient('') - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes resources, shows progress and confirmation', async function () { - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - const stub = sandbox - .stub(cloudControl, 'deleteResource') - .callsFake(async ({ TypeName: typeName, Identifier: identifier }) => { - assert.strictEqual(typeName, fakeType) - assert.strictEqual(identifier, fakeIdentifier) - }) - - await deleteResource(cloudControl, fakeType, fakeIdentifier) - - getTestWindow().getFirstMessage().assertWarn(`Delete resource ${fakeIdentifier} (${fakeType})?`) - - assert.strictEqual(stub.calledOnce, true) - - const progressNotification = getTestWindow().getSecondMessage() - assert.strictEqual(progressNotification.items.length, 0) - assert.deepStrictEqual(progressNotification.progressReports, [ - { message: `Deleting resource ${fakeIdentifier} (${fakeType})...` }, - ]) - - getTestWindow().getThirdMessage().assertInfo(`Deleted resource ${fakeIdentifier} (${fakeType})`) - }) - - it('does nothing when deletion is cancelled', async function () { - getTestWindow().onDidShowMessage((m) => m.selectItem('Cancel')) - const spy = sandbox.spy(cloudControl, 'deleteResource') - - await deleteResource(cloudControl, fakeType, fakeIdentifier) - - assertNoErrorMessages() - assert.strictEqual(spy.notCalled, true) - assert.deepStrictEqual(getTestWindow().statusBar.messages, []) - }) - - it('shows an error message when resource deletion fails', async function () { - sandbox.stub(cloudControl, 'deleteResource').callsFake(async () => { - throw new Error('fake exception') - }) - - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - - await deleteResource(cloudControl, fakeType, fakeIdentifier) - getTestWindow().getThirdMessage().assertError(`Failed to delete resource ${fakeIdentifier} (${fakeType})`) - }) - - it('shows a warning if unsupported action', async function () { - sandbox.stub(cloudControl, 'deleteResource').callsFake(async () => { - const error = new Error('fake exception') - error.name = 'UnsupportedActionException' - throw error - }) - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - - await deleteResource(cloudControl, fakeType, fakeIdentifier) - getTestWindow() - .getThirdMessage() - .assertWarn(new RegExp(`Resource type ${fakeType} does not currently support delete`)) - }) -}) diff --git a/packages/core/src/test/dynamicResources/commands/openResource.test.ts b/packages/core/src/test/dynamicResources/commands/openResource.test.ts deleted file mode 100644 index e3ff3663d0..0000000000 --- a/packages/core/src/test/dynamicResources/commands/openResource.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import { AwsResourceManager, TypeSchema } from '../../../dynamicResources/awsResourceManager' -import { ResourceNode } from '../../../dynamicResources/explorer/nodes/resourceNode' -import { ResourceTypeNode } from '../../../dynamicResources/explorer/nodes/resourceTypeNode' -import { getDiagnostics, openResource } from '../../../dynamicResources/commands/openResource' -import { getTestWindow } from '../../shared/vscode/window' -import { Stub, stub } from '../../utilities/stubber' -import sinon from 'sinon' - -describe('openResource', function () { - const fakeType = 'fakeType' - const fakeIdentifier = 'fakeIdentifier' - let mockResourceManager: Stub - let mockDiagnosticCollection: vscode.DiagnosticCollection - const fakeResourceNode = new ResourceNode({ typeName: fakeType } as ResourceTypeNode, fakeIdentifier) - - beforeEach(function () { - mockResourceManager = stub(AwsResourceManager) - mockDiagnosticCollection = {} as any as vscode.DiagnosticCollection - }) - - it('shows progress', async function () { - mockResourceManager.open = sinon.stub() - await openResource({ - source: fakeResourceNode, - preview: false, - resourceManager: mockResourceManager, - diagnostics: mockDiagnosticCollection, - }) - const progress = getTestWindow().getFirstMessage() - assert.ok(!progress.cancellable) - assert(mockResourceManager.open.calledOnceWithExactly(fakeResourceNode, false)) - assert.deepStrictEqual(progress.progressReports, [ - { message: `Opening resource ${fakeIdentifier} (${fakeType})...` }, - ]) - }) - - it('shows an error message when opening resource fails', async function () { - mockResourceManager.open = sinon.stub() - mockResourceManager.open.rejects() - await openResource({ - source: fakeResourceNode, - preview: false, - resourceManager: mockResourceManager, - diagnostics: mockDiagnosticCollection, - }) - getTestWindow().getSecondMessage().assertError(`Failed to open resource ${fakeIdentifier} (${fakeType})`) - }) - - it('handles opening ResourceNodes', async function () { - mockResourceManager.open = sinon.stub() - await openResource({ - source: fakeResourceNode, - preview: false, - resourceManager: mockResourceManager, - diagnostics: mockDiagnosticCollection, - }) - assert(mockResourceManager.open.calledOnceWithExactly(fakeResourceNode, false)) - }) - - it('handles opening uris', async function () { - const fakeUri = vscode.Uri.parse('foo') - mockResourceManager.open = sinon.stub() - mockResourceManager.fromUri = sinon.stub() - mockResourceManager.fromUri.returns(fakeResourceNode) - await openResource({ - source: fakeUri, - preview: false, - resourceManager: mockResourceManager, - diagnostics: mockDiagnosticCollection, - }) - assert(mockResourceManager.fromUri.calledOnceWithExactly(fakeUri)) - assert(mockResourceManager.open.calledOnceWithExactly(fakeResourceNode, false)) - }) - - it('can open in preview mode', async function () { - mockResourceManager.open = sinon.stub() - await openResource({ - source: fakeResourceNode, - preview: true, - resourceManager: mockResourceManager, - diagnostics: mockDiagnosticCollection, - }) - assert(mockResourceManager.open.calledOnceWithExactly(fakeResourceNode, true)) - }) - - it('generates diagnostics for read-only properties', async function () { - const schema = getFakeTypeSchema({ readOnlyProperties: ['fooProperty'] }) - const fakeDefinition = JSON.stringify({ - fooProperty: 'foo', - barProperty: 'bar', - }) - const document = await vscode.workspace.openTextDocument({ - content: fakeDefinition, - }) - - const diagnostics = getDiagnostics(schema, document) - assert.strictEqual(diagnostics.length, 1) - - const diagnostic = diagnostics[0] - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Information) - assert.strictEqual(diagnostic.message, '"fooProperty" is a read-only property and cannot be modified') - }) - - it('generates diagnostics for create-only properties', async function () { - const schema = getFakeTypeSchema({ createOnlyProperties: ['barProperty'] }) - const fakeDefinition = JSON.stringify({ - fooProperty: 'foo', - barProperty: 'bar', - }) - const document = await vscode.workspace.openTextDocument({ - content: fakeDefinition, - }) - - const diagnostics = getDiagnostics(schema, document) - assert.strictEqual(diagnostics.length, 1) - - const diagnostic = diagnostics[0] - assert.strictEqual(diagnostic.severity, vscode.DiagnosticSeverity.Information) - assert.strictEqual( - diagnostic.message, - '"barProperty" is a create-only property and cannot be modified on an existing resource' - ) - }) - - it('returns no diagnostics for no matching properties', async function () { - const schema = getFakeTypeSchema({ createOnlyProperties: ['bazProperty'] }) - const fakeDefinition = JSON.stringify({ - fooProperty: 'foo', - barProperty: 'bar', - }) - const document = await vscode.workspace.openTextDocument({ - content: fakeDefinition, - }) - - const diagnostics = getDiagnostics(schema, document) - assert.strictEqual(diagnostics.length, 0) - }) -}) - -function getFakeTypeSchema(opts: { readOnlyProperties?: string[]; createOnlyProperties?: string[] }): TypeSchema { - return { - typeName: '', - description: '', - properties: undefined, - definitions: undefined, - readOnlyProperties: opts.readOnlyProperties ?? [], - createOnlyProperties: opts.createOnlyProperties ?? [], - writeOnlyProperties: [], - required: [], - primaryIdentifier: [], - } -} diff --git a/packages/core/src/test/dynamicResources/commands/saveResource.test.ts b/packages/core/src/test/dynamicResources/commands/saveResource.test.ts deleted file mode 100644 index c57317ac89..0000000000 --- a/packages/core/src/test/dynamicResources/commands/saveResource.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { createResource, updateResource } from '../../../dynamicResources/commands/saveResource' -import { AddOperation } from 'fast-json-patch' -import { CloudControlClient } from '../../../shared/clients/cloudControl' -import { getTestWindow } from '../../shared/vscode/window' -import sinon from 'sinon' - -describe('createResource', function () { - const fakeType = 'fakeType' - const fakeDefinition = '{}' - - let mockCloudControl: CloudControlClient - - beforeEach(function () { - mockCloudControl = {} as any as CloudControlClient - }) - - it('creates resources, shows progress and confirmation', async function () { - const newIdentifier = 'newIdentifier' - const createStub = sinon.stub().resolves({ - ProgressEvent: { - Identifier: newIdentifier, - }, - }) - mockCloudControl.createResource = createStub - - await createResource(fakeType, fakeDefinition, mockCloudControl) - - assert( - createStub.calledOnceWithExactly({ - TypeName: fakeType, - DesiredState: fakeDefinition, - }) - ) - const progress = getTestWindow().getFirstMessage() - assert.ok(!progress.cancellable) - assert.deepStrictEqual(progress.progressReports, [{ message: `Creating resource (${fakeType})...` }]) - getTestWindow().getSecondMessage().assertInfo(`Created resource ${newIdentifier} (${fakeType})`) - }) - - it('shows an error message when resource creation fails', async function () { - const createStub = sinon.stub().rejects() - mockCloudControl.createResource = createStub - - try { - await createResource(fakeType, fakeDefinition, mockCloudControl) - } catch (err) { - getTestWindow().getSecondMessage().assertError(`Failed to create resource (${fakeType})`) - return - } - assert.fail('Expected exception, but none was thrown.') - }) - - it('shows an error message when definition is not valid json', async function () { - const createStub = sinon.stub() - mockCloudControl.createResource = createStub - try { - await createResource(fakeType, 'foo', mockCloudControl) - } catch (err) { - assert(createStub.notCalled) - getTestWindow().getSecondMessage().assertError(`Failed to create resource (${fakeType})`) - return - } - assert.fail('Expected exception, but none was thrown.') - }) - - it('shows a warning if unsupported action', async function () { - const error = new Error('fake exception') - error.name = 'UnsupportedActionException' - const createStub = sinon.stub().rejects(error) - mockCloudControl.createResource = createStub - - await createResource(fakeType, fakeDefinition, mockCloudControl) - getTestWindow() - .getSecondMessage() - .assertWarn(new RegExp(`^${fakeType} does not currently support resource creation`)) - }) -}) - -describe('updateResource', function () { - const fakeType = 'fakeType' - const fakeIdentifier = 'fakeIdentifier' - const fakeDefinition = '{}' - const fakeOperation = { op: 'add', value: 'Foo' } as AddOperation - const fakeDiff = [fakeOperation] - - let mockCloudControl: CloudControlClient - - beforeEach(function () { - mockCloudControl = {} as any as CloudControlClient - }) - - it('updates resources, shows progress and confirmation', async function () { - const patchJson = JSON.stringify(fakeDiff) - const updateStub = sinon.stub() - mockCloudControl.updateResource = updateStub - const getStub = sinon.stub() - mockCloudControl.getResource = getStub - - await updateResource(fakeType, fakeIdentifier, fakeDefinition, mockCloudControl, fakeDiff) - - assert( - updateStub.calledOnceWithExactly({ - TypeName: fakeType, - Identifier: fakeIdentifier, - PatchDocument: patchJson, - }) - ) - const progress = getTestWindow().getFirstMessage() - assert.ok(!progress.cancellable) - assert.deepStrictEqual(progress.progressReports, [ - { message: `Updating resource ${fakeIdentifier} (${fakeType})...` }, - ]) - getTestWindow().getSecondMessage().assertInfo(`Updated resource ${fakeIdentifier} (${fakeType})`) - }) - - it('shows an error message when resource update fails', async function () { - const updateStub = sinon.stub().rejects() - mockCloudControl.updateResource = updateStub - - try { - await updateResource(fakeType, fakeIdentifier, fakeDefinition, mockCloudControl, fakeDiff) - } catch (err) { - getTestWindow().getSecondMessage().assertError(`Failed to update resource ${fakeIdentifier} (${fakeType})`) - return - } - assert.fail('Expected exception, but none was thrown.') - }) - - it('shows a warning message when there is no diff', async function () { - const updateStub = sinon.stub() - mockCloudControl.updateResource = updateStub - const getStub = sinon.stub() - mockCloudControl.getResource = getStub - await updateResource(fakeType, fakeIdentifier, fakeDefinition, mockCloudControl, []) - - assert(updateStub.notCalled) - getTestWindow() - .getSecondMessage() - .assertWarn(/^Update cancelled/) - }) - - it('shows a warning if unsupported action', async function () { - const error = new Error('fake exception') - error.name = 'UnsupportedActionException' - const updateStub = sinon.stub().rejects(error) - mockCloudControl.updateResource = updateStub - const getStub = sinon.stub() - mockCloudControl.getResource = getStub - await updateResource(fakeType, fakeIdentifier, fakeDefinition, mockCloudControl, fakeDiff) - getTestWindow() - .getSecondMessage() - .assertWarn(new RegExp(`^${fakeType} does not currently support resource updating`)) - }) -}) diff --git a/packages/core/src/test/dynamicResources/explorer/moreResourcesNode.test.ts b/packages/core/src/test/dynamicResources/explorer/moreResourcesNode.test.ts deleted file mode 100644 index 46850b9577..0000000000 --- a/packages/core/src/test/dynamicResources/explorer/moreResourcesNode.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { ResourcesNode } from '../../../dynamicResources/explorer/nodes/resourcesNode' -import { ResourceTypeNode } from '../../../dynamicResources/explorer/nodes/resourceTypeNode' -import { CloudFormationClient } from '../../../shared/clients/cloudFormation' -import { assertNodeListOnlyHasPlaceholderNode } from '../../utilities/explorerNodeAssertions' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { CloudControlClient } from '../../../shared/clients/cloudControl' -import { Settings } from '../../../shared/settings' -import { ResourcesSettings } from '../../../dynamicResources/commands/configure' -import sinon from 'sinon' -import { TypeSummary } from '@aws-sdk/client-cloudformation' - -const unsortedText = ['zebra', 'Antelope', 'aardvark', 'elephant'] -const sortedText = ['aardvark', 'Antelope', 'elephant', 'zebra'] - -describe('ResourcesNode', function () { - let settings: ResourcesSettings - - let testNode: ResourcesNode - let mockCloudFormation: CloudFormationClient - let mockCloudControl: CloudControlClient - let resourceTypes: string[] - - before(async function () { - mockCloudFormation = {} as any as CloudFormationClient - mockCloudControl = {} as any as CloudControlClient - }) - - beforeEach(async function () { - const workspaceSettings = new Settings(vscode.ConfigurationTarget.Workspace) - settings = new ResourcesSettings(workspaceSettings) - await settings.reset() - }) - - beforeEach(async function () { - resourceTypes = ['type1', 'type2'] - prepareMock(resourceTypes) - testNode = new ResourcesNode('FAKE_REGION', mockCloudFormation, mockCloudControl, settings) - - await setConfiguration(resourceTypes) - }) - - it('returns placeholder node if no resource types are enabled', async function () { - const resourceTypes: string[] = [] - - await setConfiguration(resourceTypes) - - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has ResourceTypeNode child nodes', async function () { - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, resourceTypes.length, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof ResourceTypeNode, 'Expected child node to be ResourceTypeNode') - } - }) - - it('has child nodes with ResourceTypeNode contextValue', async function () { - const childNodes = await testNode.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue?.endsWith('ResourceTypeNode'), - true, - 'expected the node to have a resourceTypeNode contextValue' - ) - } - }) - - it('sorts child nodes', async function () { - resourceTypes = unsortedText - - await setConfiguration(resourceTypes) - - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, sortedText, 'Unexpected child sort order') - }) - - it('handles duplicate type entries without failing', async function () { - prepareMock(['type1', 'type2', 'type1']) - const childNodes = await testNode.getChildren() - assert.strictEqual(childNodes.length, 2, 'Unexpected child count') - }) - - async function setConfiguration(resourceTypes: string[]) { - await settings.update('enabledResources', resourceTypes) - } - - function prepareMock(resourceTypes: string[]) { - const listStub = sinon.stub().returns( - asyncGenerator( - resourceTypes.map((resourceType) => { - return { - TypeName: resourceType, - } - }) - ) - ) - mockCloudFormation.listTypes = listStub - } -}) diff --git a/packages/core/src/test/dynamicResources/explorer/resourceNode.test.ts b/packages/core/src/test/dynamicResources/explorer/resourceNode.test.ts deleted file mode 100644 index 28df557ff2..0000000000 --- a/packages/core/src/test/dynamicResources/explorer/resourceNode.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ResourceTypeNode } from '../../../dynamicResources/explorer/nodes/resourceTypeNode' -import { ResourceNode } from '../../../dynamicResources/explorer/nodes/resourceNode' - -const fakeIdentifier = 'someidentifier' -const fakeArn = 'arn:fooPartion:fooService:fooRegion:1234:fooType/someidentifier' -const fakeContextValue = 'FooContext' - -describe('ResourceNode', function () { - it('initializes name and tooltip', async function () { - const testNode = new ResourceNode({} as ResourceTypeNode, fakeIdentifier) - assert.strictEqual(testNode.label, fakeIdentifier) - assert.strictEqual(testNode.tooltip, fakeIdentifier) - assert.strictEqual(testNode.identifier, fakeIdentifier) - }) - - it('parses resource ARN to get friendly name for label', async function () { - const testNode = new ResourceNode({} as ResourceTypeNode, fakeArn) - assert.strictEqual(testNode.label, 'fooType/someidentifier') - }) - - it('uses full ARN for tooltip', async function () { - const testNode = new ResourceNode({} as ResourceTypeNode, fakeArn) - assert.strictEqual(testNode.tooltip, fakeArn) - }) - - it('uses provided contextValue', async function () { - const testNode = new ResourceNode({} as ResourceTypeNode, fakeArn, fakeContextValue) - assert.strictEqual(testNode.contextValue, fakeContextValue) - }) - - it('uses default contextValue if none is provided', async function () { - const testNode = new ResourceNode({} as ResourceTypeNode, fakeArn) - assert.strictEqual(testNode.contextValue, 'ResourceNode') - }) -}) diff --git a/packages/core/src/test/dynamicResources/explorer/resourceTypeNode.test.ts b/packages/core/src/test/dynamicResources/explorer/resourceTypeNode.test.ts deleted file mode 100644 index 86296885df..0000000000 --- a/packages/core/src/test/dynamicResources/explorer/resourceTypeNode.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import { ResourcesNode } from '../../../dynamicResources/explorer/nodes/resourcesNode' -import { ResourceNode } from '../../../dynamicResources/explorer/nodes/resourceNode' -import { ResourceTypeNode } from '../../../dynamicResources/explorer/nodes/resourceTypeNode' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../utilities/explorerNodeAssertions' -import { CloudControlClient } from '../../../shared/clients/cloudControl' -import { ResourceDescription } from '@aws-sdk/client-cloudcontrol' -import { ResourceTypeMetadata } from '../../../dynamicResources/model/resources' -import sinon from 'sinon' - -const fakeTypeName = 'sometype' -const unsortedText = ['zebra', 'Antelope', 'aardvark', 'elephant'] -const sortedText = ['aardvark', 'Antelope', 'elephant', 'zebra'] - -describe('ResourceTypeNode', function () { - let testNode: ResourceTypeNode - - let resourceIdentifiers: string[] - let cloudControl: CloudControlClient - - beforeEach(function () { - cloudControl = {} as any as CloudControlClient - - resourceIdentifiers = ['resource1', 'resource2', 'resource3'] - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl) - }) - - it('initializes name and tooltip', async function () { - assert.strictEqual(testNode.label, fakeTypeName) - assert.strictEqual(testNode.tooltip, fakeTypeName) - }) - - it('returns placeholder node if no resources are found', async function () { - const resourceIdentifiers: string[] = [] - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl) - - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has ResourceNode child nodes', async function () { - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, resourceIdentifiers.length, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof ResourceNode, 'Expected child node to be ResourceNode') - } - }) - - it('has child nodes with all operations contextValue when unknown operations', async function () { - const childNodes = await testNode.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - 'CreatableDeletableUpdatableResourceNode', - 'expected the node to have a ResourceNode contextValue' - ) - } - }) - - it('has child nodes with ResourceNode contextValue including single supported operation', async function () { - testNode = generateTestNode(cloudControl, ['CREATE']) - const childNodes = await testNode.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - 'CreatableResourceNode', - 'expected the node to have a CreatableResourceNode contextValue' - ) - } - }) - - it('has child nodes with ResourceNode contextValue including multiple supported operations', async function () { - testNode = generateTestNode(cloudControl, ['CREATE', 'DELETE']) - const childNodes = await testNode.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - 'CreatableDeletableResourceNode', - 'expected the node to have a CreatableDeletableResourceNode contextValue' - ) - } - }) - - it('sorts child nodes', async function () { - resourceIdentifiers = unsortedText - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl) - - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, sortedText, 'Unexpected child sort order') - }) - - it('has an error node for a child if an error happens during loading', async function () { - cloudControl.listResources = sinon.stub().rejects(new Error('foo')) - testNode = generateTestNode(cloudControl) - - const childNodes = await testNode.getChildren() - assertNodeListOnlyHasErrorNode(childNodes) - }) - - it('has a placeholder node for a child if unsupported action', async function () { - const error = new Error('foo') - error.name = 'UnsupportedActionException' - cloudControl.listResources = sinon.stub().rejects(error) - testNode = generateTestNode(cloudControl) - - const childNodes = await testNode.getChildren() - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has Documented contextValue if documentation available', function () { - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl, [], 'fooDocs') - assert.strictEqual(testNode.contextValue, 'DocumentedResourceTypeNode') - }) - - it('has Creatable contextValue if create operation present', function () { - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl, ['CREATE']) - assert.strictEqual(testNode.contextValue, 'CreatableResourceTypeNode') - }) - - it('is non expandable if not available', function () { - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl, undefined, undefined, false) - assert.strictEqual(testNode.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - - it('has a suitable description if not available', function () { - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl, undefined, undefined, false) - assert.strictEqual(testNode.description, 'Unavailable in region') - }) - - it('has UnavailableResourceTypeNode contextValue if not available', function () { - mockCloudControlClient(resourceIdentifiers) - - testNode = generateTestNode(cloudControl, undefined, undefined, false) - assert.strictEqual(testNode.contextValue, 'UnavailableResourceTypeNode') - }) - - function generateTestNode( - client: CloudControlClient, - supportedOperations?: string[], - documentation?: string, - available?: boolean - ): ResourceTypeNode { - const metadata = { - operations: supportedOperations, - documentation, - available: available ?? true, - } as ResourceTypeMetadata - return new ResourceTypeNode({} as ResourcesNode, fakeTypeName, client, metadata) - } - - function mockCloudControlClient(resourceIdentifiers: string[]): void { - cloudControl.listResources = sinon.stub().resolves({ - TypeName: fakeTypeName, - NextToken: undefined, - ResourceDescriptions: resourceIdentifiers.map((identifier) => { - return { - Identifier: identifier, - ResourceModel: '', - } - }), - }) - } -}) diff --git a/packages/core/src/test/dynamicResources/model/resources.test.ts b/packages/core/src/test/dynamicResources/model/resources.test.ts deleted file mode 100644 index 640466afb0..0000000000 --- a/packages/core/src/test/dynamicResources/model/resources.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { getResourceTypes } from '../../../dynamicResources/model/resources' - -const fakeResourceModel = { - Type1: { - operations: ['CREATE', 'READ', 'DELETE', 'UPDATE'], - documentation: 'foo', - }, - Type2: { - operations: ['CREATE', 'READ', 'UPDATE', 'DELETE', 'LIST'], - documentation: 'bar', - }, - Type3: { - operations: ['READ', 'LIST'], - documentation: 'baz', - }, -} - -describe('getResourceTypes', function () { - it('returns a map of resource types', function () { - const types = getResourceTypes(fakeResourceModel) - const fakeType = types.get('Type3') - assert.deepStrictEqual(fakeType?.operations, ['READ', 'LIST']) - assert.strictEqual(fakeType?.documentation, 'baz') - }) - - it('filters out types not supporting LIST operation', function () { - const types = getResourceTypes(fakeResourceModel) - assert.strictEqual(types.size, 2) - assert.ok(!types.has('Type1')) - }) -}) diff --git a/packages/core/src/test/eventSchemas/commands/downloadSchemaItemCode.test.ts b/packages/core/src/test/eventSchemas/commands/downloadSchemaItemCode.test.ts deleted file mode 100644 index 140248eaef..0000000000 --- a/packages/core/src/test/eventSchemas/commands/downloadSchemaItemCode.test.ts +++ /dev/null @@ -1,695 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' - -import { - DescribeCodeBindingResponse, - GetCodeBindingSourceResponse, - PutCodeBindingResponse, -} from '@aws-sdk/client-schemas' -import * as sinon from 'sinon' - -import { - CodeDownloader, - CodeExtractor, - CodeGenerationStatusPoller, - CodeGenerator, - SchemaCodeDownloader, - SchemaCodeDownloadRequestDetails, -} from '../../../eventSchemas/commands/downloadSchemaItemCode' - -import { MockOutputChannel } from '../../../test/mockOutputChannel' - -import admZip from 'adm-zip' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' -import { fs } from '../../../shared' -import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports - -describe('CodeDownloader', function () { - let tempFolder: string - let sandbox: sinon.SinonSandbox - let destinationDirectory: vscode.Uri - let request: SchemaCodeDownloadRequestDetails - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - sandbox = sinon.createSandbox() - destinationDirectory = vscode.Uri.file(tempFolder) - - request = { - registryName: testRegistryName, - schemaName: testSchemaName, - language: language, - schemaVersion: schemaVersion, - destinationDirectory: destinationDirectory, - schemaCoreCodeFileName: testSchemaName.concat('.java'), - } - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(tempFolder, { recursive: true }) - }) - const testSchemaName = 'testSchema' - const testRegistryName = 'testRegistry' - - const language = 'Java8' - const schemaVersion = 'testVersion' - const schemaClient = new DefaultSchemaClient('') - const codeDownloader = new CodeDownloader(schemaClient) - - describe('codeDownloader', async function () { - it('should return an error if the response body is not Buffer', async function () { - const response: GetCodeBindingSourceResponse = { - Body: 'Invalied body' as any, - } - sandbox.stub(schemaClient, 'getCodeBindingSource').returns(Promise.resolve(response)) - - await assert.rejects( - codeDownloader.download(request), - new TypeError('Response body should be Buffer type'), - 'Should fail for same error' - ) - }) - - it('should return arrayBuffer for valid Body type', async function () { - const myBuffer = Buffer.from('TEST STRING') - const response: GetCodeBindingSourceResponse = { - Body: myBuffer as any, - } - - sandbox.stub(schemaClient, 'getCodeBindingSource').returns(Promise.resolve(response)) - const returnedBuffer = await codeDownloader.download(request) - const comparisonResult = arrayBuffersEqual(returnedBuffer, myBuffer.buffer) - assert.ok( - comparisonResult, - `Buffers do not match, string representation of expected buffer : TEST STRING, returned : ${String( - returnedBuffer - )}` - ) - }) - - function arrayBuffersEqual(buf1: ArrayBuffer, buf2: ArrayBuffer) { - if (buf1.byteLength !== buf2.byteLength) { - return false - } - const dv1 = new Int8Array(buf1) - const dv2 = new Int8Array(buf2) - for (let i = 0; i !== buf1.byteLength; i++) { - if (dv1[i] !== dv2[i]) { - return false - } - - return true - } - } - }) -}) - -describe('CodeGenerator', function () { - let tempFolder: string - let sandbox: sinon.SinonSandbox - let destinationDirectory: vscode.Uri - let request: SchemaCodeDownloadRequestDetails - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - sandbox = sinon.createSandbox() - destinationDirectory = vscode.Uri.file(tempFolder) - - request = { - registryName: testRegistryName, - schemaName: testSchemaName, - language: language, - schemaVersion: schemaVersion, - destinationDirectory: destinationDirectory, - schemaCoreCodeFileName: testSchemaName.concat('.java'), - } - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(tempFolder, { recursive: true }) - }) - const testSchemaName = 'testSchema' - const testRegistryName = 'testRegistry' - - const language = 'Java8' - const schemaVersion = 'testVersion' - - enum CodeGenerationStatus { - CREATE_COMPLETE = 'CREATE_COMPLETE', - CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS', - CREATE_FAILED = 'CREATE_FAILED', - } - - const schemaClient = new DefaultSchemaClient('') - const codeGenerator = new CodeGenerator(schemaClient) - - describe('codeGenerator', async function () { - it('should return the current status of code generation', async function () { - const response: PutCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_IN_PROGRESS, - } - sandbox.stub(schemaClient, 'putCodeBinding').returns(Promise.resolve(response)) - - const actualResponse = await codeGenerator.generate(request) - assert.strictEqual( - actualResponse, - response, - `should return response with ${response.Status} status, but returned ${actualResponse.Status}` - ) - }) - - // If code bindings were not generated, but putCodeBinding was already called, ConflictException occurs - // Return CREATE_IN_PROGRESS and keep polling in this case - it('should return valid code generation status if it gets ConflictException', async function () { - const response: PutCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_IN_PROGRESS, - } - - const error = new Error('ConflictException occured') - sandbox.stub(schemaClient, 'putCodeBinding').returns(Promise.reject(error)) - - const actualResponse = await codeGenerator.generate(request) - assert.strictEqual( - actualResponse.Status, - response.Status, - `should return response with ${response.Status} status, but returned ${actualResponse.Status}` - ) - }) - }) -}) -describe('CodeGeneratorStatusPoller', function () { - let tempFolder: string - let sandbox: sinon.SinonSandbox - let destinationDirectory: vscode.Uri - let request: SchemaCodeDownloadRequestDetails - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - sandbox = sinon.createSandbox() - destinationDirectory = vscode.Uri.file(tempFolder) - - request = { - registryName: testRegistryName, - schemaName: testSchemaName, - language: language, - schemaVersion: schemaVersion, - destinationDirectory: destinationDirectory, - schemaCoreCodeFileName: testSchemaName.concat('.java'), - } - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(tempFolder, { recursive: true }) - }) - const testSchemaName = 'testSchema' - const testRegistryName = 'testRegistry' - - const language = 'Java8' - const schemaVersion = 'testVersion' - - const retryIntervalMs = 1 - const maxRetries = 2 - - enum CodeGenerationStatus { - CREATE_COMPLETE = 'CREATE_COMPLETE', - CREATE_IN_PROGRESS = 'CREATE_IN_PROGRESS', - CREATE_FAILED = 'CREATE_FAILED', - } - - const schemaClient = new DefaultSchemaClient('') - const statuspoller = new CodeGenerationStatusPoller(schemaClient) - - describe('getCurrentStatus', async function () { - it('should return the current status of code generation', async function () { - const firstStatus: DescribeCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_IN_PROGRESS, - } - const secondStatus: DescribeCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_COMPLETE, - } - - const clientStub = sandbox.stub(schemaClient, 'describeCodeBinding') - clientStub.onCall(0).returns(Promise.resolve(firstStatus)) - clientStub.onCall(1).returns(Promise.resolve(secondStatus)) - - const actualFirstStatus = await statuspoller.getCurrentStatus(request) - const actualSecondStatus = await statuspoller.getCurrentStatus(request) - - assert.strictEqual(actualFirstStatus, firstStatus.Status, 'status should match') - assert.strictEqual(actualSecondStatus, secondStatus.Status, 'status should match') - }) - }) - - describe('codeGeneratorStatusPoller', async function () { - it('fails if code generation status is invalid without retry', async function () { - const schemaResponse: DescribeCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_FAILED, - } - - const statusPoll = sandbox - .stub(statuspoller, 'getCurrentStatus') - .withArgs(request) - .returns(Promise.resolve(schemaResponse.Status)) - - await assert.rejects( - async () => statuspoller.pollForCompletion(request), - new Error(`Invalid Code generation status ${schemaResponse.Status}`), - 'Should fail for expected error' - ) - assert.ok( - statusPoll.calledOnce, - 'getCurrentStatus method should be called once without retry as it returns invalid status' - ) - }) - - it('times out after max attempts if status is still in progress', async function () { - const schemaResponse: DescribeCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_IN_PROGRESS, - } - - const statusPoll = sandbox - .stub(statuspoller, 'getCurrentStatus') - .withArgs(request) - .returns(Promise.resolve(schemaResponse.Status)) - - await assert.rejects( - statuspoller.pollForCompletion(request, retryIntervalMs, maxRetries), - new Error( - `Failed to download code for schema ${request.schemaName} before timeout. Please try again later` - ), - 'Should fail for expected error' - ) - assert.strictEqual( - statusPoll.callCount, - maxRetries, - 'getCurrentStatus should be called maxRetries times before timing out' - ) - }) - - it('succeeds when code is previously generated without retry', async function () { - const schemaResponse: DescribeCodeBindingResponse = { - Status: CodeGenerationStatus.CREATE_COMPLETE, - } - - const statusPoll = sandbox - .stub(statuspoller, 'getCurrentStatus') - .withArgs(request) - .returns(Promise.resolve(schemaResponse.Status)) - const status = await statuspoller.pollForCompletion(request, retryIntervalMs, maxRetries) - assert.strictEqual( - statusPoll.callCount, - 1, - 'getCurrentStatus should be called once as it returns CREATE_COMPLETE status' - ) - assert.strictEqual(status, schemaResponse.Status, 'status should match') - }) - - it('succeeds once the code generation status is complete within maxRetry attempts', async function () { - const statusPoll = sandbox.stub(statuspoller, 'getCurrentStatus') - statusPoll.onCall(0).returns(Promise.resolve(CodeGenerationStatus.CREATE_IN_PROGRESS)) - statusPoll.onCall(1).returns(Promise.resolve(CodeGenerationStatus.CREATE_COMPLETE)) // After maxAttempts - - const status = await statuspoller.pollForCompletion(request, retryIntervalMs, maxRetries) - assert.strictEqual( - statusPoll.callCount, - maxRetries, - 'getCurrentStatus should be called maxRetries(2) times as it returns CREATE_COMPLETE on the 2nd call' - ) - assert.strictEqual(status, CodeGenerationStatus.CREATE_COMPLETE, 'status should match') - }) - }) -}) - -describe('SchemaCodeDownload', function () { - let tempFolder: string - let sandbox: sinon.SinonSandbox - let destinationDirectory: vscode.Uri - let request: SchemaCodeDownloadRequestDetails - - let arrayBuffer: Buffer - let fileName: string - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - sandbox = sinon.createSandbox() - destinationDirectory = vscode.Uri.file(tempFolder) - - request = { - registryName: testRegistryName, - schemaName: testSchemaName, - language: language, - schemaVersion: schemaVersion, - destinationDirectory: destinationDirectory, - schemaCoreCodeFileName: testSchemaName.concat('.java'), - } - - fileName = testSchemaName.concat('.java') - const zip = new admZip() - zip.addFile(fileName, Buffer.from(fileContent)) - arrayBuffer = zip.toBuffer() - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(tempFolder, { recursive: true }) - }) - const testSchemaName = 'testSchema' - const testRegistryName = 'testRegistry' - - const language = 'Java8' - const schemaVersion = 'testVersion' - - const fileContent = 'Test file contents' - const schemaClient = new DefaultSchemaClient('') - const poller = new CodeGenerationStatusPoller(schemaClient) - const downloader = new CodeDownloader(schemaClient) - const generator = new CodeGenerator(schemaClient) - const outputChannel = new MockOutputChannel() - const extractor = new CodeExtractor(outputChannel) - - const schemaCodeDownloader = new SchemaCodeDownloader(downloader, generator, poller, extractor) - - describe('downloadCode', async function () { - it('should download pre-generated code and place it into requested directory ', async function () { - const codeDownloaderStub = sandbox.stub(downloader, 'download').returns(Promise.resolve(arrayBuffer)) - - await schemaCodeDownloader.downloadCode(request) - - assert.ok( - codeDownloaderStub.calledOnceWith(request), - 'download method should be called once with correct parameters' - ) - - // should extract the zip file with provided fileContent - const expectedFilePath = path.join(request.destinationDirectory.fsPath, fileName) - const response = await fs.readFileText(expectedFilePath) - assert.strictEqual(response, fileContent, `${expectedFilePath} :file content do not match`) - }) - - it('should return error if downloading code fails with anything other than ResourceNotFound', async function () { - const customError = new Error('Custom error') - const codeDownloaderStub = sandbox.stub(downloader, 'download').returns(Promise.reject(customError)) - - await assert.rejects(schemaCodeDownloader.downloadCode(request), customError, 'Should throw Custom error') - assert.ok( - codeDownloaderStub.calledOnceWith(request), - 'download method should be called once with correct parameters' - ) - }) - - it('should generate code if download fails with ResourceNotFound and place it into requested directory', async function () { - sandbox.stub(poller, 'pollForCompletion').returns(Promise.resolve('CREATE_COMPLETE')) - const codeDownloaderStub = sandbox.stub(downloader, 'download') - const codeGeneratorResponse: PutCodeBindingResponse = { - Status: 'CREATE_IN_PROGRESS', - } - sandbox.stub(generator, 'generate').returns(Promise.resolve(codeGeneratorResponse)) - - const customError = new Error('Resource Not Found Exception') - customError.name = 'ResourceNotFound' - - codeDownloaderStub.onCall(0).returns(Promise.reject(customError)) // should fail on first call - codeDownloaderStub.onCall(1).returns(Promise.resolve(arrayBuffer)) // should succeed on second - - await schemaCodeDownloader.downloadCode(request) - - assert.ok( - codeDownloaderStub.calledTwice, - 'download method should be called twice, first should fail for NotFoundException and second succeed' - ) - - const expectedFilePath = path.join(request.destinationDirectory.fsPath, fileName) - const response = await fs.readFileText(expectedFilePath) - assert.strictEqual(response, fileContent, 'Extracted file content do not match with expected') - }) - - it('should return coreCodeFilePath', async function () { - const expectedFilePath = path.join(request.destinationDirectory.fsPath, fileName) - sandbox.stub(downloader, 'download').returns(Promise.resolve(arrayBuffer)) - sandbox.stub(extractor, 'extractAndPlace').returns(Promise.resolve(expectedFilePath)) - - const coreCodeFilePath = await schemaCodeDownloader.downloadCode(request) - assert.strictEqual( - coreCodeFilePath, - expectedFilePath, - `zipContents should have ${fileName} containing requested core file ${request.schemaCoreCodeFileName}` - ) - }) - }) -}) - -describe('CodeExtractor', function () { - let destinationDirectory: string - let sandbox: sinon.SinonSandbox - - beforeEach(async function () { - destinationDirectory = await makeTemporaryToolkitFolder() - sandbox = sinon.createSandbox() - }) - - afterEach(async function () { - await fs.delete(destinationDirectory, { recursive: true }) - sandbox.restore() - }) - - const outputChannel = new MockOutputChannel() - const codeExtractor = new CodeExtractor(outputChannel) - - describe('checkFileCollisions', function () { - it('should return true when zipFile directoryFile contents clash ', async function () { - const fileName = 'test.txt' - const zipName = path.join(destinationDirectory, 'test.zip') - - let expectedMessage = 'Following files already exist in the folder hierarchy :\n' - const collidingfilePath = path.join(destinationDirectory, fileName) - expectedMessage = expectedMessage.concat(collidingfilePath) - - // Initialize a destination directory and file - let zipHandler = createZipFileInTempDirectory(fileName, 'First file content', zipName) - zipHandler.extractAllTo(destinationDirectory) - - // Create a zip file that clashes with destination content - zipHandler = createZipFileInTempDirectory(fileName, 'Second file content', zipName) - - const collisionOccured = codeExtractor.checkFileCollisions(zipName, destinationDirectory) - - assert.strictEqual(collisionOccured, true, 'should confirm that collision occurs') - assert(outputChannel.value.includes(expectedMessage), `channel missing msg: ${expectedMessage}`) - }) - - it('should return false if no collision present', async function () { - const fileName1 = 'test.txt' - const zipName = path.join(destinationDirectory, 'test.zip') - - // Initialize a destination directory and file - let zipHandler = createZipFileInTempDirectory(fileName1, 'First file content', zipName) - zipHandler.extractAllTo(destinationDirectory) - - // Create a zip file with same directory path but diff fileName - const fileName2 = 'test2.txt' - zipHandler = createZipFileInTempDirectory(fileName2, 'Second file content', zipName) - - const collisionOccured = codeExtractor.checkFileCollisions(zipName, destinationDirectory) - assert.strictEqual( - collisionOccured, - false, - `There should be no collision in files ${fileName1} and ${fileName2}` - ) - }) - }) - describe('extractAndPlace', function () { - const testSchemaName = 'aws.batch.testSchema' - const testRegistryName = 'testRegistry' - const language = 'Java8' - const schemaVersion = 'testVersion' - - let sandbox: sinon.SinonSandbox - let destinationDirectoryUri: vscode.Uri - let request: SchemaCodeDownloadRequestDetails - - beforeEach(async function () { - sandbox = sinon.createSandbox() - destinationDirectoryUri = vscode.Uri.file(destinationDirectory) - - request = { - registryName: testRegistryName, - schemaName: testSchemaName, - language: language, - schemaVersion: schemaVersion, - destinationDirectory: destinationDirectoryUri, - schemaCoreCodeFileName: 'testSchema.java', - } - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(destinationDirectory, { recursive: true }) - }) - - it('should extract files if no collision present', async function () { - const fileName1 = 'test.text' - const zipName1 = path.join(destinationDirectory, 'test.zip') - - // Initialize a destination directory and file - const zipHandler = createZipFileInTempDirectory(fileName1, 'First file content', zipName1) - zipHandler.extractAllTo(destinationDirectory) - - const fileName2 = 'test2.txt' - - const zip = new admZip() - zip.addFile(fileName2, Buffer.from('Second file content')) - const buffer = zip.toBuffer() - await codeExtractor.extractAndPlace(buffer, request) - - const file1Path = path.join(destinationDirectory, fileName1) - const file2Path = path.join(destinationDirectory, fileName2) - - // confirm both file exist - assert.ok(await fs.exists(file1Path), `${file1Path} should exist`) - assert.ok(await fs.exists(file2Path), `${file2Path} should exist`) - - // confirm file contents - const file1Content = await fs.readFileText(file1Path) - const file2Content = await fs.readFileText(file2Path) - - assert.strictEqual(file1Content, 'First file content', `${file1Path} : file content do not match`) - assert.strictEqual(file2Content, 'Second file content', `${file2Path} : file content do not match`) - }) - - it('should not override file content if user picks No when collision occurs', async function () { - sandbox.stub(codeExtractor, 'confirmOverwriteCollisions').returns(Promise.resolve(false)) - - const fileName1 = 'test.txt' - const zipFileName = path.join(destinationDirectory, 'test.zip') - const expectedFileContent = 'First file content' - - // Initialize a destination directory and file - const zipHandler = createZipFileInTempDirectory(fileName1, expectedFileContent, zipFileName) - zipHandler.extractAllTo(destinationDirectory) - - // same file name - collision occurs - const fileName2 = fileName1 - const zip = new admZip() - zip.addFile(fileName2, Buffer.from('Second file content')) - const buffer = zip.toBuffer() - - await codeExtractor.extractAndPlace(buffer, request) - - const file1Path = path.join(destinationDirectory, fileName1) - const file1Content = await fs.readFileText(file1Path) - - assert.strictEqual(file1Content, expectedFileContent, `${file1Path} :File content should not be overriden`) - }) - - it('should override file content if user picks Yes when collision occurs', async function () { - sandbox.stub(codeExtractor, 'confirmOverwriteCollisions').returns(Promise.resolve(true)) - - const fileName1 = 'test.txt' - const zipFileName = path.join(destinationDirectory, 'test.zip') - const initialFileContent = 'Initial file content' - - // Initialize a destination directory and file - const zipHandler = createZipFileInTempDirectory(fileName1, initialFileContent, zipFileName) - zipHandler.extractAllTo(destinationDirectory) - - // same file name, different file content - collision occurs - const fileName2 = fileName1 - const zip = new admZip() - const overridenFileContent = 'Replaced file content' - zip.addFile(fileName2, Buffer.from(overridenFileContent)) - const buffer = zip.toBuffer() - - await codeExtractor.extractAndPlace(buffer, request) - - const file1Path = path.join(destinationDirectory, fileName1) - const file1Content = await fs.readFileText(file1Path) - - assert.strictEqual(file1Content, overridenFileContent, `${file1Path} :File content should be overriden`) - }) - - it('should throw error if user picks Cancel when collision occurs', async function () { - const error = new Error('Download code bindings cancelled') - sandbox.stub(codeExtractor, 'confirmOverwriteCollisions').rejects(error) - - const fileName1 = 'test.txt' - const zipFileName = path.join(destinationDirectory, 'test.zip') - const expectedFileContent = 'First file content' - - // Initialize a destination directory and file - const zipHandler = createZipFileInTempDirectory(fileName1, expectedFileContent, zipFileName) - zipHandler.extractAllTo(destinationDirectory) - - // same file name - collision occurs - const fileName2 = fileName1 - const zip = new admZip() - zip.addFile(fileName2, Buffer.from('Second file content')) - const buffer = zip.toBuffer() - - await assert.rejects( - codeExtractor.extractAndPlace(buffer, request), - error, - 'Should fail for expected error' - ) - - const file1Path = path.join(destinationDirectory, fileName1) - const file1Content = await fs.readFileText(file1Path) - - assert.strictEqual(file1Content, expectedFileContent, `${file1Path} :File content should not be overriden`) - }) - - it('should return coreCodeFilePath if it exists inside zip content', async function () { - // grab the title from schemaName - const title = testSchemaName.split('.').pop() - const fileName = title!.concat('.java') - - const zip = new admZip() - zip.addFile(fileName, Buffer.from('File content')) - const buffer = zip.toBuffer() - const coreCodeFilePath = await codeExtractor.extractAndPlace(buffer, request) - - const filePath = path.join(destinationDirectoryUri.fsPath, fileName) - - assert.strictEqual(coreCodeFilePath, filePath, `should have ${title} in the path`) - }) - }) - - describe('getCoreCodeFilePath', function () { - it('shoul return file path if it exists in zipFile', async function () { - const coreFile = 'test.java' - const zipName = path.join(destinationDirectory, 'test.zip') - createZipFileInTempDirectory(coreFile, 'First file content', zipName) - - const coreCodeFilePath = codeExtractor.getCoreCodeFilePath(zipName, coreFile) - - assert.strictEqual(coreCodeFilePath, coreFile, 'Core file path should match') - }) - - it('should return undefined if file does not exist in zipFile', async function () { - const fileName = 'test.java' - const zipName = path.join(destinationDirectory, 'test.zip') - createZipFileInTempDirectory(fileName, 'First file content', zipName) - - const coreFile = 'test2.java' - const coreCodeFilePath = codeExtractor.getCoreCodeFilePath(zipName, coreFile) - - assert.strictEqual(coreCodeFilePath, undefined) - }) - }) - - function createZipFileInTempDirectory(fileName: string, fileContent: string, zipFileName: string): admZip { - const zip = new admZip() - zip.addFile(fileName, Buffer.from(fileContent)) - const buffer = zip.toBuffer() - const fd = nodefs.openSync(zipFileName, 'w') - nodefs.writeSync(fd, buffer, 0, buffer.byteLength, 0) - nodefs.closeSync(fd) - - return zip - } -}) diff --git a/packages/core/src/test/eventSchemas/commands/searchSchemas.test.ts b/packages/core/src/test/eventSchemas/commands/searchSchemas.test.ts deleted file mode 100644 index dd7c2175cc..0000000000 --- a/packages/core/src/test/eventSchemas/commands/searchSchemas.test.ts +++ /dev/null @@ -1,286 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' - -import { DescribeSchemaResponse, SearchSchemaSummary, SearchSchemaVersionSummary } from '@aws-sdk/client-schemas' -import * as sinon from 'sinon' -import { SchemasNode } from '../../../eventSchemas/explorer/schemasNode' -import { getTabSizeSetting } from '../../../shared/utilities/editorUtilities' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' - -import { MockOutputChannel } from '../../mockOutputChannel' -import { - getPageHeader, - getRegistryNames, - getSearchListForSingleRegistry, - getSearchResults, - SearchSchemasWebview, -} from '../../../eventSchemas/vue/searchSchemas' -import { RegistryItemNode } from '../../../eventSchemas/explorer/registryItemNode' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' -import { stub } from '../../utilities/stubber' -import { getTestWindow } from '../../shared/vscode/window' -import { SeverityLevel } from '../../shared/vscode/message' - -describe('Search Schemas', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - const schemaClient = new DefaultSchemaClient('') - const testRegistry = 'testRegistry' - const testRegistry2 = 'testRegistry2' - const failRegistry = 'failRegistry' - const failRegistry2 = 'failRegistry2' - - const versionSummary1: SearchSchemaVersionSummary = { - SchemaVersion: '1', - } - const versionSummary2: SearchSchemaVersionSummary = { - SchemaVersion: '2', - } - const searchSummary1: SearchSchemaSummary = { - RegistryName: testRegistry, - SchemaName: 'testSchema1', - SchemaVersions: [versionSummary1, versionSummary2], - } - - const searchSummary2: SearchSchemaSummary = { - RegistryName: testRegistry, - SchemaName: 'testSchema2', - SchemaVersions: [versionSummary1], - } - - const searchSummary3: SearchSchemaSummary = { - RegistryName: testRegistry2, - SchemaName: 'testSchema3', - SchemaVersions: [versionSummary1], - } - - function createWebview(registryNames: string[]): SearchSchemasWebview { - return new SearchSchemasWebview(new MockOutputChannel(), schemaClient, { - RegistryNames: registryNames, - Header: getPageHeader(registryNames), - SearchInputPlaceholder: '', - VersionPrefix: '', - Region: '', - LocalizedMessages: { - noSchemasFound: '', - searching: '', - loading: '', - select: '', - }, - }) - } - - describe('getSearchListForSingleRegistry', function () { - it('should return summaries', async function () { - const client = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - client.searchSchemas.returns(asyncGenerator([searchSummary1, searchSummary2])) - - const results = await getSearchListForSingleRegistry(client, testRegistry, 'searchText') - - assert.strictEqual(results.length, 2, 'search should return 2 summaries') - - assert.strictEqual(results[0].VersionList.length, 2, 'first summary has two verions') - assert.strictEqual(results[0].VersionList[0], '2', 'first version should be 2 - descending order') - assert.strictEqual(results[0].VersionList[1], '1', 'second version should be 1') - - assert.strictEqual(results[1].VersionList.length, 1, 'second summary has 1 version') - assert.strictEqual(results[1].VersionList[0], '1', 'version should be 1') - assert.strictEqual( - results[0].Title, - searchSummary1.SchemaName!, - 'title should be same as schemaName, no prefix appended' - ) - assert.strictEqual( - results[1].Title, - searchSummary2.SchemaName!, - 'title should be same as schemaName, no prefix appended' - ) - - assert.strictEqual(results[0].RegistryName, testRegistry, 'summary should belong to requested registry') - assert.strictEqual(results[1].RegistryName, testRegistry, 'summary should belong to requested registry') - }) - - it('should display an error message when search api call fails', async function () { - const client = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - const displayMessage = `Unable to search registry ${failRegistry}` - - // make an api call with non existent registryName - should return empty results - const results = await getSearchListForSingleRegistry(client, failRegistry, 'randomText') - - assert.strictEqual(results.length, 0, 'should return 0 summaries') - const errorMessages = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Error) - assert.strictEqual(errorMessages.length, 1, 'error message should be shown exactly once') - assert.strictEqual(errorMessages[0].message, displayMessage, 'should display correct error message') - }) - }) - - describe('getSearchResults', function () { - it('should display error message for failed registries and return summaries for successful ones', async function () { - const displayMessage = `Unable to search registry ${failRegistry}` - const displayMessage2 = `Unable to search registry ${failRegistry2}` - - const searchSummaryList1 = [searchSummary1, searchSummary2] - const searchSummaryList2 = [searchSummary3] - - const searchSchemaStub = sandbox.stub(schemaClient, 'searchSchemas') - searchSchemaStub.withArgs('randomText', testRegistry).returns(asyncGenerator(searchSummaryList1)).onCall(0) - searchSchemaStub.withArgs('randomText', testRegistry2).returns(asyncGenerator(searchSummaryList2)).onCall(1) - const results = await getSearchResults( - schemaClient, - [testRegistry, testRegistry2, failRegistry, failRegistry2], - 'randomText' - ) - - assert.strictEqual(results.length, 3, 'should return 3 summaries') - - // results are unordered, sort for testing purposes - results.sort(function (a, b) { - return a.RegistryName > b.RegistryName ? 1 : b.RegistryName > a.RegistryName ? -1 : 0 - }) - const expectedTitle1 = testRegistry.concat('/', searchSummary1.SchemaName!) - const expectedTitle2 = testRegistry.concat('/', searchSummary2.SchemaName!) - const expectedTitle3 = testRegistry2.concat('/', searchSummary3.SchemaName!) - - assert.strictEqual(results[0].RegistryName, testRegistry, 'sumnmary should belong to requested registry') - assert.strictEqual(results[1].RegistryName, testRegistry, 'sumnmary should belong to requested registry') - assert.strictEqual(results[2].RegistryName, testRegistry2, 'sumnmary should belong to requested registry') - - assert.strictEqual(results[0].Title, expectedTitle1, 'title should be prefixed with registryName') - assert.strictEqual(results[1].Title, expectedTitle2, 'title should be prefixed with registryName') - assert.strictEqual(results[2].Title, expectedTitle3, 'title should be prefixed with registryName') - - assert.strictEqual(results[0].VersionList.length, 2, 'first summary has 2 versions') - assert.strictEqual(results[1].VersionList.length, 1, 'second summary has 1 version') - assert.strictEqual(results[2].VersionList.length, 1, 'third summary has 1 version') - - // failed registries - const errorMessages = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Error) - assert.strictEqual(errorMessages.length, 2, 'should display 2 error message, 1 per each failed registry') - errorMessages[0].assertMessage(displayMessage) - errorMessages[1].assertMessage(displayMessage2) - }) - }) - - describe('handleMessage', function () { - const singleRegistryName = [testRegistry] - const multipleRegistryNames = [testRegistry, testRegistry2] - const awsEventSchemaRaw = - '{"openapi":"3.0.0","info":{"version":"1.0.0","title":"Event"},"paths":{},"components":{"schemas":{"Event":{"type":"object"}}}}' - const schemaResponse: DescribeSchemaResponse = { - Content: awsEventSchemaRaw, - } - - it('shows schema content for latest matching schema version by default', async function () { - const versionedSummary = { - RegistryName: testRegistry, - Title: getPageHeader(singleRegistryName), - VersionList: ['3', '2', '1'], - } - - sandbox.stub(schemaClient, 'describeSchema').returns(Promise.resolve(schemaResponse)) - - const webview = createWebview(singleRegistryName) - const resp = await webview.fetchSchemaContent(versionedSummary) - - assert.strictEqual( - resp.results, - JSON.stringify(JSON.parse(schemaResponse.Content!), undefined, getTabSizeSetting()) - ) - assert.strictEqual(resp.version, '3') - assert.strictEqual(resp.versionList, versionedSummary.VersionList) - }) - - it('shows schema content for user selected version', async function () { - const versionedSummary = { - RegistryName: testRegistry, - Title: getPageHeader(multipleRegistryNames), - VersionList: ['1'], - } - - sandbox.stub(schemaClient, 'describeSchema').returns(Promise.resolve(schemaResponse)) - - const webview = createWebview(multipleRegistryNames) - const resp = await webview.fetchSchemaContent(versionedSummary, '1') - assert.strictEqual( - resp.results, - JSON.stringify(JSON.parse(schemaResponse.Content!), undefined, getTabSizeSetting()) - ) - assert.strictEqual(resp.version, '1') - assert.strictEqual(resp.versionList, undefined) - }) - - it('shows schema list when user makes a search', async function () { - const expectResults1 = { - RegistryName: testRegistry, - Title: testRegistry + '/testSchema1', - VersionList: ['2', '1'], - } - const expectResults2 = { - RegistryName: testRegistry, - Title: testRegistry + '/testSchema2', - VersionList: ['1'], - } - - const searchSummaryList = [searchSummary1, searchSummary2] - sandbox - .stub(schemaClient, 'searchSchemas') - .withArgs('searchText', testRegistry) - .returns(asyncGenerator(searchSummaryList)) - - const webview = createWebview([testRegistry, testRegistry]) - const resp = await webview.searchSchemas('searchText') - - assert.deepStrictEqual(resp.results, [expectResults1, expectResults2]) - assert.strictEqual(resp.resultsNotFound, false) - }) - }) - - describe('getRegistryNameList', function () { - it('should return list with single registry name for registryItemNode', async function () { - const fakeRegistryNew = { - RegistryName: testRegistry, - RegistryArn: 'arn:aws:schemas:us-west-2:19930409:registry/testRegistry', - } - - const registryItemNode = new RegistryItemNode(fakeRegistryNew, schemaClient) - - const result = await getRegistryNames(registryItemNode, schemaClient) - assert.deepStrictEqual(result, [testRegistry], 'should have a single registry name in it') - }) - - it('should return list with multiple registry names for schemasNode', async function () { - const schemasNode = new SchemasNode(schemaClient) - const registrySummary1 = { RegistryArn: 'arn:aws:registry/' + testRegistry, RegistryName: testRegistry } - const registrySummary2 = { RegistryArn: 'arn:aws:registry/' + testRegistry2, RegistryName: testRegistry2 } - - sandbox.stub(schemaClient, 'listRegistries').returns(asyncGenerator([registrySummary1, registrySummary2])) - - const result = await getRegistryNames(schemasNode, schemaClient) - assert.deepStrictEqual(result, [testRegistry, testRegistry2], 'should have two registry names in it') - }) - - it('should return an empty list and display error message if schemas service not available in the region', async function () { - const schemasNode = new SchemasNode(schemaClient) - sandbox.stub(schemaClient, 'listRegistries') - - const results = await getRegistryNames(schemasNode, schemaClient) - - assert.ok(results.length === 0, 'Should return an empty array') - const errorMessages = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Error) - assert.strictEqual(errorMessages.length, 1, 'should display 1 error message') - errorMessages[0].assertMessage('Error loading Schemas resources') - }) - }) -}) diff --git a/packages/core/src/test/eventSchemas/commands/viewSchemaItem.test.ts b/packages/core/src/test/eventSchemas/commands/viewSchemaItem.test.ts deleted file mode 100644 index 9127bfe96a..0000000000 --- a/packages/core/src/test/eventSchemas/commands/viewSchemaItem.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DescribeSchemaResponse } from '@aws-sdk/client-schemas' -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { schemaFormatter, showSchemaContent, viewSchemaItem } from '../../../eventSchemas/commands/viewSchemaItem' -import { SchemaItemNode } from '../../../eventSchemas/explorer/schemaItemNode' -import { getTabSizeSetting } from '../../../shared/utilities/editorUtilities' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' - -const schemaTabSize = 2 -const awsEventSchemaRaw = - '{"openapi":"3.0.0","info":{"version":"1.0.0","title":"Event"},"paths":{},"components":{"schemas":{"Event":{"type":"object","required":["result","cause","event","request-id"],"properties":{"cause":{"type":"string"},"event":{"type":"string"},"request-id":{"type":"string"},"result":{"type":"integer"}}}}}}' - -const awsEventSchemaPretty = `{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "Event" - }, - "paths": {}, - "components": { - "schemas": { - "Event": { - "type": "object", - "required": [ - "result", - "cause", - "event", - "request-id" - ], - "properties": { - "cause": { - "type": "string" - }, - "event": { - "type": "string" - }, - "request-id": { - "type": "string" - }, - "result": { - "type": "integer" - } - } - } - } - } -}` - -describe('viewSchemaItem', async function () { - let sandbox: sinon.SinonSandbox - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('schemaFormatter', function () { - it('can pretty print schema content', function () { - const formattedSchema = schemaFormatter(awsEventSchemaRaw, schemaTabSize) - assert.strictEqual(formattedSchema, awsEventSchemaPretty, 'Schema content not pretty printed') - }) - }) - - describe('showSchemaContent', async function () { - it('inserts pretty schema content into an editor', async function () { - const insertStub = stubTextEditInsert() - await showSchemaContent(awsEventSchemaRaw, schemaTabSize) - - assert.strictEqual(insertStub.calledOnce, true, 'should be called once') - assert.strictEqual(insertStub.getCalls()[0].args[1], awsEventSchemaPretty, 'should insert pretty schema') - }) - }) - - const testSchemaName = 'testSchema' - const testRegistryName = 'testRegistry' - const fakeSchema = { - SchemaName: testSchemaName, - } - const expectedSchema = JSON.stringify(JSON.parse(awsEventSchemaRaw), undefined, getTabSizeSetting()) - - it('prettifies schema content and inserts into the editor ', async function () { - const schemaNode = generateSchemaItemNode() - const insertStub = stubTextEditInsert() - - await viewSchemaItem(schemaNode) - assert.strictEqual(insertStub.getCalls()[0].args[1], expectedSchema, 'should insert pretty schema') - }) - - function stubTextEditInsert() { - const textEdit = { - insert: () => {}, - } as any as vscode.TextEditorEdit - - const textEditor = { - edit: () => {}, - } as any as vscode.TextEditor - - sinon.stub(textEditor, 'edit').callsFake(async (editBuilder) => { - editBuilder(textEdit) - - return true - }) - - sandbox.stub(vscode.window, 'showTextDocument').returns(Promise.resolve(textEditor)) - const insertStub = sandbox.stub(textEdit, 'insert') - - return insertStub - } - - function generateSchemaItemNode(): SchemaItemNode { - const schemaResponse: DescribeSchemaResponse = { - Content: awsEventSchemaRaw, - } - const schemaClient = new DefaultSchemaClient('') - sandbox.stub(schemaClient, 'describeSchema').returns(Promise.resolve(schemaResponse)) - - return new SchemaItemNode(fakeSchema, schemaClient, testRegistryName) - } -}) diff --git a/packages/core/src/test/eventSchemas/explorer/registryItemNode.test.ts b/packages/core/src/test/eventSchemas/explorer/registryItemNode.test.ts deleted file mode 100644 index 719520cde6..0000000000 --- a/packages/core/src/test/eventSchemas/explorer/registryItemNode.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import * as sinon from 'sinon' -import { RegistrySummary, SchemaSummary } from '@aws-sdk/client-schemas' -import { RegistryItemNode } from '../../../eventSchemas/explorer/registryItemNode' -import { SchemaItemNode } from '../../../eventSchemas/explorer/schemaItemNode' -import { SchemasNode } from '../../../eventSchemas/explorer/schemasNode' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { PlaceholderNode } from '../../../shared/treeview/nodes/placeholderNode' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../utilities/explorerNodeAssertions' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { getIcon } from '../../../shared/icons' -import { stub } from '../../utilities/stubber' - -function createSchemaClient(data?: { schemas?: SchemaSummary[]; registries?: RegistrySummary[] }) { - const client = stub(DefaultSchemaClient, { regionCode: 'code' }) - client.listSchemas.callsFake(() => asyncGenerator(data?.schemas ?? [])) - client.listRegistries.callsFake(() => asyncGenerator(data?.registries ?? [])) - - return client -} - -describe('RegistryItemNode', function () { - let fakeRegistry: RegistrySummary - - before(function () { - fakeRegistry = { - RegistryName: 'myRegistry', - RegistryArn: 'arn:aws:schemas:us-west-2:434418839121:registry/myRegistry', - } - }) - - // Validates we tagged the node correctly. - it('initializes name, tooltip, and icon', async function () { - const testNode: RegistryItemNode = generateTestNode() - - assert.strictEqual(testNode.label, `${fakeRegistry.RegistryName}`) - assert.strictEqual(testNode.tooltip, `${fakeRegistry.RegistryName}${os.EOL}${fakeRegistry.RegistryArn}`) - assert.strictEqual(testNode.iconPath, getIcon('aws-schemas-registry')) - }) - - it('returns placeholder node if no children are present', async function () { - const testNode = generateTestNode() - const childNodes = await testNode.getChildren() - - assert(childNodes !== undefined) - assert.strictEqual(childNodes.length, 1) - assert.strictEqual(childNodes[0] instanceof PlaceholderNode, true) - }) - - it('returns schemas that belong to Registry', async function () { - const schema1Item: SchemaSummary = { - SchemaArn: 'arn:schema1', - SchemaName: 'schema1Name', - } - - const schema2Item: SchemaSummary = { - SchemaArn: 'arn:schema1', - SchemaName: 'schema2Name', - } - - const schema3Item: SchemaSummary = { - SchemaArn: 'arn:schema1', - SchemaName: 'schema3Name', - } - - const schemaClient = createSchemaClient({ schemas: [schema1Item, schema2Item, schema3Item] }) - const testNode: RegistryItemNode = generateTestNode(schemaClient) - - const childNodes = await testNode.getChildren() - assert(childNodes !== undefined) - assert.strictEqual(childNodes.length, 3) - - assert(childNodes[0] instanceof SchemaItemNode) - assert.strictEqual((childNodes[0] as SchemaItemNode).label, schema1Item.SchemaName) - - assert(childNodes[1] instanceof SchemaItemNode) - assert.strictEqual((childNodes[1] as SchemaItemNode).label, schema2Item.SchemaName) - - assert(childNodes[1] instanceof SchemaItemNode) - assert.strictEqual((childNodes[2] as SchemaItemNode).label, schema3Item.SchemaName) - }) - - function generateTestNode(client = createSchemaClient()): RegistryItemNode { - return new RegistryItemNode(fakeRegistry, client) - } -}) - -describe('DefaultRegistryNode', function () { - afterEach(function () { - sinon.restore() - }) - - it('Sorts Registries', async function () { - const inputRegistryNames: string[] = ['zebra', 'Antelope', 'aardvark', 'elephant'] - const client = createSchemaClient({ - registries: inputRegistryNames.map((name) => ({ RegistryName: name })), - }) - - const schemasNode = new SchemasNode(client) - const children = await schemasNode.getChildren() - - assert.ok(children, 'Expected to get Registry node children') - assert.strictEqual( - inputRegistryNames.length, - children.length, - `Expected ${inputRegistryNames.length} Registry children, got ${children.length}` - ) - - function assertChildNodeRegistryName(actualChildNode: AWSTreeNodeBase, expectedNodeText: string) { - assert.strictEqual(actualChildNode instanceof RegistryItemNode, true, 'Child node was not a Registry Node') - - const node: RegistryItemNode = actualChildNode as RegistryItemNode - assert.strictEqual( - node.registryName, - expectedNodeText, - `Expected child node to have registry ${expectedNodeText} but got ${node.registryName}` - ) - } - - assertChildNodeRegistryName(children[0], 'aardvark') - assertChildNodeRegistryName(children[1], 'Antelope') - assertChildNodeRegistryName(children[2], 'elephant') - assertChildNodeRegistryName(children[3], 'zebra') - }) - - it('returns placeholder node if no children are present', async function () { - const schemasNode = new SchemasNode(createSchemaClient()) - const childNodes = await schemasNode.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('handles error', async function () { - // typo in the name of the method - class ThrowErrorDefaultSchemaRegistrynNode extends SchemasNode { - public constructor() { - super(createSchemaClient()) - } - - public override async updateChildren(): Promise { - throw new Error('Hello there!') - } - } - - const testNode: ThrowErrorDefaultSchemaRegistrynNode = new ThrowErrorDefaultSchemaRegistrynNode() - - const childNodes = await testNode.getChildren() - assertNodeListOnlyHasErrorNode(childNodes) - }) -}) diff --git a/packages/core/src/test/eventSchemas/model/schemaCodeGenUtils.test.ts b/packages/core/src/test/eventSchemas/model/schemaCodeGenUtils.test.ts deleted file mode 100644 index 619bb24b71..0000000000 --- a/packages/core/src/test/eventSchemas/model/schemaCodeGenUtils.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SchemaCodeGenUtils, toValidIdentifier } from '../../../eventSchemas/models/schemaCodeGenUtils' - -describe('SchemaCodeGenUtils', async function () { - const testScenarios = [ - { - scenario: 'builds the "aws event" package name', - input: 'aws.ec2.EC2InstanceStateChangeNotificationEvent', - expectedResult: 'schema.aws.ec2.ec2instancestatechangenotificationevent', - }, - { - scenario: 'builds the "aws event" package name containing package seperator @', - input: 'aws.ec2@EC2InstanceStateChangeNotificationEvent', - expectedResult: 'schema.aws.ec2.ec2instancestatechangenotificationevent', - }, - { - scenario: 'builds the "partner event" package name containing slash,numbers,dash and package seperator @', - input: 'aws.partner-mongodb.com/1234567-tickets@TicketCreated', - expectedResult: 'schema.aws.partner.mongodb_com_1234567_tickets.ticketcreated', - }, - { - scenario: 'builds the "partner event" package name containing other special characters', - input: 'aws.partner-zendesk.com/some#other?special;chars@MyEvent', - expectedResult: 'schema.aws.partner.zendesk_com_some_other_special_chars.myevent', - }, - { - scenario: 'builds the "partner event" package name containing package seperator @ only', - input: 'aws.partner-pagerduty.com@YouGotPaged', - expectedResult: 'schema.aws.partner.pagerduty_com.yougotpaged', - }, - { - scenario: 'builds the "custom event" package name containing no special characters', - input: 'MyEvent', - expectedResult: 'schema.myevent', - }, - { - scenario: 'builds the "custom event" package name containing special characters', - input: 'MyEvent.Special#Characters$etc', - expectedResult: 'schema.myevent_special_characters_etc', - }, - { - scenario: 'builds the "custom event" package name containing package seperator', - input: 'MyEvent@Discriminator', - expectedResult: 'schema.myevent.discriminator', - }, - { - scenario: 'builds the "custom event" package name containing multiple package seperators', - input: 'MyEvent@Discriminator@Another', - expectedResult: 'schema.myevent.discriminator.another', - }, - ] - - describe('buildSchemaPackageName', async function () { - for (const test of testScenarios) { - it(test.scenario, async () => { - const codeGen = new SchemaCodeGenUtils() - const result = codeGen.buildSchemaPackageName(test.input) - assert.strictEqual(result, test.expectedResult, 'Invalid package name returned') - }) - } - }) -}) - -describe('toValidIdentifier', async function () { - it('replaces invalid identifier characters with underscore', async function () { - assert.strictEqual(toValidIdentifier('Review.created?today'), 'Review_created_today') - }) - - it('replaces package seperator @ with dot', async function () { - assert.strictEqual(toValidIdentifier('Review@created'), 'Review.created') - }) -}) diff --git a/packages/core/src/test/eventSchemas/model/schemaCodeLangs.test.ts b/packages/core/src/test/eventSchemas/model/schemaCodeLangs.test.ts deleted file mode 100644 index cce239a1cb..0000000000 --- a/packages/core/src/test/eventSchemas/model/schemaCodeLangs.test.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - getApiValueForSchemasDownload, - getLanguageDetails, - schemaCodeLangs, -} from '../../../eventSchemas/models/schemaCodeLangs' -import { samZipLambdaRuntimes } from '../../../lambda/models/samLambdaRuntime' -import { Runtime } from '@aws-sdk/client-lambda' - -describe('getLanguageDetails', function () { - it('should successfully return details for supported languages', function () { - for (const language of schemaCodeLangs.values()) { - const result = getLanguageDetails(language) - assert.ok( - result, - `Language : ${language}, api value : ${result.apiValue}, language extension : ${result.extension}` - ) - } - }) -}) - -describe('getApiValueForSchemasDownload', function () { - it('should return api value for runtimes supported by eventBridge application', async function () { - for (const runtime of samZipLambdaRuntimes.values()) { - switch (runtime) { - case 'python3.7': - case 'python3.8': - case 'python3.9': - case 'python3.11': - case 'python3.12': - case 'python3.13' as Runtime: - case 'python3.14' as Runtime: - case 'python3.10': { - const result = getApiValueForSchemasDownload(runtime) - assert.strictEqual(result, 'Python36', 'Api value used by schemas api') - break - } - case 'go1.x': { - const result = getApiValueForSchemasDownload(runtime) - assert.strictEqual(result, 'Go1', 'Api value used by schemas api') - break - } - default: { - const errorMessage = `Runtime ${runtime} is not supported by eventBridge application` - assert.throws( - () => getApiValueForSchemasDownload(runtime), - new Error(errorMessage), - 'Should fail for same error' - ) - break - } - } - } - }) -}) diff --git a/packages/core/src/test/eventSchemas/providers/schemasDataProvider.test.ts b/packages/core/src/test/eventSchemas/providers/schemasDataProvider.test.ts deleted file mode 100644 index d8b7211cbf..0000000000 --- a/packages/core/src/test/eventSchemas/providers/schemasDataProvider.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - Cache, - credentialsRegionDataListMap, - SchemasDataProvider, -} from '../../../eventSchemas/providers/schemasDataProvider' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { stub } from '../../utilities/stubber' - -describe('schemasDataProvider', function () { - const testRegion = 'testRegion' - const testRegion2 = 'testRegion2' - const testRegistry = 'testRegistry' - const testRegistry2 = 'testRegistry2' - const testSchema = 'testSchema' - const testSchema2 = 'testSchema2' - const registrySummary1 = { RegistryName: testRegistry } - const registrySummary2 = { RegistryName: testRegistry2 } - const schemaSummary = { SchemaName: testSchema } - const schemaSummary2 = { SchemaName: testSchema2 } - const testCredentials = {} as any as AWS.Credentials - const testCredentials2 = {} as any as AWS.Credentials - - let dataProviderObject: SchemasDataProvider - let cacheData: Cache - - function createSchemaClient() { - const client = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - - client.listRegistries.onCall(0).returns(asyncGenerator([registrySummary1, registrySummary2])) - client.listRegistries.onCall(1).returns(asyncGenerator([registrySummary1])) - - client.listSchemas.onCall(0).returns(asyncGenerator([schemaSummary, schemaSummary2])) - client.listSchemas.onCall(1).returns(asyncGenerator([schemaSummary])) - - return client - } - - beforeEach(async function () { - const regionDataWithCredentials: credentialsRegionDataListMap = { - credentials: testCredentials, - regionDataList: [], - } - cacheData = new Cache([regionDataWithCredentials]) - dataProviderObject = new SchemasDataProvider(cacheData) - }) - - describe('getRegistries', function () { - it('should return registries for given region', async function () { - const registryNames = await dataProviderObject.getRegistries( - testRegion, - createSchemaClient(), - testCredentials - ) - assert.ok(registryNames!.length === 2, 'unexpected number of registries returned') - assert.strictEqual(registryNames![0], testRegistry, 'testRegistry name should match') - assert.strictEqual(registryNames![1], testRegistry2, 'testRegistry2 name should match') - }) - - it('should retain results when it is queried with same credentials ', async function () { - const client = createSchemaClient() - await dataProviderObject.getRegistries(testRegion, client, testCredentials) - await dataProviderObject.getRegistries(testRegion2, client, testCredentials) - - assert.ok( - cacheData.credentialsRegionDataList.length === 1, - 'Cache should contain data for a single credential' - ) - assert.strictEqual( - cacheData.credentialsRegionDataList[0].credentials, - testCredentials, - 'Credentials key should match' - ) - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 2, - 'Single cache element should have two region data' - ) - - const regionData1 = cacheData.credentialsRegionDataList[0].regionDataList[0] - const regionData2 = cacheData.credentialsRegionDataList[0].regionDataList[1] - - assert.strictEqual(regionData1.region, testRegion) - assert.strictEqual(regionData2.region, testRegion2) - - assert.ok(regionData1.registryNames.length === 2, 'First region should have two registryNames') - assert.ok(regionData2.registryNames.length === 1, 'Second region should have one registryName') - - assert.deepStrictEqual( - regionData1.registrySchemasMapList, - [], - 'First region should have an empty registrySchemasMapList' - ) - - assert.deepStrictEqual( - regionData2.registrySchemasMapList, - [], - 'Second region should have an empty registrySchemasMapList' - ) - }) - - it('should retain results when it is queried with different credentials ', async function () { - const client = createSchemaClient() - await dataProviderObject.getRegistries(testRegion, client, testCredentials) - await dataProviderObject.getRegistries(testRegion, client, testCredentials2) - - assert.ok(cacheData.credentialsRegionDataList.length === 2, 'Cache should contain data for two credentials') - assert.strictEqual( - cacheData.credentialsRegionDataList[0].credentials, - testCredentials, - 'First cache element should have a matching credentials key' - ) - assert.strictEqual( - cacheData.credentialsRegionDataList[1].credentials, - testCredentials2, - 'Second cache element should have a matching credentials key' - ) - - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 1, - 'First cache element should have a single region data' - ) - assert.ok( - cacheData.credentialsRegionDataList[1].regionDataList.length === 1, - 'Second cache element should have a single region data' - ) - }) - - it('should return undefined when error occurs', async function () { - const client = createSchemaClient() - client.listRegistries.reset() - client.listRegistries.throws(new Error('Custom error')) - - const result = await dataProviderObject.getRegistries(testRegion, client, testCredentials) - - assert.strictEqual(result, undefined) - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 0, - 'No data should be cached when error occurs' - ) - }) - }) - - describe('getSchemas', function () { - it('should return schemas for given region', async function () { - const schemas = await dataProviderObject.getSchemas( - testRegion, - testRegistry, - createSchemaClient(), - testCredentials - ) - - assert.ok(schemas!.length === 2, 'Unexpected number of schemas returned') - assert.strictEqual(schemas![0], schemaSummary, 'schemaSummary should match') - assert.strictEqual(schemas![1], schemaSummary2, 'schemaSummary2 should match') - }) - - it('should retain results when it is queried with same credentials ', async function () { - const client = createSchemaClient() - await dataProviderObject.getSchemas(testRegion, testRegistry, client, testCredentials) - await dataProviderObject.getSchemas(testRegion, testRegistry2, client, testCredentials) - - assert.ok( - cacheData.credentialsRegionDataList.length === 1, - 'Cache should contain data for a single credential' - ) - assert.strictEqual( - cacheData.credentialsRegionDataList[0].credentials, - testCredentials, - 'Credentials key should match' - ) - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 1, - 'Cache should contain data for a single region' - ) - - const regionData = cacheData.credentialsRegionDataList[0].regionDataList[0] - - assert.ok( - regionData.registrySchemasMapList.length === 2, - 'There should be 2 elements in registrySchemasMapList' - ) - assert.deepStrictEqual( - regionData.registrySchemasMapList[0].schemaList, - [schemaSummary, schemaSummary2], - 'First registry should have two schemas' - ) - - assert.deepStrictEqual( - regionData.registrySchemasMapList[1].schemaList, - [schemaSummary], - 'Second registry should have one schema' - ) - }) - - it('should retain results when it is queried with different credentials ', async function () { - const client = createSchemaClient() - await dataProviderObject.getSchemas(testRegion, testRegistry, client, testCredentials) - await dataProviderObject.getSchemas(testRegion, testRegistry, client, testCredentials2) - - assert.ok(cacheData.credentialsRegionDataList.length === 2, 'Cache should contain data for two credentials') - assert.strictEqual( - cacheData.credentialsRegionDataList[0].credentials, - testCredentials, - 'Credentials key should match for first element' - ) - assert.strictEqual( - cacheData.credentialsRegionDataList[1].credentials, - testCredentials2, - 'Credentials key should match for second element' - ) - - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 1, - 'First cache element should have a single region data' - ) - assert.ok( - cacheData.credentialsRegionDataList[1].regionDataList.length === 1, - 'Second cache element should have a single region data' - ) - }) - - it('should return undefined when error occurs ', async function () { - const client = createSchemaClient() - client.listSchemas.reset() - client.listSchemas.throws(new Error('Custom error')) - - const result = await dataProviderObject.getSchemas(testRegion, testRegistry, client, testCredentials) - - assert.strictEqual(result, undefined) - assert.ok( - cacheData.credentialsRegionDataList[0].regionDataList.length === 0, - 'No data should be cached when error occurs' - ) - }) - }) -}) diff --git a/packages/core/src/test/eventSchemas/templates/schemasAppTemplateUtils.test.ts b/packages/core/src/test/eventSchemas/templates/schemasAppTemplateUtils.test.ts deleted file mode 100644 index 1a1b1056a0..0000000000 --- a/packages/core/src/test/eventSchemas/templates/schemasAppTemplateUtils.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { buildSchemaTemplateParameters } from '../../../eventSchemas/templates/schemasAppTemplateUtils' -import { DefaultSchemaClient } from '../../../shared/clients/schemaClient' -import { stub } from '../../utilities/stubber' -import { - awsEventSchemaContent, - customerUploadedSchema, - customerUploadedSchemaMultipleTypes, - partnerSchemaContent, -} from './schemasExamples' - -const awsSchemaName = 'aws.ec2@EC2InstanceStateChangeNotification' -const awsSchemaExpectedPackageName = 'schema.aws.ec2.ec2instancestatechangenotification' -const registryName = 'Registry' -const schemaVersion = '1' -const awsToolkitUserAgent = 'AWSToolkit' - -const partnerSchemaExpectedPackageName = 'schema.aws.partner.mongodb_com_1234567_tickets.ticket_created' -const partnerSchemaName = 'aws.partner-mongodb.com/1234567-tickets@Ticket.Created' - -const customerUploadedSchemaName = 'someCustomer.SomeAwesomeSchema' -/** Expected package name. */ -const customerUploadedSchemaExpectedPackage = 'schema.somecustomer_someawesomeschema' -const defaultEventSource = 'INSERT-YOUR-EVENT-SOURCE' -const defaultEventDetailType = 'INSERT-YOUR-DETAIL-TYPE' - -const customerUploadedSchemaMultipleTypesName = 'someCustomer.multipleTypes@SomeOtherAwesomeSchema' -/** Expected package name. */ -const customerUploadedSchemaMultipleTypesPkg = 'schema.somecustomer_multipletypes.someotherawesomeschema' - -describe('Build template parameters for AwsEventSchema', async function () { - it('should build correct template parameters for aws event schema', async function () { - const schemaClient = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - schemaClient.describeSchema.resolves({ - Content: awsEventSchemaContent, - SchemaVersion: schemaVersion, - }) - - const result = await buildSchemaTemplateParameters(awsSchemaName, registryName, schemaClient) - - assert.strictEqual(result.SchemaVersion, schemaVersion, 'Schema version not matching') - assert.strictEqual(result.templateExtraContent.AWS_Schema_registry, registryName, 'Registry name not matching') - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_name, - 'EC2InstanceStateChangeNotification', - 'schemaRootEventName' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_root, - awsSchemaExpectedPackageName, - 'schemaPackageHierarchy' - ) - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_source, - 'aws.ec2', - 'x-amazon-events-source field not matching' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_detail_type, - 'EC2 Instance State-change Notification', - 'x-amazon-events-detail-type field not matching' - ) - assert.strictEqual( - result.templateExtraContent.user_agent, - awsToolkitUserAgent, - 'User agent should be hardcoded to AWSToolkit' - ) - }) -}) - -describe('Build template parameters for PartnerSchema', async function () { - it('should build correct template parameters for partner schema', async function () { - const schemaClient = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - schemaClient.describeSchema.resolves({ - Content: partnerSchemaContent, - SchemaVersion: schemaVersion, - }) - - const result = await buildSchemaTemplateParameters(partnerSchemaName, registryName, schemaClient) - - assert.strictEqual(result.SchemaVersion, schemaVersion, 'Schema version not matching') - assert.strictEqual(result.templateExtraContent.AWS_Schema_registry, registryName, 'Registry name not matching') - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_name, - 'aws_partner_mongodb_com_Ticket_Created', - 'schemaRootEventName' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_root, - partnerSchemaExpectedPackageName, - 'schemaPackageHierarchy' - ) - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_source, - 'aws.partner-mongodb.com', - 'x-amazon-events-source field not matching' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_detail_type, - 'MongoDB Trigger for my_store.reviews', - 'x-amazon-events-detail-type field not matching' - ) - assert.strictEqual( - result.templateExtraContent.user_agent, - awsToolkitUserAgent, - 'User agent should be hardcoded to AWSToolkit' - ) - }) -}) - -describe('Build template parameters for CustomerUploadedSchema', async function () { - it('should build correct template parameters for customer uploaded schema with single type', async function () { - const schemaClient = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - schemaClient.describeSchema.resolves({ - Content: customerUploadedSchema, - SchemaVersion: schemaVersion, - }) - - const result = await buildSchemaTemplateParameters(customerUploadedSchemaName, registryName, schemaClient) - - assert.strictEqual(result.SchemaVersion, schemaVersion, 'Schema version not matching') - assert.strictEqual(result.templateExtraContent.AWS_Schema_registry, registryName, 'Registry name not matching') - - assert.strictEqual(result.templateExtraContent.AWS_Schema_name, 'Some_Awesome_Schema', 'schemaRootEventName') - assert.strictEqual( - result.templateExtraContent.AWS_Schema_root, - customerUploadedSchemaExpectedPackage, - 'schemaPackageHierarchy' - ) - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_source, - defaultEventSource, - 'custom schemas should have default event source' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_detail_type, - defaultEventDetailType, - 'custom schemas should have default detail type' - ) - assert.strictEqual( - result.templateExtraContent.user_agent, - awsToolkitUserAgent, - 'User agent should be hardcoded to AWSToolkit' - ) - }) -}) - -describe('Build template parameters for CustomerUploadedSchemaMultipleTypes', async function () { - it('should build correct template parameters for customer uploaded schema with multiple types', async function () { - const schemaClient = stub(DefaultSchemaClient, { regionCode: 'region-1' }) - schemaClient.describeSchema.resolves({ - Content: customerUploadedSchemaMultipleTypes, - SchemaVersion: schemaVersion, - }) - - const result = await buildSchemaTemplateParameters( - customerUploadedSchemaMultipleTypesName, - registryName, - schemaClient - ) - - assert.strictEqual(result.SchemaVersion, schemaVersion, 'Schema version not matching') - assert.strictEqual(result.templateExtraContent.AWS_Schema_registry, registryName, 'Registry name not matching') - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_name, - 'Some_Awesome_Schema_Object_1', - 'schemaRootEventName' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_root, - customerUploadedSchemaMultipleTypesPkg, - 'schemaPackageHierarchy' - ) - - assert.strictEqual( - result.templateExtraContent.AWS_Schema_source, - defaultEventSource, - 'custom schemas should have default event source' - ) - assert.strictEqual( - result.templateExtraContent.AWS_Schema_detail_type, - defaultEventDetailType, - 'custom schemas should have default detail type' - ) - assert.strictEqual( - result.templateExtraContent.user_agent, - awsToolkitUserAgent, - 'User agent should be hardcoded to AWSToolkit' - ) - }) -}) diff --git a/packages/core/src/test/eventSchemas/templates/schemasExamples.ts b/packages/core/src/test/eventSchemas/templates/schemasExamples.ts deleted file mode 100644 index a526017b80..0000000000 --- a/packages/core/src/test/eventSchemas/templates/schemasExamples.ts +++ /dev/null @@ -1,208 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -export const awsEventSchemaContent = `{ - "openapi" : "3.0.0", - "info" : { - "version" : "1.0.0", - "title" : "EC2InstanceStateChangeNotification" - }, - "paths" : { }, - "components" : { - "schemas" : { - "AWSEvent" : { - "type" : "object", - "required" : [ "detail-type", "resources", "id", "source", "time", "detail", "region", "version", "account" ], - "x-amazon-events-detail-type" : "EC2 Instance State-change Notification", - "x-amazon-events-source" : "aws.ec2", - "properties" : { - "detail" : { - "$ref" : "#/components/schemas/EC2InstanceStateChangeNotification" - }, - "detail-type" : { - "type" : "string" - }, - "resources" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "id" : { - "type" : "string" - }, - "source" : { - "type" : "string" - }, - "time" : { - "type" : "string", - "format" : "date-time" - }, - "region" : { - "type" : "string", - "enum" : [ "ap-south-1", "eu-west-3", "eu-north-1", "eu-west-2", "eu-west-1", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ca-central-1", "ap-east-1", "cn-north-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "eu-central-1", "us-east-1", "us-west-1", "cn-northwest-1", "us-west-2" ] - }, - "version" : { - "type" : "string" - }, - "account" : { - "type" : "string" - } - } - }, - "EC2InstanceStateChangeNotification" : { - "type" : "object", - "required" : [ "instance-id", "state" ], - "properties" : { - "instance-id" : { - "type" : "string" - }, - "state" : { - "type" : "string" - } - } - } - } - } - }` - -export const partnerSchemaContent = `{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "TicketCreated" - }, - "paths": {}, - "components": { - "schemas": { - "AWSEvent" : { - "type" : "object", - "required" : [ "detail-type", "resources", "id", "source", "time", "detail", "region", "version", "account" ], - "x-amazon-events-detail-type" : "MongoDB Trigger for my_store.reviews", - "x-amazon-events-source" : "aws.partner-mongodb.com", - "properties" : { - "detail" : { - "$ref" : "#/components/schemas/aws.partner/mongodb.com/Ticket.Created" - }, - "detail-type" : { - "type" : "string" - }, - "resources" : { - "type" : "array", - "items" : { - "type" : "string" - } - }, - "id" : { - "type" : "string" - }, - "source" : { - "type" : "string" - }, - "time" : { - "type" : "string", - "format" : "date-time" - }, - "region" : { - "type" : "string", - "enum" : [ "ap-south-1", "eu-west-3", "eu-north-1", "eu-west-2", "eu-west-1", "ap-northeast-2", "ap-northeast-1", "me-south-1", "sa-east-1", "ca-central-1", "ap-east-1", "cn-north-1", "us-gov-west-1", "ap-southeast-1", "ap-southeast-2", "eu-central-1", "us-east-1", "us-west-1", "cn-northwest-1", "us-west-2" ] - }, - "version" : { - "type" : "string" - }, - "account" : { - "type" : "string" - } - } - }, - "TicketCreated" : { - "type" : "object", - "required" : [ "creator", "department", "ticketId" ], - "properties" : { - "creator" : { - "type" : "string" - }, - "department" : { - "type" : "string" - }, - "ticketId" : { - "type" : "string" - } - } - } - } - } - }` - -export const customerUploadedSchema = `{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "SomeAwesomeSchema" - }, - "paths": {}, - "components": { - "schemas": { - "Some Awesome Schema" : { - "type" : "object", - "required" : [ "foo", "bar", "baz" ], - "properties" : { - "foo" : { - "type" : "string" - }, - "bar" : { - "type" : "string" - }, - "baz" : { - "type" : "string" - } - } - } - } - } - }` - -export const customerUploadedSchemaMultipleTypes = `{ - "openapi": "3.0.0", - "info": { - "version": "1.0.0", - "title": "SomeAwesomeSchema" - }, - "paths": {}, - "components": { - "schemas": { - "Some/Awesome/Schema.Object.1" : { - "type" : "object", - "required" : [ "foo", "bar", "baz" ], - "properties" : { - "foo" : { - "type" : "string" - }, - "bar" : { - "type" : "string" - }, - "baz" : { - "type" : "string" - } - } - }, - "Some/Awesome/Schema.Object$2" : { - "type" : "object", - "required" : [ "foo", "bar", "baz" ], - "properties" : { - "foo" : { - "type" : "string" - }, - "bar" : { - "type" : "string" - }, - "baz" : { - "type" : "string" - } - } - } - } - } - }` diff --git a/packages/core/src/test/eventSchemas/wizards/schemaCodeDownloadWizard.test.ts b/packages/core/src/test/eventSchemas/wizards/schemaCodeDownloadWizard.test.ts deleted file mode 100644 index dcc6ea799b..0000000000 --- a/packages/core/src/test/eventSchemas/wizards/schemaCodeDownloadWizard.test.ts +++ /dev/null @@ -1,265 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { Set } from 'immutable' -import * as path from 'path' -import * as vscode from 'vscode' -import { JAVA, PYTHON, SchemaCodeLangs } from '../../../eventSchemas/models/schemaCodeLangs' -import { - SchemaCodeDownloadWizard, - SchemaCodeDownloadWizardContext, -} from '../../../eventSchemas/wizards/schemaCodeDownloadWizard' - -function isMultiDimensionalArray(array: any[] | any[][] | undefined): boolean { - if (!array) { - return false - } - - for (const item of array) { - if (Array.isArray(item)) { - return true - } - } - - return false -} - -class MockSchemaCodeDownloadWizardContext implements SchemaCodeDownloadWizardContext { - public get schemaVersions(): string[] { - if (Array.isArray(this._schemaVersions)) { - if (this._schemaVersions!.length <= 0) { - throw new Error('schemaLangs was called more times than expected') - } - - ;(this._schemaVersions as string[]).pop() - - return (this._schemaVersions as string[]) || [] - } - - return [this._schemaVersions] || [] - } - - public get schemaLangs(): Set { - if (Array.isArray(this._schemaLangs)) { - if (this._schemaLangs!.length <= 0) { - throw new Error('schemaLangs was called more times than expected') - } - - return (this._schemaLangs as Set[]).pop() || Set() - } - - return (this._schemaLangs as Set) || Set() - } - - public get workspaceFolders(): vscode.WorkspaceFolder[] { - if (isMultiDimensionalArray(this._workspaceFolders)) { - if (this._workspaceFolders!.length <= 0) { - throw new Error('workspaceFolders was called more times than expected') - } - - return (this._workspaceFolders as vscode.WorkspaceFolder[][]).pop() || [] - } - - return (this._workspaceFolders as vscode.WorkspaceFolder[]) || [] - } - - /** - * @param {vscode.WorkspaceFolder[] | vscode.WorkspaceFolder[][]} _workspaceFolders - * The value to return from context.workspaceFolders. - * @param {immutable.Set | Set[]} _schemaLangs - * The value to return from context.schemaLangs. - * @param {string | string[]} _schemaVersions - * The value to return from context.showInputBox. - * @param {(vscode.Uri[] | undefined) | (vscode.Uri[] | undefined)[]} openDialogResult - * The value to return from context.showOpenDialog. - * - * Each parameter may be a single value (in which case that value is always returned), - * or an array of values (in which case each invocation will return the next item from the array). - */ - public constructor( - private readonly _workspaceFolders: vscode.WorkspaceFolder[] | vscode.WorkspaceFolder[][], - private readonly _schemaLangs: (Set | undefined) | (Set[] | undefined), - private readonly _schemaVersions: string | string[], - private readonly openDialogResult: (vscode.Uri[] | undefined) | (vscode.Uri[] | undefined)[] - ) { - if (isMultiDimensionalArray(this._workspaceFolders)) { - this._workspaceFolders = (_workspaceFolders as vscode.WorkspaceFolder[][]).reverse() - } - if (Array.isArray(this._schemaLangs)) { - this._schemaLangs = (_schemaLangs as Set[]).reverse() - } - if (Array.isArray(this._schemaVersions)) { - this._schemaVersions = (_schemaVersions as string[]).reverse() - } - if (isMultiDimensionalArray(this.openDialogResult)) { - this.openDialogResult = (openDialogResult as vscode.Uri[][]).reverse() - } - } - - public async showOpenDialog(options: vscode.OpenDialogOptions): Promise { - if (isMultiDimensionalArray(this.openDialogResult)) { - if (this.openDialogResult!.length <= 0) { - throw new Error('showOpenDialog was called more times than expected') - } - - return (this.openDialogResult as vscode.Uri[][]).pop() - } - - return this.openDialogResult as vscode.Uri[] - } - - public async promptUserForVersion(currVersion?: string): Promise { - return this.schemaVersions.pop() - } - - public async promptUserForLanguage(currRuntime?: SchemaCodeLangs): Promise { - return this.schemaLangs.toArray().pop() - } - - public async promptUserForLocation(): Promise { - if (this.workspaceFolders && this.workspaceFolders.length > 0) { - const temp = this.workspaceFolders[0] - - return temp ? temp.uri : undefined - } else { - const locations = await this.showOpenDialog({}) - - return locations ? locations.pop() : undefined - } - } -} - -describe('SchemaCodeDownloadWizard', async function () { - describe('version', async function () { - it('uses user response as schemaVersion', async function () { - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set([JAVA]), - 'schemaVersion3', - [vscode.Uri.file(path.join('my', 'workspace', 'folder'))] - ) - const wizard = new SchemaCodeDownloadWizard(context) - - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.schemaVersion, 'schemaVersion3') - }) - - it('exits when cancelled', async function () { - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set([JAVA]), - '', - [vscode.Uri.file(path.join('my', 'workspace', 'folder'))] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(!args) - }) - }) - - describe('language', async function () { - it('uses user response as language', async function () { - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set([JAVA]), - 'schemaVersion3', - [vscode.Uri.file(path.join('my', 'workspace', 'folder'))] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.language, JAVA) - }) - - it('backtracks when cancelled', async function () { - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set(), - ['schemaVersion1', 'schemaVersion2', 'schemaVersion3'], - [vscode.Uri.file(path.join('my', 'workspace', 'folder'))] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(!args) - }) - }) - - describe('location', async function () { - it('uses user response as location', async function () { - const locationPath = path.join('my', 'quick', 'pick', 'result') - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set([JAVA]), - 'myVersion', - [vscode.Uri.file(locationPath)] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.location.fsPath, `${path.sep}${locationPath}`) - }) - - it('backtracks when cancelled', async function () { - const locationPath = path.join('my', 'quick', 'pick', 'result') - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - [Set([PYTHON]), Set([JAVA])], - 'myName', - [undefined, [vscode.Uri.file(locationPath)]] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.language, JAVA) - assert.strictEqual(args!.location.fsPath, `${path.sep}${locationPath}`) - }) - - it("contains a 'browse' option", async () => { - const name = 'myInputBoxResult' - const locationPath = path.join('my', 'quick', 'pick', 'result') - - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - [], - Set([JAVA]), - name, - [vscode.Uri.file(locationPath)] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.location.fsPath, `${path.sep}${locationPath}`) - }) - - it('contains an option for each workspace folder', async function () { - const workspaceFolderPaths = [path.join('workspace', 'folder', '1'), path.join('workspace', 'folder', '2')] - - let index = 0 - const context: SchemaCodeDownloadWizardContext = new MockSchemaCodeDownloadWizardContext( - workspaceFolderPaths.map((p) => ({ - uri: vscode.Uri.file(p), - name: path.basename(p), - index: index++, - })), - Set([JAVA]), - 'myName', - [] - ) - const wizard = new SchemaCodeDownloadWizard(context) - const args = await wizard.run() - - assert.ok(args) - assert.strictEqual(args!.location.fsPath, `${path.sep}${workspaceFolderPaths[0]}`) - }) - }) -}) diff --git a/packages/core/src/test/fakeExtensionContext.ts b/packages/core/src/test/fakeExtensionContext.ts index 8c89302900..a0ca039e01 100644 --- a/packages/core/src/test/fakeExtensionContext.ts +++ b/packages/core/src/test/fakeExtensionContext.ts @@ -7,21 +7,10 @@ import * as vscode from 'vscode' import * as path from 'path' import { CredentialsStore } from '../auth/credentials/store' import { ExtContext } from '../shared/extensions' -import { SamCliContext } from '../shared/sam/cli/samCliContext' -import { - minSamCliVersion, - minSamCliVersionForGoSupport, - SamCliValidator, - SamCliValidatorResult, - SamCliVersionValidation, - SamCliVersionValidatorResult, -} from '../shared/sam/cli/samCliValidator' import { DefaultTelemetryService } from '../shared/telemetry/telemetryService' -import { ChildProcessResult } from '../shared/utilities/processUtils' import { UriHandler } from '../shared/vscode/uriHandler' import { FakeTelemetryPublisher } from './fake/fakeTelemetryService' import { MockOutputChannel } from './mockOutputChannel' -import { FakeChildProcessResult, TestSamCliProcessInvoker } from './shared/sam/cli/testSamCliProcessInvoker' import { createTestWorkspaceFolder } from './testUtil' import { FakeAwsContext } from './utilities/fakeAwsContext' import { createTestRegionProvider } from './shared/regions/testUtil' @@ -119,14 +108,6 @@ export class FakeExtensionContext implements vscode.ExtensionContext { public static async getFakeExtContext(): Promise { const ctx = await FakeExtensionContext.create() const awsContext = new FakeAwsContext() - const samCliContext = () => { - return { - invoker: new TestSamCliProcessInvoker((_spawnOptions, _args: any[]): ChildProcessResult => { - return new FakeChildProcessResult({}) - }), - validator: new FakeSamCliValidator(minSamCliVersionForGoSupport), - } as SamCliContext - } const regionProvider = createTestRegionProvider({ awsContext }) const outputChannel = new MockOutputChannel() const fakeTelemetryPublisher = new FakeTelemetryPublisher() @@ -135,7 +116,6 @@ export class FakeExtensionContext implements vscode.ExtensionContext { return { extensionContext: ctx, awsContext, - samCliContext, regionProvider, outputChannel, telemetryService, @@ -191,23 +171,3 @@ export class FakeSecretStorage implements vscode.SecretStorage { this._onDidChange.fire({ key }) } } - -export class FakeSamCliValidator implements SamCliValidator { - private readonly version: string - public constructor(version: string = minSamCliVersion) { - this.version = version - } - public async detectValidSamCli(): Promise { - return { - samCliFound: true, - versionValidation: { - version: this.version, - validation: SamCliVersionValidation.Valid, - }, - } - } - - public async getVersionValidatorResult(): Promise { - return { validation: SamCliVersionValidation.VersionNotParseable } - } -} diff --git a/packages/core/src/test/lambda/activation.test.ts b/packages/core/src/test/lambda/activation.test.ts deleted file mode 100644 index 89c647e5f0..0000000000 --- a/packages/core/src/test/lambda/activation.test.ts +++ /dev/null @@ -1,237 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { LambdaFunctionNode } from '../../lambda/explorer/lambdaFunctionNode' -import * as treeNodeUtils from '../../shared/utilities/treeNodeUtils' -import * as resourceNode from '../../awsService/appBuilder/explorer/nodes/resourceNode' -import * as invokeLambdaModule from '../../lambda/vue/remoteInvoke/invokeLambda' -import * as tailLogGroupModule from '../../awsService/cloudWatchLogs/commands/tailLogGroup' -import { LogDataRegistry } from '../../awsService/cloudWatchLogs/registry/logDataRegistry' -import * as searchLogGroupModule from '../../awsService/cloudWatchLogs/commands/searchLogGroup' - -const mockGeneratedLambdaNode: LambdaFunctionNode = { - functionName: 'generatedFunction', - regionCode: 'us-east-1', - configuration: { - FunctionName: 'generatedFunction', - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:generatedFunction', - }, -} as LambdaFunctionNode - -const mockTreeNode = { - resource: { - deployedResource: { LogicalResourceId: 'TestFunction' }, - region: 'us-east-1', - stackName: 'TestStack', - resource: { Id: 'TestFunction', Type: 'AWS::Serverless::Function' }, - }, -} - -const mockLambdaNode: LambdaFunctionNode = { - functionName: 'testFunction', - regionCode: 'us-west-2', - configuration: { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - LoggingConfig: { - LogGroup: '/aws/lambda/custom-log-group', - }, - }, -} as LambdaFunctionNode - -describe('Lambda activation', () => { - let sandbox: sinon.SinonSandbox - let getSourceNodeStub: sinon.SinonStub - let generateLambdaNodeFromResourceStub: sinon.SinonStub - let invokeRemoteLambdaStub: sinon.SinonStub - let tailLogGroupStub: sinon.SinonStub - let isTreeNodeStub: sinon.SinonStub - let searchLogGroupStub: sinon.SinonStub - let registry: LogDataRegistry - - beforeEach(async () => { - sandbox = sinon.createSandbox() - searchLogGroupStub = sandbox.stub(searchLogGroupModule, 'searchLogGroup') - registry = LogDataRegistry.instance - getSourceNodeStub = sandbox.stub(treeNodeUtils, 'getSourceNode') - generateLambdaNodeFromResourceStub = sandbox.stub(resourceNode, 'generateLambdaNodeFromResource') - invokeRemoteLambdaStub = sandbox.stub(invokeLambdaModule, 'invokeRemoteLambda') - tailLogGroupStub = sandbox.stub(tailLogGroupModule, 'tailLogGroup') - isTreeNodeStub = sandbox.stub(require('../../shared/treeview/resourceTreeDataProvider'), 'isTreeNode') - }) - - afterEach(() => { - sandbox.restore() - }) - describe('aws.appBuilder.searchLogs command', () => { - it('should handle LambdaFunctionNode directly', async () => { - getSourceNodeStub.returns(mockLambdaNode) - isTreeNodeStub.returns(false) - searchLogGroupStub.resolves() - - const node = {} - await vscode.commands.executeCommand('aws.appBuilder.searchLogs', node) - - assert(searchLogGroupStub.calledOnce) - assert( - searchLogGroupStub.calledWith(registry, 'AppBuilderSearchLogs', { - regionName: 'us-west-2', - groupName: '/aws/lambda/custom-log-group', - }) - ) - }) - - it('should generate LambdaFunctionNode from TreeNode when getSourceNode returns undefined', async () => { - getSourceNodeStub.returns(undefined) - isTreeNodeStub.returns(true) - generateLambdaNodeFromResourceStub.resolves(mockGeneratedLambdaNode) - searchLogGroupStub.resolves() - - await vscode.commands.executeCommand('aws.appBuilder.searchLogs', mockTreeNode) - - assert(generateLambdaNodeFromResourceStub.calledOnce) - assert(generateLambdaNodeFromResourceStub.calledWith(mockTreeNode.resource)) - assert(searchLogGroupStub.calledOnce) - assert( - searchLogGroupStub.calledWith(registry, 'AppBuilderSearchLogs', { - regionName: 'us-east-1', - groupName: '/aws/lambda/generatedFunction', - }) - ) - }) - - it('should log error and throw ToolkitError when generateLambdaNodeFromResource fails', async () => { - getSourceNodeStub.returns(undefined) - isTreeNodeStub.returns(true) - generateLambdaNodeFromResourceStub.rejects(new Error('Failed to generate node')) - searchLogGroupStub.resolves() - - await vscode.commands.executeCommand('aws.appBuilder.searchLogs', mockTreeNode) - assert(searchLogGroupStub.notCalled) - }) - }) - - describe('aws.invokeLambda command', () => { - it('should handle LambdaFunctionNode directly from AWS Explorer', async () => { - isTreeNodeStub.returns(false) - invokeRemoteLambdaStub.resolves() - - await vscode.commands.executeCommand('aws.invokeLambda', mockLambdaNode) - - assert(invokeRemoteLambdaStub.calledOnce) - const callArgs = invokeRemoteLambdaStub.getCall(0).args - assert.strictEqual(callArgs[1].source, 'AwsExplorerRemoteInvoke') - assert.strictEqual(callArgs[1].functionNode, mockLambdaNode) - }) - - it('should generate LambdaFunctionNode from TreeNode when coming from AppBuilder', async () => { - isTreeNodeStub.returns(true) - getSourceNodeStub.returns(undefined) - generateLambdaNodeFromResourceStub.resolves(mockGeneratedLambdaNode) - invokeRemoteLambdaStub.resolves() - - await vscode.commands.executeCommand('aws.invokeLambda', mockTreeNode) - - assert(generateLambdaNodeFromResourceStub.calledOnce) - assert(generateLambdaNodeFromResourceStub.calledWith(mockTreeNode.resource)) - assert(invokeRemoteLambdaStub.calledOnce) - const callArgs = invokeRemoteLambdaStub.getCall(0).args - assert.strictEqual(callArgs[1].source, 'AppBuilderRemoteInvoke') - assert.strictEqual(callArgs[1].functionNode, mockGeneratedLambdaNode) - }) - - it('should handle existing LambdaFunctionNode from TreeNode', async () => { - const mockTreeNode = { - resource: {}, - } - - isTreeNodeStub.returns(true) - getSourceNodeStub.returns(mockLambdaNode) - invokeRemoteLambdaStub.resolves() - - await vscode.commands.executeCommand('aws.invokeLambda', mockTreeNode) - - assert(generateLambdaNodeFromResourceStub.notCalled) - assert(invokeRemoteLambdaStub.calledOnce) - const callArgs = invokeRemoteLambdaStub.getCall(0).args - assert.strictEqual(callArgs[1].source, 'AppBuilderRemoteInvoke') - assert.strictEqual(callArgs[1].functionNode, mockLambdaNode) - }) - }) - - describe('aws.appBuilder.tailLogs command', () => { - it('should handle LambdaFunctionNode directly', async () => { - isTreeNodeStub.returns(false) - getSourceNodeStub.returns(mockLambdaNode) - tailLogGroupStub.resolves() - - await vscode.commands.executeCommand('aws.appBuilder.tailLogs', mockLambdaNode) - - assert(tailLogGroupStub.calledOnce) - const callArgs = tailLogGroupStub.getCall(0).args - assert.strictEqual(callArgs[1], 'AwsExplorerLambdaNode') - assert.deepStrictEqual(callArgs[3], { - regionName: 'us-west-2', - groupName: '/aws/lambda/custom-log-group', - }) - assert.deepStrictEqual(callArgs[4], { type: 'all' }) - }) - - it('should generate LambdaFunctionNode from TreeNode when getSourceNode returns undefined', async () => { - const mockGeneratedLambdaNode: LambdaFunctionNode = { - functionName: 'generatedFunction', - regionCode: 'us-east-1', - configuration: { - FunctionName: 'generatedFunction', - }, - } as LambdaFunctionNode - - isTreeNodeStub.returns(true) - getSourceNodeStub.returns(undefined) - generateLambdaNodeFromResourceStub.resolves(mockGeneratedLambdaNode) - tailLogGroupStub.resolves() - - await vscode.commands.executeCommand('aws.appBuilder.tailLogs', mockTreeNode) - - assert(generateLambdaNodeFromResourceStub.calledOnce) - assert(generateLambdaNodeFromResourceStub.calledWith(mockTreeNode.resource)) - assert(tailLogGroupStub.calledOnce) - const callArgs = tailLogGroupStub.getCall(0).args - assert.strictEqual(callArgs[1], 'AppBuilder') - assert.deepStrictEqual(callArgs[3], { - regionName: 'us-east-1', - groupName: '/aws/lambda/generatedFunction', - }) - assert.deepStrictEqual(callArgs[4], { type: 'all' }) - }) - - it('should use correct source for TreeNode', async () => { - const mockLambdaNode: LambdaFunctionNode = { - functionName: 'testFunction', - regionCode: 'us-west-2', - configuration: { - FunctionName: 'testFunction', - }, - } as LambdaFunctionNode - - const mockTreeNode = { - resource: {}, - } - - isTreeNodeStub.returns(true) - getSourceNodeStub.returns(mockLambdaNode) - tailLogGroupStub.resolves() - - await vscode.commands.executeCommand('aws.appBuilder.tailLogs', mockTreeNode) - - assert(tailLogGroupStub.calledOnce) - const callArgs = tailLogGroupStub.getCall(0).args - assert.strictEqual(callArgs[1], 'AppBuilder') - }) - }) -}) diff --git a/packages/core/src/test/lambda/commands/copyLambdaUrl.test.ts b/packages/core/src/test/lambda/commands/copyLambdaUrl.test.ts deleted file mode 100644 index acd3b98f05..0000000000 --- a/packages/core/src/test/lambda/commands/copyLambdaUrl.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { createStubInstance, SinonStubbedInstance, stub, SinonStub } from 'sinon' -import { copyLambdaUrl, createLambdaFuncUrlPrompter, noLambdaFuncMessage } from '../../../lambda/commands/copyLambdaUrl' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { DefaultLambdaClient, LambdaClient } from '../../../shared/clients/lambdaClient' -import { addCodiconToString } from '../../../shared/utilities/textUtilities' -import { env } from 'vscode' -import { FunctionUrlConfig } from '@aws-sdk/client-lambda' -import { createQuickPickPrompterTester } from '../../shared/ui/testUtils' -import { getTestWindow } from '../../shared/vscode/window' - -/** - * Builds an instance of {@link FunctionUrlConfig} without the - * need to define all values. - * - * @param options key + value of {@link FunctionUrlConfig} - */ -export function buildFunctionUrlConfig(options: Partial): FunctionUrlConfig { - return { - AuthType: options.AuthType, - CreationTime: options.CreationTime, - FunctionArn: options.FunctionArn, - FunctionUrl: options.FunctionUrl, - LastModifiedTime: options.LastModifiedTime, - } -} - -describe('copy lambda function URL to clipboard', async () => { - let client: SinonStubbedInstance - let node: Pick - let quickPickUrlFunc: SinonStub - - const nodeName = 'my-node-name' - - beforeEach(async () => { - client = createStubInstance(DefaultLambdaClient) - quickPickUrlFunc = stub() - - node = { name: nodeName, regionCode: 'myRegion' } - }) - - it('Single URL exists', async () => { - const urlConfig = buildFunctionUrlConfig({ FunctionUrl: 'url1', FunctionArn: 'arn1' }) - client.getFunctionUrlConfigs.resolves([urlConfig]) - - await copyLambdaUrl(node, client) - - assert.strictEqual(await env.clipboard.readText(), urlConfig.FunctionUrl) - }) - - it('Multiple URLs exists', async () => { - const expectedConfig = buildFunctionUrlConfig({ FunctionUrl: 'url1', FunctionArn: 'arn1' }) - const urlConfigs = [expectedConfig, buildFunctionUrlConfig({ FunctionUrl: 'url2', FunctionArn: 'arn2' })] - client.getFunctionUrlConfigs.resolves(urlConfigs) - quickPickUrlFunc.resolves(expectedConfig.FunctionUrl) - - await copyLambdaUrl(node, client, quickPickUrlFunc) - - assert.deepStrictEqual(quickPickUrlFunc.args, [[urlConfigs]]) - assert.strictEqual(await env.clipboard.readText(), expectedConfig.FunctionUrl) - }) - - describe("URL doesn't exist", async () => { - before(async () => { - await env.clipboard.writeText('') // clear clipboard - }) - - it(`URL doesn't exist`, async () => { - client.getFunctionUrlConfigs.resolves([]) - - await copyLambdaUrl(node, client) - - assert.strictEqual(await env.clipboard.readText(), '') - getTestWindow().getFirstMessage().assertWarn(noLambdaFuncMessage) - assert.deepStrictEqual(getTestWindow().statusBar.messages, [ - addCodiconToString('circle-slash', 'No URL for Lambda function.'), - ]) - }) - }) -}) - -describe('lambda func url prompter', async () => { - it('prompts for lambda function ARN', async () => { - const configList: FunctionUrlConfig[] = [ - { FunctionUrl: 'url1', FunctionArn: 'arn1' }, - { FunctionUrl: 'url2', FunctionArn: 'arn2' }, - ] - const prompter = createLambdaFuncUrlPrompter(configList) - const tester = createQuickPickPrompterTester(prompter) - tester.assertItems( - configList.map((c) => { - return { label: c.FunctionArn!, data: c.FunctionUrl } // order matters - }) - ) - tester.acceptItem(configList[1].FunctionArn!) - await tester.result(configList[1].FunctionUrl) - }) -}) diff --git a/packages/core/src/test/lambda/commands/createNewSamApp.test.ts b/packages/core/src/test/lambda/commands/createNewSamApp.test.ts deleted file mode 100644 index c31f922483..0000000000 --- a/packages/core/src/test/lambda/commands/createNewSamApp.test.ts +++ /dev/null @@ -1,311 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as pathutils from '../../../shared/utilities/pathUtils' -import * as testutil from '../../testUtil' -import * as vscode from 'vscode' -import { FakeExtensionContext } from '../../fakeExtensionContext' -import { - addInitialLaunchConfiguration, - getProjectUri, - samInitTemplateFiles, - writeToolkitReadme, -} from '../../../lambda/commands/createNewSamApp' -import { LaunchConfiguration } from '../../../shared/debug/launchConfiguration' -import { makeSampleSamTemplateYaml } from '../../shared/cloudformation/cloudformationTestUtils' -import { makeTemporaryToolkitFolder, readFileAsString } from '../../../shared/filesystemUtilities' -import { ExtContext } from '../../../shared/extensions' -import { - AwsSamDebuggerConfiguration, - TemplateTargetProperties, -} from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import { normalize } from '../../../shared/utilities/pathUtils' -import { getIdeProperties, isCloud9 } from '../../../shared/extensionUtilities' -import globals from '../../../shared/extensionGlobals' -import { Runtime } from '@aws-sdk/client-lambda' -import { stub } from '../../utilities/stubber' -import sinon from 'sinon' -import { fs } from '../../../shared' - -const templateYaml = 'template.yaml' - -describe('createNewSamApp', function () { - let mockLaunchConfiguration: LaunchConfiguration - let tempFolder: string - let tempTemplate: vscode.Uri - let fakeWorkspaceFolder: vscode.WorkspaceFolder - let fakeContext: ExtContext - let fakeResponse: { location: vscode.Uri; name: string } - let fakeTarget: string - let fakeConfig: vscode.DebugConfiguration - let addDebugStub: sinon.SinonStub - - beforeEach(async function () { - mockLaunchConfiguration = stub(LaunchConfiguration, { - workspaceFolder: undefined, - scopedResource: vscode.Uri.parse(''), - }) - fakeContext = await FakeExtensionContext.getFakeExtContext() - tempFolder = await makeTemporaryToolkitFolder() - tempTemplate = vscode.Uri.file(path.join(tempFolder, 'test.yaml')) - fakeTarget = path.join(tempFolder, templateYaml) - await testutil.toFile('target file', fakeTarget) - - fakeWorkspaceFolder = { - uri: vscode.Uri.file(path.dirname(tempFolder)), - name: 'It was me, fakeWorkspaceFolder!', - index: 0, - } - - fakeConfig = { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'template', - templatePath: '${workspaceFolder}/test.yaml', - logicalId: 'resource', - }, - } - - fakeResponse = { location: fakeWorkspaceFolder.uri, name: path.basename(tempFolder) } - - mockLaunchConfiguration.getDebugConfigurations = sinon.stub().returns([fakeConfig]) - addDebugStub = sinon.stub().resolves() - mockLaunchConfiguration.addDebugConfigurations = addDebugStub - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - const r = await globals.templateRegistry - r.reset() - }) - - describe('getProjectUri', function () { - it('returns the target file when it exists', async function () { - assert.strictEqual( - normalize((await getProjectUri(fakeResponse, samInitTemplateFiles))?.fsPath ?? ''), - normalize(fakeTarget) - ) - }) - it('returns the target ".yml" file when it exists', async function () { - await fs.delete(fakeTarget, { recursive: true }) - tempTemplate = vscode.Uri.file(path.join(tempFolder, 'test.yml')) - fakeTarget = path.join(tempFolder, 'template.yml') - await testutil.toFile('target file', fakeTarget) - assert.strictEqual( - normalize((await getProjectUri(fakeResponse, samInitTemplateFiles))?.fsPath ?? ''), - normalize(fakeTarget) - ) - }) - it('returns undefined when the target does not exist', async function () { - const badResponse1 = { location: fakeResponse.location, name: 'notreal' } - const badResponse2 = { location: vscode.Uri.parse('fake://notreal'), name: 'notafile' } - assert.strictEqual((await getProjectUri(badResponse1, samInitTemplateFiles))?.fsPath, undefined) - assert.strictEqual((await getProjectUri(badResponse2, samInitTemplateFiles))?.fsPath, undefined) - }) - }) - - describe('addInitialLaunchConfiguration', function () { - it('produces and returns initial launch configurations', async function () { - await testutil.toFile(makeSampleSamTemplateYaml(true), tempTemplate.fsPath) - - // without runtime - await (await globals.templateRegistry).addItem(tempTemplate) - const launchConfigs = await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - (await getProjectUri(fakeResponse, samInitTemplateFiles))!, - undefined, - mockLaunchConfiguration - ) - - const [arg] = addDebugStub.lastCall.args - assert.ok( - pathutils.areEqual( - fakeWorkspaceFolder.uri.fsPath, - (arg[0].invokeTarget as TemplateTargetProperties).templatePath, - tempTemplate.fsPath, - true - ) - ) - assert.ok(launchConfigs) - const matchingConfigs = launchConfigs?.filter((config) => { - return pathutils.areEqual( - fakeWorkspaceFolder.uri.fsPath, - (config.invokeTarget as TemplateTargetProperties).templatePath, - tempTemplate.fsPath, - true - ) - }) - assert.ok(matchingConfigs) - assert.strictEqual(matchingConfigs!.length, 1) - }) - - it('produces and returns initial launch configurations with runtime', async function () { - await testutil.toFile(makeSampleSamTemplateYaml(true), tempTemplate.fsPath) - - // without runtime - await (await globals.templateRegistry).addItem(tempTemplate) - const launchConfigs = (await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - tempTemplate, - 'someruntime' as Runtime, - mockLaunchConfiguration - )) as AwsSamDebuggerConfiguration[] - - const [arg] = addDebugStub.lastCall.args - assert.ok( - pathutils.areEqual( - fakeWorkspaceFolder.uri.fsPath, - (arg[0].invokeTarget as TemplateTargetProperties).templatePath, - tempTemplate.fsPath, - true - ) - ) - assert.ok(launchConfigs) - const matchingConfigs = launchConfigs?.filter((config) => { - return ( - pathutils.areEqual( - fakeWorkspaceFolder.uri.fsPath, - (config.invokeTarget as TemplateTargetProperties).templatePath, - tempTemplate.fsPath, - true - ) && config.lambda?.runtime === 'someruntime' - ) - }) - assert.ok(matchingConfigs) - assert.strictEqual(matchingConfigs!.length, 1) - }) - - it('returns a blank array if it does not match any launch configs', async function () { - await testutil.toFile(makeSampleSamTemplateYaml(true), tempTemplate.fsPath) - - await (await globals.templateRegistry).addItem(tempTemplate) - const launchConfigs = await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - vscode.Uri.file(path.join(tempFolder, 'otherFolder', 'thisAintIt.yaml')), - undefined, - mockLaunchConfiguration - ) - assert.deepStrictEqual(launchConfigs, []) - }) - - it('produces a launch config when config has a relative path', async function () { - ;(fakeConfig as any).invokeTarget.templatePath = 'test.yaml' - - await testutil.toFile(makeSampleSamTemplateYaml(true), tempTemplate.fsPath) - - await (await globals.templateRegistry).addItem(tempTemplate) - const launchConfigs = await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - (await getProjectUri(fakeResponse, samInitTemplateFiles))!, - undefined, - mockLaunchConfiguration - ) - assert.ok(launchConfigs?.length !== 0) - }) - - // File structure generated by this test: - // - // \---WorkspaceFolder - // \---tempFolder - // \---test.yaml - // \---SAM_INIT_OPEN_TARGET - // \---subfolder - // \---test.yaml - // \---otherFolder - // \---test.yaml - // \---SAM_INIT_OPEN_TARGET - // - it('returns only templates within the same base directory as target', async function () { - const otherFolder1: string = path.join(fakeWorkspaceFolder.uri.fsPath, 'otherFolder') - const otherFolder2: string = path.join(tempFolder, 'subfolder') - const otherTemplate1: vscode.Uri = vscode.Uri.file(path.join(otherFolder1, 'test.yaml')) - const otherTemplate2: vscode.Uri = vscode.Uri.file(path.join(otherFolder2, 'test.yaml')) - - await testutil.toFile(makeSampleSamTemplateYaml(true), tempTemplate.fsPath) - await testutil.toFile(makeSampleSamTemplateYaml(true), otherTemplate1.fsPath) - await testutil.toFile(makeSampleSamTemplateYaml(true), otherTemplate2.fsPath) - await testutil.toFile('target file', path.join(otherFolder1, templateYaml)) - - await (await globals.templateRegistry).addItem(tempTemplate) - await (await globals.templateRegistry).addItem(otherTemplate1) - await (await globals.templateRegistry).addItem(otherTemplate2) - - const launchConfigs1 = await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - (await getProjectUri(fakeResponse, samInitTemplateFiles))!, - undefined, - mockLaunchConfiguration - ) - - const launchConfigs2 = await addInitialLaunchConfiguration( - fakeContext, - fakeWorkspaceFolder, - (await getProjectUri( - { location: fakeWorkspaceFolder.uri, name: 'otherFolder' }, - samInitTemplateFiles - ))!, - undefined, - mockLaunchConfiguration - ) - - assert.notDeepStrictEqual(launchConfigs1, launchConfigs2) - assert.strictEqual(launchConfigs1!.length, 2) - assert.strictEqual(launchConfigs2!.length, 1) - }) - }) - - describe('writeToolkitReadme', async () => { - let tempPath: string - before(async () => { - const dir = await makeTemporaryToolkitFolder() - tempPath = path.join(dir, 'temp.md') - }) - - it('replaces flags in the file - VS Code', async () => { - const success = await writeToolkitReadme( - tempPath, - [ - { - type: 'foo', - name: 'fooName', - request: 'fooRequest', - }, - { - type: 'bar', - name: 'barName', - request: 'barRequest', - }, - ], - async (path: string) => { - return '${PRODUCTNAME} ${IDE} ${CODELENS} ${COMPANYNAME} ${COMMANDPALETTE} ${DOCURL}${LISTOFCONFIGURATIONS}' - } - ) - - assert.ok(success) - const content = await readFileAsString(tempPath) - assert.strictEqual( - content, - `${getIdeProperties().company} Toolkit For ${getIdeProperties().longName} ${ - getIdeProperties().shortName - } ${getIdeProperties().codelens} ${getIdeProperties().company} ${getIdeProperties().commandPalette} ${ - isCloud9() - ? 'https://docs.aws.amazon.com/cloud9/latest/user-guide/serverless-apps-toolkit.html' - : 'https://docs.aws.amazon.com/toolkit-for-vscode/latest/userguide/serverless-apps.html' - } -* fooName -* barName` - ) - }) - }) -}) diff --git a/packages/core/src/test/lambda/commands/deleteLambda.test.ts b/packages/core/src/test/lambda/commands/deleteLambda.test.ts deleted file mode 100644 index 8da82c2c21..0000000000 --- a/packages/core/src/test/lambda/commands/deleteLambda.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { deleteLambda } from '../../../lambda/commands/deleteLambda' -import { DefaultLambdaClient } from '../../../shared/clients/lambdaClient' -import { getTestWindow } from '../../shared/vscode/window' -import { stub } from '../../utilities/stubber' - -describe('deleteLambda', async function () { - function createLambdaClient() { - const client = stub(DefaultLambdaClient, { regionCode: 'region-1', userAgent: undefined }) - client.deleteFunction.resolves() - - return client - } - - const buildDeleteRegExp = (name: string) => new RegExp(`delete.*${name}`) - - it('throws if there is no function name', async function () { - await assert.rejects(deleteLambda({}, createLambdaClient())) - }) - - it('should delete lambda when confirmed', async function () { - const client = createLambdaClient() - const confirmDialog = getTestWindow().waitForMessage(buildDeleteRegExp('my-function')) - - await Promise.all([ - confirmDialog.then((dialog) => dialog.selectItem('Delete')), - deleteLambda({ FunctionName: 'my-function' }, client), - ]) - - assert.strictEqual(client.deleteFunction.callCount, 1) - - // TODO(sijaden): remove `duration` from metric metadata (it's not metadata?) - // assertTelemetry('lambda_delete', { result: 'Succeeded' }) - }) - - it('should not delete lambda when cancelled', async function () { - const client = createLambdaClient() - const confirmDialog = getTestWindow().waitForMessage(buildDeleteRegExp('another-function')) - - await Promise.all([ - confirmDialog.then((dialog) => dialog.close()), - deleteLambda({ FunctionName: 'another-function' }, client), - ]) - - assert.strictEqual(client.deleteFunction.callCount, 0) - // assertTelemetry('lambda_delete', { result: 'Cancelled' }) - }) - - it('should handles errors gracefully', async function () { - const client = createLambdaClient() - const confirmDialog = getTestWindow().waitForMessage(buildDeleteRegExp('bad-name')) - const viewLogs = getTestWindow().waitForMessage(/There was an error/) - - client.deleteFunction.rejects(new Error('Lambda function does not exist')) - - await Promise.all([ - viewLogs.then((dialog) => dialog.close()), - confirmDialog.then((dialog) => dialog.selectItem('Delete')), - deleteLambda({ FunctionName: 'bad-name' }, client), - ]) - - // assertTelemetry('lambda_delete', { result: 'Failed' }) - }) -}) diff --git a/packages/core/src/test/lambda/commands/downloadLambda.test.ts b/packages/core/src/test/lambda/commands/downloadLambda.test.ts deleted file mode 100644 index 12b99b66ed..0000000000 --- a/packages/core/src/test/lambda/commands/downloadLambda.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { openLambdaFile } from '../../../lambda/commands/downloadLambda' -import assert from 'assert' - -describe('downloadLambda', async function () { - describe('openLambdaFile', async function () { - it('throws if a file does not exist', async function () { - await assert.rejects(openLambdaFile('/asdfasdfasfdasdfasdf.js')) - }) - }) -}) diff --git a/packages/core/src/test/lambda/commands/editLambda.test.ts b/packages/core/src/test/lambda/commands/editLambda.test.ts deleted file mode 100644 index 44d874c14f..0000000000 --- a/packages/core/src/test/lambda/commands/editLambda.test.ts +++ /dev/null @@ -1,307 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import * as sinon from 'sinon' -import { - editLambda, - watchForUpdates, - promptForSync, - deployFromTemp, - getReadme, - deleteFilesInFolder, - overwriteChangesForEdit, -} from '../../../lambda/commands/editLambda' -import { LambdaFunction } from '../../../lambda/commands/uploadLambda' -import * as downloadLambda from '../../../lambda/commands/downloadLambda' -import * as uploadLambda from '../../../lambda/commands/uploadLambda' -import * as utils from '../../../lambda/utils' -import * as messages from '../../../shared/utilities/messages' -import fs from '../../../shared/fs/fs' -import { LambdaFunctionNodeDecorationProvider } from '../../../lambda/explorer/lambdaFunctionNodeDecorationProvider' -import path from 'path' -import globals from '../../../shared/extensionGlobals' -import { lambdaTempPath } from '../../../lambda/utils' - -describe('editLambda', function () { - let mockLambda: LambdaFunction - let mockTemp: string - let mockUri: vscode.Uri - - // Stub variables - let getFunctionInfoStub: sinon.SinonStub - let setFunctionInfoStub: sinon.SinonStub - let compareCodeShaStub: sinon.SinonStub - let downloadLambdaStub: sinon.SinonStub - let openLambdaFileStub: sinon.SinonStub - let runUploadDirectoryStub: sinon.SinonStub - let showConfirmationMessageStub: sinon.SinonStub - let createFileSystemWatcherStub: sinon.SinonStub - let existsDirStub: sinon.SinonStub - let mkdirStub: sinon.SinonStub - let promptDeployStub: sinon.SinonStub - let readdirStub: sinon.SinonStub - let readFileTextStub: sinon.SinonStub - let writeFileStub: sinon.SinonStub - let copyStub: sinon.SinonStub - let asAbsolutePathStub: sinon.SinonStub - let deleteStub: sinon.SinonStub - - beforeEach(function () { - mockLambda = { - name: 'test-function', - region: 'us-east-1', - configuration: { - FunctionName: 'test-function', - CodeSha256: 'test-sha', - Runtime: 'nodejs18.x', - }, - } - mockTemp = utils.getTempLocation(mockLambda.name, mockLambda.region) - mockUri = vscode.Uri.file(mockTemp) - - // Create stubs - getFunctionInfoStub = sinon.stub(utils, 'getFunctionInfo').resolves(undefined) - setFunctionInfoStub = sinon.stub(utils, 'setFunctionInfo').resolves() - compareCodeShaStub = sinon.stub(utils, 'compareCodeSha').resolves(true) - downloadLambdaStub = sinon.stub(downloadLambda, 'downloadLambdaInLocation').resolves() - openLambdaFileStub = sinon.stub(downloadLambda, 'openLambdaFile').resolves() - runUploadDirectoryStub = sinon.stub(uploadLambda, 'runUploadDirectory').resolves() - showConfirmationMessageStub = sinon.stub(messages, 'showConfirmationMessage').resolves(true) - createFileSystemWatcherStub = sinon.stub(vscode.workspace, 'createFileSystemWatcher').returns({ - onDidChange: sinon.stub(), - onDidCreate: sinon.stub(), - onDidDelete: sinon.stub(), - dispose: sinon.stub(), - } as any) - existsDirStub = sinon.stub(fs, 'existsDir').resolves(true) - mkdirStub = sinon.stub(fs, 'mkdir').resolves() - readdirStub = sinon.stub(fs, 'readdir').resolves([['file', vscode.FileType.File]]) - promptDeployStub = sinon.stub().resolves(true) - sinon.replace(require('../../../lambda/commands/editLambda'), 'promptDeploy', promptDeployStub) - readFileTextStub = sinon.stub(fs, 'readFileText').resolves('# Lambda Edit README') - writeFileStub = sinon.stub(fs, 'writeFile').resolves() - copyStub = sinon.stub(fs, 'copy').resolves() - asAbsolutePathStub = sinon.stub(globals.context, 'asAbsolutePath').callsFake((p) => `/absolute/${p}`) - deleteStub = sinon.stub(fs, 'delete').resolves() - - // Other stubs - sinon.stub(utils, 'getLambdaDetails').returns({ fileName: 'index.js', functionName: 'test-function' }) - sinon.stub(fs, 'stat').resolves({ ctime: Date.now() } as any) - sinon.stub(vscode.workspace, 'saveAll').resolves(true) - sinon.stub(LambdaFunctionNodeDecorationProvider.prototype, 'addBadge').resolves() - sinon.stub(LambdaFunctionNodeDecorationProvider.prototype, 'removeBadge').resolves() - sinon.stub(LambdaFunctionNodeDecorationProvider, 'getInstance').returns({ - addBadge: sinon.stub().resolves(), - removeBadge: sinon.stub().resolves(), - } as any) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('editLambda', function () { - it('returns early if folder already exists in workspace', async function () { - sinon.stub(vscode.workspace, 'workspaceFolders').value([{ uri: vscode.Uri.file(mockTemp) }]) - - const result = await editLambda(mockLambda) - - assert.strictEqual(result, mockTemp) - }) - - it('downloads lambda when no local code exists', async function () { - await editLambda(mockLambda) - - assert(downloadLambdaStub.calledOnce) - }) - - it('prompts for overwrite when local code differs from remote', async function () { - getFunctionInfoStub.resolves('old-sha') - compareCodeShaStub.resolves(false) - - await editLambda(mockLambda) - - assert(showConfirmationMessageStub.calledOnce) - }) - - it('opens existing file when user declines overwrite', async function () { - getFunctionInfoStub.resolves('old-sha') - compareCodeShaStub.resolves(false) - showConfirmationMessageStub.resolves(false) - - // Specify that it's from the explorer because otherwise there's no need to open - await editLambda(mockLambda, 'explorer') - - assert(openLambdaFileStub.calledOnce) - }) - - it('downloads lambda when directory exists but is empty', async function () { - getFunctionInfoStub.resolves('old-sha') - readdirStub.resolves([]) - - await editLambda(mockLambda) - - assert(downloadLambdaStub.calledOnce) - assert(showConfirmationMessageStub.notCalled) - }) - - it('downloads lambda when directory does not exist', async function () { - getFunctionInfoStub.resolves('old-sha') - existsDirStub.resolves(false) - - await editLambda(mockLambda) - - assert(downloadLambdaStub.calledOnce) - assert(showConfirmationMessageStub.notCalled) - }) - - it('sets up file watcher after download', async function () { - const watcherStub = { - onDidChange: sinon.stub(), - onDidCreate: sinon.stub(), - onDidDelete: sinon.stub(), - } - createFileSystemWatcherStub.returns(watcherStub) - - await editLambda(mockLambda) - - assert(watcherStub.onDidChange.calledOnce) - assert(watcherStub.onDidCreate.calledOnce) - assert(watcherStub.onDidDelete.calledOnce) - }) - }) - - describe('watchForUpdates', function () { - it('creates file system watcher with correct pattern', function () { - const watcher = { - onDidChange: sinon.stub(), - onDidCreate: sinon.stub(), - onDidDelete: sinon.stub(), - } - createFileSystemWatcherStub.returns(watcher) - - watchForUpdates(mockLambda, mockUri) - - assert(createFileSystemWatcherStub.calledOnce) - const pattern = createFileSystemWatcherStub.firstCall.args[0] - assert(pattern instanceof vscode.RelativePattern) - }) - - it('sets up change, create, and delete handlers', function () { - const watcher = { - onDidChange: sinon.stub(), - onDidCreate: sinon.stub(), - onDidDelete: sinon.stub(), - } - createFileSystemWatcherStub.returns(watcher) - - watchForUpdates(mockLambda, mockUri) - - assert(watcher.onDidChange.calledOnce) - assert(watcher.onDidCreate.calledOnce) - assert(watcher.onDidDelete.calledOnce) - }) - }) - - describe('promptForSync', function () { - it('returns early if directory does not exist', async function () { - existsDirStub.resolves(false) - - await promptForSync(mockLambda, mockUri, vscode.Uri.file('/test/file.js')) - - assert(setFunctionInfoStub.notCalled) - }) - }) - - describe('deployFromTemp', function () { - it('uploads without confirmation when code is up to date', async function () { - await deployFromTemp(mockLambda, mockUri) - - assert(showConfirmationMessageStub.notCalled) - assert(runUploadDirectoryStub.calledOnce) - }) - - it('prompts for confirmation when code is outdated', async function () { - compareCodeShaStub.resolves(false) - - await deployFromTemp(mockLambda, mockUri) - - assert(showConfirmationMessageStub.calledOnce) - }) - - it('does not upload when user declines overwrite', async function () { - compareCodeShaStub.resolves(false) - showConfirmationMessageStub.resolves(false) - - await deployFromTemp(mockLambda, mockUri) - - assert(runUploadDirectoryStub.notCalled) - }) - - it('updates function info after successful upload', async function () { - await deployFromTemp(mockLambda, mockUri) - - assert(runUploadDirectoryStub.calledOnce) - assert( - setFunctionInfoStub.calledWith(mockLambda, { - lastDeployed: sinon.match.number, - undeployed: false, - }) - ) - }) - }) - - describe('deleteFilesInFolder', function () { - it('deletes all files in the specified folder', async function () { - readdirStub.resolves([ - ['file1.js', vscode.FileType.File], - ['file2.js', vscode.FileType.File], - ]) - - await deleteFilesInFolder(path.join('test', 'folder')) - - assert(deleteStub.calledTwice) - assert(deleteStub.calledWith(path.join('test', 'folder', 'file1.js'), { recursive: true, force: true })) - assert(deleteStub.calledWith(path.join('test', 'folder', 'file2.js'), { recursive: true, force: true })) - }) - }) - - describe('overwriteChangesForEdit', function () { - it('clears directory and downloads lambda code', async function () { - await overwriteChangesForEdit(mockLambda, mockTemp) - - assert(readdirStub.calledWith(mockTemp)) - assert(downloadLambdaStub.calledWith(mockLambda, 'local', mockTemp)) - assert(setFunctionInfoStub.calledWith(mockLambda, sinon.match.object)) - }) - - it('creates directory if it does not exist', async function () { - existsDirStub.resolves(false) - - await overwriteChangesForEdit(mockLambda, mockTemp) - - assert(mkdirStub.calledWith(mockTemp)) - }) - }) - - describe('getReadme', function () { - it('reads markdown file and writes README.md to temp path', async function () { - const result = await getReadme() - - assert(readFileTextStub.calledOnce) - assert(asAbsolutePathStub.calledWith(path.join('resources', 'markdown', 'lambdaEdit.md'))) - assert(writeFileStub.calledWith(path.join(lambdaTempPath, 'README.md'), '# Lambda Edit README')) - assert.strictEqual(result, path.join(lambdaTempPath, 'README.md')) - }) - - it('copies all required icon files', async function () { - await getReadme() - - assert.strictEqual(copyStub.callCount, 3) - }) - }) -}) diff --git a/packages/core/src/test/lambda/commands/listSamResources.test.ts b/packages/core/src/test/lambda/commands/listSamResources.test.ts deleted file mode 100644 index 428a04316e..0000000000 --- a/packages/core/src/test/lambda/commands/listSamResources.test.ts +++ /dev/null @@ -1,113 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon from 'sinon' -import { getDeployedResources, StackResource } from '../../../lambda/commands/listSamResources' -import * as SamCliListResourcesModule from '../../../shared/sam/cli/samCliListResources' -import assert from 'assert' -import { getTestWindow } from '../../shared/vscode/window' -import { assertLogsContain } from '../../globalSetup.test' - -describe('listSamResources', () => { - const mockParams = { invoker: {}, listResourcesParams: {} } - describe('getDeployedResources', () => { - let sandbox: sinon.SinonSandbox - let runSamCliListResourceStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - runSamCliListResourceStub = sandbox.stub(SamCliListResourcesModule, 'runSamCliListResource') - }) - - afterEach(() => { - sandbox.restore() - }) - - it('returns only deployed resources given mixed resource returned from SAM api', async () => { - runSamCliListResourceStub.resolves(JSON.stringify([...localUndeployedResources, ...deployedResources])) - const result = await getDeployedResources(mockParams) - - assert.deepStrictEqual(result, deployedResources) - assert(runSamCliListResourceStub.calledOnceWith(mockParams.invoker, mockParams.listResourcesParams)) - }) - - it('returns empty array went given no deployed resources', async () => { - runSamCliListResourceStub.resolves(JSON.stringify(localUndeployedResources)) - const result = await getDeployedResources(mockParams) - - assert.strictEqual(result?.length, 0) - assert(runSamCliListResourceStub.calledOnce) - }) - - it('returns all resources went given all deployed resources', async () => { - runSamCliListResourceStub.resolves(JSON.stringify(deployedResources)) - const result = await getDeployedResources(mockParams) - - assert.deepStrictEqual(result, deployedResources) - assert(runSamCliListResourceStub.calledOnce) - }) - - it('return undefined given an unlikely error when calling SAM api', async () => { - runSamCliListResourceStub.rejects(new Error('Unlikely error')) - const result = await getDeployedResources(mockParams) - - assert(!result) - assert(runSamCliListResourceStub.calledOnce) - assertLogsContain('Unlikely error', true, 'error') - }) - - const testcases = [ - { name: 'stringify array', value: '[]' }, - { name: 'array object', value: [] }, - ] - // eslint-disable-next-line unicorn/no-array-for-each - testcases.forEach(async ({ name, value }) => { - it(`returns empty array given SAM CLI return ${name} given any issue`, async () => { - runSamCliListResourceStub.resolves(value) - - const result = await getDeployedResources(mockParams) - - assert.strictEqual(result?.length, 0) - assert(runSamCliListResourceStub.calledOnce) - }) - }) - - it('returns empty array given issue parsing SAM CLI result', async () => { - runSamCliListResourceStub.resolves("[{'key':, 'wihtout':, 'value' }]") - - const result = await getDeployedResources(mockParams) - - assert.strictEqual(result?.length, 0) - getTestWindow().getFirstMessage().assertMessage(new RegExp(`Failed to parse SAM CLI output.*`)) - assert(runSamCliListResourceStub.calledOnce) - }) - }) -}) - -const localUndeployedResources: StackResource[] = [ - { - LogicalResourceId: 'myUndeployedLamdaFunction', - PhysicalResourceId: '-', - }, - { - LogicalResourceId: 'myOtherLocalUndeployedLamdaFunction', - PhysicalResourceId: '-', - }, -] - -const deployedResources: StackResource[] = [ - { - LogicalResourceId: 'myLamdaFunction', - PhysicalResourceId: 'myLamdaFunction-logical-id', - }, - { - LogicalResourceId: 'myLamdaOtherFunction', - PhysicalResourceId: 'myLamdaOtherFunction-logical-id', - }, - { - LogicalResourceId: 'myLamdaOtherResource', - PhysicalResourceId: 'myLamdaOtherResource-logical-id', - }, -] diff --git a/packages/core/src/test/lambda/commands/uploadLambda.test.ts b/packages/core/src/test/lambda/commands/uploadLambda.test.ts deleted file mode 100644 index e31fb9088f..0000000000 --- a/packages/core/src/test/lambda/commands/uploadLambda.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import assert from 'assert' -import * as path from 'path' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { findApplicationJsonFile, getFunctionNames } from '../../../lambda/commands/uploadLambda' -import { assertEqualPaths, toFile } from '../../testUtil' -import { fs } from '../../../shared' - -describe('uploadLambda', async function () { - let tempFolder: string - let folderUri: vscode.Uri - const dotApplicationJsonData = `{ - "DeploymentMethod": "lambda", - "Functions": { - "sampleFunction": { - "PhysicalId": { - "us-west-2": "sampleFunction-w2", - "us-east-1": "sampleFunction-e1" - } - }, - "differentFunction": { - "PhysicalId": { - "us-east-1": "differentFunction-e1" - } - } - } - }` - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - folderUri = vscode.Uri.file(tempFolder) - }) - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('finds application.json file from dir path - flat', async function () { - await toFile('top secret data', path.join(tempFolder, '.application.json')) - assertEqualPaths( - (await findApplicationJsonFile(folderUri))?.fsPath ?? '', - path.join(tempFolder, '.application.json') - ) - }) - - it('finds application.json file from dir path - nested', async function () { - const subfolder = path.join(tempFolder, 'one', 'two') - const appjsonPath = path.join(subfolder, '.application.json') - await toFile('top secret data', appjsonPath) - - assertEqualPaths((await findApplicationJsonFile(folderUri))?.fsPath ?? '', appjsonPath) - }) - - it('finds application.json file from template file path', async function () { - const templateUri = vscode.Uri.file(path.join(tempFolder, 'template.yaml')) - const appjsonPath = path.join(tempFolder, '.application.json') - await toFile('SAM stuff...', templateUri.fsPath) - await toFile('top secret data', appjsonPath) - - assertEqualPaths((await findApplicationJsonFile(templateUri))?.fsPath ?? '', appjsonPath) - }) - - it('lists functions from .application.json', async function () { - const filePath = path.join(tempFolder, '.application.json') - await toFile(dotApplicationJsonData, filePath) - const foundFunctions1 = await getFunctionNames(vscode.Uri.file(filePath), 'us-west-2') - const foundFunctions2 = await getFunctionNames(vscode.Uri.file(filePath), 'us-east-1') - assert.deepStrictEqual(foundFunctions1, ['sampleFunction-w2']) - assert.deepStrictEqual(foundFunctions2, ['sampleFunction-e1', 'differentFunction-e1']) - }) - - it('invalid .application.json', async function () { - const filePath = path.join(tempFolder, '.application.json') - const invalidJson = '{ "DeploymentMethod": "lambda", "Functions": { ?? } }' - await toFile(invalidJson, filePath) - assert.deepStrictEqual(await getFunctionNames(vscode.Uri.file(filePath), 'us-west-2'), undefined) - assert.deepStrictEqual(await getFunctionNames(vscode.Uri.file(filePath), 'us-east-1'), undefined) - }) -}) diff --git a/packages/core/src/test/lambda/config/samParameterCompletionItemProvider.test.ts b/packages/core/src/test/lambda/config/samParameterCompletionItemProvider.test.ts deleted file mode 100644 index cdd01be8e4..0000000000 --- a/packages/core/src/test/lambda/config/samParameterCompletionItemProvider.test.ts +++ /dev/null @@ -1,399 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' -import { - SamParameterCompletionItemProvider, - SamParameterCompletionItemProviderContext, -} from '../../../lambda/config/samParameterCompletionItemProvider' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { Logger } from '../../../shared/logger' - -function getTestProviderWithTemplate( - template: CloudFormation.Template, - templatesSymbol: vscode.DocumentSymbol -): SamParameterCompletionItemProvider { - return new SamParameterCompletionItemProvider( - new MockSamParamComplItemProviderContext({ - executeCommand: async () => [templatesSymbol] as any as T, - getWorkspaceFolder: () => ({ uri: vscode.Uri.file('') }) as any as vscode.WorkspaceFolder, - loadTemplate: async () => template, - }) - ) -} - -async function getProvidedItems( - document: vscode.TextDocument, - provider: SamParameterCompletionItemProvider, - position: vscode.Position -) { - return await provider.provideCompletionItems( - document, - position, - {} as any as vscode.CancellationToken, - {} as any as vscode.CompletionContext - ) -} - -function createTemplatesSymbol({ - uri = vscode.Uri.file(path.join('my', 'template', 'uri')), - includeOverrides = false, - parameterName, -}: { - uri?: vscode.Uri - includeOverrides?: boolean - parameterName?: string -}) { - const templatesSymbol = new vscode.DocumentSymbol( - 'templates', - 'myDetail', - vscode.SymbolKind.Object, - new vscode.Range(0, 0, 10, 0), - new vscode.Range(0, 0, 0, 10) - ) - - if (!uri) { - return templatesSymbol - } - - const templateSymbol = new vscode.DocumentSymbol( - uri.fsPath, - 'myDetail', - vscode.SymbolKind.Object, - new vscode.Range(1, 0, 9, 0), - new vscode.Range(1, 0, 1, 10) - ) - templatesSymbol.children.push(templateSymbol) - - if (!includeOverrides) { - return templatesSymbol - } - - const parameterOverridesSymbol = new vscode.DocumentSymbol( - 'parameterOverrides', - 'myDetail', - vscode.SymbolKind.Object, - new vscode.Range(2, 0, 8, 0), - new vscode.Range(2, 0, 2, 10) - ) - templateSymbol.children.push(parameterOverridesSymbol) - - if (!parameterName) { - return templatesSymbol - } - - const parameterOverrideSymbol = new vscode.DocumentSymbol( - parameterName, - 'myDetail', - vscode.SymbolKind.Property, - new vscode.Range(3, 0, 7, 0), - new vscode.Range(3, 0, 3, 10) - ) - parameterOverridesSymbol.children.push(parameterOverrideSymbol) - - return templatesSymbol -} - -class MockSamParamComplItemProviderContext implements SamParameterCompletionItemProviderContext { - public readonly logger: Pick - public readonly getWorkspaceFolder: typeof vscode.workspace.getWorkspaceFolder - public readonly executeCommand: typeof vscode.commands.executeCommand - public readonly loadTemplate: typeof CloudFormation.load - - public constructor({ - logger = { - warn(...message: (Error | string)[]): number { - return 0 - }, - }, - getWorkspaceFolder = (uri) => undefined, - executeCommand = async (command, ...rest) => undefined as any, - loadTemplate = async () => ({}), - }: Partial) { - this.logger = logger - this.getWorkspaceFolder = getWorkspaceFolder - this.executeCommand = executeCommand - this.loadTemplate = loadTemplate - } -} - -describe('SamParameterCompletionItemProvider', async function () { - it('recovers gracefully if document is not in a workspace', async function () { - const warnArgs: (Error | string)[][] = [] - const provider = new SamParameterCompletionItemProvider( - new MockSamParamComplItemProviderContext({ - logger: { - warn(...message: (Error | string)[]) { - warnArgs.push(message) - return 0 - }, - }, - }) - ) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('my', 'path')), - } as any as vscode.TextDocument - const actualItems = await getProvidedItems(document, provider, new vscode.Position(0, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - assert.strictEqual(warnArgs.length, 1) - assert.strictEqual(warnArgs[0].length, 1) - assert.strictEqual( - warnArgs[0][0], - `Cannot provide completion items for '${document.uri.fsPath}' beacuse it is not in the workspace.` - ) - }) - - it('does not provide suggestions if document symbols could not be loaded', async function () { - const provider = new SamParameterCompletionItemProvider( - new MockSamParamComplItemProviderContext({ - executeCommand: async () => undefined as any, - getWorkspaceFolder: () => ({ uri: vscode.Uri.file('') }) as any as vscode.WorkspaceFolder, - }) - ) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('my', 'path')), - } as any as vscode.TextDocument - const actualItems = await getProvidedItems(document, provider, new vscode.Position(0, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('does not provide suggestions if no matching template is found', async function () { - const provider = new SamParameterCompletionItemProvider( - new MockSamParamComplItemProviderContext({ - executeCommand: async () => [] as any as T, - getWorkspaceFolder: () => ({ uri: vscode.Uri.file('') }) as any as vscode.WorkspaceFolder, - }) - ) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('my', 'path')), - } as any as vscode.TextDocument - const actualItems = await getProvidedItems(document, provider, new vscode.Position(0, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('suggests all parameter names if user has not started typing the parameter name', async function () { - const templatesSymbol = createTemplatesSymbol({ - includeOverrides: true, - parameterName: 'myParamName', - }) - - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => '', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(3, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 2) - assert.strictEqual(actualItems[0].insertText, 'MyParamName1') - assert.strictEqual(actualItems[1].insertText, 'MyParamName2') - }) - - it('suggests only matching parameter names if user has started typing the parameter name', async function () { - const templatesSymbol = createTemplatesSymbol({ - includeOverrides: true, - parameterName: 'myParamName', - }) - - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - MyOtherParamName: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => 'MyParamName', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(3, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 2) - assert.strictEqual(actualItems[0].insertText, 'MyParamName1') - assert.strictEqual(actualItems[1].insertText, 'MyParamName2') - }) - - it('recovers gracefully if templates.json is empty or invalid', async function () { - const provider = new SamParameterCompletionItemProvider( - new MockSamParamComplItemProviderContext({ - executeCommand: async () => undefined as any, - getWorkspaceFolder: () => ({ uri: vscode.Uri.file('') }) as any as vscode.WorkspaceFolder, - }) - ) - - const actualItems = await getProvidedItems( - { uri: vscode.Uri.file(path.join('.aws', 'templates.json')) } as any as vscode.TextDocument, - provider, - new vscode.Position(0, 0) - ) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('recovers gracefully if cursor is not within the `templates` property', async function () { - const templatesSymbol = createTemplatesSymbol({ - includeOverrides: true, - parameterName: 'myParamName', - }) - - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - MyOtherParamName: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => 'MyParamName', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(11, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('recovers gracefully if `parameterOverrides` is not defined for this template', async function () { - const templatesSymbol = createTemplatesSymbol({}) - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - MyOtherParamName: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => 'MyParamName', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(11, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('recovers gracefully if cursor is not within the `parameterOverrides` property', async function () { - const templatesSymbol = createTemplatesSymbol({ - includeOverrides: true, - parameterName: 'myParamName', - }) - - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - MyOtherParamName: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => 'MyParamName', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(9, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) - - it('recovers gracefully if cursor is not within a property name within `parameterOverrides`', async function () { - const templatesSymbol = createTemplatesSymbol({ - includeOverrides: true, - parameterName: 'myParamName', - }) - - const template: CloudFormation.Template = { - Parameters: { - MyParamName1: { - Type: 'String', - }, - MyParamName2: { - Type: 'String', - }, - MyOtherParamName: { - Type: 'String', - }, - }, - } - const provider = getTestProviderWithTemplate(template, templatesSymbol) - - const document: vscode.TextDocument = { - uri: vscode.Uri.file(path.join('.aws', 'templates.json')), - getWordRangeAtPosition: () => new vscode.Range(3, 0, 3, 10), - getText: () => 'MyParamName', - } as any as vscode.TextDocument - - const actualItems = await getProvidedItems(document, provider, new vscode.Position(4, 0)) - - assert.ok(actualItems) - assert.strictEqual(actualItems.length, 0) - }) -}) diff --git a/packages/core/src/test/lambda/config/templates.test.ts b/packages/core/src/test/lambda/config/templates.test.ts deleted file mode 100644 index 59c2ada308..0000000000 --- a/packages/core/src/test/lambda/config/templates.test.ts +++ /dev/null @@ -1,839 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import globals from '../../../shared/extensionGlobals' -import * as vscode from 'vscode' -import { - getExistingConfiguration, - getTemplatesConfigPath, - load, - LoadTemplatesConfigContext, - TemplatesConfigPopulator, -} from '../../../lambda/config/templates' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { makeSampleSamTemplateYaml } from '../../shared/cloudformation/cloudformationTestUtils' -import { fs } from '../../../shared' - -class MockLoadTemplatesConfigContext { - public readonly fileExists: (path: string) => Thenable - public readonly readFile: (path: string) => Thenable - public readonly saveDocumentIfDirty: (editorPath: string) => Thenable - - public constructor({ - fileExists = async (_path) => true, - readFile = async (_path) => '', - saveDocumentIfDirty = async (_path) => {}, - }: Partial) { - this.fileExists = fileExists - this.readFile = readFile - this.saveDocumentIfDirty = saveDocumentIfDirty - } -} - -describe('templates', async function () { - describe('load', async function () { - it('loads a valid file without parameter overrides', async function () { - const rawJson = `{ - "templates": { - "relative/path/to/template.yaml": { - } - } - }` - - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => rawJson, - }) - - const config = await load('', context) - - assert.ok(config) - assert.ok(config.templates) - assert.strictEqual(Object.getOwnPropertyNames(config.templates).length, 1) - - const template = config.templates['relative/path/to/template.yaml'] - assert.ok(template) - assert.strictEqual(Object.getOwnPropertyNames(template).length, 0) - }) - - it('loads a valid file with parameter overrides', async function () { - const rawJson = `{ - "templates": { - "relative/path/to/template.yaml": { - "parameterOverrides": { - "myParam1": "myValue1", - "myParam2": "myValue2" - } - } - } - }` - - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => rawJson, - }) - - const config = await load('', context) - - assert.ok(config) - assert.ok(config.templates) - assert.strictEqual(Object.getOwnPropertyNames(config.templates).length, 1) - - const template = config.templates['relative/path/to/template.yaml'] - assert.ok(template) - assert.strictEqual(Object.getOwnPropertyNames(template).length, 1) - - const parameterOverrides = template!.parameterOverrides - assert.ok(parameterOverrides) - assert.strictEqual(Object.getOwnPropertyNames(parameterOverrides).length, 2) - - const myParam1 = Object.getOwnPropertyDescriptor(parameterOverrides, 'myParam1') - const myParam2 = Object.getOwnPropertyDescriptor(parameterOverrides, 'myParam2') - assert.ok(myParam1) - assert.ok(myParam2) - assert.strictEqual(myParam1!.value, 'myValue1') - assert.strictEqual(myParam2!.value, 'myValue2') - }) - - it('returns minimal config on missing file', async function () { - const context = new MockLoadTemplatesConfigContext({ - fileExists: async (pathLike) => false, - }) - - const config = await load('', context) - - assert.ok(config) - assert.ok(config.templates) - assert.strictEqual(Object.getOwnPropertyNames(config.templates).length, 0) - }) - - it('throws on error loading file', async function () { - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => { - throw new Error('oh no') - }, - }) - - try { - await load('', context) - } catch (err) { - assert.ok(err) - assert.strictEqual(String(err), 'Error: Could not load .aws/templates.json: Error: oh no') - - return - } - - assert.fail() - }) - - it('gracefully handles invalid JSON', async function () { - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => '{', - }) - - try { - await load('', context) - } catch (err) { - assert.ok(err) - assert.strictEqual( - String(err), - 'Error: Could not load .aws/templates.json: Error: Could not parse .aws/templates.json: close brace expected at offset 1, length 0' - ) - - return - } - - assert.fail() - }) - - it('supports JSON comments', async function () { - const rawJson = `{ - "templates": { - // A single-comment. - /* - A multi-line comment. - */ - "relative/path/to/template.yaml": { - } - } - }` - - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => rawJson, - }) - - const config = await load('', context) - - assert.ok(config) - assert.ok(config.templates) - assert.strictEqual(Object.getOwnPropertyNames(config.templates).length, 1) - - const template = config.templates['relative/path/to/template.yaml'] - assert.ok(template) - assert.strictEqual(Object.getOwnPropertyNames(template).length, 0) - }) - - it('reads the correct path', async function () { - const readArgs: string[] = [] - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => { - readArgs.push(pathLike) - - return '{}' - }, - }) - - await load(path.join('my', 'path'), context) - - assert.strictEqual(readArgs.length, 1) - assert.strictEqual(readArgs[0], path.join('my', 'path', '.aws', 'templates.json')) - }) - - it('saves dirty documents before loading', async function () { - const saveArgs: string[] = [] - let read: boolean = false - let readBeforeSave: boolean = false - const context = new MockLoadTemplatesConfigContext({ - readFile: async (pathLike) => { - read = true - - return '{}' - }, - saveDocumentIfDirty: async (pathLike) => { - saveArgs.push(pathLike) - - if (read) { - // If we throw here, the exception will be swallowed by `load`'s error handling. - readBeforeSave = true - } - }, - }) - - await load(path.join('my', 'path'), context) - - assert.strictEqual(readBeforeSave, false) - assert.strictEqual(saveArgs.length, 1) - assert.strictEqual(saveArgs[0], path.join('my', 'path', '.aws', 'templates.json')) - }) - }) -}) - -describe('getTemplatesConfigPath', async function () { - it('returns expected path', async function () { - const configPath = getTemplatesConfigPath(path.join('my', 'workspace')) - - assert.strictEqual(configPath, path.join('my', 'workspace', '.aws', 'templates.json')) - }) -}) - -describe('TemplatesConfigPopulator', async function () { - const testModificationOptions = { - formattingOptions: { - tabSize: 4, - insertSpaces: true, - }, - } - - it('handles ModificationOptions', async function () { - const inputJson: string = '{}' - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": {} - } -}` - - const results = new TemplatesConfigPopulator(inputJson, { - formattingOptions: { - tabSize: 8, - insertSpaces: true, - }, - }) - .ensureTemplateSectionExists('someprocessor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - describe('ensureTemplateSectionExists', async function () { - it('handles clean data', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": {} - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateSectionExists('someprocessor') - .getResults() - - assert.strictEqual(results.isDirty, false, 'Expected results to be clean') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(inputJson)) - }) - - it('handles missing templates section', async function () { - const inputJson: string = '{}' - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": {} - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateSectionExists('someprocessor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - it('handles missing template section', async function () { - const inputJson: string = String.raw`{ - "templates": {} -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": {} - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateSectionExists('someprocessor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - it('errs with invalid templates type', async function () { - const inputJson: string = `{ - "templates": 1234 - }` - - assert.throws( - () => - new TemplatesConfigPopulator(inputJson, testModificationOptions).ensureTemplateSectionExists( - 'someprocessor' - ), - { - message: 'Invalid configuration', - jsonPath: ['templates'], - expectedTypes: ['object', 'null'], - actualType: 'number', - } - ) - }) - }) - - describe('ensureTemplateHandlerSectionExists', async function () { - it('handles clean data', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {}, - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerSectionExists('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, false, 'Expected results to be clean') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(inputJson)) - }) - - it('errs with invalid template type', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": true - } - }` - - assert.throws( - () => - new TemplatesConfigPopulator(inputJson, testModificationOptions).ensureTemplateHandlerSectionExists( - 'someprocessor', - 'processor' - ), - { - message: 'Invalid configuration', - jsonPath: ['templates', 'someprocessor'], - expectedTypes: ['object', 'null'], - actualType: 'boolean', - } - ) - }) - - it('errs with invalid handlers type', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": [1, 2, 3] - } - }` - - assert.throws( - () => - new TemplatesConfigPopulator(inputJson, testModificationOptions).ensureTemplateHandlerSectionExists( - 'someprocessor', - 'processor' - ), - { - message: 'Invalid configuration', - jsonPath: ['templates', 'someprocessor', 'handlers'], - expectedTypes: ['object', 'null'], - actualType: 'array', - } - ) - }) - - it('errs with invalid handler type', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": "hello" - } - } - }` - - assert.throws( - () => - new TemplatesConfigPopulator(inputJson, testModificationOptions).ensureTemplateHandlerSectionExists( - 'someprocessor', - 'processor' - ), - { - message: 'Invalid configuration', - jsonPath: ['templates', 'someprocessor', 'handlers', 'processor'], - expectedTypes: ['object', 'null'], - actualType: 'string', - } - ) - }) - - it('handles missing handlers section', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerSectionExists('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - it('handles missing handler section', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": {} - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerSectionExists('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - }) - - describe('ensureTemplateHandlerPropertiesExist', async function () { - it('handles clean data', async function () { - const inputJson: string = `{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerPropertiesExist('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, false, 'Expected results to be clean') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(inputJson)) - }) - - it('errs with invalid handler type', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": "hello" - } - } - }` - - assert.throws( - () => - new TemplatesConfigPopulator( - inputJson, - testModificationOptions - ).ensureTemplateHandlerPropertiesExist('someprocessor', 'processor'), - { - message: 'Invalid configuration', - jsonPath: ['templates', 'someprocessor', 'handlers', 'processor'], - expectedTypes: ['object', 'null'], - actualType: 'string', - } - ) - }) - - it('errs with invalid event type', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": 1 - } - } - } - }` - - assert.throws( - () => - new TemplatesConfigPopulator( - inputJson, - testModificationOptions - ).ensureTemplateHandlerPropertiesExist('someprocessor', 'processor'), - { - message: 'Invalid configuration', - jsonPath: ['templates', 'someprocessor', 'handlers', 'processor', 'event'], - expectedTypes: ['object', 'null'], - actualType: 'number', - } - ) - }) - - it('handles missing everything', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - } - } - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerPropertiesExist('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - it('handles missing event', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "environmentVariables": {} - } - } - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "environmentVariables": {}, - "event": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerPropertiesExist('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - - it('handles missing envvars', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {} - } - } - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "handlers": { - "processor": { - "event": {}, - "environmentVariables": {} - } - } - } - } -}` - - const results = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateHandlerPropertiesExist('someprocessor', 'processor') - .getResults() - - assert.strictEqual(results.isDirty, true, 'Expected results to be dirty') - assert.strictEqual(JSON.stringify(results.json), JSON.stringify(expectedJson)) - }) - }) - - describe('ensureTemplateParameterOverrideExists', async function () { - it('creates parameterOverrides section if it does not already exist', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": { - "myParam": "" - } - } - } -}` - const { isDirty, json } = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateParameterOverrideExists('someprocessor', 'myParam') - .getResults() - - assert.strictEqual(JSON.stringify(json), JSON.stringify(expectedJson)) - assert.strictEqual(isDirty, true, 'Expected results to be dirty') - }) - - it('adds new override if it does not already exist', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": { - } - } - } -}` - - const expectedJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": { - "myParam": "" - } - } - } -}` - const { isDirty, json } = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateParameterOverrideExists('someprocessor', 'myParam') - .getResults() - - assert.strictEqual(JSON.stringify(json), JSON.stringify(expectedJson)) - assert.strictEqual(isDirty, true, 'Expected results to be dirty') - }) - - it('does not overwrite existing overrides', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": { - "myParam": "myValue" - } - } - } -}` - - const { isDirty, json } = new TemplatesConfigPopulator(inputJson, testModificationOptions) - .ensureTemplateParameterOverrideExists('someprocessor', 'myParam') - .getResults() - - assert.strictEqual(JSON.stringify(json), JSON.stringify(inputJson)) - assert.strictEqual(isDirty, false, 'Expected results to be clean') - }) - - it('throws if parameterOverrides exists, but is not an object or null', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": "hello" - } - } -}` - - const populator = new TemplatesConfigPopulator(inputJson, testModificationOptions) - assert.throws(() => populator.ensureTemplateParameterOverrideExists('someprocessor', 'myParam')) - }) - - it('throws if override value exists, but is not a string or null', async function () { - const inputJson: string = String.raw`{ - "templates": { - "someprocessor": { - "parameterOverrides": { - "myParam": {} - } - } - } -}` - - const populator = new TemplatesConfigPopulator(inputJson, testModificationOptions) - assert.throws(() => populator.ensureTemplateParameterOverrideExists('someprocessor', 'myParam')) - }) - }) -}) - -describe('getExistingConfiguration', async function () { - let tempFolder: string - let tempTemplateFile: vscode.Uri - let tempConfigFile: string - let fakeWorkspaceFolder: vscode.WorkspaceFolder - let tempConfigFolder: string - const matchedHandler = "it'sTheSameHandler" - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - tempTemplateFile = vscode.Uri.file(path.join(tempFolder, 'test.yaml')) - fakeWorkspaceFolder = { - uri: vscode.Uri.file(tempFolder), - name: 'workspaceFolderMimic', - index: 0, - } - tempConfigFolder = path.join(tempFolder, '.aws') - await fs.mkdir(tempConfigFolder) - tempConfigFile = path.join(tempConfigFolder, 'templates.json') - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - const r = await globals.templateRegistry - r.reset() - }) - - it("returns undefined if the legacy config file doesn't exist", async () => { - const val = await getExistingConfiguration(fakeWorkspaceFolder, 'handlerDoesNotMatter', tempTemplateFile) - assert.strictEqual(val, undefined) - }) - - it('returns undefined if the legacy config file is not valid JSON', async function () { - await fs.writeFile( - tempTemplateFile.fsPath, - makeSampleSamTemplateYaml(true, { handler: matchedHandler }), - 'utf8' - ) - await fs.writeFile(tempConfigFile, makeSampleSamTemplateYaml(true, { handler: matchedHandler }), 'utf8') - await (await globals.templateRegistry).addItem(tempTemplateFile) - const val = await getExistingConfiguration(fakeWorkspaceFolder, matchedHandler, tempTemplateFile) - assert.strictEqual(val, undefined) - }) - - it('returns data from the legacy config file', async function () { - await fs.writeFile( - tempTemplateFile.fsPath, - makeSampleSamTemplateYaml(true, { handler: matchedHandler }), - 'utf8' - ) - const configData = { - templates: { - 'test.yaml': { - handlers: { - [matchedHandler]: { - event: { asdf: 'asdf' }, - environmentVariables: {}, - useContainer: false, - }, - }, - }, - }, - } - await fs.writeFile(tempConfigFile, JSON.stringify(configData), 'utf8') - await (await globals.templateRegistry).addItem(tempTemplateFile) - const val = await getExistingConfiguration(fakeWorkspaceFolder, matchedHandler, tempTemplateFile) - assert.ok(val) - if (val) { - assert.deepStrictEqual(val.environmentVariables, {}) - assert.deepStrictEqual(val.eventJson, { asdf: 'asdf' }) - assert.strictEqual(val.dockerNetwork, undefined) - assert.strictEqual(val.useContainer, false) - } - }) -}) diff --git a/packages/core/src/test/lambda/explorer/cloudFormationNodes.test.ts b/packages/core/src/test/lambda/explorer/cloudFormationNodes.test.ts deleted file mode 100644 index 2100b1ae4a..0000000000 --- a/packages/core/src/test/lambda/explorer/cloudFormationNodes.test.ts +++ /dev/null @@ -1,211 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import { - CloudFormationNode, - CloudFormationStackNode, - contextValueCloudformationLambdaFunction, -} from '../../../lambda/explorer/cloudFormationNodes' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { CloudFormationClient, StackResource, StackSummary } from '../../../shared/clients/cloudFormation' -import { DefaultLambdaClient } from '../../../shared/clients/lambdaClient' -import globals from '../../../shared/extensionGlobals' -import { TestAWSTreeNode } from '../../shared/treeview/nodes/testAWSTreeNode' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../utilities/explorerNodeAssertions' -import { stub } from '../../utilities/stubber' -import { getLabel } from '../../../shared/treeview/utils' -import { AWSCommandTreeNode } from '../../../shared/treeview/nodes/awsCommandTreeNode' - -const regionCode = 'someregioncode' - -function createLambdaClient(...functionNames: string[]) { - const client = stub(DefaultLambdaClient, { regionCode, userAgent: undefined }) - client.listFunctions.returns(asyncGenerator(functionNames.map((name) => ({ FunctionName: name })))) - - return client -} - -function createCloudFormationClient(...stackNames: string[]) { - const client = stub(CloudFormationClient, { regionCode }) - client.describeStackResources.resolves({ StackResources: [] }) - client.listStacks.returns( - asyncGenerator( - stackNames.map((name) => { - return { - StackId: name, - StackName: name, - CreationTime: new globals.clock.Date(), - StackStatus: 'CREATE_COMPLETE', - DriftInformation: { - StackDriftStatus: 'UNKNOWN', - }, - } - }) - ) - ) - - return client -} - -describe('CloudFormationStackNode', function () { - function createStackSummary(): StackSummary { - return { - CreationTime: new globals.clock.Date(), - StackId: '1', - StackName: 'myStack', - StackStatus: 'UPDATE_COMPLETE', - DriftInformation: { - StackDriftStatus: 'UNKNOWN', - }, - } - } - - function generateTestNode({ - summary = createStackSummary(), - lambdaClient = createLambdaClient(), - cloudFormationClient = createCloudFormationClient(), - } = {}): CloudFormationStackNode { - const parentNode = new TestAWSTreeNode('test node') - - return new CloudFormationStackNode(parentNode, regionCode, summary, lambdaClient, cloudFormationClient) - } - - function generateStackResources(...functionNames: string[]): StackResource[] { - return functionNames.map((name) => ({ - PhysicalResourceId: name, - LogicalResourceId: name, - ResourceStatus: 'CREATE_COMPLETE', - ResourceType: 'Lambda::Function', - Timestamp: new globals.clock.Date(), - })) - } - - it('initializes name and tooltip', async function () { - const summary = createStackSummary() - const node = generateTestNode({ summary }) - assert.strictEqual(node.label, `${summary.StackName} [${summary.StackStatus}]`) - assert.strictEqual(node.tooltip, `${summary.StackName}${os.EOL}${summary.StackId}`) - }) - - it('returns placeholder node if no children are present', async function () { - const node = generateTestNode() - const childNodes = await node.getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has LambdaFunctionNode child nodes', async function () { - const lambdaClient = createLambdaClient('function1', 'function2') - const cloudFormationClient = createCloudFormationClient('foo') - cloudFormationClient.describeStackResources.resolves({ - StackResources: generateStackResources('function1', 'function2'), - }) - const node = generateTestNode({ lambdaClient, cloudFormationClient }) - const childNodes = await node.getChildren() - - assert.strictEqual(childNodes.length, 2, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof LambdaFunctionNode, 'Expected child node to be LambdaFunctionNode') - } - }) - - it('has child nodes with CloudFormation contextValue', async function () { - const lambdaClient = createLambdaClient('function1', 'function2') - const cloudFormationClient = createCloudFormationClient('foo') - cloudFormationClient.describeStackResources.resolves({ - StackResources: generateStackResources('function1', 'function2'), - }) - const node = generateTestNode({ lambdaClient, cloudFormationClient }) - const childNodes = await node.getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - contextValueCloudformationLambdaFunction, - 'expected the node to have a CloudFormation contextValue' - ) - } - }) - - it('only includes functions which are in a CloudFormation stack', async function () { - const lambdaClient = createLambdaClient('lambda3', 'lambda1') - const cloudFormationClient = createCloudFormationClient('foo') - cloudFormationClient.describeStackResources.resolves({ - StackResources: generateStackResources('lambda1', 'lambda2', 'lambda3'), - }) - const node = generateTestNode({ lambdaClient, cloudFormationClient }) - const childNodes = await node.getChildren() - - assert.strictEqual(childNodes.length, 2, 'Unexpected child node count') - - assert.deepStrictEqual( - new Set(childNodes.map((node) => getLabel(node))), - new Set(['lambda1', 'lambda3']), - 'Unexpected child sort order' - ) - }) - - it('has an error node for a child if an error happens during loading', async function () { - const cloudFormationClient = createCloudFormationClient() - cloudFormationClient.describeStackResources.throws() - - const node = generateTestNode({ cloudFormationClient }) - assertNodeListOnlyHasErrorNode(await node.getChildren()) - }) -}) - -describe('CloudFormationNode', function () { - it('has CloudFormationStackNode child nodes', async function () { - const client = createCloudFormationClient('stack123') - const cloudFormationNode = new CloudFormationNode(regionCode, client) - const children = await cloudFormationNode.getChildren() - - // First node should be the panel promotion node - assert.ok(children[0] instanceof AWSCommandTreeNode, 'Expected first child to be panel promotion node') - - // Remaining nodes should be CloudFormationStackNode - for (let i = 1; i < children.length; i++) { - assert.ok( - children[i] instanceof CloudFormationStackNode, - 'Expected child node to be CloudFormationStackNode' - ) - } - }) - - it('has sorted child nodes', async function () { - const client = createCloudFormationClient('b', 'a') - const cloudFormationNode = new CloudFormationNode(regionCode, client) - const children = await cloudFormationNode.getChildren() - - // Skip the first node (panel promotion) and check stack sorting - const stackNodes = children.slice(1) as CloudFormationStackNode[] - const actualChildOrder = stackNodes.map((node) => node.stackName) - assert.deepStrictEqual(actualChildOrder, ['a', 'b'], 'Unexpected child sort order') - }) - - it('returns panel promotion node if no stacks are present', async function () { - const client = createCloudFormationClient() - const cloudFormationNode = new CloudFormationNode(regionCode, client) - const children = await cloudFormationNode.getChildren() - - assert.strictEqual(children.length, 1, 'Expected exactly one child node') - assert.ok(children[0] instanceof AWSCommandTreeNode, 'Expected panel promotion node') - }) - - it('has an error node for a child if an error happens during loading', async function () { - const client = createCloudFormationClient() - client.listStacks.throws() - const cloudFormationNode = new CloudFormationNode(regionCode, client) - - assertNodeListOnlyHasErrorNode(await cloudFormationNode.getChildren()) - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaCapacityProviderNode.test.ts b/packages/core/src/test/lambda/explorer/lambdaCapacityProviderNode.test.ts deleted file mode 100644 index d1b72598cd..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaCapacityProviderNode.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { LambdaCapacityProviderNode } from '../../../lambda/explorer/lambdaCapacityProviderNode' -import { contextValueLambdaCapacityProvider } from '../../../lambda/explorer/lambdaCapacityProviderNode' - -describe('LambdaCapacityProviderNode', function () { - it('instantiates without issue', async function () { - const fakeCapacityProviderResource = { - LogicalResourceId: 'testLogicalResourceId', - PhysicalResourceId: 'testPhysicalResourceId', - } - - const testNode = new LambdaCapacityProviderNode( - 'someregioncode', - fakeCapacityProviderResource, - contextValueLambdaCapacityProvider - ) - assert.ok(testNode) - assert.strictEqual(testNode.regionCode, 'someregioncode') - assert.strictEqual(testNode.label, fakeCapacityProviderResource.LogicalResourceId) - assert.strictEqual(testNode.name, fakeCapacityProviderResource.LogicalResourceId) - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaFunctionFileNode.test.ts b/packages/core/src/test/lambda/explorer/lambdaFunctionFileNode.test.ts deleted file mode 100644 index 59235bf755..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaFunctionFileNode.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { TestAWSTreeNode } from '../../shared/treeview/nodes/testAWSTreeNode' -import { LambdaFunctionFileNode } from '../../../lambda/explorer/lambdaFunctionFileNode' -import path from 'path' - -describe('LambdaFunctionFileNode', function () { - const fakeFunctionConfig = { - FunctionName: 'testFunctionName', - FunctionArn: 'testFunctionARN', - } - const fakeFilename = 'fakeFile' - const fakeRegion = 'fakeRegion' - const functionNode = new LambdaFunctionNode(new TestAWSTreeNode('test node'), fakeRegion, fakeFunctionConfig) - const filePath = path.join( - '/tmp/aws-toolkit-vscode/lambda', - fakeRegion, - fakeFunctionConfig.FunctionName, - fakeFilename - ) - - let testNode: LambdaFunctionFileNode - - before(async function () { - testNode = new LambdaFunctionFileNode(functionNode, fakeFilename, filePath) - }) - - it('instantiates without issue', function () { - assert.ok(testNode) - }) - - it('initializes the parent node', function () { - assert.equal(testNode.parent, functionNode, 'unexpected parent node') - }) - - it('initializes the label', function () { - assert.equal(testNode.label, fakeFilename) - }) - - it('has no children', async function () { - const childNodes = await testNode.getChildren() - assert.ok(childNodes) - assert.strictEqual(childNodes.length, 0, 'Expected zero children') - }) - - it('has correct command', function () { - assert.deepStrictEqual(testNode.command, { - command: 'aws.openLambdaFile', - title: 'Open file', - arguments: [filePath], - }) - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaFunctionFolderNode.test.ts b/packages/core/src/test/lambda/explorer/lambdaFunctionFolderNode.test.ts deleted file mode 100644 index 67471bc4e2..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaFunctionFolderNode.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { TestAWSTreeNode } from '../../shared/treeview/nodes/testAWSTreeNode' -import path from 'path' -import { LambdaFunctionFolderNode } from '../../../lambda/explorer/lambdaFunctionFolderNode' -import { fs } from '../../../shared/fs/fs' - -describe('LambdaFunctionFileNode', function () { - const fakeFunctionConfig = { - FunctionName: 'testFunctionName', - FunctionArn: 'testFunctionARN', - } - const fakeRegion = 'fakeRegion' - const fakeSubFolder = 'fakeSubFolder' - const fakeFile = 'fakeFilename' - const functionNode = new LambdaFunctionNode(new TestAWSTreeNode('test node'), fakeRegion, fakeFunctionConfig) - - const regionPath = path.join('/tmp/aws-toolkit-vscode/lambda', fakeRegion) - const functionPath = path.join(regionPath, fakeFunctionConfig.FunctionName) - const subFolderPath = path.join(functionPath, fakeSubFolder) - - let testNode: LambdaFunctionFolderNode - - before(async function () { - await fs.mkdir(subFolderPath) - await fs.writeFile(path.join(subFolderPath, fakeFile), 'fakefilecontent') - - testNode = new LambdaFunctionFolderNode(functionNode, fakeSubFolder, subFolderPath) - }) - - after(async function () { - await fs.delete(regionPath, { recursive: true }) - }) - - it('instantiates without issue', function () { - assert.ok(testNode) - }) - - it('initializes the parent node', function () { - assert.equal(testNode.parent, functionNode, 'unexpected parent node') - }) - - it('initializes the label', function () { - assert.equal(testNode.label, fakeSubFolder) - }) - - it('loads function files', async function () { - const functionFiles = await testNode.loadFunctionFiles() - assert.equal(functionFiles.length, 1) - assert.equal(functionFiles[0].label, fakeFile) - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaFunctionNode.test.ts b/packages/core/src/test/lambda/explorer/lambdaFunctionNode.test.ts deleted file mode 100644 index 184bdd915b..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaFunctionNode.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { TestAWSTreeNode } from '../../shared/treeview/nodes/testAWSTreeNode' -import path from 'path' -import { fs } from '../../../shared/fs/fs' -import { - contextValueLambdaFunction, - contextValueLambdaFunctionImportable, -} from '../../../lambda/explorer/lambdaFunctionNode' -import sinon from 'sinon' -import * as editLambdaModule from '../../../lambda/commands/editLambda' - -describe('LambdaFunctionNode', function () { - const parentNode = new TestAWSTreeNode('test node') - const fakeRegion = 'fakeRegion' - const fakeFilename = 'fakeFilename' - - const fakeFunctionConfig = { - FunctionName: 'testFunctionName', - FunctionArn: 'testFunctionARN', - } - - const regionPath = path.join('/tmp/aws-toolkit-vscode/lambda', fakeRegion) - const functionPath = path.join(regionPath, fakeFunctionConfig.FunctionName) - const filePath = path.join(functionPath, fakeFilename) - - let testNode: LambdaFunctionNode - - let editLambdaStub: sinon.SinonStub - - before(async function () { - await fs.mkdir(functionPath) - await fs.writeFile(filePath, 'fakefilecontent') - - // Stub the editLambdaCommand to return the function path - editLambdaStub = sinon.stub(editLambdaModule, 'editLambdaCommand').resolves(functionPath) - - testNode = new LambdaFunctionNode( - parentNode, - 'someregioncode', - fakeFunctionConfig, - contextValueLambdaFunctionImportable - ) - }) - - after(async function () { - await fs.delete(regionPath, { recursive: true }) - editLambdaStub.restore() - }) - - it('instantiates without issue', async function () { - assert.ok(testNode) - }) - - it('initializes the parent node', async function () { - assert.strictEqual(testNode.parent, parentNode, 'unexpected parent node') - }) - - it('initializes the region code', async function () { - assert.strictEqual(testNode.regionCode, 'someregioncode') - }) - - it('initializes the label', async function () { - assert.strictEqual(testNode.label, fakeFunctionConfig.FunctionName) - }) - - it('initializes the functionName', async function () { - assert.strictEqual(testNode.functionName, fakeFunctionConfig.FunctionName) - }) - - it('initializes resourceUri', async function () { - assert.strictEqual(testNode.resourceUri?.scheme, 'lambda') - assert.strictEqual(testNode.resourceUri?.path, `someregioncode/${fakeFunctionConfig.FunctionName}`) - }) - - it('initializes the tooltip', async function () { - assert.strictEqual( - testNode.tooltip, - `${fakeFunctionConfig.FunctionName}${os.EOL}${fakeFunctionConfig.FunctionArn}` - ) - }) - - it('loads function files', async function () { - const functionFiles = await testNode.loadFunctionFiles(functionPath) - assert.equal(functionFiles.length, 1) - assert.equal(functionFiles[0].label, fakeFilename) - }) - - it('has child if importable', async function () { - const childNodes = await testNode.getChildren() - assert.ok(childNodes) - assert.equal(childNodes.length, 1, 'Expected node to have one child, should be "failed to load resources"') - }) - - it('is not collapsible if not importable', async function () { - const nonImportableNode = new LambdaFunctionNode( - parentNode, - fakeRegion, - fakeFunctionConfig, - contextValueLambdaFunction - ) - const childNodes = await nonImportableNode.getChildren() - assert.ok(childNodes) - assert.equal(childNodes.length, 0, 'Expected node to have no children') - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaFunctionNodeDecorationProvider.test.ts b/packages/core/src/test/lambda/explorer/lambdaFunctionNodeDecorationProvider.test.ts deleted file mode 100644 index 19a2662815..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaFunctionNodeDecorationProvider.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as sinon from 'sinon' -import * as path from 'path' -import { LambdaFunctionNodeDecorationProvider } from '../../../lambda/explorer/lambdaFunctionNodeDecorationProvider' -import * as utils from '../../../lambda/utils' -import { fs } from '../../../shared/fs/fs' - -describe('LambdaFunctionNodeDecorationProvider', function () { - let provider: LambdaFunctionNodeDecorationProvider - let getFunctionInfoStub: sinon.SinonStub - let fsStatStub: sinon.SinonStub - let fsReaddirStub: sinon.SinonStub - - const filepath = path.join(utils.getTempLocation('test-function', 'us-east-1'), 'index.js') - const functionUri = vscode.Uri.parse('lambda:us-east-1/test-function') - const fileUri = vscode.Uri.file(filepath) - - beforeEach(function () { - provider = LambdaFunctionNodeDecorationProvider.getInstance() - getFunctionInfoStub = sinon.stub(utils, 'getFunctionInfo') - fsStatStub = sinon.stub(fs, 'stat') - fsReaddirStub = sinon.stub(fs, 'readdir') - }) - - afterEach(function () { - sinon.restore() - }) - - describe('provideFileDecoration', function () { - it('returns decoration for lambda URI with undeployed changes', async function () { - getFunctionInfoStub.resolves(true) - - const decoration = await provider.provideFileDecoration(functionUri) - - assert.ok(decoration) - assert.strictEqual(decoration.badge, 'M') - assert.strictEqual(decoration.tooltip, 'This function has undeployed changes') - assert.strictEqual(decoration.propagate, false) - }) - - it('returns undefined for lambda URI without undeployed changes', async function () { - getFunctionInfoStub.resolves(false) - - const decoration = await provider.provideFileDecoration(functionUri) - - assert.strictEqual(decoration, undefined) - }) - - it('returns decoration for file URI with modifications after deployment', async function () { - const lastDeployed = 1 - const fileModified = 2 - - getFunctionInfoStub.resolves({ lastDeployed, undeployed: true }) - fsStatStub.resolves({ mtime: fileModified }) - - const decoration = await provider.provideFileDecoration(fileUri) - - assert.ok(decoration) - assert.strictEqual(decoration.badge, 'M') - assert.strictEqual(decoration.tooltip, 'This function has undeployed changes') - assert.strictEqual(decoration.propagate, true) - }) - - it('returns undefined for file URI without modifications after deployment', async function () { - const lastDeployed = 2 - const fileModified = 1 - - getFunctionInfoStub.resolves({ lastDeployed, undeployed: true }) - fsStatStub.resolves({ mtime: fileModified }) - - const decoration = await provider.provideFileDecoration(fileUri) - - assert.strictEqual(decoration, undefined) - }) - - it('returns undefined for file URI when no deployment info exists', async function () { - getFunctionInfoStub.resolves(undefined) - - const decoration = await provider.provideFileDecoration(fileUri) - - assert.strictEqual(decoration, undefined) - }) - - it('returns undefined for file URI that does not match lambda pattern', async function () { - const uri = vscode.Uri.file(path.join('not', 'in', 'tmp')) - - const decoration = await provider.provideFileDecoration(uri) - - assert.strictEqual(decoration, undefined) - }) - - it('handles errors gracefully when checking file modification', async function () { - getFunctionInfoStub.resolves(0) - fsStatStub.rejects(new Error('File not found')) - - const decoration = await provider.provideFileDecoration(fileUri) - - assert.strictEqual(decoration, undefined) - }) - }) - - describe('addBadge', function () { - it('fires decoration change events for both URIs', async function () { - const fileUri = vscode.Uri.file(path.join('test', 'file.js')) - const functionUri = vscode.Uri.parse('lambda:us-east-1/test-function') - - let eventCount = 0 - const disposable = provider.onDidChangeFileDecorations(() => { - eventCount++ - }) - - await provider.addBadge(fileUri, functionUri) - - assert.strictEqual(eventCount, 2) - disposable.dispose() - }) - }) - - describe('getFilePaths', function () { - it('returns all file paths recursively', async function () { - const basePath = path.join('test', 'dir') - - // Mock first readdir call - fsReaddirStub.onFirstCall().resolves([ - ['file1.js', vscode.FileType.File], - ['subdir', vscode.FileType.Directory], - ]) - - // Mock second readdir call for subdirectory - fsReaddirStub.onSecondCall().resolves([['file2.js', vscode.FileType.File]]) - - // Access private method through any cast for testing - const paths = await (provider as any).getFilePaths(basePath) - - assert.ok(paths.includes(basePath)) - assert.ok(paths.includes(path.join('test', 'dir', 'file1.js'))) - assert.ok(paths.includes(path.join('test', 'dir', 'subdir'))) - assert.ok(paths.includes(path.join('test', 'dir', 'subdir', 'file2.js'))) - }) - }) -}) diff --git a/packages/core/src/test/lambda/explorer/lambdaNodes.test.ts b/packages/core/src/test/lambda/explorer/lambdaNodes.test.ts deleted file mode 100644 index 86ed7bbe44..0000000000 --- a/packages/core/src/test/lambda/explorer/lambdaNodes.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { contextValueLambdaFunction, LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { LambdaNode } from '../../../lambda/explorer/lambdaNodes' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../utilities/explorerNodeAssertions' -import { stub } from '../../utilities/stubber' -import { DefaultLambdaClient } from '../../../shared/clients/lambdaClient' - -const regionCode = 'someregioncode' - -function createLambdaClient(...functionNames: string[]) { - const client = stub(DefaultLambdaClient, { regionCode, userAgent: undefined }) - client.listFunctions.returns(asyncGenerator(functionNames.map((name) => ({ FunctionName: name })))) - - return client -} - -function createNode(...functionNames: string[]) { - return new LambdaNode(regionCode, createLambdaClient(...functionNames)) -} - -describe('LambdaNode', function () { - it('returns placeholder node if no children are present', async function () { - const childNodes = await createNode().getChildren() - - assertNodeListOnlyHasPlaceholderNode(childNodes) - }) - - it('has LambdaFunctionNode child nodes', async function () { - const childNodes = await createNode('f1', 'f2').getChildren() - - assert.strictEqual(childNodes.length, 2, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof LambdaFunctionNode, 'Expected child node to be LambdaFunctionNode') - } - }) - - it('has child nodes with Lambda Function contextValue', async function () { - const childNodes = await createNode('f1', 'f2').getChildren() - - for (const node of childNodes) { - assert.strictEqual( - node.contextValue, - contextValueLambdaFunction, - 'expected the node to have a CloudFormation contextValue' - ) - } - }) - - it('sorts child nodes', async function () { - const childNodes = await createNode('b', 'c', 'a').getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, ['a', 'b', 'c'], 'Unexpected child sort order') - }) - - it('has an error node for a child if an error happens during loading', async function () { - const client = createLambdaClient() - client.listFunctions.throws() - const node = new LambdaNode(regionCode, client) - - assertNodeListOnlyHasErrorNode(await node.getChildren()) - }) -}) diff --git a/packages/core/src/test/lambda/local/debugConfiguration.test.ts b/packages/core/src/test/lambda/local/debugConfiguration.test.ts deleted file mode 100644 index 0a0aab0dc9..0000000000 --- a/packages/core/src/test/lambda/local/debugConfiguration.test.ts +++ /dev/null @@ -1,327 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import * as vscode from 'vscode' -import { RuntimeFamily } from '../../../lambda/models/samLambdaRuntime' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { DefaultSamLocalInvokeCommand } from '../../../shared/sam/cli/samCliLocalInvoke' -import { makeDotnetDebugConfiguration } from '../../../shared/sam/debugger/csharpSamDebug' -import * as testutil from '../../testUtil' -import { SamLaunchRequestArgs } from '../../../shared/sam/debugger/awsSamDebugger' -import * as pathutil from '../../../shared/utilities/pathUtils' -import * as path from 'path' -import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' -import { getArchitecture, isImageLambdaConfig } from '../../../lambda/local/debugConfiguration' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import globals from '../../../shared/extensionGlobals' -import { Runtime } from '@aws-sdk/client-lambda' -import { fs } from '../../../shared' - -describe('makeCoreCLRDebugConfiguration', function () { - let tempFolder: string - let fakeWorkspaceFolder: vscode.WorkspaceFolder - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - fakeWorkspaceFolder = { - uri: vscode.Uri.file(tempFolder), - name: 'It was me, fakeWorkspaceFolder!', - index: 0, - } - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - async function makeFakeSamLaunchConfig() { - const config: SamLaunchRequestArgs = { - name: 'fake-launch-config', - workspaceFolder: fakeWorkspaceFolder, - codeRoot: fakeWorkspaceFolder.uri.fsPath, - runtimeFamily: RuntimeFamily.DotNet, - type: 'coreclr', - request: 'attach', - // cfnTemplate?: CloudFormation.Template - runtime: 'fakedotnet' as Runtime, - handlerName: 'fakehandlername', - noDebug: false, - apiPort: 4242, - debugPort: 4243, - - baseBuildDir: '/fake/build/dir/', - envFile: '/fake/build/dir/env-vars.json', - eventPayloadFile: '/fake/build/dir/event.json', - documentUri: vscode.Uri.parse('/fake/path/foo.txt'), - templatePath: '/fake/sam/path', - samLocalInvokeCommand: new DefaultSamLocalInvokeCommand(), - - // debuggerPath?: - - invokeTarget: { - target: 'code', - lambdaHandler: 'fakehandlername', - projectRoot: fakeWorkspaceFolder.uri.fsPath, - }, - } - return config - } - - async function makeConfig({ codeUri = tempFolder }: { codeUri?: string; port?: number }) { - const fakeLaunchConfig = await makeFakeSamLaunchConfig() - return makeDotnetDebugConfiguration(fakeLaunchConfig, codeUri) - } - - describe('windows', function () { - if (os.platform() === 'win32') { - it('massages drive letters to uppercase', async function () { - const config = await makeConfig({}) - assert.strictEqual( - config.windows.pipeTransport.pipeCwd.substring(0, 1), - tempFolder.substring(0, 1).toUpperCase() - ) - }) - } - - it('uses powershell', async function () { - const config = await makeConfig({}) - assert.strictEqual(config.windows.pipeTransport.pipeProgram, 'powershell') - }) - - it('uses the specified port', async function () { - const config = await makeConfig({}) - assert.strictEqual( - config.windows.pipeTransport.pipeArgs.some((arg) => arg.includes(config.debugPort!.toString())), - true - ) - }) - }) - describe('*nix', function () { - it('uses the default shell', async function () { - const config = await makeConfig({}) - - assert.strictEqual(config.pipeTransport.pipeProgram, 'sh') - }) - - it('uses the specified port', async function () { - const config = await makeConfig({}) - - assert.strictEqual( - config.pipeTransport.pipeArgs.some((arg) => arg.includes(config.debugPort!.toString())), - true - ) - }) - }) -}) - -describe('isImageLambdaConfig', function () { - let tempFolder: string - let fakeWorkspaceFolder: vscode.WorkspaceFolder - - let registry: CloudFormationTemplateRegistry - let appDir: string - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - fakeWorkspaceFolder = { - uri: vscode.Uri.file(tempFolder), - name: 'It was me, fakeWorkspaceFolder!', - index: 0, - } - registry = await globals.templateRegistry - appDir = pathutil.normalize(path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/')) - }) - - it('true for Image-backed template', async function () { - const templatePath = vscode.Uri.file(path.join(appDir, 'python3.7-image-sam-app/template.yaml')) - await registry.addItem(templatePath) - - const input = { - name: 'fake-launch-config', - workspaceFolder: fakeWorkspaceFolder, - codeRoot: fakeWorkspaceFolder.uri.fsPath, - runtimeFamily: RuntimeFamily.DotNet, - request: 'launch', - type: 'launch', - runtime: 'fakedotnet' as Runtime, - handlerName: 'fakehandlername', - envFile: '/fake/build/dir/env-vars.json', - eventPayloadFile: '/fake/build/dir/event.json', - documentUri: vscode.Uri.parse('/fake/path/foo.txt'), - templatePath: '/fake/sam/path', - invokeTarget: { - target: 'template', - templatePath: templatePath.fsPath, - logicalId: 'HelloWorldFunction', - lambdaHandler: 'fakehandlername', - projectRoot: fakeWorkspaceFolder.uri.fsPath, - }, - } as SamLaunchRequestArgs - - assert.strictEqual(await isImageLambdaConfig(input), true) - }) - - it('false for ZIP-backed template', async function () { - const templatePath = vscode.Uri.file(path.join(appDir, 'python3.7-plain-sam-app/template.yaml')) - await registry.addItem(templatePath) - - const input = { - name: 'fake-launch-config', - workspaceFolder: fakeWorkspaceFolder, - codeRoot: fakeWorkspaceFolder.uri.fsPath, - runtimeFamily: RuntimeFamily.DotNet, - request: 'launch', - type: 'launch', - runtime: 'fakedotnet' as Runtime, - handlerName: 'fakehandlername', - envFile: '/fake/build/dir/env-vars.json', - eventPayloadFile: '/fake/build/dir/event.json', - documentUri: vscode.Uri.parse('/fake/path/foo.txt'), - templatePath: '/fake/sam/path', - invokeTarget: { - target: 'template', - templatePath: templatePath.fsPath, - logicalId: 'HelloWorldFunction', - lambdaHandler: 'fakehandlername', - projectRoot: fakeWorkspaceFolder.uri.fsPath, - }, - } as SamLaunchRequestArgs - - assert.strictEqual(await isImageLambdaConfig(input), false) - }) - - it('false for code-type', async function () { - const input = { - name: 'fake-launch-config', - workspaceFolder: fakeWorkspaceFolder, - codeRoot: fakeWorkspaceFolder.uri.fsPath, - runtimeFamily: RuntimeFamily.DotNet, - request: 'launch', - type: 'launch', - runtime: 'fakedotnet' as Runtime, - handlerName: 'fakehandlername', - envFile: '/fake/build/dir/env-vars.json', - eventPayloadFile: '/fake/build/dir/event.json', - documentUri: vscode.Uri.parse('/fake/path/foo.txt'), - invokeTarget: { - target: 'code', - lambdaHandler: 'fakehandlername', - projectRoot: fakeWorkspaceFolder.uri.fsPath, - }, - } as SamLaunchRequestArgs - - assert.strictEqual(await isImageLambdaConfig(input), false) - }) -}) - -describe('getArchitecture', function () { - it('returns a valid architecture from a template', function () { - const resource: CloudFormation.Resource = { - Type: 'AWS::Serverless::Function', - Properties: { - CodeUri: 'foo', - Handler: 'foo', - Architectures: ['arm64'], - }, - } - const template: CloudFormation.Template = { - Resources: { - myResource: resource, - }, - } - assert.strictEqual( - getArchitecture(template, resource, { - target: 'template', - logicalId: 'myResource', - templatePath: 'foo', - }), - 'arm64' - ) - }) - - it('returns x86_64 for an invalid architecture from a template', function () { - const resource: CloudFormation.Resource = { - Type: 'AWS::Serverless::Function', - Properties: { - CodeUri: 'foo', - Handler: 'foo', - Architectures: ['powerPc' as 'x86_64'], - }, - } - const template: CloudFormation.Template = { - Resources: { - myResource: resource, - }, - } - assert.strictEqual( - getArchitecture(template, resource, { - target: 'template', - logicalId: 'myResource', - templatePath: 'foo', - }), - 'x86_64' - ) - }) - - it('returns a valid architecture from CodeTargetProperties', function () { - assert.strictEqual( - getArchitecture(undefined, undefined, { - lambdaHandler: 'foo', - projectRoot: 'foo', - target: 'code', - architecture: 'arm64', - }), - 'arm64' - ) - }) - - it('returns x86_64 for an invalid architecture from CodeTargetProperties', function () { - assert.strictEqual( - getArchitecture(undefined, undefined, { - lambdaHandler: 'foo', - projectRoot: 'foo', - target: 'code', - architecture: 'powerPc' as 'x86_64', - }), - 'x86_64' - ) - }) - - it('returns undefined if no value is present in the template', function () { - const resource: CloudFormation.Resource = { - Type: 'AWS::Serverless::Function', - Properties: { - CodeUri: 'foo', - Handler: 'foo', - }, - } - const template: CloudFormation.Template = { - Resources: { - myResource: resource, - }, - } - assert.strictEqual( - getArchitecture(template, resource, { - target: 'template', - logicalId: 'myResource', - templatePath: 'foo', - }), - undefined - ) - }) - - it('returns undefined if no value is present in CodeTargetProperties', function () { - assert.strictEqual( - getArchitecture(undefined, undefined, { - lambdaHandler: 'foo', - projectRoot: 'foo', - target: 'code', - }), - undefined - ) - }) -}) diff --git a/packages/core/src/test/lambda/local/util.ts b/packages/core/src/test/lambda/local/util.ts deleted file mode 100644 index 5b14939004..0000000000 --- a/packages/core/src/test/lambda/local/util.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EOL } from 'os' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { fs } from '../../../shared' - -export async function saveTemplate(templatePath: string, runtime: string, ...functionNames: string[]) { - const functionResources = functionNames - .map( - (functionName) => ` ${functionName}: - Type: ${CloudFormation.SERVERLESS_FUNCTION_TYPE} - Properties: - CodeUri: hello_world/ - Handler: app.lambdaHandler - Runtime: ${runtime} - Environment: - Variables: - PARAM1: VALUE - Events: - HelloWorld: - Type: Api - Properties: - Path: /hello - Method: get` - ) - .join(EOL) - - const templateContent = `AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - my-sam-app - - Sample SAM Template for my-sam-app - -Globals: - Function: - Timeout: 3 - -Resources: -${functionResources} - -Outputs: - - HelloWorldApi: - Description: "API Gateway endpoint URL for Prod stage for Hello World function" - Value: !Sub "https://\${ServerlessRestApi}.execute-api.\${AWS::Region}.amazonaws.com/Prod/hello/" - - HelloWorldFunction: - Description: "Hello World Lambda Function ARN" - Value: !GetAtt HelloWorldFunction.Arn - - HelloWorldFunctionIamRole: - Description: "Implicit IAM Role created for Hello World function" - Value: !GetAtt HelloWorldFunctionRole.Arn -` - - await fs.writeFile(templatePath, templateContent) -} diff --git a/packages/core/src/test/lambda/models/samLambdaRuntime.test.ts b/packages/core/src/test/lambda/models/samLambdaRuntime.test.ts deleted file mode 100644 index dc87253041..0000000000 --- a/packages/core/src/test/lambda/models/samLambdaRuntime.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - compareSamLambdaRuntime, - getDependencyManager, - getFamily, - samZipLambdaRuntimes, - RuntimeFamily, - samImageLambdaRuntimes, - samLambdaCreatableRuntimes, - getNodeMajorVersion, - nodeJsRuntimes, -} from '../../../lambda/models/samLambdaRuntime' -import { Runtime } from '@aws-sdk/client-lambda' - -describe('compareSamLambdaRuntime', async function () { - const scenarios: { - lowerRuntime: string - higherRuntime: string - }[] = [ - { lowerRuntime: 'nodejs14.x', higherRuntime: 'nodejs16.x' }, - { lowerRuntime: 'nodejs16.x', higherRuntime: 'nodejs16.x (Image)' }, - { lowerRuntime: 'nodejs14.x (Image)', higherRuntime: 'nodejs16.x' }, - ] - - for (const scenario of scenarios) { - it(`${scenario.lowerRuntime} < ${scenario.higherRuntime}`, () => { - assert.ok(compareSamLambdaRuntime(scenario.lowerRuntime, scenario.higherRuntime) < 0) - }) - - it(`${scenario.higherRuntime} > ${scenario.lowerRuntime}`, () => { - assert.ok(compareSamLambdaRuntime(scenario.higherRuntime, scenario.lowerRuntime) > 0) - }) - } -}) - -describe('getDependencyManager', function () { - it('all runtimes are handled', function () { - for (const runtime of samZipLambdaRuntimes) { - assert.ok(getDependencyManager(runtime)) - } - }) - it('throws on deprecated runtimes', function () { - assert.throws(() => getDependencyManager('nodejs')) - }) - it('throws on unknown runtimes', function () { - assert.throws(() => getDependencyManager('BASIC' as Runtime)) - }) -}) - -describe('getFamily', function () { - it('unknown runtime name', function () { - assert.strictEqual(getFamily('foo' as Runtime), RuntimeFamily.Unknown) - }) - it('handles all known runtimes', function () { - for (const runtime of samZipLambdaRuntimes) { - assert.notStrictEqual(getFamily(runtime), RuntimeFamily.Unknown) - } - }) - it('throws on deprecated runtimes', function () { - assert.throws(() => getFamily('nodejs')) - }) -}) - -describe('runtimes', function () { - it('cloud9', function () { - assert.deepStrictEqual(samLambdaCreatableRuntimes(true).toArray().sort(), [ - 'nodejs14.x', - 'nodejs16.x', - 'nodejs18.x', - 'nodejs20.x', - 'nodejs22.x', - 'nodejs24.x', - 'python3.10', - 'python3.11', - 'python3.12', - 'python3.13', - 'python3.14', - 'python3.7', - 'python3.8', - 'python3.9', - ]) - assert.deepStrictEqual(samImageLambdaRuntimes(true).toArray().sort(), [ - 'nodejs14.x', - 'nodejs16.x', - 'nodejs18.x', - 'nodejs20.x', - 'nodejs22.x', - 'nodejs24.x', - 'python3.10', - 'python3.11', - 'python3.12', - 'python3.13', - 'python3.14', - 'python3.7', - 'python3.8', - 'python3.9', - ]) - }) - it('vscode', function () { - assert.deepStrictEqual(samLambdaCreatableRuntimes(false).toArray().sort(), [ - 'dotnet6', - 'dotnet8', - 'go1.x', - 'java11', - 'java17', - 'java21', - 'java25', - 'java8', - 'java8.al2', - 'nodejs14.x', - 'nodejs16.x', - 'nodejs18.x', - 'nodejs20.x', - 'nodejs22.x', - 'nodejs24.x', - 'python3.10', - 'python3.11', - 'python3.12', - 'python3.13', - 'python3.14', - 'python3.7', - 'python3.8', - 'python3.9', - ]) - assert.deepStrictEqual(samImageLambdaRuntimes(false).toArray().sort(), [ - 'dotnet5.0', - 'dotnet6', - 'dotnet8', - 'go1.x', - 'java11', - 'java17', - 'java21', - 'java25', - 'java8', - 'java8.al2', - 'nodejs14.x', - 'nodejs16.x', - 'nodejs18.x', - 'nodejs20.x', - 'nodejs22.x', - 'nodejs24.x', - 'python3.10', - 'python3.11', - 'python3.12', - 'python3.13', - 'python3.14', - 'python3.7', - 'python3.8', - 'python3.9', - ]) - }) -}) - -describe('getNodeMajorVersion()', () => { - it('returns node version', () => { - assert.strictEqual(getNodeMajorVersion('nodejs12.x'), 12) - assert.strictEqual(getNodeMajorVersion('nodejs18.x'), 18) - }) - - it('returns undefined on invalid input', () => { - const version = getNodeMajorVersion('python12.x') - assert.strictEqual(version, undefined) - }) - - describe('extracts a version from existing runtimes', function () { - for (const versionString of nodeJsRuntimes) { - it(`extracts from runtime: "${versionString}"`, () => { - const version = getNodeMajorVersion(versionString) - assert(version !== undefined) - assert(0 < version && version < 999) - }) - } - }) -}) diff --git a/packages/core/src/test/lambda/models/samTemplates.test.ts b/packages/core/src/test/lambda/models/samTemplates.test.ts deleted file mode 100644 index 32d2f3caa7..0000000000 --- a/packages/core/src/test/lambda/models/samTemplates.test.ts +++ /dev/null @@ -1,135 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - cliVersionStepFunctionsTemplate, - getSamCliTemplateParameter, - getSamTemplateWizardOption, - getTemplateDescription, - repromptUserForTemplate, - SamTemplate, - helloWorldTemplate, - eventBridgeHelloWorldTemplate, - eventBridgeStarterAppTemplate, - stepFunctionsSampleApp, - typeScriptBackendTemplate, -} from '../../../lambda/models/samTemplates' -import { Set } from 'immutable' - -import { samZipLambdaRuntimes } from '../../../lambda/models/samLambdaRuntime' -import { Runtime } from '@aws-sdk/client-lambda' - -let validTemplateOptions: Set -let validPythonTemplateOptions: Set -let validNodeTemplateOptions: Set -let validGoTemplateOptions: Set -let defaultTemplateOptions: Set - -before(function () { - validTemplateOptions = Set([ - helloWorldTemplate, - eventBridgeHelloWorldTemplate, - eventBridgeStarterAppTemplate, - stepFunctionsSampleApp, - typeScriptBackendTemplate, - ]) - - validPythonTemplateOptions = Set([ - helloWorldTemplate, - eventBridgeHelloWorldTemplate, - eventBridgeStarterAppTemplate, - stepFunctionsSampleApp, - ]) - - validNodeTemplateOptions = Set([helloWorldTemplate, stepFunctionsSampleApp, typeScriptBackendTemplate]) - - validGoTemplateOptions = Set([ - helloWorldTemplate, - eventBridgeHelloWorldTemplate, - eventBridgeStarterAppTemplate, - stepFunctionsSampleApp, - ]) - - defaultTemplateOptions = Set([helloWorldTemplate, stepFunctionsSampleApp]) -}) - -describe('getSamTemplateWizardOption', function () { - it('should successfully return available templates for specific runtime', function () { - for (const runtime of samZipLambdaRuntimes.values()) { - const result = getSamTemplateWizardOption(runtime, 'Zip', cliVersionStepFunctionsTemplate) - switch (runtime) { - case 'python3.7': - case 'python3.8': - case 'python3.9': - case 'python3.10': - case 'python3.11': - case 'python3.12': - case 'python3.13' as Runtime: - case 'python3.14' as Runtime: - assert.deepStrictEqual( - result, - validPythonTemplateOptions, - 'Python 3.x supports additional template options' - ) - break - case 'nodejs16.x': - assert.deepStrictEqual( - result, - validNodeTemplateOptions, - 'Node supports default and TS template options' - ) - break - case 'go1.x': - assert.deepStrictEqual( - result, - validGoTemplateOptions, - 'Go 1.x supports additional template options' - ) - break - default: - assert.deepStrictEqual( - result, - defaultTemplateOptions, - 'Rest of the runtimes support default templates only' - ) - break - } - } - }) - - it('should not return Step Functions templates for a SAM CLI version that does not support them', function () { - for (const runtime of samZipLambdaRuntimes.values()) { - const result = getSamTemplateWizardOption(runtime, 'Zip', '0.40.0') - assert(!result.contains(stepFunctionsSampleApp)) - } - }) -}) - -describe('getSamCliTemplateParameter', function () { - it('should successfully return template values used by sam cli', function () { - for (const template of validTemplateOptions.values()) { - const result = getSamCliTemplateParameter(template) - assert.ok(result, `Template name on the wizard : ${template}, sam cli parameter value : ${result}`) - } - }) - - it('should return error if the template option is not valid', async function () { - assert.throws( - () => getSamCliTemplateParameter(repromptUserForTemplate), - new Error(`${repromptUserForTemplate} is not valid sam template`), - 'Should fail for same error' - ) - }) -}) - -describe('getTemplateDescription', async function () { - it('all templates are handled', async function () { - for (const template of validTemplateOptions) { - // Checking that call does not throw - getTemplateDescription(template) - } - }) -}) diff --git a/packages/core/src/test/lambda/remoteDebugging/ldkClient.test.ts b/packages/core/src/test/lambda/remoteDebugging/ldkClient.test.ts deleted file mode 100644 index eb4678da2f..0000000000 --- a/packages/core/src/test/lambda/remoteDebugging/ldkClient.test.ts +++ /dev/null @@ -1,682 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { FunctionConfiguration } from '@aws-sdk/client-lambda' -import type { UserAgent } from '@aws-sdk/types' -import { LdkClient, getRegionFromArn, isTunnelInfo } from '../../../lambda/remoteDebugging/ldkClient' -import { LocalProxy } from '../../../lambda/remoteDebugging/localProxy' -import * as utils from '../../../lambda/remoteDebugging/utils' -import * as telemetryUtil from '../../../shared/telemetry/util' -import globals from '../../../shared/extensionGlobals' -import { createMockFunctionConfig, createMockProgress } from './testUtils' -import { - IoTSecureTunnelingClient, - IoTSecureTunnelingClientResolvedConfig, - ListTunnelsCommand, - OpenTunnelCommand, - RotateTunnelAccessTokenCommand, - ServiceInputTypes, - ServiceOutputTypes, - TunnelStatus, -} from '@aws-sdk/client-iotsecuretunneling' -import { AwsStub, mockClient } from 'aws-sdk-client-mock' -import * as http from 'http' -import { AWSClientBuilderV3 } from '../../../shared/awsClientBuilderV3' -import { FakeAwsContext } from '../../utilities/fakeAwsContext' -import { Any } from '../../../shared/utilities/typeConstructors' - -describe('Remote Debugging User-Agent test', () => { - let sandbox: sinon.SinonSandbox - let ldkClient: LdkClient - let mockServer: http.Server - let capturedHeaders: http.IncomingHttpHeaders | undefined - let sdkBuilderTmp: Any - let mockLocalProxy: any - - before(async () => { - sdkBuilderTmp = globals.sdkClientBuilderV3 - - mockServer = http.createServer((req, res) => { - capturedHeaders = req.headers - res.writeHead(200, { 'Content-Type': 'application/json' }) - res.end() - }) - - // Start the mock server - await new Promise((resolve) => { - mockServer.listen(0, '127.0.0.1', () => { - resolve() - }) - }) - - const port = (mockServer.address() as any).port - globals.sdkClientBuilderV3 = new AWSClientBuilderV3( - new FakeAwsContext({ - contextCredentials: { - endpointUrl: `http://127.0.0.1:${port}`, - credentials: undefined as any, - credentialsId: '', - }, - }) - ) - }) - - beforeEach(() => { - sandbox = sinon.createSandbox() - sandbox.stub(telemetryUtil, 'getClientId').returns('test-client-id') - capturedHeaders = undefined - // Mock LocalProxy - mockLocalProxy = { - start: sandbox.stub(), - stop: sandbox.stub(), - } - sandbox.stub(LocalProxy.prototype, 'start').callsFake(mockLocalProxy.start) - sandbox.stub(LocalProxy.prototype, 'stop').callsFake(mockLocalProxy.stop) - ldkClient = LdkClient.instance - ;(ldkClient as any).localProxy = mockLocalProxy - ldkClient.dispose() - }) - - afterEach(() => { - sandbox.restore() - }) - - after(async () => { - globals.sdkClientBuilderV3 = sdkBuilderTmp - // Close the server - mockServer.close() - }) - - for (const scenario of ['Lambda', 'IoT']) { - it(`should send ${scenario} request with correct User-Agent header to mock server`, async () => { - try { - switch (scenario) { - case 'Lambda': - await ldkClient.getFunctionDetail('arn:aws:lambda:us-east-1:123456789012:function:testFunction') - break - case 'IoT': - await ldkClient.createOrReuseTunnel('us-east-1') - break - } - } catch (e) { - // Ignore errors from the mock response, we just want to capture headers - } - - // Verify the User-Agent header was sent correctly - assert(capturedHeaders, 'Should have captured request headers') - const userAgent = capturedHeaders!['user-agent'] || capturedHeaders!['User-Agent'] - assert(userAgent, 'Should have User-Agent header') - - // The User-Agent should contain our custom user agent pairs - assert( - userAgent.includes('LAMBDA-DEBUG/1.0.0'), - `User-Agent should include LAMBDA-DEBUG/1.0.0, got: ${userAgent}` - ) - // Check for presence of other user agent components without checking specific values - assert( - userAgent.includes('AWS-Toolkit-For-VSCode/'), - `User-Agent should include AWS-Toolkit-For-VSCode/, got: ${userAgent}` - ) - assert( - userAgent.includes('Visual-Studio-Code'), - `User-Agent should include Visual-Studio-Code, got: ${userAgent}` - ) - assert(userAgent.includes('ClientId/'), `User-Agent should include ClientId/, got: ${userAgent}`) - }) - } -}) - -describe('LdkClient', () => { - let sandbox: sinon.SinonSandbox - let ldkClient: LdkClient - let mockLambdaClient: any - let mockIoTSTClient: AwsStub - let mockLocalProxy: any - - beforeEach(() => { - sandbox = sinon.createSandbox() - - // Mock Lambda client - mockLambdaClient = { - getFunction: sandbox.stub(), - updateFunctionConfiguration: sandbox.stub(), - publishVersion: sandbox.stub(), - deleteFunction: sandbox.stub(), - } - sandbox.stub(utils, 'getLambdaClientWithAgent').returns(mockLambdaClient) - - mockIoTSTClient = mockClient(IoTSecureTunnelingClient) - sandbox.stub(utils, 'getIoTSTClientWithAgent').returns(mockIoTSTClient as any) - - // Mock LocalProxy - mockLocalProxy = { - start: sandbox.stub(), - stop: sandbox.stub(), - } - sandbox.stub(LocalProxy.prototype, 'start').callsFake(mockLocalProxy.start) - sandbox.stub(LocalProxy.prototype, 'stop').callsFake(mockLocalProxy.stop) - - // Mock global state - const stateStorage = new Map() - const mockGlobalState = { - get: (key: string) => stateStorage.get(key), - update: async (key: string, value: any) => { - stateStorage.set(key, value) - return Promise.resolve() - }, - } - sandbox.stub(globals, 'globalState').value(mockGlobalState) - - // Mock telemetry util - sandbox.stub(telemetryUtil, 'getClientId').returns('test-client-id') - ldkClient = LdkClient.instance - ldkClient.dispose() - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('Singleton Pattern', () => { - it('should return the same instance', () => { - const instance1 = LdkClient.instance - const instance2 = LdkClient.instance - assert.strictEqual(instance1, instance2, 'Should return the same singleton instance') - }) - }) - - describe('dispose()', () => { - it('should dispose resources properly', () => { - // Set up a mock local proxy - ;(ldkClient as any).localProxy = mockLocalProxy - - ldkClient.dispose() - - assert(mockLocalProxy.stop.calledOnce, 'Should stop local proxy') - assert.strictEqual((ldkClient as any).localProxy, undefined, 'Should clear local proxy reference') - }) - - it('should clear client caches', () => { - // Add some clients to cache - ;(ldkClient as any).lambdaClientCache.set('us-east-1', mockLambdaClient) - ;(ldkClient as any).lambdaClientCache.set('us-west-2', mockLambdaClient) - - assert.strictEqual((ldkClient as any).lambdaClientCache.size, 2, 'Should have cached clients') - - ldkClient.dispose() - - assert.strictEqual((ldkClient as any).lambdaClientCache.size, 0, 'Should clear Lambda client cache') - }) - }) - - describe('createOrReuseTunnel()', () => { - it('should create new tunnel when none exists', async () => { - mockIoTSTClient.on(ListTunnelsCommand).resolves({ tunnelSummaries: [] }) - mockIoTSTClient.on(OpenTunnelCommand).resolves({ - tunnelId: 'tunnel-123', - sourceAccessToken: 'source-token', - destinationAccessToken: 'dest-token', - }) - - const result = await ldkClient.createOrReuseTunnel('us-east-1') - - assert(result, 'Should return tunnel info') - assert.strictEqual(result?.tunnelID, 'tunnel-123') - assert.strictEqual(result?.sourceToken, 'source-token') - assert.strictEqual(result?.destinationToken, 'dest-token') - assert.strictEqual( - mockIoTSTClient.commandCalls(ListTunnelsCommand).length, - 1, - 'Should list existing tunnels' - ) - assert.strictEqual(mockIoTSTClient.commandCalls(OpenTunnelCommand).length, 1, 'Should create new tunnel') - }) - - it('should reuse existing tunnel with sufficient time remaining', async () => { - const existingTunnel = { - tunnelId: 'existing-tunnel', - description: 'RemoteDebugging+test-client-id', - status: TunnelStatus.OPEN, - createdAt: new Date(Date.now() - 60 * 60 * 1000), // 1 hour ago - } - - mockIoTSTClient.on(ListTunnelsCommand).resolves({ tunnelSummaries: [existingTunnel] }) - mockIoTSTClient.on(RotateTunnelAccessTokenCommand).resolves({ - sourceAccessToken: 'rotated-source-token', - destinationAccessToken: 'rotated-dest-token', - }) - - const result = await ldkClient.createOrReuseTunnel('us-east-1') - - assert(result, 'Should return tunnel info') - assert.strictEqual(result?.tunnelID, 'existing-tunnel') - assert.strictEqual(result?.sourceToken, 'rotated-source-token') - assert.strictEqual(result?.destinationToken, 'rotated-dest-token') - }) - - it('should handle tunnel creation errors', async () => { - mockIoTSTClient.on(ListTunnelsCommand).resolves({ tunnelSummaries: [] }) - mockIoTSTClient.on(OpenTunnelCommand).rejects(new Error('Tunnel creation failed')) - - await assert.rejects( - async () => await ldkClient.createOrReuseTunnel('us-east-1'), - /Error creating\/reusing tunnel/, - 'Should throw error on tunnel creation failure' - ) - }) - }) - - describe('refreshTunnelTokens()', () => { - it('should refresh tunnel tokens successfully', async () => { - mockIoTSTClient.on(RotateTunnelAccessTokenCommand).resolves({ - sourceAccessToken: 'new-source-token', - destinationAccessToken: 'new-dest-token', - }) - - const result = await ldkClient.refreshTunnelTokens('tunnel-123', 'us-east-1') - - assert(result, 'Should return tunnel info') - assert.strictEqual(result?.tunnelID, 'tunnel-123') - assert.strictEqual(result?.sourceToken, 'new-source-token') - assert.strictEqual(result?.destinationToken, 'new-dest-token') - }) - - it('should handle token refresh errors', async () => { - mockIoTSTClient.on(RotateTunnelAccessTokenCommand).rejects(new Error('Token refresh failed')) - - await assert.rejects( - async () => await ldkClient.refreshTunnelTokens('tunnel-123', 'us-east-1'), - /Error refreshing tunnel tokens/, - 'Should throw error on token refresh failure' - ) - }) - }) - - describe('getFunctionDetail()', () => { - const mockFunctionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - }) - - it('should get function details successfully', async () => { - mockLambdaClient.getFunction.resolves({ Configuration: mockFunctionConfig }) - - const result = await ldkClient.getFunctionDetail(mockFunctionConfig.FunctionArn!) - - assert.deepStrictEqual(result, mockFunctionConfig, 'Should return function configuration') - }) - - it('should handle function details retrieval errors', async () => { - mockLambdaClient.getFunction.reset() - mockLambdaClient.getFunction.rejects(new Error('Function not found')) - - const result = await ldkClient.getFunctionDetail(mockFunctionConfig.FunctionArn!) - - assert.strictEqual(result, undefined, 'Should return undefined on error') - }) - - it('should handle invalid ARN', async () => { - const result = await ldkClient.getFunctionDetail('invalid-arn') - - assert.strictEqual(result, undefined, 'Should return undefined for invalid ARN') - }) - }) - - describe('createDebugDeployment()', () => { - const mockFunctionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - }) - - const mockProgress = createMockProgress() - - beforeEach(() => { - mockLambdaClient.updateFunctionConfiguration.resolves({}) - mockLambdaClient.publishVersion.resolves({ Version: 'v1' }) - }) - - it('should create debug deployment successfully without version publishing', async () => { - const result = await ldkClient.createDebugDeployment( - mockFunctionConfig, - 'dest-token', - 900, - false, - 'layer-arn', - mockProgress as any - ) - - assert.strictEqual(result, '$Latest', 'Should return $Latest for non-version deployment') - assert(mockLambdaClient.updateFunctionConfiguration.calledOnce, 'Should update function configuration') - assert(mockLambdaClient.publishVersion.notCalled, 'Should not publish version') - }) - - it('should create debug deployment with version publishing', async () => { - const result = await ldkClient.createDebugDeployment( - mockFunctionConfig, - 'dest-token', - 900, - true, - 'layer-arn', - mockProgress as any - ) - - assert.strictEqual(result, 'v1', 'Should return version number') - assert(mockLambdaClient.publishVersion.calledOnce, 'Should publish version') - }) - - it('should handle deployment errors', async () => { - mockLambdaClient.updateFunctionConfiguration.reset() - mockLambdaClient.updateFunctionConfiguration.rejects(new Error('Update failed')) - - await assert.rejects( - async () => - await ldkClient.createDebugDeployment( - mockFunctionConfig, - 'dest-token', - 900, - false, - 'layer-arn', - mockProgress as any - ), - /Failed to create debug deployment/, - 'Should throw error on deployment failure' - ) - }) - - it('should handle missing function ARN', async () => { - const configWithoutArn = { ...mockFunctionConfig, FunctionArn: undefined } - - await assert.rejects( - async () => - await ldkClient.createDebugDeployment( - configWithoutArn, - 'dest-token', - 900, - false, - 'layer-arn', - mockProgress as any - ), - /Function ARN is missing/, - 'Should throw error for missing ARN' - ) - }) - }) - - describe('removeDebugDeployment()', () => { - const mockFunctionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - }) - - beforeEach(() => { - mockLambdaClient.updateFunctionConfiguration.resolves({}) - }) - - it('should remove debug deployment successfully', async () => { - const result = await ldkClient.removeDebugDeployment(mockFunctionConfig, false) - - assert.strictEqual(result, true, 'Should return true on successful removal') - assert(mockLambdaClient.updateFunctionConfiguration.calledOnce, 'Should update function configuration') - }) - - it('should handle removal errors', async () => { - mockLambdaClient.updateFunctionConfiguration.rejects(new Error('Update failed')) - - await assert.rejects( - async () => await ldkClient.removeDebugDeployment(mockFunctionConfig, false), - /Error removing debug deployment/, - 'Should throw error on removal failure' - ) - }) - - it('should handle missing function ARN', async () => { - const configWithoutArn = { ...mockFunctionConfig, FunctionArn: undefined, FunctionName: undefined } - - await assert.rejects( - async () => await ldkClient.removeDebugDeployment(configWithoutArn, false), - /Error removing debug deployment/, - 'Should throw error for missing ARN' - ) - }) - }) - - describe('deleteDebugVersion()', () => { - it('should delete debug version successfully', async () => { - mockLambdaClient.deleteFunction.resolves({}) - - const result = await ldkClient.deleteDebugVersion( - 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - 'v1' - ) - - assert.strictEqual(result, true, 'Should return true on successful deletion') - assert(mockLambdaClient.deleteFunction.calledOnce, 'Should call deleteFunction') - }) - - it('should handle version deletion errors', async () => { - mockLambdaClient.deleteFunction.rejects(new Error('Delete failed')) - - const result = await ldkClient.deleteDebugVersion( - 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - 'v1' - ) - - assert.strictEqual(result, false, 'Should return false on deletion error') - }) - - it('should handle invalid ARN for version deletion', async () => { - const result = await ldkClient.deleteDebugVersion('invalid-arn', 'v1') - - assert.strictEqual(result, false, 'Should return false for invalid ARN') - }) - }) - - describe('startProxy()', () => { - beforeEach(() => { - mockLocalProxy.start.resolves(9229) - mockLocalProxy.stop.returns() - }) - - it('should start proxy successfully', async () => { - const result = await ldkClient.startProxy('us-east-1', 'source-token', 9229) - - assert.strictEqual(result, true, 'Should return true on successful start') - assert( - mockLocalProxy.start.calledWith('us-east-1', 'source-token', 9229), - 'Should start proxy with correct parameters' - ) - }) - - it('should stop existing proxy before starting new one', async () => { - // Create a spy for the stop method - const stopSpy = sandbox.spy() - - // Set up existing proxy with the spy - ;(ldkClient as any).localProxy = { stop: stopSpy } - - await ldkClient.startProxy('us-east-1', 'source-token', 9229) - - assert(stopSpy.called, 'Should stop existing proxy') - }) - - it('should handle proxy start errors', async () => { - mockLocalProxy.start.rejects(new Error('Proxy start failed')) - - await assert.rejects( - async () => await ldkClient.startProxy('us-east-1', 'source-token', 9229), - /Failed to start proxy/, - 'Should throw error on proxy start failure' - ) - }) - }) - - describe('stopProxy()', () => { - it('should stop proxy successfully', async () => { - // Set up existing proxy - ;(ldkClient as any).localProxy = { stop: mockLocalProxy.stop } - - const result = await ldkClient.stopProxy() - - assert.strictEqual(result, true, 'Should return true on successful stop') - assert(mockLocalProxy.stop.calledOnce, 'Should stop proxy') - assert.strictEqual((ldkClient as any).localProxy, undefined, 'Should clear proxy reference') - }) - - it('should handle stopping when no proxy exists', async () => { - const result = await ldkClient.stopProxy() - - assert.strictEqual(result, true, 'Should return true when no proxy to stop') - }) - }) - - describe('Client User-Agent', () => { - let expectedUserAgentPairs: UserAgent - let userAgentSandbox: sinon.SinonSandbox - beforeEach(() => { - userAgentSandbox = sinon.createSandbox() - // Stub getUserAgentPairs at the telemetryUtil level to return known pairs - const getUserAgentPairsStub = userAgentSandbox.stub(telemetryUtil, 'getUserAgentPairs') - const generalUserAgents: UserAgent = [ - ['AWS-Toolkit-For-VSCode', 'testVersionForUA'], - ['Visual-Studio-Code', '1.0.0'], - ['ClientId', 'test-client-id'], - ] - getUserAgentPairsStub.returns(generalUserAgents) - const lambdaDebugUserAgent: UserAgent = [['LAMBDA-DEBUG', '1.0.0']] - expectedUserAgentPairs = lambdaDebugUserAgent.concat(generalUserAgents) - }) - - afterEach(() => { - userAgentSandbox.restore() - }) - - it('should create Lambda client with correct user-agent', async () => { - // Restore the existing stub and create a new one to track calls - const existingStub = (utils.getLambdaClientWithAgent as any).restore - ? (utils.getLambdaClientWithAgent as sinon.SinonStub) - : undefined - if (existingStub) { - existingStub.restore() - } - - // Stub the Lambda sdkClientBuilderV3 to capture the client options - let capturedClientOptions: any - const createAwsServiceStubLambda = userAgentSandbox.stub(globals.sdkClientBuilderV3, 'createAwsService') - createAwsServiceStubLambda.callsFake((options: any) => { - capturedClientOptions = options - // Return a mock Lambda client that has the required methods - return { - send: async () => ({ - Configuration: createMockFunctionConfig({ - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - }), - }), - middlewareStack: {} as any, - destroy: () => {}, - } as any - }) - - const mockFunctionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:testFunction', - }) - - await ldkClient.getFunctionDetail(mockFunctionConfig.FunctionArn!) - - assert(createAwsServiceStubLambda.called, 'Should call createAwsService') - assert.strictEqual(capturedClientOptions.clientOptions.region, 'us-east-1', 'Should use correct region') - assert.deepStrictEqual( - capturedClientOptions.clientOptions.customUserAgent, - expectedUserAgentPairs, - 'Should include correct customUserAgent pairs with LAMBDA-DEBUG prefix in Lambda API calls' - ) - }) - - it('should create IoT client with correct user-agent', async () => { - // Restore the existing stub and create a new one to track calls - const existingStub = (utils.getIoTSTClientWithAgent as any).restore - ? (utils.getIoTSTClientWithAgent as sinon.SinonStub) - : undefined - if (existingStub) { - existingStub.restore() - } - // Stub the sdkClientBuilderV3 to capture the client options - let capturedClientOptions: any - const createAwsServiceStubIoT = userAgentSandbox.stub(globals.sdkClientBuilderV3, 'createAwsService') - createAwsServiceStubIoT.callsFake((options: any) => { - capturedClientOptions = options - return mockIoTSTClient as any - }) - - mockIoTSTClient.on(ListTunnelsCommand).resolves({ tunnelSummaries: [] }) - mockIoTSTClient.on(OpenTunnelCommand).resolves({ - tunnelId: 'tunnel-123', - sourceAccessToken: 'source-token', - destinationAccessToken: 'dest-token', - }) - - await ldkClient.createOrReuseTunnel('us-east-1') - - assert(createAwsServiceStubIoT.calledOnce, 'Should call createAwsService once') - assert.strictEqual(capturedClientOptions.clientOptions.region, 'us-east-1', 'Should use correct region') - assert.deepStrictEqual( - capturedClientOptions.clientOptions.customUserAgent, - expectedUserAgentPairs, - 'Should include correct customUserAgent pairs with LAMBDA-DEBUG prefix' - ) - }) - }) -}) - -describe('Helper Functions', () => { - describe('getRegionFromArn', () => { - it('should extract region from valid ARN', () => { - const arn = 'arn:aws:lambda:us-east-1:123456789012:function:testFunction' - const result = getRegionFromArn(arn) - assert.strictEqual(result, 'us-east-1', 'Should extract region correctly') - }) - - it('should handle undefined ARN', () => { - const result = getRegionFromArn(undefined) - assert.strictEqual(result, undefined, 'Should return undefined for undefined ARN') - }) - - it('should handle invalid ARN format', () => { - const result = getRegionFromArn('invalid-arn') - assert.strictEqual(result, undefined, 'Should return undefined for invalid ARN') - }) - - it('should handle ARN with insufficient parts', () => { - const result = getRegionFromArn('arn:aws:lambda') - assert.strictEqual(result, undefined, 'Should return undefined for ARN with insufficient parts') - }) - }) - - describe('isTunnelInfo', () => { - it('should validate correct tunnel info', () => { - const tunnelInfo = { - tunnelID: 'tunnel-123', - sourceToken: 'source-token', - destinationToken: 'dest-token', - } - const result = isTunnelInfo(tunnelInfo) - assert.strictEqual(result, true, 'Should validate correct tunnel info') - }) - - it('should reject invalid tunnel info', () => { - const invalidTunnelInfo = { - tunnelID: 'tunnel-123', - sourceToken: 'source-token', - // missing destinationToken - } - const result = isTunnelInfo(invalidTunnelInfo as any) - assert.strictEqual(result, false, 'Should reject invalid tunnel info') - }) - - it('should reject non-object types', () => { - assert.strictEqual(isTunnelInfo('string' as any), false, 'Should reject string') - assert.strictEqual(isTunnelInfo(123 as any), false, 'Should reject number') - assert.strictEqual(isTunnelInfo(undefined as any), false, 'Should reject undefined') - }) - }) -}) diff --git a/packages/core/src/test/lambda/remoteDebugging/ldkController.test.ts b/packages/core/src/test/lambda/remoteDebugging/ldkController.test.ts deleted file mode 100644 index 3add037c1d..0000000000 --- a/packages/core/src/test/lambda/remoteDebugging/ldkController.test.ts +++ /dev/null @@ -1,895 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import sinon, { SinonStubbedInstance, createStubInstance } from 'sinon' -import { FunctionConfiguration } from '@aws-sdk/client-lambda' -import { - RemoteDebugController, - activateRemoteDebugging, - revertExistingConfig, - tryAutoDetectOutFile, - validateSourceMapFiles, -} from '../../../lambda/remoteDebugging/ldkController' -import { getLambdaSnapshot, type DebugConfig } from '../../../lambda/remoteDebugging/lambdaDebugger' -import { LdkClient } from '../../../lambda/remoteDebugging/ldkClient' -import globals from '../../../shared/extensionGlobals' -import * as messages from '../../../shared/utilities/messages' -import { getOpenExternalStub } from '../../globalSetup.test' -import { assertTelemetry } from '../../testUtil' -import { - createMockFunctionConfig, - createMockDebugConfig, - createMockGlobalState, - setupMockLdkClientOperations, - setupMockVSCodeDebugAPIs, - setupMockRevertExistingConfig, - setupDebuggingState, - setupMockCleanupOperations, -} from './testUtils' -import { getRemoteDebugLayer } from '../../../lambda/remoteDebugging/remoteLambdaDebugger' -import { fs } from '../../../shared/fs/fs' -import * as detectCdkProjects from '../../../awsService/cdk/explorer/detectCdkProjects' -import * as glob from 'glob' - -describe('RemoteDebugController', () => { - let sandbox: sinon.SinonSandbox - let mockLdkClient: SinonStubbedInstance - let controller: RemoteDebugController - let mockGlobalState: any - - beforeEach(() => { - sandbox = sinon.createSandbox() - - // Mock LdkClient - mockLdkClient = createStubInstance(LdkClient) - sandbox.stub(LdkClient, 'instance').get(() => mockLdkClient) - - // Mock global state with actual storage - mockGlobalState = createMockGlobalState() - sandbox.stub(globals, 'globalState').value(mockGlobalState) - - // Get controller instance - controller = RemoteDebugController.instance - - // Ensure clean state - controller.ensureCleanState() - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('Singleton Pattern', () => { - it('should return the same instance', () => { - const instance1 = RemoteDebugController.instance - const instance2 = RemoteDebugController.instance - assert.strictEqual(instance1, instance2, 'Should return the same singleton instance') - }) - }) - - describe('State Management', () => { - it('should initialize with clean state', () => { - controller.ensureCleanState() - - assert.strictEqual(controller.isDebugging, false, 'Should not be debugging initially') - assert.strictEqual(controller.qualifier, undefined, 'Qualifier should be undefined initially') - }) - - it('should clean up disposables on ensureCleanState', () => { - // Set up some mock disposables - const mockDisposable = { dispose: sandbox.stub() } - ;(controller as any).debugSessionDisposables.set('test-arn', mockDisposable) - - controller.ensureCleanState() - - assert(mockDisposable.dispose.calledOnce, 'Should dispose existing disposables') - assert.strictEqual((controller as any).debugSessionDisposables.size, 0, 'Should clear disposables map') - }) - }) - - describe('Runtime Support Checks', () => { - it('should support code download for node and python runtimes', () => { - assert.strictEqual(controller.supportCodeDownload('nodejs18.x'), true, 'Should support Node.js') - assert.strictEqual(controller.supportCodeDownload('python3.9'), true, 'Should support Python') - assert.strictEqual( - controller.supportCodeDownload('java11'), - false, - 'Should not support Java for code download' - ) - assert.strictEqual(controller.supportCodeDownload(undefined), false, 'Should not support undefined runtime') - }) - - it('should not support code download for hot-reloading LocalStack functions', () => { - assert.strictEqual(controller.supportCodeDownload('nodejs18.x', 'hot-reloading-hash-not-available'), false) - }) - - it('should support remote debug for node, python, and java runtimes', () => { - assert.strictEqual(controller.supportRuntimeRemoteDebug('nodejs18.x'), true, 'Should support Node.js') - assert.strictEqual(controller.supportRuntimeRemoteDebug('python3.9'), true, 'Should support Python') - assert.strictEqual(controller.supportRuntimeRemoteDebug('java11'), true, 'Should support Java') - assert.strictEqual(controller.supportRuntimeRemoteDebug('dotnet6'), false, 'Should not support .NET') - assert.strictEqual( - controller.supportRuntimeRemoteDebug(undefined), - false, - 'Should not support undefined runtime' - ) - }) - - it('should get remote debug layer for supported regions and architectures', () => { - const result = getRemoteDebugLayer('us-east-1', ['x86_64']) - - assert.strictEqual(typeof result, 'string', 'Should return layer ARN for supported region and architecture') - assert(result?.includes('us-east-1'), 'Should contain the region in the ARN') - assert(result?.includes('LDKLayerX86'), 'Should contain the x86 layer name') - }) - - it('should return undefined for unsupported regions', () => { - const result = getRemoteDebugLayer('unsupported-region', ['x86_64']) - - assert.strictEqual(result, undefined, 'Should return undefined for unsupported region') - }) - - it('should return undefined when region or architectures are undefined', () => { - assert.strictEqual(getRemoteDebugLayer(undefined, ['x86_64']), undefined) - assert.strictEqual(getRemoteDebugLayer('us-west-2', undefined), undefined) - }) - }) - - describe('Extension Installation', () => { - it('should return true when extension is already installed', async () => { - // Mock VSCode extensions API - return extension as already installed - const mockExtension = { id: 'ms-vscode.js-debug', isActive: true } - sandbox.stub(vscode.extensions, 'getExtension').returns(mockExtension as any) - - const result = await controller.installDebugExtension('nodejs18.x') - - assert.strictEqual(result, true, 'Should return true when extension is already installed') - }) - - it('should return true when extension installation succeeds', async () => { - // Mock extension as not installed initially, then installed after command - const getExtensionStub = sandbox.stub(vscode.extensions, 'getExtension') - getExtensionStub.onFirstCall().returns(undefined) // Not installed initially - getExtensionStub.onSecondCall().returns({ isActive: true } as any) // Installed after command - - sandbox.stub(vscode.commands, 'executeCommand').resolves() - sandbox.stub(messages, 'showConfirmationMessage').resolves(true) - - const result = await controller.installDebugExtension('python3.9') - - assert.strictEqual(result, true, 'Should return true when installation succeeds') - }) - - it('should return false when user cancels extension installation', async () => { - // Mock extension as not installed - sandbox.stub(vscode.extensions, 'getExtension').returns(undefined) - sandbox.stub(messages, 'showConfirmationMessage').resolves(false) - - const result = await controller.installDebugExtension('python3.9') - - assert.strictEqual(result, false, 'Should return false when user cancels') - }) - - it('should handle Java runtime workflow', async () => { - // Mock extension as already installed to skip extension installation - const mockExtension = { id: 'redhat.java', isActive: true } - sandbox.stub(vscode.extensions, 'getExtension').returns(mockExtension as any) - - // Mock no Java path found - sandbox.stub(require('../../../shared/utilities/pathFind'), 'findJavaPath').resolves(undefined) - - // Mock user choosing to install JVM - const showConfirmationStub = sandbox.stub(messages, 'showConfirmationMessage').resolves(true) - - // Mock openExternal to prevent actual URL opening - // sandbox.stub(vscode.env, 'openExternal').resolves(true) - getOpenExternalStub().resolves(true) - const result = await controller.installDebugExtension('java11') - - assert.strictEqual(result, false, 'Should return false to allow user to install JVM') - assert(showConfirmationStub.calledOnce, 'Should show JVM installation dialog') - }) - - it('should throw error for undefined runtime', async () => { - await assert.rejects( - async () => await controller.installDebugExtension(undefined), - /Runtime is undefined/, - 'Should throw error for undefined runtime' - ) - }) - }) - - describe('Debug Session Management', () => { - let mockConfig: DebugConfig - let mockFunctionConfig: FunctionConfiguration - - beforeEach(() => { - mockConfig = createMockDebugConfig({ - layerArn: 'arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6', - }) - - mockFunctionConfig = createMockFunctionConfig() - }) - - it('should start debugging successfully', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - // Mock successful LdkClient operations - setupMockLdkClientOperations(mockLdkClient, mockFunctionConfig) - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - await controller.startDebugging(mockConfig.functionArn, 'nodejs18.x', mockConfig) - - // Assert state changes - assert.strictEqual(controller.isDebugging, true, 'Should be in debugging state') - // Qualifier is only set for version publishing, not for $LATEST - assert.strictEqual(controller.qualifier, undefined, 'Should not set qualifier for $LATEST') - - // Verify LdkClient calls - assert(mockLdkClient.getFunctionDetail.calledWith(mockConfig.functionArn), 'Should get function details') - assert(mockLdkClient.createOrReuseTunnel.calledOnce, 'Should create tunnel') - assert(mockLdkClient.createDebugDeployment.calledOnce, 'Should create debug deployment') - assert(mockLdkClient.startProxy.calledOnce, 'Should start proxy') - - assertTelemetry('lambda_remoteDebugStart', { - result: 'Succeeded', - source: 'remoteDebug', - action: '{"port":9229,"remoteRoot":"/var/task","skipFiles":[],"shouldPublishVersion":false,"lambdaTimeout":900,"layerArn":"arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6","isLambdaRemote":true}', - runtimeString: 'nodejs18.x', - }) - }) - - it('should handle debugging start failure and cleanup', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - // Mock function config retrieval success but tunnel creation failure - setupMockLdkClientOperations(mockLdkClient, mockFunctionConfig) - mockLdkClient.createOrReuseTunnel.rejects(new Error('Tunnel creation failed')) - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - let errorThrown = false - try { - await controller.startDebugging(mockConfig.functionArn, 'nodejs18.x', mockConfig) - } catch (error) { - errorThrown = true - assert(error instanceof Error, 'Should throw an error') - assert( - error.message.includes('Error StartDebugging') || error.message.includes('Tunnel creation failed'), - 'Should throw relevant error' - ) - } - - assert(errorThrown, 'Should have thrown an error') - - // Assert state is cleaned up - assert.strictEqual(controller.isDebugging, false, 'Should not be in debugging state after failure') - assert(mockLdkClient.stopProxy.calledOnce, 'Should attempt cleanup') - }) - - it('should handle version publishing workflow', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - const versionConfig = { ...mockConfig, shouldPublishVersion: true } - - // Mock successful LdkClient operations with version publishing - setupMockLdkClientOperations(mockLdkClient, mockFunctionConfig) - mockLdkClient.createDebugDeployment.resolves('v1') - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - await controller.startDebugging(versionConfig.functionArn, 'nodejs18.x', versionConfig) - - assert.strictEqual(controller.isDebugging, true, 'Should be in debugging state') - assert.strictEqual(controller.qualifier, 'v1', 'Should set version qualifier') - // Verify telemetry was emitted with version action - assertTelemetry('lambda_remoteDebugStart', { - result: 'Succeeded', - source: 'remoteDebug', - action: '{"port":9229,"remoteRoot":"/var/task","skipFiles":[],"shouldPublishVersion":true,"lambdaTimeout":900,"layerArn":"arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6","isLambdaRemote":true}', - runtimeString: 'nodejs18.x', - }) - }) - - it('should prevent multiple debugging sessions', async () => { - // Set controller to already debugging - controller.isDebugging = true - - await controller.startDebugging(mockConfig.functionArn, 'nodejs18.x', mockConfig) - - // Should not call LdkClient methods - assert(mockLdkClient.getFunctionDetail.notCalled, 'Should not start new session') - }) - }) - - describe('Stop Debugging', () => { - it('should stop debugging successfully', async () => { - // Mock VSCode APIs - sandbox.stub(vscode.commands, 'executeCommand').resolves() - - // Set up debugging state - await setupDebuggingState(controller, mockGlobalState) - - // Mock successful cleanup operations - setupMockCleanupOperations(mockLdkClient) - - await controller.stopDebugging() - - // Assert state is cleaned up - assert.strictEqual(controller.isDebugging, false, 'Should not be in debugging state') - - // Verify cleanup operations - assert(mockLdkClient.stopProxy.calledOnce, 'Should stop proxy') - assert(mockLdkClient.removeDebugDeployment.calledOnce, 'Should remove debug deployment') - assert(mockLdkClient.deleteDebugVersion.calledOnce, 'Should delete debug version') - assertTelemetry('lambda_remoteDebugStop', { - result: 'Succeeded', - }) - }) - - it('should handle stop debugging when not debugging', async () => { - controller.isDebugging = false - - await controller.stopDebugging() - - // Should complete without error when not debugging - assert.strictEqual(controller.isDebugging, false, 'Should remain not debugging') - }) - - it('should handle cleanup errors gracefully', async () => { - // Mock VSCode APIs - sandbox.stub(vscode.commands, 'executeCommand').resolves() - - controller.isDebugging = true - - const mockFunctionConfig = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - } - // Set up the snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockFunctionConfig) - - // Mock cleanup failure - mockLdkClient.stopProxy.rejects(new Error('Cleanup failed')) - mockLdkClient.removeDebugDeployment.resolves(true) - - await assert.rejects( - async () => await controller.stopDebugging(), - /error when stopping remote debug/, - 'Should throw error on cleanup failure' - ) - - // State should still be cleaned up - assert.strictEqual(controller.isDebugging, false, 'Should clean up state even on error') - // Verify telemetry was emitted for failure - assertTelemetry('lambda_remoteDebugStop', { - result: 'Failed', - }) - }) - }) - - describe('Snapshot Management', () => { - it('should get lambda snapshot from global state', async () => { - const mockSnapshot = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - } - // Set up the snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockSnapshot) - - const result = getLambdaSnapshot() - - assert.deepStrictEqual(result, mockSnapshot, 'Should return snapshot from global state') - }) - - it('should return undefined when no snapshot exists', () => { - const result = getLambdaSnapshot() - - assert.strictEqual(result, undefined, 'Should return undefined when no snapshot') - }) - }) - - describe('Telemetry Verification', () => { - let mockConfig: DebugConfig - let mockFunctionConfig: FunctionConfiguration - - beforeEach(() => { - mockConfig = createMockDebugConfig({ - layerArn: 'arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6', - }) - - mockFunctionConfig = createMockFunctionConfig() - }) - - it('should emit lambda_remoteDebugStart telemetry for failed debugging start', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - // Mock function config retrieval success but tunnel creation failure - setupMockLdkClientOperations(mockLdkClient, mockFunctionConfig) - mockLdkClient.createOrReuseTunnel.rejects(new Error('Tunnel creation failed')) - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - try { - await controller.startDebugging(mockConfig.functionArn, 'nodejs18.x', mockConfig) - } catch (error) { - // Expected to throw - } - - // Verify telemetry was emitted for failure - assertTelemetry('lambda_remoteDebugStart', { - result: 'Failed', - source: 'remoteDebug', - action: '{"port":9229,"remoteRoot":"/var/task","skipFiles":[],"shouldPublishVersion":false,"lambdaTimeout":900,"layerArn":"arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6","isLambdaRemote":true}', - runtimeString: 'nodejs18.x', - }) - }) - }) -}) - -describe('tryAutoDetectOutFile', () => { - let sandbox: sinon.SinonSandbox - - // Common test constants - const testFunctionName = 'TestFunction' - const testSamProjectRoot = vscode.Uri.file('/path/to/sam-project') - const testSamLogicalId = 'MyFunction' - const testCdkProjectRoot = vscode.Uri.file('/path/to/cdk-project') - const testCdkAssetPath = 'asset.728566f9cc2388f3c89a024fd2e887b4d82715454a0fc478f57d7d034364fdd5' - const testCdkOutDir = vscode.Uri.joinPath(testCdkProjectRoot, 'cdk.out') - const testMockWorkspaceFolder: vscode.WorkspaceFolder = { - uri: testCdkProjectRoot, - name: 'cdk-project', - index: 0, - } - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should return undefined for non-TypeScript files', async () => { - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.js', // JavaScript file, not TypeScript - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, undefined, 'Should return undefined for non-TypeScript files') - }) - - it('should return undefined when handlerFile is not provided', async () => { - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: undefined, - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, undefined, 'Should return undefined when handlerFile is not provided') - }) - - it('should detect SAM build path when SAM parameters are provided', async () => { - const expectedPath = vscode.Uri.joinPath(testSamProjectRoot, '.aws-sam', 'build', testSamLogicalId) - - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.ts', - samProjectRoot: testSamProjectRoot, - samFunctionLogicalId: testSamLogicalId, - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - // Mock fs.exists to return true for SAM build path - sandbox.stub(fs, 'exists').resolves(true) - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, expectedPath.fsPath, 'Should return SAM build path') - }) - - it('should return undefined when SAM build path does not exist', async () => { - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.ts', - samProjectRoot: testSamProjectRoot, - samFunctionLogicalId: testSamLogicalId, - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - // Mock fs.exists to return false - sandbox.stub(fs, 'exists').resolves(false) - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, undefined, 'Should return undefined when SAM build path does not exist') - }) - - it('should detect CDK asset path from template.json', async () => { - const expectedAssetDir = vscode.Uri.joinPath(testCdkOutDir, testCdkAssetPath) - - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/cdk-project/src/handler.ts', - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionName: testFunctionName, - }) - - // Mock workspace folder - sandbox.stub(vscode.workspace, 'getWorkspaceFolder').returns(testMockWorkspaceFolder) - - // Mock CDK project detection - const detectCdkProjectsStub = sandbox.stub(detectCdkProjects, 'detectCdkProjects') - detectCdkProjectsStub.resolves([ - { - cdkJsonUri: vscode.Uri.joinPath(testMockWorkspaceFolder.uri, 'cdk.json'), - treeUri: vscode.Uri.joinPath(testCdkOutDir, 'tree.json'), - }, - ]) - - // Mock finding template files - sandbox - .stub(vscode.workspace, 'findFiles') - .resolves([vscode.Uri.joinPath(testCdkOutDir, 'stack.template.json')]) - - // Mock reading template file - const mockTemplate = { - Resources: { - MyFunctionB75F74F2: { - Type: 'AWS::Lambda::Function', - Properties: { - FunctionName: testFunctionName, - }, - Metadata: { - 'aws:asset:path': testCdkAssetPath, - }, - }, - }, - } - const readTextStub = sandbox.stub(fs, 'readFileText') - readTextStub.resolves(JSON.stringify(mockTemplate)) - sandbox.stub(fs, 'exists').resolves(true) - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, expectedAssetDir.fsPath, 'Should return CDK asset directory path') - - const functionNonExistConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionName: 'NonExistentFunction', - }) - const result2 = await tryAutoDetectOutFile(debugConfig, functionNonExistConfig) - - assert.strictEqual(result2, undefined, 'Should return undefined when function not found in template') - - readTextStub.resolves('{ invalid json }') - - const result3 = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result3, undefined, 'Should return undefined on template parsing error') - }) - - it('should return undefined when no workspace folder is found', async () => { - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.ts', - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - // Mock no workspace folder - sandbox.stub(vscode.workspace, 'getWorkspaceFolder').returns(undefined) - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, undefined, 'Should return undefined when no workspace folder') - }) - - it('should prioritize SAM detection over CDK detection', async () => { - const samPath = vscode.Uri.joinPath(testSamProjectRoot, '.aws-sam', 'build', testSamLogicalId) - - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.ts', - samProjectRoot: testSamProjectRoot, - samFunctionLogicalId: testSamLogicalId, - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig({ - FunctionName: testFunctionName, - }) - - // Mock fs.exists to return true for SAM path - const existsStub = sandbox.stub(fs, 'exists') - existsStub.withArgs(samPath).resolves(true) - - // Even though we could detect CDK, SAM should be prioritized - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, samPath.fsPath, 'Should prioritize SAM detection over CDK') - }) - - it('should handle .tsx TypeScript files', async () => { - const expectedPath = vscode.Uri.joinPath(testSamProjectRoot, '.aws-sam', 'build', testSamLogicalId) - - const debugConfig: DebugConfig = createMockDebugConfig({ - handlerFile: '/path/to/handler.tsx', // TSX file - samProjectRoot: testSamProjectRoot, - samFunctionLogicalId: testSamLogicalId, - }) - const functionConfig: FunctionConfiguration = createMockFunctionConfig() - - // Mock fs.exists to return true - sandbox.stub(fs, 'exists').resolves(true) - - const result = await tryAutoDetectOutFile(debugConfig, functionConfig) - - assert.strictEqual(result, expectedPath.fsPath, 'Should handle .tsx files') - }) -}) - -describe('Source Map Pattern Extraction', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should extract temp patterns from source map files', async () => { - assert(vscode.workspace.workspaceFolders?.[0]?.uri, 'Test env should have a workdir') - const testPath = vscode.Uri.joinPath(vscode.workspace.workspaceFolders?.[0]?.uri, 'remote-debug-ts-app').fsPath - - // Call validateSourceMapFiles which will extract temp patterns - const result = await validateSourceMapFiles([`${testPath}/*`]) - - assert(result.isValid, 'Should find valid source map files') - assert(result.tempPatterns.has('tmp5bmwuffn'), 'Should extract temp pattern tmp5bmwuffn from source map') - }) - - it('should handle multiple temp patterns in source maps', async () => { - // Create a mock source map with multiple temp patterns - // Updated to use lowercase and underscore patterns matching Python's tempfile.mkdtemp() - const mockSourceMap = { - version: 3, - file: 'index.js', - sources: [ - '../../../../../../tmpa1b2c3d4/index.ts', - '../../../../../../tmpx9y8_7w6/utils.ts', - '../../../../../../tmp_test123/helper.ts', - '/var/task/regular-path.ts', // This should not match - ], - mappings: 'AAAA', - } - - // Mock fs.readFileText to return our mock source map - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockSourceMap)) - - // Call extractTempPatternsFromSourceMaps directly (we need to export it first) - // For now, we'll test through validateSourceMapFiles - - sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map']) - - const result = await validateSourceMapFiles(['/test/path/*']) - - assert(result.isValid, 'Should be valid') - assert(result.tempPatterns.has('tmpa1b2c3d4'), 'Should extract first temp pattern') - assert(result.tempPatterns.has('tmpx9y8_7w6'), 'Should extract second temp pattern') - assert(result.tempPatterns.has('tmp_test123'), 'Should extract third temp pattern') - assert.strictEqual(result.tempPatterns.size, 3, 'Should have exactly 3 temp patterns') - }) - - it('should handle source maps without temp patterns', async () => { - // Create a mock source map without temp patterns - const mockSourceMap = { - version: 3, - file: 'index.js', - sources: ['./index.ts', '../utils/helper.ts'], - mappings: 'AAAA', - } - - // Mock fs.readFileText - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockSourceMap)) - - // Mock glob - sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map']) - - const result = await validateSourceMapFiles(['/test/path/*']) - - assert(result.isValid, 'Should be valid even without temp patterns') - assert.strictEqual(result.tempPatterns.size, 0, 'Should have no temp patterns') - }) - - it('should handle malformed source map files gracefully', async () => { - // Mock fs.readFileText to return invalid JSON - sandbox.stub(fs, 'readFileText').resolves('{ invalid json }') - - sandbox.stub(glob, 'glob').resolves(['/test/path/index.js', '/test/path/index.js.map']) - - const result = await validateSourceMapFiles(['/test/path/*']) - - assert(result.isValid, 'Should still be valid despite malformed source map') - assert.strictEqual(result.tempPatterns.size, 0, 'Should have no temp patterns when parsing fails') - }) -}) - -describe('Module Functions', () => { - let sandbox: sinon.SinonSandbox - let mockGlobalState: any - - beforeEach(() => { - sandbox = sinon.createSandbox() - - // Mock global state with actual storage - mockGlobalState = createMockGlobalState() - sandbox.stub(globals, 'globalState').value(mockGlobalState) - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('activateRemoteDebugging', () => { - it('should activate remote debugging and ensure clean state', async () => { - // Mock revertExistingConfig - sandbox - .stub(require('../../../lambda/remoteDebugging/ldkController'), 'revertExistingConfig') - .resolves(true) - - // Mock controller - const mockController = { - ensureCleanState: sandbox.stub(), - } - sandbox.stub(RemoteDebugController, 'instance').get(() => mockController) - - await activateRemoteDebugging() - - assert(mockController.ensureCleanState.calledOnce, 'Should ensure clean state') - }) - - it('should handle activation errors gracefully', async () => { - // Mock revertExistingConfig to throw error - sandbox - .stub(require('../../../lambda/remoteDebugging/ldkController'), 'revertExistingConfig') - .rejects(new Error('Revert failed')) - - // Should not throw error, just handle gracefully - await activateRemoteDebugging() - - // Test passes if no error is thrown - assert(true, 'Should handle activation errors gracefully') - }) - }) - - describe('revertExistingConfig', () => { - let mockLdkClient: SinonStubbedInstance - - beforeEach(() => { - mockLdkClient = createStubInstance(LdkClient) - sandbox.stub(LdkClient, 'instance').get(() => mockLdkClient) - }) - - it('should return true when no existing config', async () => { - // mockGlobalState.get.returns(undefined) - - const result = await revertExistingConfig() - - assert.strictEqual(result, true, 'Should return true when no config to revert') - }) - - it('should revert existing config successfully', async () => { - const mockSnapshot = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - Timeout: 30, - } - const mockCurrentConfig = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - Timeout: 900, // Different from snapshot - } - - // Set up the snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockSnapshot) - mockLdkClient.getFunctionDetail.resolves(mockCurrentConfig) - mockLdkClient.removeDebugDeployment.resolves(true) - - const showConfirmationStub = sandbox.stub(messages, 'showConfirmationMessage').resolves(true) - const result = await revertExistingConfig() - - assert.strictEqual(result, true, 'Should return true on successful revert') - assert(showConfirmationStub.calledOnce, 'Should show confirmation dialog') - assert(mockLdkClient.removeDebugDeployment.calledWith(mockSnapshot, false), 'Should revert config') - }) - - it('should handle user cancellation of revert', async () => { - const mockSnapshot = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - } - const mockCurrentConfig = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - Timeout: 900, - } - - // Set up the snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockSnapshot) - mockLdkClient.getFunctionDetail.resolves(mockCurrentConfig) - - sandbox.stub(messages, 'showConfirmationMessage').resolves(false) - - const result = await revertExistingConfig() - - assert.strictEqual(result, true, 'Should return true when user cancels') - // Verify snapshot was cleared - assert.strictEqual( - mockGlobalState.get('aws.lambda.remoteDebugSnapshot'), - undefined, - 'Should clear snapshot' - ) - }) - - it('should handle corrupted snapshot gracefully', async () => { - const corruptedSnapshot = { - // Missing FunctionArn and FunctionName - Timeout: 30, - } - - // Set up corrupted snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', corruptedSnapshot) - - const result = await revertExistingConfig() - - assert.strictEqual(result, true, 'Should return true for corrupted snapshot') - // Verify snapshot was cleared - assert.strictEqual( - mockGlobalState.get('aws.lambda.remoteDebugSnapshot'), - undefined, - 'Should clear corrupted snapshot' - ) - }) - - it('should handle revert errors', async () => { - const mockSnapshot = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - } - - // Set up the snapshot in mock state - await mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockSnapshot) - mockLdkClient.getFunctionDetail.rejects(new Error('Failed to get function')) - - await assert.rejects( - async () => await revertExistingConfig(), - /Error in revertExistingConfig/, - 'Should throw error on revert failure' - ) - }) - }) -}) diff --git a/packages/core/src/test/lambda/remoteDebugging/localProxy.test.ts b/packages/core/src/test/lambda/remoteDebugging/localProxy.test.ts deleted file mode 100644 index 7c1bd0479b..0000000000 --- a/packages/core/src/test/lambda/remoteDebugging/localProxy.test.ts +++ /dev/null @@ -1,421 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import WebSocket from 'ws' -import { LocalProxy } from '../../../lambda/remoteDebugging/localProxy' - -describe('LocalProxy', () => { - let sandbox: sinon.SinonSandbox - let localProxy: LocalProxy - - beforeEach(() => { - sandbox = sinon.createSandbox() - localProxy = new LocalProxy() - }) - - afterEach(() => { - localProxy.stop() - sandbox.restore() - }) - - describe('Constructor', () => { - it('should initialize with default values', () => { - const proxy = new LocalProxy() - assert.strictEqual((proxy as any).isConnected, false, 'Should not be connected initially') - assert.strictEqual((proxy as any).reconnectAttempts, 0, 'Should have zero reconnect attempts') - assert.strictEqual((proxy as any).currentStreamId, 1, 'Should start with stream ID 1') - assert.strictEqual((proxy as any).nextConnectionId, 1, 'Should start with connection ID 1') - }) - }) - - describe('Protobuf Loading', () => { - it('should load protobuf definition successfully', async () => { - const proxy = new LocalProxy() - await (proxy as any).loadProtobufDefinition() - - assert((proxy as any).Message, 'Should load Message type') - assert.strictEqual(typeof (proxy as any).Message, 'object', 'Message should be a protobuf Type object') - assert.strictEqual((proxy as any).Message.constructor.name, 'Type', 'Message should be a protobuf Type') - }) - - it('should not reload protobuf definition if already loaded', async () => { - const proxy = new LocalProxy() - await (proxy as any).loadProtobufDefinition() - const firstMessage = (proxy as any).Message - - await (proxy as any).loadProtobufDefinition() - const secondMessage = (proxy as any).Message - - assert.strictEqual(firstMessage, secondMessage, 'Should not reload protobuf definition') - }) - }) - - describe('TCP Server Management', () => { - it('should close TCP server and connections properly', () => { - const mockSocket = { - removeAllListeners: sandbox.stub(), - destroy: sandbox.stub(), - } - - const mockServer = { - removeAllListeners: sandbox.stub(), - close: sandbox.stub().callsArg(0), - } - - // Set up mock state - ;(localProxy as any).tcpServer = mockServer - ;(localProxy as any).tcpConnections = new Map([[1, { socket: mockSocket }]]) - ;(localProxy as any).closeTcpServer() - - assert(mockSocket.removeAllListeners.called, 'Should remove socket listeners') - assert(mockSocket.destroy.calledOnce, 'Should destroy socket') - assert(mockServer.removeAllListeners.called, 'Should remove server listeners') - assert(mockServer.close.calledOnce, 'Should close server') - }) - }) - - describe('WebSocket Connection Management', () => { - it('should create WebSocket with correct URL and headers', async () => { - const mockWs = { - on: sandbox.stub(), - once: sandbox.stub(), - readyState: WebSocket.OPEN, - removeAllListeners: sandbox.stub(), - close: sandbox.stub(), - terminate: sandbox.stub(), - } - - // Set up LocalProxy with required properties - ;(localProxy as any).region = 'us-east-1' - ;(localProxy as any).accessToken = 'test-access-token' - - // Mock the WebSocket constructor - const WebSocketStub = sandbox.stub().returns(mockWs) - sandbox.stub(WebSocket, 'WebSocket').callsFake(WebSocketStub) - - // Mock the open event to resolve the promise - mockWs.on.withArgs('open').callsArg(1) - - await (localProxy as any).connectWebSocket() - - assert(WebSocketStub.calledOnce, 'Should create WebSocket') - const [url, protocols, options] = WebSocketStub.getCall(0).args - - assert(url.includes('wss://data.tunneling.iot.'), 'Should use correct WebSocket URL') - assert(url.includes('.amazonaws.com:443/tunnel'), 'Should use correct WebSocket URL') - assert(url.includes('local-proxy-mode=source'), 'Should set local proxy mode') - assert.deepStrictEqual(protocols, ['aws.iot.securetunneling-3.0'], 'Should use correct protocol') - assert(options && options.headers && options.headers['access-token'], 'Should include access token header') - assert(options && options.headers && options.headers['client-token'], 'Should include client token header') - }) - - it('should handle WebSocket connection errors', async () => { - const mockWs = { - on: sandbox.stub(), - once: sandbox.stub(), - readyState: WebSocket.CONNECTING, - removeAllListeners: sandbox.stub(), - close: sandbox.stub(), - terminate: sandbox.stub(), - } - - sandbox.stub(WebSocket, 'WebSocket').returns(mockWs) - - // Mock the error event - mockWs.on.withArgs('error').callsArgWith(1, new Error('Connection failed')) - - await assert.rejects( - async () => await (localProxy as any).connectWebSocket(), - /Connection failed/, - 'Should throw error on WebSocket connection failure' - ) - }) - - it('should close WebSocket connection properly', () => { - const mockWs = { - readyState: WebSocket.OPEN, - removeAllListeners: sandbox.stub(), - close: sandbox.stub(), - terminate: sandbox.stub(), - once: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - ;(localProxy as any).closeWebSocket() - - assert(mockWs.removeAllListeners.called, 'Should remove all listeners') - assert(mockWs.close.calledWith(1000, 'Normal Closure'), 'Should close with normal closure code') - }) - - it('should terminate WebSocket if not open', () => { - const mockWs = { - readyState: WebSocket.CONNECTING, - removeAllListeners: sandbox.stub(), - close: sandbox.stub(), - terminate: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - ;(localProxy as any).closeWebSocket() - - assert(mockWs.terminate.calledOnce, 'Should terminate WebSocket if not open') - }) - }) - - describe('Ping/Pong Management', () => { - it('should start ping interval', () => { - const setIntervalStub = sandbox.stub(global, 'setInterval').returns({} as any) - - ;(localProxy as any).startPingInterval() - - assert(setIntervalStub.calledOnce, 'Should start ping interval') - assert.strictEqual(setIntervalStub.getCall(0).args[1], 30000, 'Should ping every 30 seconds') - }) - - it('should stop ping interval', () => { - const clearIntervalStub = sandbox.stub(global, 'clearInterval') - const mockInterval = {} as any - ;(localProxy as any).pingInterval = mockInterval - ;(localProxy as any).stopPingInterval() - - assert(clearIntervalStub.calledWith(mockInterval), 'Should clear ping interval') - assert.strictEqual((localProxy as any).pingInterval, undefined, 'Should clear interval reference') - }) - - it('should send ping when WebSocket is open', () => { - const mockWs = { - readyState: WebSocket.OPEN, - ping: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - - // Simulate ping interval callback - const setIntervalStub = sandbox.stub(global, 'setInterval') - ;(localProxy as any).startPingInterval() - - const pingCallback = setIntervalStub.getCall(0).args[0] - pingCallback() - - assert(mockWs.ping.calledOnce, 'Should send ping') - }) - }) - - describe('Message Processing', () => { - beforeEach(async () => { - // Load protobuf definition - await (localProxy as any).loadProtobufDefinition() - }) - - it('should process binary WebSocket messages', () => { - const processMessageStub = sandbox.stub(localProxy as any, 'processMessage') - - // Create a mock message buffer with length prefix - const messageData = Buffer.from('test message') - const buffer = Buffer.alloc(2 + messageData.length) - buffer.writeUInt16BE(messageData.length, 0) - messageData.copy(buffer, 2) - ;(localProxy as any).handleWebSocketMessage(buffer) - - assert(processMessageStub.calledOnce, 'Should process message') - assert(processMessageStub.calledWith(messageData), 'Should pass correct message data') - }) - - it('should handle incomplete message data', () => { - const processMessageStub = sandbox.stub(localProxy as any, 'processMessage') - - // Create incomplete buffer (only length prefix) - const buffer = Buffer.alloc(2) - buffer.writeUInt16BE(100, 0) // Claims 100 bytes but buffer is only 2 - ;(localProxy as any).handleWebSocketMessage(buffer) - - assert(processMessageStub.notCalled, 'Should not process incomplete message') - }) - - it('should handle non-buffer WebSocket messages', () => { - const processMessageStub = sandbox.stub(localProxy as any, 'processMessage') - - ;(localProxy as any).handleWebSocketMessage('string message') - - assert(processMessageStub.notCalled, 'Should not process non-buffer messages') - }) - }) - - describe('TCP Connection Handling', () => { - beforeEach(() => { - ;(localProxy as any).isConnected = true - ;(localProxy as any).isDisposed = false - }) - - it('should handle new TCP connections when connected', () => { - const mockSocket = { - on: sandbox.stub(), - destroy: sandbox.stub(), - once: sandbox.stub(), - } - - const sendStreamStartStub = sandbox.stub(localProxy as any, 'sendStreamStart') - - ;(localProxy as any).handleNewTcpConnection(mockSocket) - - assert(mockSocket.on.calledWith('data'), 'Should listen for data events') - assert(mockSocket.on.calledWith('error'), 'Should listen for error events') - assert(mockSocket.on.calledWith('close'), 'Should listen for close events') - assert(sendStreamStartStub.calledOnce, 'Should send stream start for first connection') - }) - - it('should reject TCP connections when not connected', () => { - ;(localProxy as any).isConnected = false - - const mockSocket = { - destroy: sandbox.stub(), - } - - ;(localProxy as any).handleNewTcpConnection(mockSocket) - - assert(mockSocket.destroy.calledOnce, 'Should destroy socket when not connected') - }) - - it('should reject TCP connections when disposed', () => { - ;(localProxy as any).isDisposed = true - - const mockSocket = { - destroy: sandbox.stub(), - } - - ;(localProxy as any).handleNewTcpConnection(mockSocket) - - assert(mockSocket.destroy.calledOnce, 'Should destroy socket when disposed') - }) - - it('should send connection start for subsequent connections', () => { - ;(localProxy as any).nextConnectionId = 2 // Second connection - - const mockSocket = { - on: sandbox.stub(), - destroy: sandbox.stub(), - once: sandbox.stub(), - } - - const sendConnectionStartStub = sandbox.stub(localProxy as any, 'sendConnectionStart') - - ;(localProxy as any).handleNewTcpConnection(mockSocket) - - assert(sendConnectionStartStub.calledOnce, 'Should send connection start for subsequent connections') - }) - }) - - describe('Lifecycle Management', () => { - it('should start proxy successfully', async () => { - const startTcpServerStub = sandbox.stub(localProxy as any, 'startTcpServer').resolves(9229) - const connectWebSocketStub = sandbox.stub(localProxy as any, 'connectWebSocket').resolves() - - const port = await localProxy.start('us-east-1', 'source-token', 9229) - - assert.strictEqual(port, 9229, 'Should return assigned port') - assert(startTcpServerStub.calledWith(9229), 'Should start TCP server') - assert(connectWebSocketStub.calledOnce, 'Should connect WebSocket') - assert.strictEqual((localProxy as any).region, 'us-east-1', 'Should store region') - assert.strictEqual((localProxy as any).accessToken, 'source-token', 'Should store access token') - }) - - it('should handle start errors and cleanup', async () => { - sandbox.stub(localProxy as any, 'startTcpServer').resolves(9229) - sandbox.stub(localProxy as any, 'connectWebSocket').rejects(new Error('WebSocket failed')) - const stopStub = sandbox.stub(localProxy, 'stop') - - await assert.rejects( - async () => await localProxy.start('us-east-1', 'source-token', 9229), - /WebSocket failed/, - 'Should throw error on start failure' - ) - - assert(stopStub.calledOnce, 'Should cleanup on start failure') - }) - - it('should stop proxy and cleanup resources', () => { - const stopPingIntervalStub = sandbox.stub(localProxy as any, 'stopPingInterval') - const closeWebSocketStub = sandbox.stub(localProxy as any, 'closeWebSocket') - const closeTcpServerStub = sandbox.stub(localProxy as any, 'closeTcpServer') - - // Set up some state - ;(localProxy as any).isConnected = true - ;(localProxy as any).reconnectAttempts = 5 - ;(localProxy as any).clientToken = 'test-token' - - localProxy.stop() - - assert(stopPingIntervalStub.calledOnce, 'Should stop ping interval') - assert(closeWebSocketStub.calledOnce, 'Should close WebSocket') - assert(closeTcpServerStub.calledOnce, 'Should close TCP server') - assert.strictEqual((localProxy as any).isConnected, false, 'Should reset connection state') - assert.strictEqual((localProxy as any).reconnectAttempts, 0, 'Should reset reconnect attempts') - assert.strictEqual((localProxy as any).clientToken, '', 'Should clear client token') - assert.strictEqual((localProxy as any).isDisposed, true, 'Should mark as disposed') - }) - - it('should handle duplicate stop calls gracefully', () => { - const stopPingIntervalStub = sandbox.stub(localProxy as any, 'stopPingInterval') - - localProxy.stop() - localProxy.stop() // Second call - - // Should not throw error and should handle gracefully - assert(stopPingIntervalStub.calledOnce, 'Should only stop once') - }) - }) - - describe('Message Sending', () => { - beforeEach(async () => { - await (localProxy as any).loadProtobufDefinition() - }) - - it('should send messages when WebSocket is open', () => { - const mockWs = { - readyState: WebSocket.OPEN, - send: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - ;(localProxy as any).serviceId = 'WSS' - ;(localProxy as any).sendMessage(1, 1, 1, Buffer.from('test')) - - assert(mockWs.send.calledOnce, 'Should send message') - const sentData = mockWs.send.getCall(0).args[0] - assert(Buffer.isBuffer(sentData), 'Should send buffer data') - assert(sentData.length > 2, 'Should include length prefix') - }) - - it('should not send messages when WebSocket is not open', () => { - const mockWs = { - readyState: WebSocket.CONNECTING, - send: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - ;(localProxy as any).sendMessage(1, 1, 1, Buffer.from('test')) - - assert(mockWs.send.notCalled, 'Should not send when WebSocket is not open') - }) - - it('should split large data into chunks', () => { - const mockWs = { - readyState: WebSocket.OPEN, - send: sandbox.stub(), - } - - ;(localProxy as any).ws = mockWs - - // Create data larger than max chunk size (63KB) - const largeData = Buffer.alloc(70 * 1024, 'a') - - ;(localProxy as any).sendData(1, 1, largeData) - - assert(mockWs.send.calledTwice, 'Should split large data into chunks') - }) - }) -}) diff --git a/packages/core/src/test/lambda/remoteDebugging/localStackLambdaDebugger.test.ts b/packages/core/src/test/lambda/remoteDebugging/localStackLambdaDebugger.test.ts deleted file mode 100644 index d91fc150f7..0000000000 --- a/packages/core/src/test/lambda/remoteDebugging/localStackLambdaDebugger.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import sinon, { SinonStubbedInstance, createStubInstance } from 'sinon' -import { LdkClient } from '../../../lambda/remoteDebugging/ldkClient' -import { RemoteDebugController } from '../../../lambda/remoteDebugging/ldkController' -import globals from '../../../shared/extensionGlobals' - -import { - createMockDebugConfig, - createMockFunctionConfig, - createMockGlobalState, - setupMockRevertExistingConfig, - setupMockVSCodeDebugAPIs, -} from './testUtils' -import { DebugConfig } from '../../../lambda/remoteDebugging/lambdaDebugger' -import { FunctionConfiguration, Runtime } from '@aws-sdk/client-lambda' -import { assertTelemetry } from '../../testUtil' -import * as remoteDebuggingUtils from '../../../lambda/remoteDebugging/utils' -import { DefaultLambdaClient } from '../../../shared/clients/lambdaClient' - -const LocalStackEndpoint = 'https://localhost.localstack.cloud:4566' - -describe('RemoteDebugController with LocalStackLambdaDebugger', () => { - let sandbox: sinon.SinonSandbox - let mockLdkClient: SinonStubbedInstance - let controller: RemoteDebugController - let mockGlobalState: any - let mockConfig: DebugConfig - let mockFunctionConfig: FunctionConfiguration - let fetchStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - - fetchStub = sandbox.stub(global, 'fetch') - - // Mock LdkClient - mockLdkClient = createStubInstance(LdkClient) - sandbox.stub(LdkClient, 'instance').get(() => mockLdkClient) - - // Mock global state with actual storage - mockGlobalState = createMockGlobalState() - sandbox.stub(globals, 'globalState').value(mockGlobalState) - sandbox.stub(globals.awsContext, 'getCredentialEndpointUrl').returns(LocalStackEndpoint) - - // Get controller instance - controller = RemoteDebugController.instance - - // Ensure clean state - controller.ensureCleanState() - - mockConfig = createMockDebugConfig({ - isLambdaRemote: false, - port: undefined, - layerArn: undefined, - lambdaTimeout: undefined, - }) - mockFunctionConfig = createMockFunctionConfig({ Runtime: 'nodejs22.x' as Runtime }) - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('Debug Session Management', () => { - it('should start debugging successfully', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - // Mock successful LdkClient operations - mockLdkClient.getFunctionDetail.resolves(mockFunctionConfig) - - // Mock waiting for Lambda function to be active - sandbox.stub(remoteDebuggingUtils, 'getLambdaClientWithAgent').returns( - sandbox.createStubInstance(DefaultLambdaClient, { - waitForActive: sandbox.stub().resolves() as any, - }) as any - ) - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - // Mock LocalStack health check - const fetchStubHealth = fetchStub.withArgs(`${LocalStackEndpoint}/_localstack/health`) - fetchStubHealth.resolves(new Response(undefined, { status: 200 })) - - // Mock LocalStack debug config setup - const assignedPort = 8228 - const userAgent = - 'LAMBDA-DEBUG/1.0.0 AWS-Toolkit-For-VSCode/testPluginVersion Visual-Studio-Code/1.102.2 ClientId/11111111-1111-1111-1111-111111111111' - const fetchStubSetup = fetchStub.withArgs( - `${LocalStackEndpoint}/_aws/lambda/debug_configs/${mockFunctionConfig.FunctionArn}:$LATEST`, - { - method: 'PUT', - body: sinon.match.string, - } - ) - fetchStubSetup.resolves( - new Response( - JSON.stringify({ - port: assignedPort, - user_agent: userAgent, - }), - { status: 200 } - ) - ) - - // Mock LocalStack debug config polling - const fetchStubStatus = fetchStub.withArgs( - `${LocalStackEndpoint}/_aws/lambda/debug_configs/${mockFunctionConfig.FunctionArn}:$LATEST?debug_server_ready_timeout=300` - ) - fetchStubStatus.resolves( - new Response( - JSON.stringify({ - port: assignedPort, - user_agent: userAgent, - is_debug_server_running: true, - }), - { status: 200 } - ) - ) - - await controller.startDebugging(mockConfig.functionArn, 'nodejs22.x', mockConfig) - - // Assert state changes - assert.strictEqual(controller.isDebugging, true, 'Should be in debugging state') - // Qualifier is not set for LocalStack - assert.strictEqual(controller.qualifier, undefined, 'Should not set qualifier for $LATEST') - - assert(mockLdkClient.getFunctionDetail.calledWith(mockConfig.functionArn), 'Should get function details') - - assert(fetchStubHealth.calledOnce, 'Should call LocalStack health check once') - assert(fetchStubSetup.calledOnce, 'Should call LocalStack LDM setup once') - assert(fetchStubStatus.calledOnce, 'Should call LocalStack LDM status once') - - assertTelemetry('lambda_remoteDebugStart', { - result: 'Succeeded', - source: 'LocalStackDebug', - action: '{"remoteRoot":"/var/task","skipFiles":[],"shouldPublishVersion":false,"isLambdaRemote":false}', - runtimeString: 'nodejs22.x', - }) - }) - - it('should handle debugging start failure and cleanup', async () => { - // Mock VSCode APIs - setupMockVSCodeDebugAPIs(sandbox) - - // Mock runtime support - sandbox.stub(controller, 'supportRuntimeRemoteDebug').returns(true) - - // Mock function config retrieval - mockLdkClient.getFunctionDetail.resolves(mockFunctionConfig) - - // Mock LocalStack health check - const fetchStubHealth = fetchStub.withArgs(`${LocalStackEndpoint}/_localstack/health`) - fetchStubHealth.resolves(new Response(undefined, { status: 200 })) - - // Mock LocalStack debug config setup error - const fetchStubSetup = fetchStub.withArgs( - `${LocalStackEndpoint}/_aws/lambda/debug_configs/${mockFunctionConfig.FunctionArn}:$LATEST`, - { - method: 'PUT', - body: sinon.match.string, - } - ) - fetchStubSetup.resolves(new Response('Unknown error occurred during setup', { status: 500 })) - - // Mock LocalStack debug config cleanup - const fetchStubCleanup = fetchStub.withArgs( - `${LocalStackEndpoint}/_aws/lambda/debug_configs/${mockFunctionConfig.FunctionArn}:$LATEST`, - { - method: 'DELETE', - } - ) - fetchStubCleanup.resolves(new Response(undefined, { status: 200 })) - - // Mock revertExistingConfig - setupMockRevertExistingConfig(sandbox) - - try { - await controller.startDebugging(mockConfig.functionArn, 'nodejs22.x', mockConfig) - assert.fail('Should have thrown an error') - } catch (error) { - assert(error instanceof Error, 'Should throw an error') - assert( - error.message.includes('Error StartDebugging') || - error.message.includes( - 'Failed to startup execution environment or debugger for Lambda function' - ), - 'Should throw relevant error' - ) - } - - // Assert state is cleaned up - assert.strictEqual(controller.isDebugging, false, 'Should not be in debugging state after failure') - assert(fetchStubCleanup.calledOnce, 'Should attempt cleanup') - }) - }) - - describe('Stop Debugging', () => { - it('should stop debugging successfully', async () => { - // Mock VSCode APIs - sandbox.stub(vscode.commands, 'executeCommand').resolves() - - // Set up debugging state - controller.isDebugging = true - controller.qualifier = '$LATEST' - ;(controller as any).lastDebugStartTime = Date.now() - 5000 // 5 seconds ago - mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockFunctionConfig) - - // Mock successful cleanup - const fetchStubCleanup = fetchStub.withArgs( - `${LocalStackEndpoint}/_aws/lambda/debug_configs/${mockFunctionConfig.FunctionArn}:$LATEST`, - { - method: 'DELETE', - } - ) - fetchStubCleanup.resolves(new Response(undefined, { status: 200 })) - - await controller.stopDebugging() - - // Assert state is cleaned up - assert.strictEqual(controller.isDebugging, false, 'Should not be in debugging state') - - // Verify cleanup operations - assert(fetchStubCleanup.calledOnce, 'Should cleanup the LocalStack debug config') - assertTelemetry('lambda_remoteDebugStop', { - result: 'Succeeded', - }) - }) - }) -}) diff --git a/packages/core/src/test/lambda/remoteDebugging/testUtils.ts b/packages/core/src/test/lambda/remoteDebugging/testUtils.ts deleted file mode 100644 index 42deaa4ec9..0000000000 --- a/packages/core/src/test/lambda/remoteDebugging/testUtils.ts +++ /dev/null @@ -1,176 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import sinon from 'sinon' -import { Architecture, FunctionConfiguration, Runtime, SnapStartApplyOn } from '@aws-sdk/client-lambda' -import { LambdaFunctionNode } from '../../../lambda/explorer/lambdaFunctionNode' -import { InitialData } from '../../../lambda/vue/remoteInvoke/invokeLambda' -import type { DebugConfig } from '../../../lambda/remoteDebugging/lambdaDebugger' - -/** - * Creates a mock Lambda function configuration for testing - */ -export function createMockFunctionConfig(overrides: Partial = {}): FunctionConfiguration { - return { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - Runtime: Runtime.nodejs18x, - Handler: 'index.handler', - Timeout: 30, - Layers: [], - Environment: { Variables: {} }, - Architectures: [Architecture.x86_64], - SnapStart: { ApplyOn: SnapStartApplyOn.None }, - ...overrides, - } -} - -/** - * Creates a mock Lambda function node for testing - */ -export function createMockFunctionNode(overrides: Partial = {}): LambdaFunctionNode { - const config = createMockFunctionConfig() - return { - configuration: config, - regionCode: 'us-west-2', - localDir: '/local/path', - ...overrides, - } as LambdaFunctionNode -} - -/** - * Creates mock initial data for RemoteInvokeWebview testing - */ -export function createMockInitialData(overrides: Partial = {}): InitialData { - const mockFunctionNode = createMockFunctionNode() - return { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - FunctionRegion: 'us-west-2', - InputSamples: [], - Runtime: 'nodejs18.x', - LocalRootPath: '/local/path', - LambdaFunctionNode: mockFunctionNode, - supportCodeDownload: true, - runtimeSupportsRemoteDebug: true, - regionSupportsRemoteDebug: true, - ...overrides, - } as InitialData -} - -/** - * Creates a mock debug configuration for testing - */ -export function createMockDebugConfig(overrides: Partial = {}): DebugConfig { - return { - functionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - functionName: 'testFunction', - port: 9229, - localRoot: '/local/path', - remoteRoot: '/var/task', - skipFiles: [], - shouldPublishVersion: false, - lambdaTimeout: 900, - layerArn: 'arn:aws:lambda:us-west-2:123456789012:layer:LDKLayerX86:6', - isLambdaRemote: true, - ...overrides, - } -} - -/** - * Creates a mock global state for testing - */ -export function createMockGlobalState(): any { - const stateStorage = new Map() - return { - get: (key: string) => stateStorage.get(key), - tryGet: (key: string, type?: any, defaultValue?: any) => { - const value = stateStorage.get(key) - return value !== undefined ? value : defaultValue - }, - update: async (key: string, value: any) => { - stateStorage.set(key, value) - return Promise.resolve() - }, - } -} - -/** - * Sets up common mocks for VSCode APIs - */ -export function setupVSCodeMocks(sandbox: sinon.SinonSandbox) { - return { - startDebugging: sandbox.stub(), - executeCommand: sandbox.stub(), - onDidTerminateDebugSession: sandbox.stub().returns({ dispose: sandbox.stub() }), - } -} - -/** - * Creates a mock progress reporter for testing - */ -export function createMockProgress(): any { - return { - report: sinon.stub(), - } -} - -/** - * Sets up common debugging state for stop debugging tests - */ -export function setupDebuggingState(controller: any, mockGlobalState: any, qualifier: string = 'v1') { - controller.isDebugging = true - controller.qualifier = qualifier - ;(controller as any).lastDebugStartTime = Date.now() - 5000 // 5 seconds ago - - const mockFunctionConfig = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - } - - return mockGlobalState.update('aws.lambda.remoteDebugSnapshot', mockFunctionConfig) -} - -/** - * Sets up common mock operations for successful cleanup - */ -export function setupMockCleanupOperations(mockLdkClient: any) { - mockLdkClient.stopProxy.resolves(true) - mockLdkClient.removeDebugDeployment.resolves(true) - mockLdkClient.deleteDebugVersion.resolves(true) -} - -/** - * Sets up common mock operations for LdkClient testing - */ -export function setupMockLdkClientOperations(mockLdkClient: any, mockFunctionConfig: any) { - mockLdkClient.getFunctionDetail.resolves(mockFunctionConfig) - mockLdkClient.createOrReuseTunnel.resolves({ - tunnelID: 'tunnel-123', - sourceToken: 'source-token', - destinationToken: 'dest-token', - }) - mockLdkClient.createDebugDeployment.resolves('$LATEST') - mockLdkClient.startProxy.resolves(true) - mockLdkClient.stopProxy.resolves(true) - mockLdkClient.removeDebugDeployment.resolves(true) - mockLdkClient.deleteDebugVersion.resolves(true) -} - -/** - * Sets up common VSCode debug API mocks - */ -export function setupMockVSCodeDebugAPIs(sandbox: sinon.SinonSandbox) { - sandbox.stub(require('vscode').debug, 'startDebugging').resolves(true) - sandbox.stub(require('vscode').commands, 'executeCommand').resolves() - sandbox.stub(require('vscode').debug, 'onDidTerminateDebugSession').returns({ dispose: sandbox.stub() }) -} - -/** - * Sets up mock for revertExistingConfig function - */ -export function setupMockRevertExistingConfig(sandbox: sinon.SinonSandbox) { - return sandbox.stub(require('../../../lambda/remoteDebugging/ldkController'), 'revertExistingConfig').resolves(true) -} diff --git a/packages/core/src/test/lambda/uriHandlers.test.ts b/packages/core/src/test/lambda/uriHandlers.test.ts deleted file mode 100644 index f3e8ae7c36..0000000000 --- a/packages/core/src/test/lambda/uriHandlers.test.ts +++ /dev/null @@ -1,36 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SearchParams } from '../../shared/vscode/uriHandler' -import { parseOpenParams } from '../../lambda/uriHandlers' -import { globals } from '../../shared' - -describe('Lambda URI Handler', function () { - describe('load-function', function () { - it('registers for "/lambda/load-function"', function () { - assert.throws(() => globals.uriHandler.onPath('/lambda/load-function', () => {})) - }) - - it('parses parameters', function () { - let query = new SearchParams({ - functionName: 'example', - }) - assert.throws(() => parseOpenParams(query), /A region must be provided/) - query = new SearchParams({ - region: 'example', - }) - assert.throws(() => parseOpenParams(query), /A function name must be provided/) - - const valid = { - functionName: 'example', - region: 'example', - isCfn: 'false', - } - query = new SearchParams(valid) - assert.deepEqual(parseOpenParams(query), valid) - }) - }) -}) diff --git a/packages/core/src/test/lambda/utilities/parameterUtils.test.ts b/packages/core/src/test/lambda/utilities/parameterUtils.test.ts deleted file mode 100644 index 84e9d338f3..0000000000 --- a/packages/core/src/test/lambda/utilities/parameterUtils.test.ts +++ /dev/null @@ -1,230 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' -import { - getOverriddenParameters, - GetOverriddenParametersContext, - getParameterNames, - getParameters, - GetParametersContext, -} from '../../../lambda/config/parameterUtils' -import { getNormalizedRelativePath } from '../../../shared/utilities/pathUtils' - -describe('parameterUtils', async function () { - describe('getParameters', async function () { - it('returns an empty map if template has no parameters section', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({}), - } - - const actual = await getParameters(vscode.Uri.file(''), context) - assert.strictEqual(actual.size, 0) - }) - - it('returns an empty map if parameters section is empty', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({ - Parameters: {}, - }), - } - - const actual = await getParameters(vscode.Uri.file(''), context) - assert.strictEqual(actual.size, 0) - }) - - it('sets `required` to true if default is undefined', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({ - Parameters: { - MyParam: { - Type: 'String', - }, - }, - }), - } - - const actual = await getParameters(vscode.Uri.file(''), context) - assert.strictEqual(actual.size, 1) - - const parameter = actual.get('MyParam') - assert.ok(parameter) - assert.strictEqual(parameter!.required, true) - }) - - it('sets `required` to false if default is defined, but falsy', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({ - Parameters: { - MyParam: { - Type: 'String', - Default: false, - }, - }, - }), - } - - const actual = await getParameters(vscode.Uri.file(''), context) - assert.strictEqual(actual.size, 1) - - const parameter = actual.get('MyParam') - assert.ok(parameter) - assert.strictEqual(parameter!.required, false) - }) - - it('sets `required` to false if default is defined and truthy', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({ - Parameters: { - MyParam: { - Type: 'String', - Default: true, - }, - }, - }), - } - - const actual = await getParameters(vscode.Uri.file(''), context) - assert.strictEqual(actual.size, 1) - - const parameter = actual.get('MyParam') - assert.ok(parameter) - assert.strictEqual(parameter!.required, false) - }) - }) - - describe('getParameterNames', async function () { - it('returns an empty array if no parameters were found', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({}), - } - - const actual = await getParameterNames(vscode.Uri.file(''), context) - assert.strictEqual(actual.length, 0) - }) - - it('returns the names of each parameter', async function () { - const context: GetParametersContext = { - loadTemplate: async () => ({ - Parameters: { - MyParam1: { - Type: 'String', - }, - MyParam2: { - Type: 'String', - }, - }, - }), - } - - const actual = await getParameterNames(vscode.Uri.file(''), context) - assert.strictEqual(actual.length, 2) - assert.strictEqual(actual[0], 'MyParam1') - assert.strictEqual(actual[1], 'MyParam2') - }) - }) - - describe('getOverriddenParameters', async function () { - const workspaceFolderUri = vscode.Uri.file(path.join('my', 'workspace', 'folder')) - const templateUri = vscode.Uri.file(path.join(workspaceFolderUri.fsPath, 'some', 'template.yaml')) - const templateId = getNormalizedRelativePath(workspaceFolderUri.fsPath, templateUri.fsPath) - - it('throws if template is not in the workspace', async function () { - const context: GetOverriddenParametersContext = { - getWorkspaceFolder: (uri) => undefined, - loadTemplatesConfig: async () => ({ - templates: {}, - }), - } - - try { - await getOverriddenParameters(templateUri, context) - } catch (err) { - assert.strictEqual(String(err), `Error: The template ${templateUri.fsPath} is not in the workspace`) - - return - } - - assert.fail('expected exception, but none occurred') - }) - - it('returns undefined if no config is found for this template', async function () { - const context: GetOverriddenParametersContext = { - getWorkspaceFolder: () => ({ - uri: workspaceFolderUri, - }), - loadTemplatesConfig: async () => ({ - templates: {}, - }), - } - - const actual = await getOverriddenParameters(templateUri, context) - assert.strictEqual(actual, undefined) - }) - - it('returns undefined if config for this template does not contain `parameterOverrides`', async function () { - const context: GetOverriddenParametersContext = { - getWorkspaceFolder: () => ({ - uri: workspaceFolderUri, - }), - loadTemplatesConfig: async () => ({ - templates: { - [templateId]: {}, - }, - }), - } - - const actual = await getOverriddenParameters(templateUri, context) - assert.strictEqual(actual, undefined) - }) - - it('returns an empty map if this template contains an empty `parameterOverrides`', async function () { - const context: GetOverriddenParametersContext = { - getWorkspaceFolder: () => ({ - uri: vscode.Uri.file(workspaceFolderUri.fsPath), - }), - loadTemplatesConfig: async () => ({ - templates: { - [templateId]: { - parameterOverrides: {}, - }, - }, - }), - } - - const actual = await getOverriddenParameters(templateUri, context) - assert.ok(actual) - assert.strictEqual(actual!.size, 0) - }) - - it('returns a map of parameter names to their overridden values', async function () { - const context: GetOverriddenParametersContext = { - getWorkspaceFolder: () => ({ - uri: workspaceFolderUri, - }), - loadTemplatesConfig: async () => ({ - templates: { - [templateId]: { - parameterOverrides: { - MyParamName1: 'MyParamValue1', - MyParamName2: 'MyParamValue2', - }, - }, - }, - }), - } - - const actual = await getOverriddenParameters(templateUri, context) - assert.ok(actual) - assert.strictEqual(actual!.size, 2) - assert.ok(actual!.has('MyParamName1')) - assert.ok(actual!.has('MyParamName2')) - assert.strictEqual(actual!.get('MyParamName1'), 'MyParamValue1') - assert.strictEqual(actual!.get('MyParamName2'), 'MyParamValue2') - }) - }) -}) diff --git a/packages/core/src/test/lambda/utils.test.ts b/packages/core/src/test/lambda/utils.test.ts deleted file mode 100644 index 7a8e82043c..0000000000 --- a/packages/core/src/test/lambda/utils.test.ts +++ /dev/null @@ -1,193 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { - getLambdaDetails, - getTempLocation, - getTempRegionLocation, - getFunctionInfo, - setFunctionInfo, - compareCodeSha, -} from '../../lambda/utils' -import { LambdaFunction } from '../../lambda/commands/uploadLambda' -import { DefaultLambdaClient } from '../../shared/clients/lambdaClient' -import { fs } from '../../shared/fs/fs' -import { tempDirPath } from '../../shared/filesystemUtilities' -import path from 'path' -import { Runtime } from '@aws-sdk/client-lambda' - -describe('lambda utils', function () { - const mockLambda = { - name: 'test-function', - region: 'us-east-1', - configuration: { FunctionName: 'test-function' }, - } - describe('getLambdaDetails', function () { - it('returns valid filenames and function names', function () { - const jsNonNestedParsedName = getLambdaDetails({ - Runtime: 'nodejs16.x', - Handler: 'app.lambda_handler', - }) - const pyNonNestedParsedName = getLambdaDetails({ - Runtime: 'python3.8', - Handler: 'app.lambda_handler', - }) - const jsNestedParsedName = getLambdaDetails({ - Runtime: 'nodejs16.x', - Handler: 'asdf/jkl/app.lambda_handler', - }) - const node18ModuleParsedName = getLambdaDetails({ - Runtime: 'nodejs18.x', - Handler: 'asdf/jkl/app.lambda_handler', - }) - const PyNestedParsedName = getLambdaDetails({ - Runtime: 'python3.8', - Handler: 'asdf/jkl/app.lambda_handler', - }) - assert.strictEqual(jsNonNestedParsedName.fileName, 'app.js') - assert.strictEqual(pyNonNestedParsedName.fileName, 'app.py') - assert.strictEqual(jsNestedParsedName.fileName, 'asdf/jkl/app.js') - assert.strictEqual(node18ModuleParsedName.fileName, 'asdf/jkl/app.mjs') - assert.strictEqual(PyNestedParsedName.fileName, 'asdf/jkl/app.py') - assert.strictEqual(jsNonNestedParsedName.functionName, 'lambda_handler') - assert.strictEqual(pyNonNestedParsedName.functionName, 'lambda_handler') - assert.strictEqual(jsNestedParsedName.functionName, 'lambda_handler') - assert.strictEqual(PyNestedParsedName.functionName, 'lambda_handler') - }) - - it('throws if the handler is not a supported runtime', async function () { - // unsupported runtime for import - assert.throws(() => - getLambdaDetails({ - Runtime: 'dotnetcore3.1', - Handler: 'HelloWorld::HelloWorld.Function::FunctionHandler', - }) - ) - // runtime that isn't present, period - assert.throws(() => getLambdaDetails({ Runtime: 'COBOL-60' as Runtime, Handler: 'asdf.asdf' })) - }) - }) - - describe('getTempLocation', function () { - it('returns correct temp location path', function () { - const result = getTempLocation('test-function', 'us-east-1') - const expected = path.join(tempDirPath, 'lambda', 'us-east-1', 'test-function') - assert.strictEqual(result, expected) - }) - }) - - describe('getTempRegionLocation', function () { - it('returns correct temp region path', function () { - const result = getTempRegionLocation('us-west-2') - const expected = path.join(tempDirPath, 'lambda', 'us-west-2') - assert.strictEqual(result, expected) - }) - }) - - describe('getFunctionInfo', function () { - afterEach(function () { - sinon.restore() - }) - - it('returns parsed data when file exists', async function () { - const mockData = { lastDeployed: 123456, undeployed: false, sha: 'test-sha' } - sinon.stub(fs, 'readFileText').resolves(JSON.stringify(mockData)) - - const result = await getFunctionInfo(mockLambda) - assert.deepStrictEqual(result, mockData) - }) - - it('returns specific field when requested', async function () { - const mockData = { lastDeployed: 123456, undeployed: false, sha: 'test-sha' } - sinon.stub(fs, 'readFileText').resolves(JSON.stringify(mockData)) - - const result = await getFunctionInfo(mockLambda, 'sha') - assert.strictEqual(result, 'test-sha') - }) - - it('returns empty object when file does not exist', async function () { - sinon.stub(fs, 'readFileText').rejects(new Error('File not found')) - - const result = await getFunctionInfo(mockLambda) - assert.deepStrictEqual(result, {}) - }) - }) - - describe('setFunctionInfo', function () { - let mockLambda: LambdaFunction - - // jscpd:ignore-start - beforeEach(function () { - mockLambda = { - name: 'test-function', - region: 'us-east-1', - configuration: { FunctionName: 'test-function' }, - } - }) - - afterEach(function () { - sinon.restore() - }) - // jscpd:ignore-end - - it('merges with existing data', async function () { - const existingData = { lastDeployed: 123456, undeployed: true, sha: 'old-sha', handlerFile: 'index.js' } - sinon.stub(fs, 'readFileText').resolves(JSON.stringify(existingData)) - const writeStub = sinon.stub(fs, 'writeFile').resolves() - sinon.stub(DefaultLambdaClient.prototype, 'getFunction').resolves({ - Configuration: { CodeSha256: 'new-sha' }, - } as any) - - await setFunctionInfo(mockLambda, { undeployed: false }) - - assert(writeStub.calledOnce) - const writtenData = JSON.parse(writeStub.firstCall.args[1] as string) - assert.strictEqual(writtenData.lastDeployed, 123456) - assert.strictEqual(writtenData.undeployed, false) - assert.strictEqual(writtenData.sha, 'new-sha') - assert.strictEqual(writtenData.handlerFile, 'index.js') - }) - }) - - describe('compareCodeSha', function () { - let mockLambda: LambdaFunction - - // jscpd:ignore-start - beforeEach(function () { - mockLambda = { - name: 'test-function', - region: 'us-east-1', - configuration: { FunctionName: 'test-function' }, - } - }) - - afterEach(function () { - sinon.restore() - }) - // jscpd:ignore-end - - it('returns true when local and remote SHA match', async function () { - sinon.stub(fs, 'readFileText').resolves(JSON.stringify({ sha: 'same-sha' })) - sinon.stub(DefaultLambdaClient.prototype, 'getFunction').resolves({ - Configuration: { CodeSha256: 'same-sha' }, - } as any) - - const result = await compareCodeSha(mockLambda) - assert.strictEqual(result, true) - }) - - it('returns false when local and remote SHA differ', async function () { - sinon.stub(fs, 'readFileText').resolves(JSON.stringify({ sha: 'local-sha' })) - sinon.stub(DefaultLambdaClient.prototype, 'getFunction').resolves({ - Configuration: { CodeSha256: 'remote-sha' }, - } as any) - - const result = await compareCodeSha(mockLambda) - assert.strictEqual(result, false) - }) - }) -}) diff --git a/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambda.test.ts b/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambda.test.ts deleted file mode 100644 index c39238304f..0000000000 --- a/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambda.test.ts +++ /dev/null @@ -1,932 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { RemoteInvokeWebview, invokeRemoteLambda, InitialData } from '../../../../lambda/vue/remoteInvoke/invokeLambda' -import { LambdaClient, DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import * as vscode from 'vscode' -import * as path from 'path' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import sinon, { SinonStubbedInstance, createStubInstance } from 'sinon' -import { fs } from '../../../../shared' -import * as picker from '../../../../shared/ui/picker' -import { getTestWindow } from '../../../shared/vscode/window' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import * as utils from '../../../../lambda/utils' -import { HttpResourceFetcher } from '../../../../shared/resourcefetcher/httpResourceFetcher' -import { ExtContext } from '../../../../shared/extensions' -import { FakeExtensionContext } from '../../../fakeExtensionContext' -import * as samCliRemoteTestEvent from '../../../../shared/sam/cli/samCliRemoteTestEvent' -import { TestEventsOperation, SamCliRemoteTestEventsParameters } from '../../../../shared/sam/cli/samCliRemoteTestEvent' -import { assertLogsContain } from '../../../globalSetup.test' -import { createResponse } from '../../../testUtil' -import { InvocationResponse } from '@aws-sdk/client-lambda' - -describe('RemoteInvokeWebview', () => { - let outputChannel: vscode.OutputChannel - let client: SinonStubbedInstance - let remoteInvokeWebview: RemoteInvokeWebview - let data: InitialData - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - client = createStubInstance(DefaultLambdaClient) - outputChannel = { - appendLine: (line: string) => {}, - show: () => {}, - } as vscode.OutputChannel - data = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - FunctionRegion: 'us-west-2', - InputSamples: [], - LambdaFunctionNode: { - configuration: { - State: 'Active', - }, - } as LambdaFunctionNode, - } as InitialData - - remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, data) - }) - describe('init', () => { - it('should return the data property', () => { - const mockData: InitialData = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - FunctionRegion: 'us-west-2', - InputSamples: [], - LambdaFunctionNode: { - configuration: { - State: 'Active', - }, - } as LambdaFunctionNode, - } - const result = remoteInvokeWebview.init() - assert.deepEqual(result, mockData) - }) - }) - describe('invokeLambda', () => { - it('invokes Lambda function successfully', async () => { - const input = '{"key": "value"}' - const mockResponse = { - LogResult: Buffer.from('Test log').toString('base64'), - Payload: new TextEncoder().encode('{"result": "success"}'), - } satisfies InvocationResponse - client.invoke.resolves(mockResponse) - - const appendedLines: string[] = [] - outputChannel.appendLine = (line: string) => { - appendedLines.push(line) - } - - await remoteInvokeWebview.invokeLambda(input) - assert(client.invoke.calledOnce) - assert(client.invoke.calledWith(data.FunctionArn, input, sinon.match.any, 'Tail')) - assert.deepStrictEqual(appendedLines, [ - 'Loading response...', - 'Invocation result for arn:aws:lambda:us-west-2:123456789012:function:testFunction', - 'Logs:', - 'Test log', - '', - 'Payload:', - '{"result": "success"}', - '', - ]) - }) - it('handles Lambda invocation with no payload', async () => { - const mockResponse = { - LogResult: Buffer.from('Test log').toString('base64'), - Payload: new TextEncoder().encode(''), - } satisfies InvocationResponse - - client.invoke.resolves(mockResponse) - const appendedLines: string[] = [] - outputChannel.appendLine = (line: string) => { - appendedLines.push(line) - } - - await remoteInvokeWebview.invokeLambda('') - - assert.deepStrictEqual(appendedLines, [ - 'Loading response...', - 'Invocation result for arn:aws:lambda:us-west-2:123456789012:function:testFunction', - 'Logs:', - 'Test log', - '', - 'Payload:', - '{}', - '', - ]) - }) - it('handles Lambda invocation with undefined LogResult', async () => { - const mockResponse = { - Payload: new TextEncoder().encode('{"result": "success"}'), - } satisfies InvocationResponse - - client.invoke.resolves(mockResponse) - - const appendedLines: string[] = [] - outputChannel.appendLine = (line: string) => { - appendedLines.push(line) - } - - await remoteInvokeWebview.invokeLambda('{}') - - assert.deepStrictEqual(appendedLines, [ - 'Loading response...', - 'Invocation result for arn:aws:lambda:us-west-2:123456789012:function:testFunction', - 'Logs:', - '', - '', - 'Payload:', - '{"result": "success"}', - '', - ]) - }) - it('handles Lambda invocation error', async () => { - const input = '{"key": "value"}' - const mockError = new Error('Lambda invocation failed') - - client.invoke.rejects(mockError) - - const appendedLines: string[] = [] - outputChannel.appendLine = (line: string) => { - appendedLines.push(line) - } - - try { - await remoteInvokeWebview.invokeLambda(input) - assert.fail('Expected an error to be thrown') - } catch (err) { - assert.ok(err instanceof Error) - assert.strictEqual(err.message, 'Expected an error to be thrown') - } - - assert.deepStrictEqual(appendedLines, [ - 'Loading response...', - 'There was an error invoking arn:aws:lambda:us-west-2:123456789012:function:testFunction', - mockError.toString(), - '', - ]) - }) - }) - - describe('promptFile', () => { - it('prompts the user for a file and returns the selected file', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const placeholderEventFile = path.join(tempFolder, 'file.json') - await fs.writeFile(placeholderEventFile, '{"sample": ""}') - const fileUri = vscode.Uri.file(placeholderEventFile) - - getTestWindow().onDidShowDialog((d) => d.selectItem(fileUri)) - - const response = await remoteInvokeWebview.promptFile() - assert.deepStrictEqual(response?.sample, '{"sample": ""}') - assert.deepStrictEqual(response.selectedFile, 'file.json') - assert.deepStrictEqual(response.selectedFilePath, fileUri.fsPath) - }) - it('Returns undefined if no file is selected', async () => { - getTestWindow().onDidShowDialog((d) => d.close()) - const response = await remoteInvokeWebview.promptFile() - assert.strictEqual(response, undefined) - }) - it('logs an error and throws ToolkitError when reading the file fails', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const placeholderEventFile = path.join(tempFolder, 'file.json') - const fileUri = vscode.Uri.file(placeholderEventFile) - - getTestWindow().onDidShowDialog((d) => d.selectItem(fileUri)) - - await assert.rejects( - async () => await remoteInvokeWebview.promptFile(), - new Error('Failed to read selected file') - ) - assertLogsContain('readFileSync: Failed to read file at path', false, 'error') - }) - }) - - describe('loadFile', () => { - it('loads a file successfully', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const placeholderEventFile = path.join(tempFolder, 'file.json') - await fs.writeFile(placeholderEventFile, '{"sample": ""}') - const result = await remoteInvokeWebview.loadFile(placeholderEventFile) - - assert.strictEqual(result?.sample, '{"sample": ""}') - assert.strictEqual( - path.normalize(result.selectedFilePath).toLowerCase(), - path.normalize(placeholderEventFile).toLowerCase() - ) - assert.strictEqual(result?.selectedFile, 'file.json') - await fs.delete(tempFolder, { recursive: true }) - }) - it('handles invalid JSON file', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const invalidJsonFile = path.join(tempFolder, 'invalid.json') - await fs.writeFile(invalidJsonFile, '{"invalid": "json",}') - try { - await remoteInvokeWebview.loadFile(invalidJsonFile) - assert.fail('Failed to parse selected file') - } catch (err) { - assert.ok(err instanceof Error) - assert.strictEqual(err.message, 'Failed to parse selected file') - } - await fs.delete(tempFolder, { recursive: true }) - }) - it('handles file load error', async () => { - const nonExistentFile = '/path/to/non-existent-file.json' - try { - await remoteInvokeWebview.loadFile(nonExistentFile) - assert.fail('Expected an error to be thrown') - } catch (err) { - assert.ok(err instanceof Error) - assert.strictEqual(err.message, `Failed to read selected file`) - } - }) - it('should return undefined if filePath is empty in loadFile', async () => { - const result = await remoteInvokeWebview.loadFile('') - assert.strictEqual(result, undefined, 'Expected result to be undefined for empty file path') - }) - }) - - describe('Remote Test Events', () => { - let runSamCliStub: sinon.SinonStub - sandbox = sinon.createSandbox() - beforeEach(() => { - runSamCliStub = sandbox.stub(samCliRemoteTestEvent, 'runSamCliRemoteTestEvents') - // Mock getSamCliContext module - const samCliContext = require('../../../../shared/sam/cli/samCliContext') - sandbox.stub(samCliContext, 'getSamCliContext').returns({ - invoker: {} as any, - }) - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('listRemoteTestEvents', () => { - it('should list remote test events successfully', async () => { - runSamCliStub.resolves('event1\nevent2\nevent3\n') - - const events = await remoteInvokeWebview.listRemoteTestEvents(data.FunctionArn, data.FunctionRegion) - - assert.deepStrictEqual(events, ['event1', 'event2', 'event3']) - assert(runSamCliStub.calledOnce) - assert( - runSamCliStub.calledWith( - sinon.match({ - functionArn: data.FunctionArn, - operation: 'list', - region: data.FunctionRegion, - }) - ) - ) - }) - - it('should return empty array when no events exist (registry not found)', async () => { - runSamCliStub.rejects(new Error('lambda-testevent-schemas registry not found')) - - const events = await remoteInvokeWebview.listRemoteTestEvents(data.FunctionArn, data.FunctionRegion) - - assert.deepStrictEqual(events, []) - }) - - it('should return empty array when there are no saved events', async () => { - runSamCliStub.rejects(new Error('There are no saved events')) - - const events = await remoteInvokeWebview.listRemoteTestEvents(data.FunctionArn, data.FunctionRegion) - - assert.deepStrictEqual(events, []) - }) - - it('should re-throw other errors', async () => { - runSamCliStub.rejects(new Error('Network error')) - - await assert.rejects( - async () => await remoteInvokeWebview.listRemoteTestEvents(data.FunctionArn, data.FunctionRegion), - /Network error/ - ) - }) - }) - - describe('selectRemoteTestEvent', () => { - it('should show quickpick and return selected event content', async () => { - // Mock list events - runSamCliStub.onFirstCall().resolves('event1\nevent2\n') - // Mock get event content - runSamCliStub.onSecondCall().resolves('{"test": "content"}') - - // Mock quickpick selection using test window - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('event1') - }) - - const result = await remoteInvokeWebview.selectRemoteTestEvent(data.FunctionArn, data.FunctionRegion) - - assert.strictEqual(result, '{"test": "content"}') - }) - - it('should show info message when no events exist', async () => { - runSamCliStub.onFirstCall().resolves('') - - let infoMessageShown = false - getTestWindow().onDidShowMessage((message) => { - if (message.message.includes('No remote test events found')) { - infoMessageShown = true - } - }) - - const result = await remoteInvokeWebview.selectRemoteTestEvent(data.FunctionArn, data.FunctionRegion) - - assert.strictEqual(result, undefined) - assert(infoMessageShown, 'Info message should be shown') - }) - - it('should return undefined when user cancels quickpick', async () => { - runSamCliStub.onFirstCall().resolves('event1\nevent2\n') - - // Mock user canceling quickpick - getTestWindow().onDidShowQuickPick((picker) => { - picker.hide() - }) - - const result = await remoteInvokeWebview.selectRemoteTestEvent(data.FunctionArn, data.FunctionRegion) - - assert.strictEqual(result, undefined) - }) - - it('should handle list events error gracefully', async () => { - runSamCliStub.rejects(new Error('API error')) - - let errorMessageShown = false - getTestWindow().onDidShowMessage((message) => { - // Check if it's an error message - errorMessageShown = true - }) - - const result = await remoteInvokeWebview.selectRemoteTestEvent(data.FunctionArn, data.FunctionRegion) - - assert.strictEqual(result, undefined) - assert(errorMessageShown, 'Error message should be shown') - }) - }) - - describe('saveRemoteTestEvent', () => { - it('should create new test event', async () => { - // Mock empty list (no existing events) - runSamCliStub.onFirstCall().resolves('') - // Mock create event success - runSamCliStub.onSecondCall().resolves('Event created') - - // Mock quickpick to select "Create new" - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('$(add) Create new test event') - }) - - // Mock input box for event name - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('MyNewEvent') - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"test": "data"}' - ) - - assert.strictEqual(result, 'MyNewEvent') - assert(runSamCliStub.calledTwice) - assert( - runSamCliStub.secondCall.calledWith( - sinon.match({ - functionArn: data.FunctionArn, - operation: 'put', - name: 'MyNewEvent', - eventSample: '{"test": "data"}', - region: data.FunctionRegion, - force: false, - }) - ) - ) - }) - - it('should overwrite existing test event with force flag', async () => { - // Mock list with existing events - runSamCliStub.onFirstCall().resolves('existingEvent1\nexistingEvent2\n') - // Mock update event success - runSamCliStub.onSecondCall().resolves('Event updated') - - // Mock quickpick to select existing event - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('existingEvent1') - }) - - // Mock confirmation dialog - getTestWindow().onDidShowMessage((message) => { - // Select the overwrite option - message.selectItem('Overwrite') - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"updated": "data"}' - ) - - assert.strictEqual(result, 'existingEvent1') - assert(runSamCliStub.calledTwice) - assert( - runSamCliStub.secondCall.calledWith( - sinon.match({ - functionArn: data.FunctionArn, - operation: 'put', - name: 'existingEvent1', - eventSample: '{"updated": "data"}', - region: data.FunctionRegion, - force: true, // Should use force flag for overwrite - }) - ) - ) - }) - - it('should handle user cancellation of overwrite', async () => { - runSamCliStub.onFirstCall().resolves('existingEvent1\n') - - // Mock quickpick to select existing event - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('existingEvent1') - }) - - // User cancels overwrite warning - getTestWindow().onDidShowMessage((message) => { - // Cancel the dialog - message.close() - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"test": "data"}' - ) - - assert.strictEqual(result, undefined) - assert(runSamCliStub.calledOnce) // Only list was called - }) - - it('should validate event name for new events', async () => { - runSamCliStub.onFirstCall().resolves('existingEvent\n') - runSamCliStub.onSecondCall().resolves('Event created') - - // Mock quickpick to select "Create new" - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('$(add) Create new test event') - }) - - // Mock input box with validation - let validationTested = false - getTestWindow().onDidShowInputBox((input) => { - // We can't directly test validation in this test framework - // Just accept a valid value - input.acceptValue('NewEvent') - validationTested = true - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"test": "data"}' - ) - - assert.strictEqual(result, 'NewEvent') - assert(validationTested, 'Input box should have been shown') - }) - - it('should handle list events error gracefully', async () => { - // List events fails but should continue - runSamCliStub.onFirstCall().rejects(new Error('List failed')) - runSamCliStub.onSecondCall().resolves('Event created') - - // Mock quickpick to select "Create new" - getTestWindow().onDidShowQuickPick((picker) => { - picker.acceptItem('$(add) Create new test event') - }) - - // Mock input box for event name - getTestWindow().onDidShowInputBox((input) => { - input.acceptValue('NewEvent') - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"test": "data"}' - ) - - assert.strictEqual(result, 'NewEvent') - // Should still create the event even if list failed - assert(runSamCliStub.calledTwice) - }) - - it('should return undefined when user cancels quickpick', async () => { - runSamCliStub.onFirstCall().resolves('event1\n') - - // Mock user canceling quickpick - getTestWindow().onDidShowQuickPick((picker) => { - picker.hide() - }) - - const result = await remoteInvokeWebview.saveRemoteTestEvent( - data.FunctionArn, - data.FunctionRegion, - '{"test": "data"}' - ) - - assert.strictEqual(result, undefined) - }) - }) - - describe('createRemoteTestEvents', () => { - it('should create event without force flag', async () => { - runSamCliStub.resolves('Event created') - - const result = await remoteInvokeWebview.createRemoteTestEvents({ - name: 'TestEvent', - event: '{"test": "data"}', - region: 'us-west-2', - arn: data.FunctionArn, - }) - - assert.strictEqual(result, 'Event created') - assert( - runSamCliStub.calledWith( - sinon.match({ - functionArn: data.FunctionArn, - operation: 'put', - name: 'TestEvent', - eventSample: '{"test": "data"}', - region: 'us-west-2', - force: false, - }) - ) - ) - }) - - it('should create event with force flag for overwrite', async () => { - runSamCliStub.resolves('Event updated') - - const result = await remoteInvokeWebview.createRemoteTestEvents( - { - name: 'ExistingEvent', - event: '{"updated": "data"}', - region: 'us-west-2', - arn: data.FunctionArn, - }, - true // force flag - ) - - assert.strictEqual(result, 'Event updated') - assert( - runSamCliStub.calledWith( - sinon.match({ - force: true, - }) - ) - ) - }) - }) - - describe('getRemoteTestEvents', () => { - it('should get remote test event content', async () => { - runSamCliStub.resolves('{"event": "content"}') - - const result = await remoteInvokeWebview.getRemoteTestEvents({ - name: 'TestEvent', - region: 'us-west-2', - arn: data.FunctionArn, - }) - - assert.strictEqual(result, '{"event": "content"}') - assert( - runSamCliStub.calledWith( - sinon.match({ - name: 'TestEvent', - operation: 'get', - functionArn: data.FunctionArn, - region: 'us-west-2', - }) - ) - ) - }) - }) - }) - describe('listRemoteTestEvents', () => { - let runSamCliRemoteTestEventsStub: sinon.SinonStub - beforeEach(() => { - runSamCliRemoteTestEventsStub = sinon.stub(samCliRemoteTestEvent, 'runSamCliRemoteTestEvents') - }) - afterEach(() => { - sinon.restore() - }) - - it('should call remoteTestEvents with correct parameters and return a split result', async () => { - const functionArn = 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda' - const region = 'us-west-2' - const mockResponse = 'event1\nevent2\nevent3' - const expectedParams = { - functionArn: functionArn, - operation: TestEventsOperation.List, - region: region, - } - runSamCliRemoteTestEventsStub.resolves(mockResponse) - const result = await remoteInvokeWebview.listRemoteTestEvents(functionArn, region) - assert(runSamCliRemoteTestEventsStub.calledOnce) - assert(runSamCliRemoteTestEventsStub.calledWith(expectedParams)) - const expectedResult = ['event1', 'event2', 'event3'] - assert.deepEqual(result, expectedResult) - }) - - it('should handle errors thrown by remoteTestEvents', async () => { - const functionArn = 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda' - const region = 'us-west-2' - const errorMessage = 'Error listing remote test events' - runSamCliRemoteTestEventsStub.rejects(new Error(errorMessage)) - await assert.rejects(async () => { - await remoteInvokeWebview.listRemoteTestEvents(functionArn, region) - }, new RegExp(errorMessage)) - assert(runSamCliRemoteTestEventsStub.calledOnce) - }) - }) - - describe('createRemoteTestEvents', () => { - let runSamCliRemoteTestEventsStub: sinon.SinonStub - beforeEach(() => { - runSamCliRemoteTestEventsStub = sinon.stub(samCliRemoteTestEvent, 'runSamCliRemoteTestEvents') - }) - afterEach(() => { - sinon.restore() - }) - it('should call remoteTestEvents with correct parameters', async () => { - const mockPutEvent = { - arn: 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda', - name: 'TestEvent', - event: '{"key": "value"}', - region: 'us-west-2', - } - await remoteInvokeWebview.createRemoteTestEvents(mockPutEvent) - const expectedParams: SamCliRemoteTestEventsParameters = { - functionArn: mockPutEvent.arn, - operation: TestEventsOperation.Put, - name: mockPutEvent.name, - eventSample: mockPutEvent.event, - region: mockPutEvent.region, - force: false, // Default value when not overwriting - } - assert(runSamCliRemoteTestEventsStub.calledOnce, 'remoteTestEvents should be called once') - assert( - runSamCliRemoteTestEventsStub.calledWith(expectedParams), - 'remoteTestEvents should be called with correct parameters' - ) - }) - - it('should return the result from remoteTestEvents', async () => { - const mockPutEvent = { - arn: 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda', - name: 'TestEvent', - event: '{"key": "value"}', - region: 'us-west-2', - } - const mockResponse = 'Success' - runSamCliRemoteTestEventsStub.resolves(mockResponse) - const result = await remoteInvokeWebview.createRemoteTestEvents(mockPutEvent) - assert.strictEqual(result, mockResponse, 'The result should match the mock response') - }) - - it('should call remoteTestEvents with force flag when overwriting', async () => { - const mockPutEvent = { - arn: 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda', - name: 'ExistingEvent', - event: '{"key": "updated value"}', - region: 'us-west-2', - } - await remoteInvokeWebview.createRemoteTestEvents(mockPutEvent, true) // force = true - const expectedParams: SamCliRemoteTestEventsParameters = { - functionArn: mockPutEvent.arn, - operation: TestEventsOperation.Put, - name: mockPutEvent.name, - eventSample: mockPutEvent.event, - region: mockPutEvent.region, - force: true, // Should include force flag when overwriting - } - assert(runSamCliRemoteTestEventsStub.calledOnce, 'remoteTestEvents should be called once') - assert( - runSamCliRemoteTestEventsStub.calledWith(expectedParams), - 'remoteTestEvents should be called with force flag' - ) - }) - }) - - describe('getRemoteTestEvents', () => { - let runSamCliRemoteTestEventsStub: sinon.SinonStub - beforeEach(() => { - runSamCliRemoteTestEventsStub = sinon.stub(samCliRemoteTestEvent, 'runSamCliRemoteTestEvents') - }) - afterEach(() => { - sinon.restore() - }) - it('should call remoteTestEvents with correct parameters', async () => { - const mockEvent = { - name: 'TestLambda', - arn: 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda', - region: 'us-west-2', - } - const expectedParams = { - name: mockEvent.name, - operation: TestEventsOperation.Get, - functionArn: mockEvent.arn, - region: mockEvent.region, - } - const mockResponse = 'mockResponse' - runSamCliRemoteTestEventsStub.resolves(mockResponse) - const result = await remoteInvokeWebview.getRemoteTestEvents(mockEvent) - assert(runSamCliRemoteTestEventsStub.calledOnce) - assert(runSamCliRemoteTestEventsStub.calledWith(expectedParams)) - assert.strictEqual(result, mockResponse) - }) - - it('should handle errors thrown by remoteTestEvents', async () => { - const mockEvent = { - name: 'TestLambda', - arn: 'arn:aws:lambda:us-west-2:123456789012:function:TestLambda', - region: 'us-west-2', - } - const errorMessage = 'Error invoking remote test events' - runSamCliRemoteTestEventsStub.rejects(new Error(errorMessage)) - await assert.rejects(async () => { - await remoteInvokeWebview.getRemoteTestEvents(mockEvent) - }, new RegExp(errorMessage)) - assert(runSamCliRemoteTestEventsStub.calledOnce) - }) - }) - - describe('getSamplePayload', () => { - let getSampleLambdaPayloadsStub: sinon.SinonStub - let createQuickPickStub: sinon.SinonStub - let promptUserStub: sinon.SinonStub - let verifySinglePickerOutputStub: sinon.SinonStub - let httpFetcherStub: sinon.SinonStub - - beforeEach(() => { - getSampleLambdaPayloadsStub = sinon.stub(utils, 'getSampleLambdaPayloads') - createQuickPickStub = sinon.stub(picker, 'createQuickPick') - promptUserStub = sinon.stub(picker, 'promptUser') - verifySinglePickerOutputStub = sinon.stub(picker, 'verifySinglePickerOutput') - httpFetcherStub = sinon.stub(HttpResourceFetcher.prototype, 'get') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should return sample payload when user selects a sample', async () => { - const mockPayloads = [{ name: 'testEvent', filename: 'testEvent.json' }] - const mockSampleContent = '{ "test": "data" }' - - getSampleLambdaPayloadsStub.resolves(mockPayloads) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns({ label: 'testEvent', filename: 'testEvent.json' }) - httpFetcherStub.resolves(createResponse(mockSampleContent)) - - const result = await remoteInvokeWebview.getSamplePayload() - - assert.strictEqual(result, mockSampleContent) - }) - - it('should throw an error if fetching sample data fails', async () => { - getSampleLambdaPayloadsStub.resolves([{ name: 'testEvent', filename: 'testEvent.json' }]) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns({ label: 'testEvent', filename: 'testEvent.json' }) - httpFetcherStub.rejects(new Error('Fetch failed')) - - await assert.rejects(async () => { - await remoteInvokeWebview.getSamplePayload() - }, /getting manifest data/) - }) - it('returns undefined when no file is selected', async () => { - const mockPayloads = [{ name: 'testEvent', filename: 'testEvent.json' }] - getSampleLambdaPayloadsStub.resolves(mockPayloads) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns(undefined) - const result = await remoteInvokeWebview.getSamplePayload() - assert.strictEqual(result, undefined) - }) - }) - describe('tryOpenHandlerFile', () => { - let sandbox: sinon.SinonSandbox - let fsExistsStub: sinon.SinonStub - let getLambdaHandlerFileStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - fsExistsStub = sandbox.stub(fs, 'exists') - getLambdaHandlerFileStub = sandbox.stub( - require('../../../../awsService/appBuilder/utils'), - 'getLambdaHandlerFile' - ) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should return false when LocalRootPath is not set', async () => { - const result = await remoteInvokeWebview.tryOpenHandlerFile() - assert.strictEqual(result, false) - }) - - it('should not watch for updates when LocalRootPath is already set (appbuilder case)', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const handlerPath = path.join(tempFolder, 'handler.js') - await fs.writeFile(handlerPath, 'exports.handler = () => {}') - - // Set LocalRootPath first to simulate appbuilder case - data.LocalRootPath = tempFolder - data.LambdaFunctionNode = { - configuration: { - Handler: 'handler.handler', - CodeSha256: 'abc123', - }, - } as any - data.Runtime = 'nodejs20.x' - - getLambdaHandlerFileStub.resolves(vscode.Uri.file(handlerPath)) - fsExistsStub.resolves(true) - - const result = await remoteInvokeWebview.tryOpenHandlerFile(tempFolder) - - assert.strictEqual(result, true) - // In appbuilder case, watchForUpdates should be false - - await fs.delete(tempFolder, { recursive: true }) - }) - }) - - describe('invokeRemoteLambda', () => { - let sandbox: sinon.SinonSandbox - let outputChannel: vscode.OutputChannel - let mockExtContext: ExtContext - let mockFunctionNode: LambdaFunctionNode - let createWebviewPanelStub: sinon.SinonStub - let getSampleLambdaPayloadsStub: sinon.SinonStub - - beforeEach(async function () { - sandbox = sinon.createSandbox() - outputChannel = { append: sandbox.stub(), appendLine: sandbox.stub() } as unknown as vscode.OutputChannel - mockExtContext = await FakeExtensionContext.getFakeExtContext() - mockFunctionNode = { - configuration: { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - }, - regionCode: 'us-west-2', - } as LambdaFunctionNode - createWebviewPanelStub = sandbox.stub(vscode.window, 'createWebviewPanel').returns({ - webview: { - html: '', - asWebviewUri: sandbox.stub().returns(vscode.Uri.parse('https://mock-webview-uri.com')), - onDidReceiveMessage: sandbox.stub(), - }, - onDidDispose: sandbox.stub(), - reveal: sandbox.stub(), - } as unknown as vscode.WebviewPanel) - getSampleLambdaPayloadsStub = sandbox.stub(utils, 'getSampleLambdaPayloads').resolves([ - { name: 'Sample1', filename: 'sample1.json' }, - { name: 'Sample2', filename: 'sample2.json' }, - ]) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should invoke lambda with a LambdaFunctionNode', async () => { - await invokeRemoteLambda(mockExtContext, { outputChannel: outputChannel, functionNode: mockFunctionNode }) - assert(getSampleLambdaPayloadsStub.calledOnce) - assert(createWebviewPanelStub.calledOnce) - const createWebviewPanelArgs = createWebviewPanelStub.getCall(0).args - assert.strictEqual(createWebviewPanelArgs[0], 'remoteInvoke') - assert.strictEqual( - createWebviewPanelArgs[1], - `Invoke Lambda ${mockFunctionNode.configuration.FunctionName}` - ) - // opens in side panel - assert.deepStrictEqual(createWebviewPanelArgs[2], { viewColumn: vscode.ViewColumn.Beside }) - }) - }) -}) diff --git a/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambdaDebugging.test.ts b/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambdaDebugging.test.ts deleted file mode 100644 index 79f7863cd0..0000000000 --- a/packages/core/src/test/lambda/vue/remoteInvoke/invokeLambdaDebugging.test.ts +++ /dev/null @@ -1,581 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { RemoteInvokeWebview, InitialData } from '../../../../lambda/vue/remoteInvoke/invokeLambda' -import { LambdaClient, DefaultLambdaClient } from '../../../../shared/clients/lambdaClient' -import * as vscode from 'vscode' -import sinon, { SinonStubbedInstance, createStubInstance } from 'sinon' -import { RemoteDebugController } from '../../../../lambda/remoteDebugging/ldkController' -import type { DebugConfig } from '../../../../lambda/remoteDebugging/lambdaDebugger' -import { getTestWindow } from '../../../shared/vscode/window' -import { LambdaFunctionNode } from '../../../../lambda/explorer/lambdaFunctionNode' -import * as downloadLambda from '../../../../lambda/commands/downloadLambda' -import * as uploadLambda from '../../../../lambda/commands/uploadLambda' -import * as appBuilderUtils from '../../../../awsService/appBuilder/utils' -import * as messages from '../../../../shared/utilities/messages' -import globals from '../../../../shared/extensionGlobals' -import fs from '../../../../shared/fs/fs' -import { ToolkitError } from '../../../../shared' -import { createMockDebugConfig } from '../../remoteDebugging/testUtils' -import { InvocationResponse } from '@aws-sdk/client-lambda' - -describe('RemoteInvokeWebview - Debugging Functionality', () => { - let outputChannel: vscode.OutputChannel - let client: SinonStubbedInstance - let remoteInvokeWebview: RemoteInvokeWebview - let data: InitialData - let sandbox: sinon.SinonSandbox - let mockDebugController: SinonStubbedInstance - let mockFunctionNode: LambdaFunctionNode - - beforeEach(() => { - sandbox = sinon.createSandbox() - client = createStubInstance(DefaultLambdaClient) - outputChannel = { - appendLine: sandbox.stub(), - show: sandbox.stub(), - } as unknown as vscode.OutputChannel - - mockFunctionNode = { - configuration: { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - Handler: 'index.handler', - Runtime: 'nodejs18.x', - SnapStart: { ApplyOn: 'None' }, - }, - regionCode: 'us-west-2', - localDir: '/local/path', - } as LambdaFunctionNode - - data = { - FunctionName: 'testFunction', - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:testFunction', - FunctionRegion: 'us-west-2', - InputSamples: [], - Runtime: 'nodejs18.x', - LocalRootPath: '/local/path', - LambdaFunctionNode: mockFunctionNode, - supportCodeDownload: true, - runtimeSupportsRemoteDebug: true, - regionSupportsRemoteDebug: true, - } as InitialData - - remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, data) - - // Mock RemoteDebugController - mockDebugController = createStubInstance(RemoteDebugController) - sandbox.stub(RemoteDebugController, 'instance').get(() => mockDebugController) - - // Set handler file as available by default to avoid timeout issues - ;(remoteInvokeWebview as any).handlerFileAvailable = true - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('Debug Timer Management', () => { - it('should start debug timer and count down', async () => { - remoteInvokeWebview.startDebugTimer() - - // Check initial state - assert.strictEqual(remoteInvokeWebview.getDebugTimeRemaining(), 60) - - // Wait a bit and check if timer is counting down - await new Promise((resolve) => { - setTimeout(() => { - const timeRemaining = remoteInvokeWebview.getDebugTimeRemaining() - assert(timeRemaining < 60 && timeRemaining > 0, 'Timer should be counting down') - remoteInvokeWebview.stopDebugTimer() - resolve() - }, 1100) // Wait slightly more than 1 second - }) - }) - - it('should stop debug timer', () => { - remoteInvokeWebview.startDebugTimer() - assert(remoteInvokeWebview.getDebugTimeRemaining() > 0) - - remoteInvokeWebview.stopDebugTimer() - assert.strictEqual(remoteInvokeWebview.getDebugTimeRemaining(), 0) - }) - }) - - describe('Debug State Management', () => { - it('should reset server state correctly', () => { - // Set up some state - remoteInvokeWebview.startDebugTimer() - - // Mock the debugging state - mockDebugController.isDebugging = true - - remoteInvokeWebview.resetServerState() - - assert.strictEqual(remoteInvokeWebview.getDebugTimeRemaining(), 0) - assert.strictEqual(remoteInvokeWebview.isWebViewDebugging(), false) - }) - - it('should check if ready to invoke when not invoking', () => { - const result = remoteInvokeWebview.checkReadyToInvoke() - assert.strictEqual(result, true) - }) - - it('should show warning when invoke is in progress', () => { - // Mock the window.showWarningMessage through getTestWindow - getTestWindow().onDidShowMessage(() => { - // Message handler for warning - }) - - // Set invoking state - ;(remoteInvokeWebview as any).isInvoking = true - - const result = remoteInvokeWebview.checkReadyToInvoke() - - assert.strictEqual(result, false) - // The warning should be shown but we can't easily verify it in this test setup - }) - - it('should return correct debugging states', () => { - mockDebugController.isDebugging = true - assert.strictEqual(remoteInvokeWebview.isLDKDebugging(), true) - - mockDebugController.isDebugging = false - assert.strictEqual(remoteInvokeWebview.isLDKDebugging(), false) - }) - }) - - describe('Debug Configuration and Validation', () => { - let mockConfig: DebugConfig - - beforeEach(() => { - mockConfig = createMockDebugConfig({ - functionArn: data.FunctionArn, - functionName: data.FunctionName, - }) - }) - - it('should check ready to debug with valid config', async () => { - // Ensure handler file is available to avoid confirmation dialog - ;(remoteInvokeWebview as any).handlerFileAvailable = true - - const result = await remoteInvokeWebview.checkReadyToDebug(mockConfig) - assert.strictEqual(result, true) - }) - - it('should return false when LambdaFunctionNode is undefined', async () => { - remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, { - ...data, - LambdaFunctionNode: undefined, - }) - - const result = await remoteInvokeWebview.checkReadyToDebug(mockConfig) - assert.strictEqual(result, false) - }) - - it('should show warning when handler file is not available', async () => { - const showConfirmationStub = sandbox.stub(messages, 'showConfirmationMessage').resolves(false) - - // Set handler file as not available - ;(remoteInvokeWebview as any).handlerFileAvailable = false - - const result = await remoteInvokeWebview.checkReadyToDebug(mockConfig) - - assert.strictEqual(result, false) - assert(showConfirmationStub.calledOnce) - }) - - it('should show snapstart warning when publishing version with snapstart enabled', async () => { - const showConfirmationStub = sandbox.stub(messages, 'showConfirmationMessage').resolves(false) - - mockConfig.shouldPublishVersion = true - data.LambdaFunctionNode!.configuration.SnapStart = { ApplyOn: 'PublishedVersions' } - - const result = await remoteInvokeWebview.checkReadyToDebug(mockConfig) - - assert.strictEqual(result, false) - assert(showConfirmationStub.calledOnce) - }) - }) - - describe('Debug Session Management', () => { - let mockConfig: DebugConfig - - beforeEach(() => { - mockConfig = createMockDebugConfig({ - functionArn: data.FunctionArn, - functionName: data.FunctionName, - }) - }) - - it('should start debugging successfully', async () => { - // Ensure handler file is available to avoid confirmation dialog - ;(remoteInvokeWebview as any).handlerFileAvailable = true - - mockDebugController.startDebugging.resolves() - mockDebugController.isDebugging = true - - const result = await remoteInvokeWebview.startDebugging(mockConfig) - - assert.strictEqual(result, true) - assert(mockDebugController.startDebugging.calledOnce) - }) - - it('should call stop debugging', async () => { - mockDebugController.isDebugging = true - mockDebugController.stopDebugging.resolves() - - await remoteInvokeWebview.stopDebugging() - - // The method doesn't return a boolean, it returns void - assert(mockDebugController.stopDebugging.calledOnce) - }) - - it('should handle debug pre-check with existing session', async () => { - const showConfirmationStub = sandbox.stub(messages, 'showConfirmationMessage').resolves(true) - const stopDebuggingStub = sandbox.stub(remoteInvokeWebview, 'stopDebugging').resolves(false) - mockDebugController.isDebugging = true - mockDebugController.installDebugExtension.resolves(true) - - // Mock revertExistingConfig - need to import it properly - const ldkController = require('../../../../lambda/remoteDebugging/ldkController') - const revertStub = sandbox.stub(ldkController, 'revertExistingConfig').resolves(true) - - await remoteInvokeWebview.debugPreCheck() - - assert(showConfirmationStub.calledOnce) - assert(stopDebuggingStub.calledOnce) - assert(mockDebugController.installDebugExtension.calledOnce) - assert(revertStub.calledOnce) - }) - }) - - describe('File Operations and Code Management', () => { - it('should prompt for folder selection', async () => { - const mockUri = vscode.Uri.file('/selected/folder') - getTestWindow().onDidShowDialog((d) => d.selectItem(mockUri)) - - const result = await remoteInvokeWebview.promptFolder() - - assert.strictEqual(result, mockUri.fsPath) - assert.strictEqual(remoteInvokeWebview.getLocalPath(), mockUri.fsPath) - }) - - it('should return undefined when no folder is selected', async () => { - getTestWindow().onDidShowDialog((d) => d.close()) - - const result = await remoteInvokeWebview.promptFolder() - - assert.strictEqual(result, undefined) - }) - - it('should try to open handler file successfully', async () => { - const mockHandlerUri = vscode.Uri.file('/local/path/index.js') - sandbox.stub(appBuilderUtils, 'getLambdaHandlerFile').resolves(mockHandlerUri) - sandbox.stub(fs, 'exists').resolves(true) - sandbox.stub(downloadLambda, 'openLambdaFile').resolves() - - const result = await remoteInvokeWebview.tryOpenHandlerFile('/local/path') - - assert.strictEqual(result, true) - assert.strictEqual(remoteInvokeWebview.getHandlerAvailable(), true) - }) - - it('should handle handler file not found', async () => { - sandbox.stub(appBuilderUtils, 'getLambdaHandlerFile').resolves(undefined) - - // Mock the warning message through getTestWindow - getTestWindow().onDidShowMessage(() => { - // Message handler for warning - }) - - const result = await remoteInvokeWebview.tryOpenHandlerFile('/local/path') - - assert.strictEqual(result, false) - assert.strictEqual(remoteInvokeWebview.getHandlerAvailable(), false) - }) - - it('should download remote code successfully', async () => { - const mockUri = vscode.Uri.file('/downloaded/path') - sandbox.stub(downloadLambda, 'runDownloadLambda').resolves(mockUri) - - // Mock workspace state operations - const mockWorkspaceState = { - get: sandbox.stub().returns({}), - update: sandbox.stub().resolves(), - } - sandbox.stub(globals, 'context').value({ - workspaceState: mockWorkspaceState, - }) - - const result = await remoteInvokeWebview.downloadRemoteCode() - - assert.strictEqual(result, mockUri.fsPath) - assert.strictEqual(data.LocalRootPath, mockUri.fsPath) - }) - - it('should handle download failure', async () => { - sandbox.stub(downloadLambda, 'runDownloadLambda').rejects(new Error('Download failed')) - - await assert.rejects( - async () => await remoteInvokeWebview.downloadRemoteCode(), - /Failed to download remote code/ - ) - }) - }) - - describe('File Watching and Code Synchronization', () => { - it('should setup file watcher when local root path exists', () => { - const createFileSystemWatcherStub = sandbox.stub(vscode.workspace, 'createFileSystemWatcher') - const mockWatcher = { - onDidChange: sandbox.stub(), - onDidCreate: sandbox.stub(), - onDidDelete: sandbox.stub(), - } - createFileSystemWatcherStub.returns(mockWatcher as any) - - // Call the private method through reflection - ;(remoteInvokeWebview as any).setupFileWatcher() - - assert(createFileSystemWatcherStub.calledOnce) - assert(mockWatcher.onDidChange.calledOnce) - assert(mockWatcher.onDidCreate.calledOnce) - assert(mockWatcher.onDidDelete.calledOnce) - }) - - it('should handle file changes and prompt for upload', async () => { - const showConfirmationStub = sandbox.stub(messages, 'showMessage').resolves('Yes') - const runUploadDirectoryStub = sandbox.stub(uploadLambda, 'runUploadDirectory').resolves() - - // Mock file watcher setup - let changeHandler: () => Promise - const mockWatcher = { - onDidChange: (handler: () => Promise) => { - changeHandler = handler - }, - onDidCreate: sandbox.stub(), - onDidDelete: sandbox.stub(), - } - sandbox.stub(vscode.workspace, 'createFileSystemWatcher').returns(mockWatcher as any) - - // Setup file watcher - ;(remoteInvokeWebview as any).setupFileWatcher() - - // Trigger file change - await changeHandler!() - - assert(showConfirmationStub.calledOnce) - assert(runUploadDirectoryStub.calledOnce) - }) - }) - - describe('Lambda Invocation with Debugging', () => { - it('should invoke lambda with remote debugging enabled', async () => { - const mockResponse = { - LogResult: Buffer.from('Debug log').toString('base64'), - Payload: new TextEncoder().encode('{"result": "debug success"}'), - } satisfies InvocationResponse - client.invoke.resolves(mockResponse) - mockDebugController.isDebugging = true - mockDebugController.qualifier = 'v1' - - const focusStub = sandbox.stub(vscode.commands, 'executeCommand').resolves() - - await remoteInvokeWebview.invokeLambda('{"test": "input"}', 'test', true) - - assert(client.invoke.calledWith(data.FunctionArn, '{"test": "input"}', 'v1')) - assert(focusStub.calledWith('workbench.action.focusFirstEditorGroup')) - }) - - it('should handle timer management during debugging invocation', async () => { - const mockResponse = { - LogResult: Buffer.from('Debug log').toString('base64'), - Payload: new TextEncoder().encode('{"result": "debug success"}'), - } satisfies InvocationResponse - client.invoke.resolves(mockResponse) - mockDebugController.isDebugging = true - - const stopTimerStub = sandbox.stub(remoteInvokeWebview, 'stopDebugTimer') - const startTimerStub = sandbox.stub(remoteInvokeWebview, 'startDebugTimer') - - await remoteInvokeWebview.invokeLambda('{"test": "input"}', 'test', true) - - // Timer should be stopped at least once during invoke - assert(stopTimerStub.calledOnce) - assert(startTimerStub.calledOnce) // Called after invoke - }) - }) - - describe('Dispose and Cleanup', () => { - it('should dispose server and clean up resources', async () => { - // Set up debugging state and disposables - ;(remoteInvokeWebview as any).debugging = true - mockDebugController.isDebugging = true - - // Mock disposables - const mockDisposable = { dispose: sandbox.stub() } - ;(remoteInvokeWebview as any).watcherDisposable = mockDisposable - ;(remoteInvokeWebview as any).fileWatcherDisposable = mockDisposable - - await remoteInvokeWebview.disposeServer() - - assert(mockDisposable.dispose.calledTwice) - assert(mockDebugController.stopDebugging.calledOnce) - }) - - it('should handle dispose when not debugging', async () => { - mockDebugController.isDebugging = false - - const mockDisposable = { dispose: sandbox.stub() } - ;(remoteInvokeWebview as any).watcherDisposable = mockDisposable - - await remoteInvokeWebview.disposeServer() - - assert(mockDisposable.dispose.calledOnce) - }) - }) - - describe('Debug Session Event Handling', () => { - it('should handle debug session termination', async () => { - const resetStateStub = sandbox.stub(remoteInvokeWebview, 'resetServerState') - - // Mock debug session termination event - let terminationHandler: (session: vscode.DebugSession) => Promise - sandbox.stub(vscode.debug, 'onDidTerminateDebugSession').callsFake((handler) => { - terminationHandler = handler - return { dispose: sandbox.stub() } - }) - - // Initialize the webview to set up event handlers - remoteInvokeWebview.init() - - // Simulate debug session termination - const mockSession = { name: 'test-session' } as vscode.DebugSession - await terminationHandler!(mockSession) - - assert(resetStateStub.calledOnce) - }) - }) - - describe('Debugging Flow', () => { - let mockConfig: DebugConfig - - beforeEach(() => { - mockConfig = createMockDebugConfig({ - functionArn: data.FunctionArn, - functionName: data.FunctionName, - }) - async function mockRun(fn: (span: any) => T): Promise { - const span = { record: sandbox.stub() } - return fn(span) - } - // Mock telemetry to avoid issues - sandbox.stub(require('../../../../shared/telemetry/telemetry'), 'telemetry').value({ - lambda_invokeRemote: { - run: mockRun, - }, - }) - }) - - it('should handle complete debugging workflow', async () => { - // Setup mocks for successful debugging - mockDebugController.startDebugging.resolves() - mockDebugController.stopDebugging.resolves() - mockDebugController.isDebugging = false - - // Mock the debugging state change after startDebugging is called - mockDebugController.startDebugging.callsFake(async () => { - mockDebugController.isDebugging = true - return Promise.resolve() - }) - - // 1. Start debugging - const startResult = await remoteInvokeWebview.startDebugging(mockConfig) - assert.strictEqual(startResult, true, 'Debug session should start successfully') - - // Set qualifier for invocation - mockDebugController.qualifier = '$LATEST' - - // 2. Test lambda invocation during debugging - const mockResponse = { - LogResult: Buffer.from('Debug invocation log').toString('base64'), - Payload: new TextEncoder().encode('{"debugResult": "success"}'), - } satisfies InvocationResponse - client.invoke.resolves(mockResponse) - - await remoteInvokeWebview.invokeLambda('{"debugInput": "test"}', 'integration-test', true) - - // Verify invocation was called with correct parameters - assert(client.invoke.calledWith(data.FunctionArn, '{"debugInput": "test"}', '$LATEST')) - - // 3. Stop debugging - await remoteInvokeWebview.stopDebugging() - - // Verify cleanup operations were called - assert(mockDebugController.stopDebugging.calledOnce, 'Should stop debugging') - }) - - it('should handle debugging failure gracefully', async () => { - // Setup mock for debugging failure - mockDebugController.startDebugging.rejects(new Error('Debug start failed')) - mockDebugController.isDebugging = false - - // Attempt to start debugging - should throw error - try { - await remoteInvokeWebview.startDebugging(mockConfig) - assert.fail('Expected error to be thrown') - } catch (error) { - assert(error instanceof ToolkitError) - assert(error.message.includes('Failed to start debugging')) - assert(error.cause?.message.includes('Debug start failed')) - } - - assert.strictEqual( - remoteInvokeWebview.isWebViewDebugging(), - false, - 'Webview should not be in debugging state' - ) - }) - - it('should handle version publishing workflow', async () => { - // Setup config for version publishing - const versionConfig = { ...mockConfig, shouldPublishVersion: true } - - // Setup mocks for version publishing - mockDebugController.startDebugging.resolves() - mockDebugController.stopDebugging.resolves() - mockDebugController.isDebugging = false - - // Mock the debugging state change after startDebugging is called - mockDebugController.startDebugging.callsFake(async () => { - mockDebugController.isDebugging = true - mockDebugController.qualifier = 'v1' - return Promise.resolve() - }) - - // Start debugging with version publishing - const startResult = await remoteInvokeWebview.startDebugging(versionConfig) - assert.strictEqual(startResult, true, 'Debug session should start successfully') - - // Test invocation with version qualifier - const mockResponse = { - LogResult: Buffer.from('Version debug log').toString('base64'), - Payload: new TextEncoder().encode('{"versionResult": "success"}'), - } satisfies InvocationResponse - client.invoke.resolves(mockResponse) - - await remoteInvokeWebview.invokeLambda('{"versionInput": "test"}', 'version-test', true) - - // Should invoke with version qualifier - assert(client.invoke.calledWith(data.FunctionArn, '{"versionInput": "test"}', 'v1')) - - // Stop debugging - await remoteInvokeWebview.stopDebugging() - - assert(mockDebugController.stopDebugging.calledOnce, 'Should stop debugging') - }) - }) -}) diff --git a/packages/core/src/test/lambda/vue/remoteInvoke/remoteInvoke.test.ts b/packages/core/src/test/lambda/vue/remoteInvoke/remoteInvoke.test.ts deleted file mode 100644 index 29527de6ed..0000000000 --- a/packages/core/src/test/lambda/vue/remoteInvoke/remoteInvoke.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { DefaultLambdaClient, LambdaClient } from '../../../../shared/clients/lambdaClient' -import { RemoteInvokeWebview } from '../../../../lambda/vue/remoteInvoke/invokeLambda' -import * as vscode from 'vscode' -import * as samCliRemoteTestEvent from '../../../../shared/sam/cli/samCliRemoteTestEvent' -import { TestEventsOperation } from '../../../../shared/sam/cli/samCliRemoteTestEvent' -import sinon, { SinonStubbedInstance, createStubInstance } from 'sinon' -import { InvocationResponse } from '@aws-sdk/client-lambda' - -// Tests to check that the internal integration between the functions operates correctly - -describe('RemoteInvokeWebview', function () { - let client: SinonStubbedInstance - let outputChannel: vscode.OutputChannel - const mockData = { - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:my-function', - } as any - const input = '{"key": "value"}' - const mockResponse = { - LogResult: Buffer.from('Test log').toString('base64'), - Payload: new TextEncoder().encode('{"result": "success"}'), - } satisfies InvocationResponse - - before(() => { - outputChannel = { - appendLine: (line: string) => {}, - show: () => {}, - } as vscode.OutputChannel - }) - beforeEach(() => { - client = createStubInstance(DefaultLambdaClient) - }) - afterEach(() => { - sinon.restore() - }) - const invokeScenarios: { - name: string - data: any - expectedQualifier: string | undefined - expectedLogType: 'Tail' | 'None' - }[] = [ - { - name: 'should invoke with a simple payload', - data: mockData, - expectedQualifier: undefined, - expectedLogType: 'Tail', - }, - { - name: 'should invoke with no tail in LMI', - data: { - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:my-function', - LambdaFunctionNode: { - configuration: { - CapacityProviderConfig: { - blah: 'blah', - }, - }, - }, - }, - expectedQualifier: undefined, - expectedLogType: 'None', - }, - { - name: 'should invoke $LATEST in Durable Function', - data: { - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:my-function', - LambdaFunctionNode: { - configuration: { - DurableConfig: { - blah: 'blah', - }, - }, - }, - }, - expectedQualifier: '$LATEST', - expectedLogType: 'Tail', - }, - { - name: 'should invoke $LATEST.PUBLISHED in LMI Durable Function', - data: { - FunctionArn: 'arn:aws:lambda:us-west-2:123456789012:function:my-function', - LambdaFunctionNode: { - configuration: { - DurableConfig: { - blah: 'blah', - }, - CapacityProviderConfig: { - blah: 'blah', - }, - }, - }, - }, - expectedQualifier: '$LATEST.PUBLISHED', - expectedLogType: 'None', - }, - ] - - for (const scenario of invokeScenarios) { - it(scenario.name, async function () { - const remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, scenario.data) - client.invoke.resolves(mockResponse) - await remoteInvokeWebview.invokeLambda(input) - sinon.assert.calledOnce(client.invoke) - sinon.assert.calledWith( - client.invoke, - scenario.data.FunctionArn, - input, - scenario.expectedQualifier, - scenario.expectedLogType - ) - }) - } - - const mockEvent = { - name: 'TestEvent', - arn: 'arn:aws:lambda:us-west-2:123456789012:function:myFunction', - region: 'us-west-2', - } - const expectedParams = { - name: mockEvent.name, - operation: TestEventsOperation.Get, - functionArn: mockEvent.arn, - region: mockEvent.region, - } - const mockEventResponse = 'true' - - it('should get saved event and invoke with it', async function () { - const remoteInvokeWebview = new RemoteInvokeWebview(outputChannel, client, client, mockData) - const runSamCliRemoteTestEventsStub = sinon.stub(samCliRemoteTestEvent, 'runSamCliRemoteTestEvents') - runSamCliRemoteTestEventsStub.resolves(mockEventResponse) - await remoteInvokeWebview.getRemoteTestEvents(mockEvent) - - sinon.assert.calledOnce(runSamCliRemoteTestEventsStub) - sinon.assert.calledWith(runSamCliRemoteTestEventsStub, expectedParams) - }) -}) diff --git a/packages/core/src/test/lambda/vue/samInvoke.test.ts b/packages/core/src/test/lambda/vue/samInvoke.test.ts deleted file mode 100644 index ad8c0a2eaf..0000000000 --- a/packages/core/src/test/lambda/vue/samInvoke.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { finalizeConfig } from '../../../lambda/vue/configEditor/samInvokeBackend' -import { AwsSamDebuggerConfiguration } from '../../../shared/sam/debugger/awsSamDebugConfiguration' - -describe('Sam Invoke Vue Backend', () => { - describe('finalizeConfig', () => { - it('prunes configs correctly', () => { - const configs: { input: AwsSamDebuggerConfiguration; output: AwsSamDebuggerConfiguration }[] = [ - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'noprune', - type: 'aws-sam', - request: 'direct-invoke', - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'noprune', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - lambda: { - payload: { - json: {}, - }, - }, - name: 'prunejson', - type: 'aws-sam', - request: 'direct-invoke', - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunejson', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunestr', - type: 'aws-sam', - request: 'direct-invoke', - lambda: { - runtime: '', - }, - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunestr', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunearr', - type: 'aws-sam', - request: 'direct-invoke', - lambda: { - pathMappings: [], - }, - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunearr', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - ] - - for (const config of configs) { - assert.deepStrictEqual( - finalizeConfig(config.input, config.input.name), - config.output, - `Test failed for input: ${config.input.name}` - ) - } - }) - }) -}) diff --git a/packages/core/src/test/lambda/vue/samInvokeBackend.test.ts b/packages/core/src/test/lambda/vue/samInvokeBackend.test.ts deleted file mode 100644 index cbad9bfff5..0000000000 --- a/packages/core/src/test/lambda/vue/samInvokeBackend.test.ts +++ /dev/null @@ -1,804 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - AwsSamDebuggerConfigurationLoose, - LaunchConfigPickItem, - ResourceData, - SamInvokeWebview, - finalizeConfig, -} from '../../../lambda/vue/configEditor/samInvokeBackend' -import { AwsSamDebuggerConfiguration } from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import assert from 'assert' -import * as picker from '../../../shared/ui/picker' -import * as input from '../../../shared/ui/input' -import * as utils from '../../../lambda/utils' -import { HttpResourceFetcher } from '../../../shared/resourcefetcher/httpResourceFetcher' -import * as vscode from 'vscode' -import path from 'path' -import { addCodiconToString, fs, makeTemporaryToolkitFolder } from '../../../shared' -import { LaunchConfiguration } from '../../../shared/debug/launchConfiguration' -import { getTestWindow } from '../..' -import * as samInvokeBackend from '../../../lambda/vue/configEditor/samInvokeBackend' -import sinon from 'sinon' -import * as nls from 'vscode-nls' -import { assertLogsContain } from '../../../test/globalSetup.test' -import { createResponse } from '../../testUtil' - -const localize = nls.loadMessageBundle() - -function createMockWorkspaceFolder(uriPath: string): vscode.WorkspaceFolder { - return { - uri: vscode.Uri.file(uriPath), - name: 'mock-folder', - index: 0, - } -} - -const mockResourceData: ResourceData = { - logicalId: 'MockFunction', - region: 'us-west-2', - arn: 'arn:aws:lambda:us-west-2:123456789012:function:MockFunction', - location: '/path/to/function', - handler: 'index.handler', - runtime: 'nodejs14.x', - stackName: 'MockStack', - source: 'path/to/source', -} -const mockConfig: AwsSamDebuggerConfigurationLoose = { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - lambdaHandler: 'index.handler', - projectRoot: '/path/to/project', - }, - name: 'noprune', - type: 'aws-sam', - request: 'direct-invoke', - sam: { - containerBuild: false, - skipNewImageCheck: false, - }, -} - -describe('SamInvokeWebview', () => { - let samInvokeWebview: SamInvokeWebview - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - samInvokeWebview = new SamInvokeWebview(mockConfig, mockResourceData) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should return undefined when no resource data is provided', () => { - const noResourceWebview = new SamInvokeWebview(mockConfig, undefined) - const data = noResourceWebview.getResourceData() - - // Using assert to check if the data is undefined - assert.strictEqual(data, undefined, 'Expected resource data to be undefined when no resource is provided') - }) - - describe('getFileName', () => { - it('should return the base name of a file path', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const testCases = [{ input: vscode.Uri.file(path.join(tempFolder, 'file.txt')), expected: 'file.txt' }] - - for (const { input, expected } of testCases) { - const result = samInvokeWebview.getFileName(input.fsPath) - assert.strictEqual(result, expected, `getFileName("${input}") should return "${expected}"`) - - // Double-check using Node's path.basename - const nodeResult = path.basename(input.fsPath) - assert.strictEqual( - result, - nodeResult, - `getFileName result should match Node's path.basename for "${input}"` - ) - } - await fs.delete(tempFolder, { recursive: true }) - }) - }) - - describe('getResourceData', () => { - it('should return the provided resource data', () => { - const result = samInvokeWebview.getResourceData() - assert.deepStrictEqual(result, mockResourceData) - }) - }) - - describe('init', () => { - it('should return the provided config', () => { - // Call the init method and store the result - const result = samInvokeWebview.init() - // Use strictEqual to assert that the result matches the provided config - assert.strictEqual(result, mockConfig, 'The init method should return the provided config object') - }) - }) - describe('getRuntimes', () => { - it('should return sorted runtimes', () => { - const runtimes = samInvokeWebview.getRuntimes() - // Assert that the runtimes are defined - assert(runtimes !== undefined, 'Runtimes should be defined') - // Assert that runtimes is an array - assert(Array.isArray(runtimes), 'Runtimes should be an array') - // Assert that the runtimes array contains specific values - assert(runtimes.includes('nodejs18.x'), "Runtimes should include 'nodejs18.x'") - assert(runtimes.includes('python3.12'), "Runtimes should include 'python3.12'") - // Assert that the runtimes array is sorted - assert.deepStrictEqual(runtimes, [...runtimes].sort(), 'Runtimes should be sorted') - }) - }) - describe('getSamplePayload', () => { - let getSampleLambdaPayloadsStub: sinon.SinonStub - let createQuickPickStub: sinon.SinonStub - let promptUserStub: sinon.SinonStub - let verifySinglePickerOutputStub: sinon.SinonStub - let httpFetcherStub: sinon.SinonStub - - beforeEach(() => { - getSampleLambdaPayloadsStub = sinon.stub(utils, 'getSampleLambdaPayloads') - createQuickPickStub = sinon.stub(picker, 'createQuickPick') - promptUserStub = sinon.stub(picker, 'promptUser') - verifySinglePickerOutputStub = sinon.stub(picker, 'verifySinglePickerOutput') - httpFetcherStub = sinon.stub(HttpResourceFetcher.prototype, 'get') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should return sample payload when user selects a sample', async () => { - const mockPayloads = [{ name: 'testEvent', filename: 'testEvent.json' }] - const mockSampleContent = '{ "test": "data" }' - - getSampleLambdaPayloadsStub.resolves(mockPayloads) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns({ label: 'testEvent', filename: 'testEvent.json' }) - httpFetcherStub.resolves(createResponse(mockSampleContent)) - - const result = await samInvokeWebview.getSamplePayload() - - assert.strictEqual(result, mockSampleContent) - }) - - it('should throw an error if fetching sample data fails', async () => { - getSampleLambdaPayloadsStub.resolves([{ name: 'testEvent', filename: 'testEvent.json' }]) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns({ label: 'testEvent', filename: 'testEvent.json' }) - httpFetcherStub.rejects(new Error('Fetch failed')) - - await assert.rejects(async () => { - await samInvokeWebview.getSamplePayload() - }, /Error: getting manifest data/) - }) - it('returns undefined if no sample is selected', async () => { - const mockPayloads = [{ name: 'testEvent', filename: 'testEvent.json' }] - getSampleLambdaPayloadsStub.resolves(mockPayloads) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'testEvent', filename: 'testEvent.json' }]) - verifySinglePickerOutputStub.returns(undefined) - const result = await samInvokeWebview.getSamplePayload() - - assert.strictEqual(result, undefined) - }) - }) - describe('loadSamLaunchConfig', () => { - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('loadSamLaunchConfig should return selected config when available', async () => { - const mockPickerItem: LaunchConfigPickItem = { - index: 1, - label: 'Tester Config', - config: mockConfig, - } - - sandbox.stub(samInvokeWebview, 'getSamLaunchConfigs').resolves([mockPickerItem]) - const pickerStub = sandbox.stub(picker, 'createQuickPick') - const promptUserStub = sandbox.stub(picker, 'promptUser').resolves([mockPickerItem]) - - const result = await samInvokeWebview.loadSamLaunchConfig() - - assert.strictEqual(result, mockConfig) // No config should be returned - assert.ok(pickerStub.calledOnce) - assert.ok(promptUserStub.calledOnce) - }) - - it('loadSamLaunchConfig should show no config message when none available', async () => { - sandbox.stub(samInvokeWebview, 'getSamLaunchConfigs').resolves([]) - sandbox.stub(picker, 'createQuickPick') - sandbox.stub(picker, 'promptUser').resolves([]) - - const result = await samInvokeWebview.loadSamLaunchConfig() - - assert.strictEqual(result, undefined) - }) - }) - describe('Sam Invoke Vue Backend', () => { - describe('finalizeConfig', () => { - it('prunes configs correctly', () => { - const configs: { input: AwsSamDebuggerConfiguration; output: AwsSamDebuggerConfiguration }[] = [ - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'noprune', - type: 'aws-sam', - request: 'direct-invoke', - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'noprune', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - lambda: { - payload: { - json: {}, - }, - }, - name: 'prunejson', - type: 'aws-sam', - request: 'direct-invoke', - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunejson', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunestr', - type: 'aws-sam', - request: 'direct-invoke', - lambda: { - runtime: '', - }, - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunestr', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - { - input: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunearr', - type: 'aws-sam', - request: 'direct-invoke', - lambda: { - pathMappings: [], - }, - }, - output: { - invokeTarget: { - target: 'template', - logicalId: 'foobar', - templatePath: 'template.yaml', - }, - name: 'prunearr', - type: 'aws-sam', - request: 'direct-invoke', - }, - }, - ] - - for (const config of configs) { - assert.deepStrictEqual( - finalizeConfig(config.input, config.input.name), - config.output, - `Test failed for input: ${config.input.name}` - ) - } - }) - }) - }) - - describe('SamInvokeWebview - getSamLaunchConfigs', function () { - let workspaceFoldersStub: sinon.SinonStub - let launchConfigStub: sinon.SinonStub - let getLaunchConfigQuickPickItemsStub: sinon.SinonStub - - beforeEach(() => { - // Initialize a new webview instance before each test - workspaceFoldersStub = sandbox.stub(vscode.workspace, 'workspaceFolders') - launchConfigStub = sandbox.stub(LaunchConfiguration.prototype, 'getDebugConfigurations') - getLaunchConfigQuickPickItemsStub = sandbox.stub() - }) - afterEach(() => { - sandbox.restore() - }) - - it('should return undefined and show error when no workspace folder is found', async function () { - // Mock workspace without folders - workspaceFoldersStub.value([]) - const result = await samInvokeWebview.getSamLaunchConfigs() - assert.strictEqual(result, undefined) - }) - - it('should return picker items if valid configurations exist', async () => { - const mockFolder = createMockWorkspaceFolder('/mock-path') - const mockUri = mockFolder.uri - workspaceFoldersStub.value([mockFolder]) - - // Mock a launch configuration - launchConfigStub.returns([mockConfig]) - - // Mock picker items - const mockPickerItems = [{ index: 0, config: mockConfig, label: 'Test Config' }] - getLaunchConfigQuickPickItemsStub.resolves(mockPickerItems) - - // Override the internal `getLaunchConfigQuickPickItems` function - sandbox.replace(samInvokeWebview as any, 'getLaunchConfigQuickPickItems', getLaunchConfigQuickPickItemsStub) - - const result = await samInvokeWebview.getSamLaunchConfigs() - - assert.deepStrictEqual(result, mockPickerItems) - assert(getLaunchConfigQuickPickItemsStub.calledOnceWithExactly(sinon.match.any, mockUri)) - }) - }) - describe('promptFile', () => { - it('should return the base name of a file path', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const testCases = [{ input: vscode.Uri.file(path.join(tempFolder, 'file.txt')), expected: 'file.txt' }] - - for (const { input, expected } of testCases) { - const result = samInvokeWebview.getFileName(input.fsPath) - assert.strictEqual(result, expected, `getFileName("${input}") should return "${expected}"`) - - // Double-check using Node's path.basename - const nodeResult = path.basename(input.fsPath) - assert.strictEqual( - result, - nodeResult, - `getFileName result should match Node's path.basename for "${input}"` - ) - } - await fs.delete(tempFolder, { recursive: true }) - }) - it('prompts the user for a file and returns the selected file', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const placeholderEventFile = path.join(tempFolder, 'file.json') - await fs.writeFile(placeholderEventFile, '{"sample": "{test: event}"}') - const fileUri = vscode.Uri.file(placeholderEventFile) - - getTestWindow().onDidShowDialog((window) => window.selectItem(fileUri)) - - const response = await samInvokeWebview.promptFile() - if (response === undefined) { - assert.fail('Response should not be undefined') - } - assert.deepStrictEqual(response.selectedFile, 'file.json') - assert.deepStrictEqual(response.selectedFilePath, fileUri.fsPath) - await fs.delete(tempFolder, { recursive: true }) - }) - it('Returns undefined if no file is selected', async () => { - getTestWindow().onDidShowDialog((window) => window.close()) - const response = await samInvokeWebview.promptFile() - assert.strictEqual(response, undefined) - }) - it('logs an error and throws ToolkitError when reading the file fails', async () => { - const tempFolder = await makeTemporaryToolkitFolder() - const placeholderEventFile = path.join(tempFolder, 'file.json') - const fileUri = vscode.Uri.file(placeholderEventFile) - - getTestWindow().onDidShowDialog((window) => window.selectItem(fileUri)) - - try { - await assert.rejects( - async () => await samInvokeWebview.promptFile(), - new Error('Failed to read selected file') - ) - assertLogsContain('readFileSync: Failed to read file at path', false, 'error') - } finally { - await fs.delete(tempFolder, { recursive: true }) - } - }) - }) - describe('getTemplate', () => { - let templateRegistryStub: sinon.SinonStub - let createQuickPickStub: sinon.SinonStub - let promptUserStub: sinon.SinonStub - let verifySinglePickerOutputStub: sinon.SinonStub - - beforeEach(() => { - templateRegistryStub = sandbox.stub() - createQuickPickStub = sinon.stub(picker, 'createQuickPick') - promptUserStub = sinon.stub(picker, 'promptUser') - verifySinglePickerOutputStub = sinon.stub(picker, 'verifySinglePickerOutput') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should return undefined if no valid templates are found', async () => { - templateRegistryStub.resolves({ items: [] }) - sandbox.replace(samInvokeWebview as any, 'getTemplateRegistry', templateRegistryStub) - const result = await samInvokeWebview.getTemplate() - - assert.strictEqual(result, undefined) - }) - - it('should return a template if a valid SAM function is found', async () => { - const mockTemplate = { - path: '/path/to/template.yaml', - item: { - Resources: { - MyLambda: { Type: 'AWS::Serverless::Function' }, - }, - }, - } - templateRegistryStub.resolves({ items: [mockTemplate] }) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'MyLambda', templatePath: '/path/to/template.yaml' }]) - verifySinglePickerOutputStub.returns({ label: 'MyLambda', templatePath: '/path/to/template.yaml' }) - - const result = await samInvokeWebview.getTemplate() - - assert.deepEqual(result, { - logicalId: 'MyLambda', - template: '/path/to/template.yaml', - }) - }) - - it('should return undefined if user selects no valid template', async () => { - templateRegistryStub.resolves({ items: [] }) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ templatePath: 'NOTEMPLATEFOUND' }]) - verifySinglePickerOutputStub.returns({ templatePath: 'NOTEMPLATEFOUND' }) - - const result = await samInvokeWebview.getTemplate() - - assert.strictEqual(result, undefined) - }) - - it('should handle multiple templates and filter by resource type', async () => { - const mockTemplates = [ - { - path: '/path/to/first.yaml', - item: { - Resources: { - Lambda1: { Type: 'AWS::Serverless::Function' }, - }, - }, - }, - { - path: '/path/to/second.yaml', - item: { - Resources: { - API: { Type: 'AWS::Serverless::Api' }, - }, - }, - }, - ] - templateRegistryStub.resolves({ items: mockTemplates }) - createQuickPickStub.returns({}) - promptUserStub.resolves([{ label: 'Lambda1', templatePath: '/path/to/first.yaml' }]) - verifySinglePickerOutputStub.returns({ label: 'Lambda1', templatePath: '/path/to/first.yaml' }) - - const result = await samInvokeWebview.getTemplate() - - assert.deepEqual(result, { - logicalId: 'Lambda1', - template: '/path/to/first.yaml', - }) - }) - - it('should return undefined if picker returns no selection', async () => { - const mockTemplate = { - path: '/path/to/template.yaml', - item: { - Resources: { - Lambda1: { Type: 'AWS::Serverless::Function' }, - }, - }, - } - templateRegistryStub.resolves({ items: [mockTemplate] }) - createQuickPickStub.returns({}) - promptUserStub.resolves(undefined) - verifySinglePickerOutputStub.returns(undefined) - - const result = await samInvokeWebview.getTemplate() - - assert.strictEqual(result, undefined) - }) - }) - describe('InvokeLocalWebview', function () { - let sandbox: sinon.SinonSandbox - let mockFolder: vscode.WorkspaceFolder - let mockUri: vscode.Uri - let getUriFromLaunchConfigStub: sinon.SinonStub - let workspaceFoldersStub: sinon.SinonStub - - this.beforeEach(async function () { - sandbox = sinon.createSandbox() - mockFolder = createMockWorkspaceFolder('/mock-path') - mockUri = mockFolder.uri - - sandbox.stub(samInvokeBackend, 'finalizeConfig').returns(mockConfig) - getUriFromLaunchConfigStub = sinon.stub() - sandbox.stub(vscode.workspace, 'getWorkspaceFolder').returns(mockFolder) - workspaceFoldersStub = sandbox.stub(vscode.workspace, 'workspaceFolders') - }) - afterEach(() => { - sandbox.restore() - }) - - it('should invoke launch config', async () => { - workspaceFoldersStub.value([mockFolder]) - sandbox.replace(samInvokeWebview as any, 'getUriFromLaunchConfig', getUriFromLaunchConfigStub) - getUriFromLaunchConfigStub.resolves(mockUri) - - const startDebuggingStub = sandbox.stub(vscode.debug, 'startDebugging').resolves(true) - - await samInvokeWebview.invokeLaunchConfig(mockConfig) - - assert(startDebuggingStub.called) - }) - }) - describe('saveLaunchConfig', function () { - let sandbox: sinon.SinonSandbox - let mockFolder: vscode.WorkspaceFolder - let mockUri: vscode.Uri - let getUriFromLaunchConfigStub: sinon.SinonStub - let workspaceFoldersStub: sinon.SinonStub - let getUriStub: sinon.SinonStub - let launchConfigurationsStub: sinon.SinonStub - let getLaunchConfigQuickPickItemsStub: sinon.SinonStub - let editDebugConfigurationStub: sinon.SinonStub - let verifySinglePickerOutputStub: sinon.SinonStub - let createQuickPickStub: sinon.SinonStub - - this.beforeEach(async function () { - sandbox = sinon.createSandbox() - mockFolder = createMockWorkspaceFolder('/mock-path') - mockUri = mockFolder.uri - - getUriStub = sandbox.stub(samInvokeWebview as any, 'getUriFromLaunchConfig') - getUriFromLaunchConfigStub = sinon.stub() - sandbox.stub(vscode.workspace, 'getWorkspaceFolder').returns(mockFolder) - workspaceFoldersStub = sandbox.stub(vscode.workspace, 'workspaceFolders') - launchConfigurationsStub = sandbox.stub(LaunchConfiguration.prototype, 'getDebugConfigurations') - editDebugConfigurationStub = sandbox.stub(LaunchConfiguration.prototype, 'editDebugConfiguration') - getLaunchConfigQuickPickItemsStub = sandbox.stub() - - verifySinglePickerOutputStub = sandbox.stub(picker, 'verifySinglePickerOutput') - createQuickPickStub = sandbox.stub(picker, 'createQuickPick') - }) - afterEach(() => { - sandbox.restore() - }) - - it('should create quick pick with correct items', async () => { - getUriFromLaunchConfigStub.resolves(mockUri) - getLaunchConfigQuickPickItemsStub.resolves([{ label: 'Existing Config', index: 0 }]) - verifySinglePickerOutputStub.returns(undefined) - createQuickPickStub.returns({}) - sandbox.stub(picker, 'promptUser').resolves([]) - - sandbox.replace(samInvokeWebview as any, 'getLaunchConfigQuickPickItems', getLaunchConfigQuickPickItemsStub) - sandbox.replace(samInvokeWebview as any, 'getUriFromLaunchConfig', getUriFromLaunchConfigStub) - - await samInvokeWebview.saveLaunchConfig(mockConfig) - assert(getLaunchConfigQuickPickItemsStub.called) - - sinon.assert.calledWith( - createQuickPickStub, - sinon.match({ - items: sinon.match.array.deepEquals([ - { - label: addCodiconToString( - 'add', - localize( - 'AWS.command.addSamDebugConfiguration', - 'Add Local Invoke and Debug Configuration' - ) - ), - index: -1, - alwaysShow: true, - }, - { label: 'Existing Config', index: 0 }, - ]), - }) - ) - }) - describe('Save Launch Config with no URI', () => { - let promptUserStub: sinon.SinonStub - let testConfig: any - beforeEach(() => { - const testConfig = { - label: '$(add) Add Local Invoke and Debug Configuration', - index: -1, - alwaysShow: true, - } - // Create a stub for the promptUser function - promptUserStub = sinon.stub(picker, 'promptUser') - - // Configure the stub to return the desired array - promptUserStub.resolves([testConfig]) - }) - afterEach(() => { - // Restore the original function after each test - promptUserStub.restore() - }) - it('should not save launch config', async () => { - workspaceFoldersStub.value([mockFolder]) - sandbox.replace(samInvokeWebview as any, 'getUriFromLaunchConfig', getUriFromLaunchConfigStub) - const launchConfigItems = launchConfigurationsStub.resolves([]) - getUriFromLaunchConfigStub.resolves(mockUri) - - const mockPickerItems = [ - { - label: addCodiconToString( - 'add', - localize('AWS.command.addSamDebugConfiguration', 'Add Local Invoke and Debug Configuration') - ), - index: -1, - alwaysShow: true, - }, - launchConfigItems, - ] - verifySinglePickerOutputStub.resolves([testConfig]) - getLaunchConfigQuickPickItemsStub.resolves(mockPickerItems) - sandbox.replace( - samInvokeWebview as any, - 'getLaunchConfigQuickPickItems', - getLaunchConfigQuickPickItemsStub - ) - sinon.stub(input, 'createInputBox').resolves('testConfig') - const updateStub = sandbox.stub(vscode.workspace, 'updateWorkspaceFolders') - - await samInvokeWebview.saveLaunchConfig(mockConfig) - assert(getLaunchConfigQuickPickItemsStub.called) - assert(verifySinglePickerOutputStub.called) - assert(updateStub.notCalled) - }) - }) - - describe('Save Launch Config with no URI', () => { - it('should not save if no URI is found', async () => { - getUriStub.resolves(undefined) - await samInvokeWebview.saveLaunchConfig(mockConfig) - assert(launchConfigurationsStub.notCalled) - }) - }) - describe('Save Launch Config', () => { - let promptUserStub: sinon.SinonStub - let mockSavedConfig: AwsSamDebuggerConfiguration - - beforeEach(() => { - mockSavedConfig = { - type: 'aws-sam', - request: 'direct-invoke', - invokeTarget: { - target: 'template', - logicalId: 'HelloWorldFunction', - templatePath: 'template.yaml', - }, - lambda: { - runtime: 'python3.9', - environmentVariables: { - PARAM1: 'VALUE', - }, - }, - sam: { - containerBuild: false, - localArguments: ['-e', '/tester/events.json'], - skipNewImageCheck: false, - }, - api: { - path: 'asdad', - httpMethod: 'get', - }, - name: 'tester', - } - // Create a stub for the promptUser function - promptUserStub = sinon.stub(picker, 'promptUser') - - // Configure the stub to return the desired array - promptUserStub.resolves([mockSavedConfig]) - }) - - afterEach(() => { - // Restore the original function after each test - promptUserStub.restore() - }) - - it('should save launch config', async () => { - workspaceFoldersStub.value([mockFolder]) - getUriFromLaunchConfigStub.resolves(mockUri) - sandbox.replace(samInvokeWebview as any, 'getUriFromLaunchConfig', getUriFromLaunchConfigStub) - const launchConfigItems = launchConfigurationsStub.resolves([ - { - config: mockSavedConfig, - index: 0, - label: 'tester', - }, - ]) - const mockPickerItems = [ - { - label: addCodiconToString( - 'add', - localize('AWS.command.addSamDebugConfiguration', 'Add Local Invoke and Debug Configuration') - ), - index: -1, - alwaysShow: true, - }, - launchConfigItems, - ] - verifySinglePickerOutputStub.resolves([ - { - label: 'tester', - index: 0, - alwaysShow: true, - }, - ]) - getLaunchConfigQuickPickItemsStub.resolves(mockPickerItems) - sandbox.replace( - samInvokeWebview as any, - 'getLaunchConfigQuickPickItems', - getLaunchConfigQuickPickItemsStub - ) - const updateStub = sandbox.stub(vscode.workspace, 'updateWorkspaceFolders') - - await samInvokeWebview.saveLaunchConfig(mockSavedConfig) - - assert(getLaunchConfigQuickPickItemsStub.called) - assert(editDebugConfigurationStub.called) - assert(verifySinglePickerOutputStub.called) - assert(updateStub.notCalled) - }) - }) - }) -}) diff --git a/packages/core/src/test/lambda/wizards/samDeployWizard.test.ts b/packages/core/src/test/lambda/wizards/samDeployWizard.test.ts deleted file mode 100644 index 67d1dc6c73..0000000000 --- a/packages/core/src/test/lambda/wizards/samDeployWizard.test.ts +++ /dev/null @@ -1,804 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import * as paramUtils from '../../../lambda/config/parameterUtils' -import * as input from '../../../shared/ui/input' -import * as picker from '../../../shared/ui/picker' -import { - ParameterPromptResult, - SamDeployWizard, - SamDeployWizardContext, - DefaultSamDeployWizardContext, -} from '../../../lambda/wizards/samDeployWizard' -import { EcrRepository } from '../../../shared/clients/ecrClient' -import { FakeExtensionContext } from '../../fakeExtensionContext' -import { ExtContext } from '../../../shared/extensions' - -interface QuickPickUriResponseItem extends vscode.QuickPickItem { - uri: vscode.Uri -} - -function createQuickPickUriResponseItem(uri: vscode.Uri): QuickPickUriResponseItem { - return { - label: '', - uri: uri, - } -} - -interface QuickPickRegionResponseItem extends vscode.QuickPickItem { - detail: string -} - -function createQuickPickRegionResponseItem(detail: string): QuickPickRegionResponseItem { - return { - label: '', - detail: detail, - } -} - -class MockSamDeployWizardContext implements SamDeployWizardContext { - public get workspaceFolders(): vscode.Uri[] | undefined { - if (this.workspaceFoldersResponses.length <= 0) { - throw new Error('workspaceFolders was called more times than expected') - } - - return this.workspaceFoldersResponses.pop() - } - - public constructor( - readonly extContext: ExtContext, - private readonly workspaceFoldersResponses: (vscode.Uri[] | undefined)[] = [], - private readonly promptForSamTemplateResponses: (QuickPickUriResponseItem | undefined)[] = [], - private readonly promptForRegionResponses: (QuickPickRegionResponseItem | undefined)[] = [], - private readonly promptForS3BucketResponses: (string | undefined)[] = [], - private readonly promptForNewS3BucketResponses: (string | undefined)[] = [], - private readonly promptForEcrRepoResponses: (EcrRepository | undefined)[] = [], - private readonly promptForStackNameResponses: (string | undefined)[] = [], - private readonly hasImages: boolean = false - ) { - this.workspaceFoldersResponses = workspaceFoldersResponses.reverse() - this.promptForSamTemplateResponses = promptForSamTemplateResponses.reverse() - this.promptForRegionResponses = promptForRegionResponses.reverse() - this.promptForS3BucketResponses = promptForS3BucketResponses.reverse() - this.promptForNewS3BucketResponses = promptForNewS3BucketResponses.reverse() - this.promptForEcrRepoResponses = promptForEcrRepoResponses.reverse() - this.promptForStackNameResponses = promptForStackNameResponses.reverse() - } - - additionalSteps: number = 0 - - public readonly getOverriddenParameters: typeof paramUtils.getOverriddenParameters = async () => undefined - - public readonly getParameters: typeof paramUtils.getParameters = async () => new Map() - - public readonly promptUserForParametersIfApplicable: (options: { - templateUri: vscode.Uri - missingParameters?: Set - }) => Promise = async () => ParameterPromptResult.Continue - - public async determineIfTemplateHasImages(templatePath: vscode.Uri): Promise { - return this.hasImages - } - - public async promptUserForSamTemplate(): Promise { - if (this.promptForSamTemplateResponses.length <= 0) { - throw new Error('promptUserForSamTemplate was called more times than expected') - } - - const response = this.promptForSamTemplateResponses.pop() - if (!response) { - return undefined - } - - return response.uri - } - - public async promptUserForS3Bucket(stepNumber: number, initialValue?: string): Promise { - if (this.promptForS3BucketResponses.length <= 0) { - throw new Error('promptUserForS3Bucket was called more times than expected') - } - - return this.promptForS3BucketResponses.pop() - } - - public async promptUserForS3BucketName( - step: number, - bucketProps: { - title: string - prompt?: string | undefined - placeHolder?: string | undefined - value?: string | undefined - buttons?: vscode.QuickInputButton[] | undefined - buttonHandler?: - | (( - button: vscode.QuickInputButton, - inputBox: vscode.InputBox, - resolve: (value: string | PromiseLike | undefined) => void, - reject: (value: string | PromiseLike | undefined) => void - ) => void) - | undefined - } - ): Promise { - if (this.promptForNewS3BucketResponses.length <= 0) { - throw new Error('promptUserForS3BucketName was called more times than expected') - } - - return this.promptForNewS3BucketResponses.pop() - } - - public async promptUserForEcrRepo( - step: number, - selectedRegion?: string, - initialValue?: EcrRepository - ): Promise { - if (this.promptForEcrRepoResponses.length <= 0) { - throw new Error('promptUserForS3Bucket was called more times than expected') - } - - return this.promptForEcrRepoResponses.pop() - } - - public async promptUserForRegion(step: number, initialValue?: string): Promise { - if (this.promptForRegionResponses.length <= 0) { - throw new Error('promptUserForRegion was called more times than expected') - } - - const response = this.promptForRegionResponses.pop() - if (!response) { - return undefined - } - - return response.detail - } - - public async promptUserForStackName({ - validateInput, - }: { - validateInput(value: string): string | undefined - }): Promise { - if (this.promptForStackNameResponses.length <= 0) { - throw new Error('promptUserForStackName was called more times than expected') - } - - const response = this.promptForStackNameResponses.pop() - - if (response && validateInput) { - const validationResult = validateInput(response) - if (validationResult) { - throw new Error(`Validation error: ${validationResult}`) - } - } - - return response - } -} - -function normalizePath(...paths: string[]): string { - return vscode.Uri.file(path.join(...paths)).fsPath -} - -describe('SamDeployWizard', async function () { - let extContext: ExtContext - before(async function () { - extContext = await FakeExtensionContext.getFakeExtContext() - }) - - describe('TEMPLATE', async function () { - it('fails gracefully when no templates are found', async function () { - const wizard = new SamDeployWizard(new MockSamDeployWizardContext(extContext, [[]], [undefined], [], [])) - const result = await wizard.run() - - assert.ok(!result) - }) - - it('exits wizard when cancelled', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [undefined], - [], - [] - ) - ) - const result = await wizard.run() - - assert.ok(!result) - }) - - it('skips template picker if passed as argument', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const arg = vscode.Uri.file('/path/to/template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file('/wrong/template.yaml'))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ), - arg - ) - const result = await wizard.run() - assert.deepStrictEqual(result?.template, arg) - }) - - it('uses user response as template', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.template.fsPath, templatePath) - }) - }) - - describe('PARAMETER_OVERRIDES', async function () { - async function makeFakeContext({ - getParameters, - getOverriddenParameters, - promptUserForParametersIfApplicable, - templatePath = path.join('my', 'template'), - region = 'us-east-1', - s3Bucket = 'mys3bucket', - stackName = 'mystackname', - hasImages = false, - }: Pick< - SamDeployWizardContext, - 'getParameters' | 'getOverriddenParameters' | 'promptUserForParametersIfApplicable' - > & { - templatePath?: string - region?: string - s3Bucket?: string - stackName?: string - hasImages?: boolean - }): Promise { - return { - extContext: await FakeExtensionContext.getFakeExtContext(), - // It's fine to return an empty list if promptUserForSamTemplate is overridden. - additionalSteps: 0, - workspaceFolders: [], - - getParameters, - getOverriddenParameters, - promptUserForParametersIfApplicable, - promptUserForSamTemplate: async () => vscode.Uri.file(templatePath), - promptUserForRegion: async () => region, - promptUserForS3Bucket: async () => s3Bucket, - promptUserForS3BucketName: async () => undefined, - promptUserForEcrRepo: async () => undefined, - promptUserForStackName: async () => stackName, - determineIfTemplateHasImages: async () => hasImages, - } - } - - describe('SAM template has no parameters', async function () { - it('skips configuring overrides and continues wizard', async function () { - const context = await makeFakeContext({ - getParameters: async () => new Map([]), - getOverriddenParameters: async () => { - throw new Error('Should skip loading overrides') - }, - promptUserForParametersIfApplicable: async () => { - throw new Error('Should skip configuring overrides') - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.parameterOverrides.size, 0) - }) - }) - - describe('SAM template has only optional parameters', async function () { - it('skips configuring overrides and continues wizard if parameterOverrides is defined', async function () { - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: false }]]), - getOverriddenParameters: async () => new Map(), - promptUserForParametersIfApplicable: async () => { - throw new Error('Should skip configuring overrides') - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.parameterOverrides.size, 0) - }) - - it('skips configuring overrides and continues wizard if parameterOverrides is undefined and user declines prompt', async function () { - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: false }]]), - getOverriddenParameters: async () => undefined, - promptUserForParametersIfApplicable: async () => ParameterPromptResult.Continue, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.parameterOverrides.size, 0) - }) - - it('configures overrides and cancels wizard if parameterOverrides is undefined and user accepts prompt', async function () { - const configureParameterOverridesArgs: { - templateUri: vscode.Uri - missingParameters?: Set | undefined - }[] = [] - - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: false }]]), - getOverriddenParameters: async () => undefined, - async promptUserForParametersIfApplicable(options): Promise { - configureParameterOverridesArgs.push(options) - - return ParameterPromptResult.Cancel - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.strictEqual(result, undefined) - assert.strictEqual(configureParameterOverridesArgs.length, 1) - assert.strictEqual(configureParameterOverridesArgs[0].missingParameters, undefined) - }) - }) - - describe('SAM template has required parameters', async function () { - it('configures overrides and cancels wizard if overrides are not defined', async function () { - const configureParameterOverridesArgs: { - templateUri: vscode.Uri - missingParameters?: Set | undefined - }[] = [] - - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: true }]]), - getOverriddenParameters: async () => undefined, - async promptUserForParametersIfApplicable(options): Promise { - configureParameterOverridesArgs.push(options) - - return ParameterPromptResult.Cancel - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.strictEqual(result, undefined) - assert.strictEqual(configureParameterOverridesArgs.length, 1) - assert.ok(configureParameterOverridesArgs[0].missingParameters) - assert.strictEqual(configureParameterOverridesArgs[0].missingParameters!.has('myParam'), true) - }) - - it('configures overrides and cancels wizard if there are missing overrides', async function () { - const configureParameterOverridesArgs: { - templateUri: vscode.Uri - missingParameters?: Set | undefined - }[] = [] - - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: true }]]), - getOverriddenParameters: async () => new Map(), - async promptUserForParametersIfApplicable(options): Promise { - configureParameterOverridesArgs.push(options) - - return ParameterPromptResult.Cancel - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.strictEqual(result, undefined) - assert.strictEqual(configureParameterOverridesArgs.length, 1) - assert.ok(configureParameterOverridesArgs[0].missingParameters) - assert.strictEqual(configureParameterOverridesArgs[0].missingParameters!.has('myParam'), true) - }) - - it('stores existing overrides and continues without configuring overrides if there are no missing overrides', async function () { - const configureParameterOverridesArgs: { - templateUri: vscode.Uri - missingParameters?: Set | undefined - }[] = [] - - const context = await makeFakeContext({ - getParameters: async () => - new Map([['myParam', { required: true }]]), - getOverriddenParameters: async () => new Map([['myParam', 'myValue']]), - async promptUserForParametersIfApplicable(options): Promise { - configureParameterOverridesArgs.push(options) - - return ParameterPromptResult.Cancel - }, - }) - - const wizard = new SamDeployWizard(context) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.parameterOverrides.size, 1) - assert.strictEqual(result!.parameterOverrides.has('myParam'), true) - assert.strictEqual(result!.parameterOverrides.get('myParam'), 'myValue') - assert.strictEqual(configureParameterOverridesArgs.length, 0) - }) - }) - }) - - describe('REGION', async function () { - it('uses user response for region', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder', '1') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const region = 'us-east-1' - - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem(region)], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.region, region) - }) - - it('goes back when cancelled', async function () { - const workspaceFolderPath1 = normalizePath('my', 'workspace', 'folder', '1') - const workspaceFolderPath2 = normalizePath('my', 'workspace', 'folder', '2') - const templatePath1 = normalizePath(workspaceFolderPath1, 'template.yaml') - const templatePath2 = normalizePath(workspaceFolderPath2, 'template.yaml') - const region = 'us-east-1' - - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath1)], [vscode.Uri.file(workspaceFolderPath2)]], - [ - createQuickPickUriResponseItem(vscode.Uri.file(templatePath1)), - createQuickPickUriResponseItem(vscode.Uri.file(templatePath2)), - ], - [ - undefined, // First time we ask about the S3 Bucket, cancel back to the template step - createQuickPickRegionResponseItem(region), - ], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.template.fsPath, templatePath2) - }) - }) - - describe('S3_BUCKET', async function () { - it('goes back when cancelled', async function () { - const workspaceFolderPath1 = normalizePath('my', 'workspace', 'folder', '1') - const workspaceFolderPath2 = normalizePath('my', 'workspace', 'folder', '2') - const templatePath1 = normalizePath(workspaceFolderPath1, 'template.yaml') - const templatePath2 = normalizePath(workspaceFolderPath2, 'template.yaml') - const region1 = 'us-east-1' - const region2 = 'us-east-2' - - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath1)], [vscode.Uri.file(workspaceFolderPath2)]], - [ - createQuickPickUriResponseItem(vscode.Uri.file(templatePath1)), - createQuickPickUriResponseItem(vscode.Uri.file(templatePath2)), - ], - [createQuickPickRegionResponseItem(region1), createQuickPickRegionResponseItem(region2)], - [ - undefined, // First time we ask about the S3 Bucket, cancel back to the region step - 'mys3bucketname', - ], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.template.fsPath, templatePath1) - assert.strictEqual(result!.region, region2) - }) - - it('uses user response as s3Bucket', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.s3Bucket, 'mys3bucketname') - }) - }) - - describe('ECR_REPO', async function () { - it('goes back when cancelled', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname', 'mys3bucketname'], - [], - // go back the first time - [undefined, { repositoryUri: 'uri', repositoryName: 'name', repositoryArn: 'arn' }], - ['myStackName'], - true - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result?.ecrRepo?.repositoryUri, 'uri') - }) - - it('uses user response as repo', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname'], - [], - [{ repositoryUri: 'uri', repositoryName: 'name', repositoryArn: 'arn' }], - ['myStackName'], - true - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result?.ecrRepo?.repositoryUri, 'uri') - }) - }) - - describe('STACK_NAME', async function () { - it('goes back when cancelled', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname1', 'mys3bucketname2'], - [], - [], - [undefined, 'myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.s3Bucket, 'mys3bucketname2') - }) - - it('uses user response as stackName', async function () { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - const wizard = new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['mys3bucketname'], - [], - [], - ['myStackName'] - ) - ) - const result = await wizard.run() - - assert.ok(result) - assert.strictEqual(result!.stackName, 'myStackName') - }) - - describe('validation', async function () { - async function assertValidationFails(stackName: string | undefined): Promise { - const workspaceFolderPath = normalizePath('my', 'workspace', 'folder') - const templatePath = normalizePath(workspaceFolderPath, 'template.yaml') - - try { - await new SamDeployWizard( - new MockSamDeployWizardContext( - extContext, - [[vscode.Uri.file(workspaceFolderPath)]], - [createQuickPickUriResponseItem(vscode.Uri.file(templatePath))], - [createQuickPickRegionResponseItem('asdf')], - ['myBucketName'], - [], - [], - [stackName] - ) - ).run() - } catch (err) { - return - } - - assert.fail(`Expected validation for stack name '${stackName}' to fail, but it passed.`) - } - - it('validates that stackName does not contain invalid charcters', async function () { - await assertValidationFails('ab_c') - await assertValidationFails('ab$c') - await assertValidationFails('ab.c') - }) - - it('validates that stackName begins with an alphabetic character', async function () { - await assertValidationFails('1abc') - await assertValidationFails('-abc') - }) - - it('validates that stackName is not longer than 128 characters', async function () { - const parts = [] - for (let i = 0; i < 129; i++) { - parts.push('a') - } - - await assertValidationFails(parts.join('')) - }) - }) - }) -}) - -describe('DefaultSamDeployWizardContext', async function () { - let context: DefaultSamDeployWizardContext - let sandbox: sinon.SinonSandbox - - beforeEach(async function () { - sandbox = sinon.createSandbox() - context = new DefaultSamDeployWizardContext(await FakeExtensionContext.getFakeExtContext()) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('promptUserForS3Bucket', async function () { - it('returns an s3 bucket name', async function () { - const bucketName = 'strictlyForBuckets' - sandbox - .stub(picker, 'promptUser') - .onFirstCall() - .returns(Promise.resolve([{ label: bucketName }])) - const output = await context.promptUserForS3Bucket(1, 'us-weast-1', 'accountId') - assert.strictEqual(output, bucketName) - }) - - it('returns undefined on receiving undefined from the picker (back button)', async function () { - sandbox.stub(picker, 'promptUser').onFirstCall().returns(Promise.resolve(undefined)) - const output = await context.promptUserForS3Bucket(1, 'us-weast-1', 'accountId') - assert.strictEqual(output, undefined) - }) - - it('returns undefined if the user selects a no items/error message', async function () { - const messages = { - noBuckets: "NO! We're out of bear claws", - bucketError: 'One box of one dozen, starving, crazed weasels', - } - sandbox - .stub(picker, 'promptUser') - .onFirstCall() - .returns(Promise.resolve([{ label: messages.noBuckets }])) - .onSecondCall() - .returns(Promise.resolve([{ label: messages.bucketError }])) - const firstOutput = await context.promptUserForS3Bucket( - 1, - 'us-weast-1', - 'profile', - 'accountId', - undefined, - messages - ) - assert.strictEqual(firstOutput, undefined) - - const secondOutput = await context.promptUserForS3Bucket( - 1, - 'us-weast-1', - 'profile', - 'accountId', - undefined, - messages - ) - assert.strictEqual(secondOutput, undefined) - }) - }) - - describe('promptUserForEcrRepo', async function () { - it('returns an ECR Repo', async function () { - const repoName = 'repo' - sandbox - .stub(picker, 'promptUser') - .onFirstCall() - .returns(Promise.resolve([{ label: repoName, repository: { repositoryUri: 'uri' } }])) - const output = await context.promptUserForEcrRepo(1, 'us-weast-1') - assert.notStrictEqual(output, { repositoryUri: 'uri' }) - }) - - it('returns undefined on receiving undefined from the picker (back button)', async function () { - sandbox.stub(picker, 'promptUser').onFirstCall().returns(Promise.resolve(undefined)) - const output = await context.promptUserForEcrRepo(1, 'us-weast-1') - assert.strictEqual(output, undefined) - }) - }) - - describe('promptUserForNewS3Bucket', async function () { - it('returns an S3 bucket name', async function () { - const bucketName = 'shinyNewBucket' - sandbox.stub(input, 'promptUser').onFirstCall().returns(Promise.resolve(bucketName)) - const output = await context.promptUserForS3BucketName(1, { title: 'asdf' }) - assert.strictEqual(output, bucketName) - }) - - it('returns undefined if nothing is entered', async function () { - sandbox.stub(input, 'promptUser').onFirstCall().returns(Promise.resolve(undefined)) - const output = await context.promptUserForS3BucketName(1, { title: 'asdf' }) - assert.strictEqual(output, undefined) - }) - }) -}) diff --git a/packages/core/src/test/lambda/wizards/samInitWizard.test.ts b/packages/core/src/test/lambda/wizards/samInitWizard.test.ts deleted file mode 100644 index 7102b95cf3..0000000000 --- a/packages/core/src/test/lambda/wizards/samInitWizard.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { eventBridgeStarterAppTemplate } from '../../../lambda/models/samTemplates' -import { CreateNewSamAppWizard, CreateNewSamAppWizardForm } from '../../../lambda/wizards/samInitWizard' -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' - -describe('CreateNewSamAppWizard', async function () { - let tester: WizardTester - - beforeEach(async function () { - tester = await createWizardTester(new CreateNewSamAppWizard({ samCliVersion: '1.0.0', schemaRegions: [] })) - }) - - it('prompts for runtime first', function () { - tester.runtimeAndPackage.assertShowFirst() - }) - - it('always prompts for at least 4 things', function () { - tester.assertShowCount(4) - }) - - it('leaves architecture undefined by default', function () { - tester.architecture.assertValue(undefined) - }) - - it('prompts for dependency manager if there are multiple', function () { - tester.dependencyManager.assertDoesNotShow() - tester.runtimeAndPackage.applyInput({ runtime: 'java11', packageType: 'Zip' }) - tester.dependencyManager.assertShow() - }) - - it('always prompts for template after runtime and dependency manager', function () { - tester.template.assertShowSecond() - tester.runtimeAndPackage.applyInput({ runtime: 'java11', packageType: 'Zip' }) - tester.template.assertShowSecond() - }) - - it('prompts for location before name', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'nodejs20.x', packageType: 'Zip' }) - tester.template.applyInput('template') - tester.location.assertShowFirst() - tester.name.assertShowSecond() - }) - - it('prompts for schema configuration if a schema template is selected', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'nodejs20.x', packageType: 'Zip' }) - tester.template.applyInput(eventBridgeStarterAppTemplate) - tester.region.assertShowFirst() - tester.registryName.assertShowSecond() - tester.schemaName.assertShowThird() - }) - - describe('architecture', function () { - beforeEach(async function () { - tester = await createWizardTester(new CreateNewSamAppWizard({ samCliVersion: '1.33.0', schemaRegions: [] })) - }) - - it('prompts for architecture after runtime (no dependency manager) if SAM CLI >= 1.33', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'python3.9', packageType: 'Zip' }) - tester.architecture.assertShowFirst() - }) - - it('prompts for architecture after the dependency manager if SAM CLI >= 1.33', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'java11', packageType: 'Zip' }) - tester.dependencyManager.assertShowFirst() - tester.architecture.assertShowSecond() - }) - - it('skips prompt for earlier versions of SAM CLI', async function () { - tester = await createWizardTester(new CreateNewSamAppWizard({ samCliVersion: '1.32.0', schemaRegions: [] })) - tester.runtimeAndPackage.applyInput({ runtime: 'java11', packageType: 'Zip' }) - tester.architecture.assertDoesNotShow() - }) - - it('skips prompt if runtime has no ARM support', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'go1.x', packageType: 'Zip' }) - tester.architecture.assertDoesNotShow() - }) - - it('skips prompt for maven + Image type', function () { - tester.runtimeAndPackage.applyInput({ runtime: 'java11', packageType: 'Image' }) - tester.dependencyManager.applyInput('maven') - tester.architecture.assertDoesNotShow() - }) - }) -}) diff --git a/packages/core/src/test/lambda/wizards/stateController.test.ts b/packages/core/src/test/lambda/wizards/stateController.test.ts deleted file mode 100644 index a9c2297788..0000000000 --- a/packages/core/src/test/lambda/wizards/stateController.test.ts +++ /dev/null @@ -1,233 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { StateMachineController, ControlSignal, StepResult } from '../../../shared/wizards/stateController' - -function assertStepsPassthrough( - controller: StateMachineController, - current: number, - total: number, - result?: StepResult -): StepResult | undefined { - assert.strictEqual(controller.currentStep, current) - assert.strictEqual(controller.totalSteps, total) - return result -} - -describe('StateMachineController', function () { - it('runs with no steps', async function () { - const controller = new StateMachineController() - await assert.doesNotReject(controller.run()) - }) - - it('handles undefined steps', async function () { - const controller = new StateMachineController() - controller.addStep(async () => ({ nextState: {}, nextSteps: undefined })) - await assert.doesNotReject(controller.run()) - }) - - it('can exit mid-run', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const step2 = sinon.stub() - const step3 = sinon.stub() - step1.returns(0) - step2.returns({ controlSignal: ControlSignal.Exit }) - step3.returns(1) - controller.addStep(step1) - controller.addStep(step2) - controller.addStep(step3) - - assert.strictEqual(await controller.run(), undefined, 'State machine did not exit with an undefined state') - assert.strictEqual(step3.called, false, 'The third step should not be called') - }) - - it('detects cycle', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const step2 = sinon.stub() - step1.returns({}) - step2.onFirstCall().returns(undefined) - step2.onSecondCall().returns({ nextState: {}, nextSteps: [step1] }) - controller.addStep(step1) - controller.addStep(step2) - - await assert.rejects(controller.run(), /Cycle/) - }) - - it('can add the same function as a step multiple times', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - step1.onFirstCall().returns(0) - step1.onSecondCall().returns(1) - controller.addStep(step1) - controller.addStep(step1) - - assert.strictEqual(await controller.run(), 1) - }) - - it('step functions do not have side effects', async function () { - const mystate = { mystring: '', isGood: true } - const controller = new StateMachineController<{ mystring: string; isGood: boolean }>(mystate) - const step1 = sinon.stub() - const step2 = sinon.stub() - const step3 = sinon.stub() - step1.callsFake((state) => { - assert.strictEqual(state.mystring, '') - state.mystring = 'a string' - return state - }) - step2.callsFake((state) => { - const last = state.isGood - state.isGood = false - return { ...state, isGood: last } - }) - step3.onFirstCall().returns(undefined) - step3.onSecondCall().callsFake((state) => state) - controller.addStep(step1) - controller.addStep(step2) - controller.addStep(step3) - - const result = await controller.run() - assert.strictEqual(result?.mystring, 'a string') - assert.strictEqual(result?.isGood, true) - }) - - describe('retry', function () { - it('repeats current step', async function () { - const controller = new StateMachineController() - const stub = sinon.stub() - stub.onFirstCall().returns({ controlSignal: ControlSignal.Retry }) - stub.onSecondCall().returns({}) - controller.addStep(stub) - - await controller.run() - - assert.strictEqual(stub.callCount, 2) - }) - - it('preserves last state', async function () { - const controller = new StateMachineController<{ answer: boolean }>() - const stub1 = sinon.stub() - const stub2 = sinon.stub() - stub1.returns({ nextState: { answer: true } }) - stub2.onFirstCall().returns({ controlSignal: ControlSignal.Retry }) - stub2.onSecondCall().callsFake((state) => ({ nextState: state })) - controller.addStep(stub1) - controller.addStep(stub2) - - assert.strictEqual((await controller.run())?.answer, true) - }) - - it('does not remember state on retry', async function () { - const controller = new StateMachineController<{ answer: boolean }>() - const stub = sinon.stub() - stub.onFirstCall().returns({ nextState: { answer: true }, controlSignal: ControlSignal.Retry }) - stub.onSecondCall().returns({ answer: false }) - controller.addStep(stub) - - assert.strictEqual((await controller.run())?.answer, false) - assert.strictEqual(stub.callCount, 2) - }) - }) - - describe('branching', function () { - it('supports multiple steps per branch', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const branchStep1 = sinon.stub() - const branchStep2 = sinon.stub() - step1.returns({ nextState: {}, nextSteps: [branchStep1, branchStep2] }) - branchStep1.callsFake((state) => assertStepsPassthrough(controller, 2, 3, { nextState: state })) - branchStep2.returns({}) - controller.addStep(step1) - - assert.notStrictEqual(await controller.run(), undefined) - }) - - it('branches can also branch', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const branch1Step1 = sinon.stub() - const branch1Step2 = sinon.stub() - const branch2 = sinon.stub() - const step5 = sinon.stub() - step1.returns({ nextState: {}, nextSteps: [branch1Step1, branch1Step2] }) - branch1Step1.callsFake((state) => assertStepsPassthrough(controller, 2, 4, state)) - branch1Step2.returns({ nextState: {}, nextSteps: [branch2] }) - branch2.callsFake((state) => assertStepsPassthrough(controller, 4, 5, state)) - step5.returns({}) - controller.addStep(step1) - controller.addStep(step5) - - assert.notStrictEqual(await controller.run(), undefined) - }) - }) - - describe('go back', function () { - it('goes back', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const step2 = sinon.stub() - step1.returns({}) - step2.onFirstCall().returns(undefined) - step2.onSecondCall().returns({}) - controller.addStep(step1) - controller.addStep(step2) - - await controller.run() - - assert.strictEqual(step1.callCount, 2) - sinon.assert.callOrder(step1, step2, step1, step2) - }) - - it('goes back and terminates', async function () { - const controller = new StateMachineController() - const step1 = sinon.stub() - const step2 = sinon.stub() - step1.onFirstCall().returns({}) - step2.onFirstCall().returns(undefined) - step1.onSecondCall().returns(undefined) - controller.addStep(step1) - controller.addStep(step2) - - assert.strictEqual(await controller.run(), undefined) - assert.strictEqual(step1.callCount, 2) - sinon.assert.callOrder(step1, step2, step1) - }) - - it('handles branches', async function () { - const controller = new StateMachineController<{ branch1: string; branch2: string }>() - const step1 = sinon.stub() - const branch1 = sinon.stub() - const branch2 = sinon.stub() - const step3 = sinon.stub() - // step1 -> branch1 -> step1 -> branch2 -> step3 -> branch2 -> step3 -> terminate - step1.onFirstCall().returns({ nextState: { branch2: 'no' }, nextSteps: [branch1] }) - step1.onSecondCall().callsFake(() => - assertStepsPassthrough(controller, 1, 2, { - nextState: { branch1: 'no', branch2: 'no' }, - nextSteps: [branch2], - }) - ) - branch1.returns(undefined) - branch2.callsFake((state) => - assertStepsPassthrough(controller, 2, 3, { nextState: { branch2: 'yes', branch1: state.branch1 } }) - ) - step3.onFirstCall().returns(undefined) - step3.onSecondCall().callsFake((state) => state) - controller.addStep(step1) - controller.addStep(step3) - - const finalState = await controller.run() - assert.ok(finalState !== undefined) - assert.strictEqual(finalState.branch1, 'no') - assert.strictEqual(finalState.branch2, 'yes') - sinon.assert.callOrder(step1, branch1, step1, branch2, step3, branch2, step3) - }) - }) -}) diff --git a/packages/core/src/test/lambda/wizards/uploadLambdaWizard.test.ts b/packages/core/src/test/lambda/wizards/uploadLambdaWizard.test.ts deleted file mode 100644 index a42cf5b3ce..0000000000 --- a/packages/core/src/test/lambda/wizards/uploadLambdaWizard.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as path from 'path' -import assert from 'assert' -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { UploadLambdaWizard, UploadLambdaWizardState, LambdaFunction } from '../../../lambda/commands/uploadLambda' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { fs } from '../../../shared' - -describe('UploadLambdaWizard', function () { - let tester: WizardTester - - describe('invoked from command palette', function () { - beforeEach(async () => { - tester = await createWizardTester(new UploadLambdaWizard()) - }) - - it('shows all but build prompts', function () { - tester.lambda.region.assertShow(1) - tester.uploadType.assertShow(2) - tester.targetUri.assertShow(3) - tester.lambda.name.assertShow(4) - tester.confirmedDeploy.assertShow(5) - }) - }) - - describe('invoked from lambda node', function () { - beforeEach(async function () { - tester = await createWizardTester( - new UploadLambdaWizard({ name: 'lambdaName', region: 'us-east-1', configuration: {} } as LambdaFunction) - ) - }) - - it('shows build prompt when directory is chosen', function () { - tester.uploadType.applyInput('directory') - tester.directoryBuildType.assertShow() - tester.targetUri.assertShow() - tester.confirmedDeploy.assertShow() - }) - it('no build prompt when zip is chosen', function () { - tester.uploadType.applyInput('zip') - tester.directoryBuildType.assertDoesNotShow() - tester.targetUri.assertShow() - tester.confirmedDeploy.assertShow() - }) - }) - - describe('invoked from directory', function () { - let tempDir: string - let invokePath: vscode.Uri - beforeEach(async function () { - tempDir = await makeTemporaryToolkitFolder() - invokePath = vscode.Uri.file(tempDir) - tester = await createWizardTester(new UploadLambdaWizard(undefined, invokePath)) - }) - afterEach(async function () { - await fs.delete(tempDir, { recursive: true }) - }) - - it('skip select directory, auto selected', function () { - tester.lambda.region.assertShow(1) - tester.uploadType.assertDoesNotShow() - tester.uploadType.assertValue('directory') - tester.targetUri.assertDoesNotShow() - tester.targetUri.assertValue(invokePath) - tester.lambda.name.assertShow(2) - tester.confirmedDeploy.assertShow(3) - }) - }) - - describe('invoked from template', function () { - let tempDir: string - let tempDirUri: vscode.Uri - let invokePath: vscode.Uri - beforeEach(async function () { - tempDir = await makeTemporaryToolkitFolder() - tempDirUri = vscode.Uri.file(tempDir) - await fs.writeFile(path.join(tempDir, 'template.yaml'), '') - invokePath = vscode.Uri.file(path.join(tempDir, 'template.yaml')) - tester = await createWizardTester(new UploadLambdaWizard(undefined, invokePath)) - }) - afterEach(async function () { - await fs.delete(tempDir, { recursive: true }) - }) - - it('skip select directory, auto selected', function () { - tester.lambda.region.assertShow(1) - tester.uploadType.assertDoesNotShow() - tester.uploadType.assertValue('directory') - tester.targetUri.assertDoesNotShow() - assert.strictEqual(tester.targetUri.value?.fsPath, tempDirUri.fsPath) - tester.lambda.name.assertShow(2) - tester.confirmedDeploy.assertShow(3) - }) - }) -}) diff --git a/packages/core/src/test/login/webview/vue/backend_toolkit.test.ts b/packages/core/src/test/login/webview/vue/backend_toolkit.test.ts deleted file mode 100644 index 1294a2d057..0000000000 --- a/packages/core/src/test/login/webview/vue/backend_toolkit.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { SinonSandbox, createSandbox } from 'sinon' -import { assertTelemetry } from '../../../testUtil' -import assert from 'assert' -import { createTestAuth } from '../../../credentials/testUtil' -import { Auth } from '../../../../auth' -import { isBuilderIdConnection, isIdcSsoConnection, scopesSsoAccountAccess } from '../../../../auth/connection' -import { getOpenExternalStub } from '../../../globalSetup.test' -import { ToolkitLoginWebview } from '../../../../login/webview/vue/toolkit/backend_toolkit' -import { - CodeCatalystAuthenticationProvider, - CodeCatalystAuthStorage, - defaultScopes, -} from '../../../../codecatalyst/auth' -import { FakeSecretStorage } from '../../../fakeExtensionContext' -import * as authUtils from '../../../../auth/utils' -import globals from '../../../../shared/extensionGlobals' - -// TODO: remove auth page and tests -describe('Toolkit Login', function () { - const region = 'fakeRegion' - const startUrl = 'fakeUrl' - const profileName = 'profile' - const accessKey = 'fakeKey' - const secretKey = 'fakeSecret' - - let sandbox: SinonSandbox - let auth: ReturnType - let codecatalystAuth: CodeCatalystAuthenticationProvider - let backend: ToolkitLoginWebview - - beforeEach(function () { - sandbox = createSandbox() - auth = createTestAuth(globals.globalState) - codecatalystAuth = new CodeCatalystAuthenticationProvider( - new CodeCatalystAuthStorage(new FakeSecretStorage()), - auth - ) - sandbox.stub(Auth, 'instance').value(auth) - getOpenExternalStub().resolves(true) - - backend = new ToolkitLoginWebview(codecatalystAuth) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('signs into builder ID and emits telemetry', async function () { - sandbox.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - - await backend.startBuilderIdSetup() - - assert.ok(isBuilderIdConnection(auth.activeConnection)) - assert.deepStrictEqual(auth.activeConnection.scopes, defaultScopes) - assert.deepStrictEqual(auth.activeConnection.state, 'valid') - - assertTelemetry('auth_addConnection', { - result: 'Succeeded', - credentialSourceId: 'awsId', - authEnabledFeatures: 'codecatalyst', - isReAuth: false, - }) - }) - - it('signs into account IdC and emits telemetry', async function () { - await backend.startEnterpriseSetup(startUrl, region) - - assert.ok(isIdcSsoConnection(auth.activeConnection)) - assert.deepStrictEqual(auth.activeConnection.scopes, scopesSsoAccountAccess) - assert.deepStrictEqual(auth.activeConnection.state, 'valid') - assert.deepStrictEqual(auth.activeConnection.startUrl, startUrl) - assert.deepStrictEqual(auth.activeConnection.ssoRegion, region) - - assertTelemetry('auth_addConnection', { - result: 'Succeeded', - credentialSourceId: 'iamIdentityCenter', - authEnabledFeatures: 'awsExplorer', - credentialStartUrl: startUrl, - awsRegion: region, - isReAuth: false, - }) - }) - - it('signs into codecatalyst IdC and emits telemetry', async function () { - sandbox.stub(codecatalystAuth, 'isConnectionOnboarded').resolves(true) - backend.setLoginService('codecatalyst') - - await backend.startEnterpriseSetup(startUrl, region) - - assert.ok(isIdcSsoConnection(auth.activeConnection)) - assert.deepStrictEqual(auth.activeConnection.scopes, defaultScopes) - assert.deepStrictEqual(auth.activeConnection.state, 'valid') - assert.deepStrictEqual(auth.activeConnection.startUrl, startUrl) - assert.deepStrictEqual(auth.activeConnection.ssoRegion, region) - - assertTelemetry('auth_addConnection', { - result: 'Succeeded', - credentialSourceId: 'iamIdentityCenter', - authEnabledFeatures: 'codecatalyst,awsExplorer', - credentialStartUrl: startUrl, - awsRegion: region, - isReAuth: false, - }) - }) - - it('signs in with Iam credentials and emits telemetry', async function () { - sandbox.stub(auth, 'authenticateData').resolves() - const stub = sandbox.stub(authUtils, 'tryAddCredentials').resolves() - await backend.startIamCredentialSetup(profileName, accessKey, secretKey) - - assert.ok(stub.calledOnceWith(profileName, { aws_access_key_id: accessKey, aws_secret_access_key: secretKey })) - assertTelemetry('auth_addConnection', { - result: 'Succeeded', - credentialSourceId: 'sharedCredentials', - authEnabledFeatures: 'awsExplorer', - }) - }) -}) diff --git a/packages/core/src/test/notifications/rules.test.ts b/packages/core/src/test/notifications/rules.test.ts index 0df695c1be..88587c4c9d 100644 --- a/packages/core/src/test/notifications/rules.test.ts +++ b/packages/core/src/test/notifications/rules.test.ts @@ -8,7 +8,7 @@ import assert from 'assert' import { RuleEngine, getRuleContext } from '../../notifications/rules' import { DisplayIf, ToolkitNotification, RuleContext } from '../../notifications/types' import globals from '../../shared/extensionGlobals' -import { Connection, scopesCodeCatalyst } from '../../auth/connection' +import { Connection } from '../../auth/connection' import { getOperatingSystem } from '../../shared/telemetry/util' import { getAuthFormIdsFromConnection } from '../../auth/utils' import { amazonQScopes } from '../../codewhisperer' @@ -518,11 +518,6 @@ describe('Notifications getRuleContext()', function () { scopes: amazonQScopes, startUrl: builderIdStartUrl, }, - { - type: 'sso', - scopes: scopesCodeCatalyst, - startUrl: builderIdStartUrl, - }, { type: 'sso', scopes: amazonQScopes, diff --git a/packages/core/src/test/sagemakerunifiedstudio/activation.test.ts b/packages/core/src/test/sagemakerunifiedstudio/activation.test.ts deleted file mode 100644 index 7c92077199..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/activation.test.ts +++ /dev/null @@ -1,300 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { activate } from '../../sagemakerunifiedstudio/activation' -import * as extensionUtilities from '../../shared/extensionUtilities' -import * as connectionMagicsSelectorActivation from '../../sagemakerunifiedstudio/connectionMagicsSelector/activation' -import * as explorerActivation from '../../sagemakerunifiedstudio/explorer/activation' -import * as resourceMetadataUtils from '../../sagemakerunifiedstudio/shared/utils/resourceMetadataUtils' -import * as setContext from '../../shared/vscode/setContext' -import { SmusUtils } from '../../sagemakerunifiedstudio/shared/smusUtils' -import * as smusUriHandlers from '../../sagemakerunifiedstudio/uriHandlers' -import { ExtContext } from '../../shared/extensions' - -describe('SageMaker Unified Studio Main Activation', function () { - let mockToolkitExtContext: ExtContext - let mockExtensionContext: vscode.ExtensionContext - let isSageMakerStub: sinon.SinonStub - let initializeResourceMetadataStub: sinon.SinonStub - let setContextStub: sinon.SinonStub - let isInSmusSpaceEnvironmentStub: sinon.SinonStub - let activateConnectionMagicsSelectorStub: sinon.SinonStub - let activateExplorerStub: sinon.SinonStub - let registerUriHandlerStub: sinon.SinonStub - - beforeEach(function () { - mockExtensionContext = { - subscriptions: [], - extensionPath: '/test/path', - globalState: { - get: sinon.stub(), - update: sinon.stub(), - }, - workspaceState: { - get: sinon.stub(), - update: sinon.stub(), - }, - } as any - - mockToolkitExtContext = { - extensionContext: mockExtensionContext, - awsContext: {} as any, - samCliContext: sinon.stub() as any, - regionProvider: {} as any, - outputChannel: {} as any, - telemetryService: {} as any, - uriHandler: {} as any, - credentialsStore: {} as any, - } - - // Stub all dependencies - isSageMakerStub = sinon.stub(extensionUtilities, 'isSageMaker') - initializeResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'initializeResourceMetadata') - setContextStub = sinon.stub(setContext, 'setContext') - isInSmusSpaceEnvironmentStub = sinon.stub(SmusUtils, 'isInSmusSpaceEnvironment') - activateConnectionMagicsSelectorStub = sinon.stub(connectionMagicsSelectorActivation, 'activate') - activateExplorerStub = sinon.stub(explorerActivation, 'activate') - registerUriHandlerStub = sinon.stub(smusUriHandlers, 'register') - - // Set default return values - isSageMakerStub.returns(false) - initializeResourceMetadataStub.resolves() - setContextStub.resolves() - isInSmusSpaceEnvironmentStub.returns(false) - activateConnectionMagicsSelectorStub.resolves() - activateExplorerStub.resolves() - registerUriHandlerStub.returns({ dispose: sinon.stub() } as any) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('activate function', function () { - it('should always activate explorer regardless of environment', async function () { - isSageMakerStub.returns(false) - - await activate(mockToolkitExtContext) - - assert.ok(activateExplorerStub.calledOnceWith(mockExtensionContext)) - }) - - it('should not initialize SMUS components when not in SageMaker environment', async function () { - isSageMakerStub.returns(false) - - await activate(mockToolkitExtContext) - - assert.ok(initializeResourceMetadataStub.notCalled) - assert.ok(setContextStub.notCalled) - assert.ok(activateConnectionMagicsSelectorStub.notCalled) - assert.ok(activateExplorerStub.calledOnceWith(mockExtensionContext)) - }) - - it('should initialize SMUS components when in SMUS environment', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - isInSmusSpaceEnvironmentStub.returns(true) - - await activate(mockToolkitExtContext) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnceWith('aws.smus.inSmusSpaceEnvironment', true)) - assert.ok(activateConnectionMagicsSelectorStub.calledOnceWith(mockExtensionContext)) - assert.ok(activateExplorerStub.calledOnceWith(mockExtensionContext)) - }) - - it('should initialize SMUS components when in SMUS-SPACE-REMOTE-ACCESS environment', async function () { - isSageMakerStub.withArgs('SMUS').returns(false) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(true) - isInSmusSpaceEnvironmentStub.returns(false) - - await activate(mockToolkitExtContext) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnceWith('aws.smus.inSmusSpaceEnvironment', false)) - assert.ok(activateConnectionMagicsSelectorStub.calledOnceWith(mockExtensionContext)) - assert.ok(activateExplorerStub.calledOnceWith(mockExtensionContext)) - }) - - it('should call functions in correct order for SMUS environment', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - isInSmusSpaceEnvironmentStub.returns(true) - - await activate(mockToolkitExtContext) - - // Verify the order of calls - assert.ok(initializeResourceMetadataStub.calledBefore(setContextStub)) - assert.ok(setContextStub.calledBefore(activateConnectionMagicsSelectorStub)) - assert.ok(activateConnectionMagicsSelectorStub.calledBefore(activateExplorerStub)) - }) - - it('should handle initializeResourceMetadata errors', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - const error = new Error('Resource metadata initialization failed') - initializeResourceMetadataStub.rejects(error) - - await assert.rejects(() => activate(mockToolkitExtContext), /Resource metadata initialization failed/) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.notCalled) - assert.ok(activateConnectionMagicsSelectorStub.notCalled) - }) - - it('should handle setContext errors', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isInSmusSpaceEnvironmentStub.returns(true) - const error = new Error('Set context failed') - setContextStub.rejects(error) - - await assert.rejects(() => activate(mockToolkitExtContext), /Set context failed/) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.notCalled) - }) - - it('should handle connectionMagicsSelector activation errors', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isInSmusSpaceEnvironmentStub.returns(true) - const error = new Error('Connection magics selector activation failed') - activateConnectionMagicsSelectorStub.rejects(error) - - await assert.rejects(() => activate(mockToolkitExtContext), /Connection magics selector activation failed/) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.calledOnce) - }) - - it('should handle explorer activation errors', async function () { - const error = new Error('Explorer activation failed') - activateExplorerStub.rejects(error) - - await assert.rejects(() => activate(mockToolkitExtContext), /Explorer activation failed/) - - assert.ok(activateExplorerStub.calledOnce) - }) - - it('should pass correct extension context to all activation functions', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isInSmusSpaceEnvironmentStub.returns(true) - - await activate(mockToolkitExtContext) - - assert.ok(activateConnectionMagicsSelectorStub.calledWith(mockExtensionContext)) - assert.ok(activateExplorerStub.calledWith(mockExtensionContext)) - }) - - it('should register URI handler', async function () { - await activate(mockToolkitExtContext) - - assert.ok(registerUriHandlerStub.calledOnceWith(mockToolkitExtContext)) - assert.ok(mockExtensionContext.subscriptions.length > 0) - }) - }) - - describe('environment detection logic', function () { - it('should check both SMUS and SMUS-SPACE-REMOTE-ACCESS environments', async function () { - isSageMakerStub.withArgs('SMUS').returns(false) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - - await activate(mockToolkitExtContext) - - assert.ok(isSageMakerStub.calledWith('SMUS')) - assert.ok(isSageMakerStub.calledWith('SMUS-SPACE-REMOTE-ACCESS')) - }) - - it('should activate SMUS components if either environment check returns true', async function () { - // Test case 1: Only SMUS returns true - isSageMakerStub.withArgs('SMUS').returns(true) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - isInSmusSpaceEnvironmentStub.returns(true) - - await activate(mockToolkitExtContext) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.calledOnce) - - // Reset stubs for second test - initializeResourceMetadataStub.resetHistory() - activateConnectionMagicsSelectorStub.resetHistory() - - // Test case 2: Only SMUS-SPACE-REMOTE-ACCESS returns true - isSageMakerStub.withArgs('SMUS').returns(false) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(true) - isInSmusSpaceEnvironmentStub.returns(false) - - await activate(mockToolkitExtContext) - - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.calledOnce) - }) - - it('should use SmusUtils.isInSmusSpaceEnvironment() result for context setting', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - - // Test with true - isInSmusSpaceEnvironmentStub.returns(true) - await activate(mockToolkitExtContext) - assert.ok(setContextStub.calledWith('aws.smus.inSmusSpaceEnvironment', true)) - - // Reset and test with false - setContextStub.resetHistory() - isInSmusSpaceEnvironmentStub.returns(false) - await activate(mockToolkitExtContext) - assert.ok(setContextStub.calledWith('aws.smus.inSmusSpaceEnvironment', false)) - }) - }) - - describe('integration scenarios', function () { - it('should handle mixed success and failure scenarios gracefully', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isInSmusSpaceEnvironmentStub.returns(true) - - // initializeResourceMetadata succeeds, setContext fails - const setContextError = new Error('Context setting failed') - setContextStub.rejects(setContextError) - - await assert.rejects(() => activate(mockToolkitExtContext), /Context setting failed/) - - // Verify that initializeResourceMetadata was called but subsequent functions were not - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.notCalled) - assert.ok(activateExplorerStub.notCalled) - }) - - it('should complete successfully when all components initialize properly', async function () { - isSageMakerStub.withArgs('SMUS').returns(true) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - isInSmusSpaceEnvironmentStub.returns(true) - - // All functions should succeed - await activate(mockToolkitExtContext) - - // Verify all expected functions were called - assert.ok(initializeResourceMetadataStub.calledOnce) - assert.ok(setContextStub.calledOnce) - assert.ok(activateConnectionMagicsSelectorStub.calledOnce) - assert.ok(activateExplorerStub.calledOnce) - assert.ok(registerUriHandlerStub.calledOnce) - }) - - it('should handle minimal extension context gracefully', async function () { - const minimalContext = { - extensionContext: mockExtensionContext, - } as any - - // Should not throw with minimal context - await activate(minimalContext) - - assert.ok(activateExplorerStub.called) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/authenticationOrchestrator.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/authenticationOrchestrator.test.ts deleted file mode 100644 index 095922dfc6..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/authenticationOrchestrator.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { SmusAuthenticationOrchestrator } from '../../../sagemakerunifiedstudio/auth/authenticationOrchestrator' - -describe('SmusAuthenticationOrchestrator', function () { - // Note: Due to AWS Toolkit test framework restrictions on mocking vscode.window, - // these tests focus on the interface and behavior rather than deep mocking. - // The actual authentication flows are tested through integration tests. - - describe('handleIamAuthentication', function () { - it('should export the correct interface', function () { - // Verify the class exists and has the expected static method - assert.ok('handleIamAuthentication' in SmusAuthenticationOrchestrator) - assert.strictEqual(typeof SmusAuthenticationOrchestrator.handleIamAuthentication, 'function') - }) - - it('should be callable without throwing', function () { - // Verify the method exists and is accessible - assert.doesNotThrow(() => { - assert.ok('handleIamAuthentication' in SmusAuthenticationOrchestrator) - }) - }) - }) - - describe('handleSsoAuthentication', function () { - it('should export the correct interface', function () { - // Verify the class exists and has the expected static method - assert.ok('handleSsoAuthentication' in SmusAuthenticationOrchestrator) - assert.strictEqual(typeof SmusAuthenticationOrchestrator.handleSsoAuthentication, 'function') - }) - - it('should be callable without throwing', function () { - // Verify the method exists and is accessible - assert.doesNotThrow(() => { - assert.ok('handleSsoAuthentication' in SmusAuthenticationOrchestrator) - }) - }) - }) - - describe('return types', function () { - it('should handle SUCCESS and BACK return types correctly', function () { - // Test that the return types are properly defined for both methods - const testResult1: 'SUCCESS' | 'BACK' = 'SUCCESS' - const testResult2: 'SUCCESS' | 'BACK' = 'BACK' - - assert.strictEqual(testResult1, 'SUCCESS') - assert.strictEqual(testResult2, 'BACK') - }) - }) - - describe('class structure', function () { - it('should be a class with static methods', function () { - // Verify the orchestrator is properly structured - assert.strictEqual(typeof SmusAuthenticationOrchestrator, 'function') - assert.ok(SmusAuthenticationOrchestrator.prototype) - }) - - it('should have both required authentication methods', function () { - // Verify both authentication methods exist - const methods = ['handleIamAuthentication', 'handleSsoAuthentication'] - - for (const method of methods) { - assert.ok(method in SmusAuthenticationOrchestrator, `Missing method: ${method}`) - assert.strictEqual( - typeof SmusAuthenticationOrchestrator[method as keyof typeof SmusAuthenticationOrchestrator], - 'function', - `${method} should be a function` - ) - } - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/connectionCredentialsProvider.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/connectionCredentialsProvider.test.ts deleted file mode 100644 index 8d14d21ba5..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/connectionCredentialsProvider.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { ConnectionCredentialsProvider } from '../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' -import { SmusAuthenticationProvider } from '../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { DataZoneClient } from '../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { ToolkitError } from '../../../shared/errors' - -describe('ConnectionCredentialsProvider', function () { - let mockAuthProvider: sinon.SinonStubbedInstance - let mockDataZoneClient: sinon.SinonStubbedInstance - let connectionProvider: ConnectionCredentialsProvider - let dataZoneClientStub: sinon.SinonStub - - const testProjectId = 'proj-123456' - const testConnectionId = 'conn-123456' - const testDomainId = 'dzd_testdomain' - const testRegion = 'us-east-2' - - const mockConnectionCredentials = { - accessKeyId: 'AKIA-CONNECTION-KEY', - secretAccessKey: 'connection-secret-key', - sessionToken: 'connection-session-token', - expiration: new Date(Date.now() + 3600000), // 1 hour from now - } - - const mockGetConnectionResponse = { - connectionId: testConnectionId, - name: 'Test Connection', - type: 'S3', - domainId: testDomainId, - projectId: 'project-123', - connectionCredentials: mockConnectionCredentials, - } - - beforeEach(function () { - // Mock auth provider - mockAuthProvider = { - isConnected: sinon.stub().returns(true), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns(testRegion), - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - activeConnection: { - ssoRegion: testRegion, - }, - } as any - - // Mock DataZone client - mockDataZoneClient = { - getConnection: sinon.stub().resolves(mockGetConnectionResponse), - } as any - - // Stub DataZoneClient.createWithCredentials - dataZoneClientStub = sinon.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - - connectionProvider = new ConnectionCredentialsProvider(mockAuthProvider as any, testConnectionId, testProjectId) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should create provider with correct properties', function () { - assert.strictEqual(connectionProvider.getConnectionId(), testConnectionId) - assert.strictEqual(connectionProvider.getDefaultRegion(), testRegion) - }) - }) - - describe('getCredentialsId', function () { - it('should return correct credentials ID', function () { - const credentialsId = connectionProvider.getCredentialsId() - assert.strictEqual(credentialsId.credentialSource, 'temp') - assert.strictEqual(credentialsId.credentialTypeId, `${testDomainId}:${testConnectionId}`) - }) - }) - - describe('getHashCode', function () { - it('should return correct hash code', function () { - const hashCode = connectionProvider.getHashCode() - assert.strictEqual(hashCode, `smus-connection:${testDomainId}:${testConnectionId}`) - }) - }) - - describe('isAvailable', function () { - it('should return true when auth provider is connected', async function () { - mockAuthProvider.isConnected.returns(true) - const isAvailable = await connectionProvider.isAvailable() - assert.strictEqual(isAvailable, true) - }) - - it('should return false when auth provider is not connected', async function () { - mockAuthProvider.isConnected.returns(false) - const isAvailable = await connectionProvider.isAvailable() - assert.strictEqual(isAvailable, false) - }) - - it('should return false when auth provider throws error', async function () { - mockAuthProvider.isConnected.throws(new Error('Connection error')) - const isAvailable = await connectionProvider.isAvailable() - assert.strictEqual(isAvailable, false) - }) - }) - - describe('canAutoConnect', function () { - it('should return false', async function () { - const canAutoConnect = await connectionProvider.canAutoConnect() - assert.strictEqual(canAutoConnect, false) - }) - }) - - describe('getCredentials', function () { - it('should fetch and return connection credentials', async function () { - const credentials = await connectionProvider.getCredentials() - - assert.strictEqual(credentials.accessKeyId, mockConnectionCredentials.accessKeyId) - assert.strictEqual(credentials.secretAccessKey, mockConnectionCredentials.secretAccessKey) - assert.strictEqual(credentials.sessionToken, mockConnectionCredentials.sessionToken) - assert(credentials.expiration instanceof Date) - - // Verify DataZone client was called correctly - sinon.assert.calledOnce(dataZoneClientStub) - sinon.assert.calledWith(mockDataZoneClient.getConnection, { - domainIdentifier: testDomainId, - identifier: testConnectionId, - withSecret: true, - }) - }) - - it('should use cached credentials on subsequent calls', async function () { - // First call - const credentials1 = await connectionProvider.getCredentials() - // Second call - const credentials2 = await connectionProvider.getCredentials() - - assert.strictEqual(credentials1, credentials2) - // DataZone client should only be called once due to caching - sinon.assert.calledOnce(mockDataZoneClient.getConnection) - }) - - it('should throw error when no connection credentials available', async function () { - mockDataZoneClient.getConnection.resolves({ - ...mockGetConnectionResponse, - connectionCredentials: undefined, - }) - - await assert.rejects( - () => connectionProvider.getCredentials(), - (err: ToolkitError) => { - assert.strictEqual(err.code, 'NoConnectionCredentials') - return true - } - ) - }) - - it('should throw error when connection credentials are invalid', async function () { - mockDataZoneClient.getConnection.resolves({ - ...mockGetConnectionResponse, - connectionCredentials: { - accessKeyId: '', // Invalid empty string - secretAccessKey: 'valid-secret', - sessionToken: 'valid-token', - }, - }) - - await assert.rejects( - () => connectionProvider.getCredentials(), - (err: ToolkitError) => { - assert.strictEqual(err.code, 'InvalidConnectionCredentials') - return true - } - ) - }) - - it('should throw error when DataZone client fails', async function () { - const dataZoneError = new Error('DataZone API error') - mockDataZoneClient.getConnection.rejects(dataZoneError) - - await assert.rejects( - () => connectionProvider.getCredentials(), - (err: ToolkitError) => { - assert.strictEqual(err.code, 'ConnectionCredentialsFetchFailed') - return true - } - ) - }) - }) - - describe('invalidate', function () { - it('should clear cached credentials', async function () { - // Get credentials to populate cache - await connectionProvider.getCredentials() - sinon.assert.calledOnce(mockDataZoneClient.getConnection) - - // Invalidate cache - connectionProvider.invalidate() - - // Get credentials again - should make new API call - await connectionProvider.getCredentials() - sinon.assert.calledTwice(mockDataZoneClient.getConnection) - }) - }) - - describe('provider metadata', function () { - it('should return correct provider type', function () { - assert.strictEqual(connectionProvider.getProviderType(), 'temp') - }) - - it('should return correct telemetry type', function () { - assert.strictEqual(connectionProvider.getTelemetryType(), 'other') - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/domainExecRoleCredentialsProvider.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/domainExecRoleCredentialsProvider.test.ts deleted file mode 100644 index 7e8cdd8632..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/domainExecRoleCredentialsProvider.test.ts +++ /dev/null @@ -1,583 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { DomainExecRoleCredentialsProvider } from '../../../sagemakerunifiedstudio/auth/providers/domainExecRoleCredentialsProvider' -import { ToolkitError } from '../../../shared/errors' -import fetch from 'node-fetch' -import { SmusTimeouts } from '../../../sagemakerunifiedstudio/shared/smusUtils' - -describe('DomainExecRoleCredentialsProvider', function () { - let derProvider: DomainExecRoleCredentialsProvider - let mockGetAccessToken: sinon.SinonStub - let fetchStub: sinon.SinonStub - - const testDomainId = 'dzd_testdomain' - const testDomainUrl = 'https://test-domain.sagemaker.us-east-2.on.aws' - const testSsoRegion = 'us-east-2' - const testAccessToken = 'test-access-token-12345' - - const mockCredentialsResponse = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - }, - } - - beforeEach(function () { - // Mock access token function - mockGetAccessToken = sinon.stub().resolves(testAccessToken) - - // Mock fetch - fetchStub = sinon.stub(fetch, 'default' as any).resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(mockCredentialsResponse)), - json: sinon.stub().resolves(mockCredentialsResponse), - } as any) - - derProvider = new DomainExecRoleCredentialsProvider( - testDomainUrl, - testDomainId, - testSsoRegion, - mockGetAccessToken - ) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should initialize with correct properties', function () { - assert.strictEqual(derProvider.getDomainId(), testDomainId) - assert.strictEqual(derProvider.getDomainUrl(), testDomainUrl) - assert.strictEqual(derProvider.getDefaultRegion(), testSsoRegion) - }) - }) - - describe('getCredentialsId', function () { - it('should return correct credentials ID', function () { - const credentialsId = derProvider.getCredentialsId() - assert.strictEqual(credentialsId.credentialSource, 'sso') - assert.strictEqual(credentialsId.credentialTypeId, testDomainId) - }) - }) - - describe('getProviderType', function () { - it('should return sso provider type', function () { - assert.strictEqual(derProvider.getProviderType(), 'sso') - }) - }) - - describe('getTelemetryType', function () { - it('should return ssoProfile telemetry type', function () { - assert.strictEqual(derProvider.getTelemetryType(), 'ssoProfile') - }) - }) - - describe('getHashCode', function () { - it('should return correct hash code', function () { - const hashCode = derProvider.getHashCode() - assert.strictEqual(hashCode, `smus-der:${testDomainId}:${testSsoRegion}`) - }) - }) - - describe('canAutoConnect', function () { - it('should return false', async function () { - const result = await derProvider.canAutoConnect() - assert.strictEqual(result, false) - }) - }) - - describe('isAvailable', function () { - it('should return true when access token is available', async function () { - const result = await derProvider.isAvailable() - assert.strictEqual(result, true) - assert.ok(mockGetAccessToken.called) - }) - - it('should return false when access token throws error', async function () { - mockGetAccessToken.rejects(new Error('Token error')) - const result = await derProvider.isAvailable() - assert.strictEqual(result, false) - }) - }) - - describe('getCredentials', function () { - it('should fetch and cache DER credentials', async function () { - const credentials = await derProvider.getCredentials() - - // Verify access token was fetched - assert.ok(mockGetAccessToken.called) - - // Verify fetch was called with correct parameters - assert.ok(fetchStub.called) - const fetchCall = fetchStub.firstCall - assert.strictEqual(fetchCall.args[0], `${testDomainUrl}/sso/redeem-token`) - - const fetchOptions = fetchCall.args[1] - assert.strictEqual(fetchOptions.method, 'POST') - assert.strictEqual(fetchOptions.headers['Content-Type'], 'application/json') - assert.strictEqual(fetchOptions.headers['Accept'], 'application/json') - assert.strictEqual(fetchOptions.headers['User-Agent'], 'aws-toolkit-vscode') - - const requestBody = JSON.parse(fetchOptions.body) - assert.strictEqual(requestBody.domainId, testDomainId) - assert.strictEqual(requestBody.accessToken, testAccessToken) - - // Verify timeout is set - assert.strictEqual(fetchOptions.timeout, SmusTimeouts.apiCallTimeoutMs) - assert.strictEqual(fetchOptions.timeout, 10000) // 10 seconds - - // Verify returned credentials - assert.strictEqual(credentials.accessKeyId, mockCredentialsResponse.credentials.accessKeyId) - assert.strictEqual(credentials.secretAccessKey, mockCredentialsResponse.credentials.secretAccessKey) - assert.strictEqual(credentials.sessionToken, mockCredentialsResponse.credentials.sessionToken) - assert.ok(credentials.expiration) - }) - - it('should use cached credentials when available', async function () { - // First call should fetch credentials - const credentials1 = await derProvider.getCredentials() - - // Second call should use cache - const credentials2 = await derProvider.getCredentials() - - // Fetch should only be called once - assert.strictEqual(fetchStub.callCount, 1) - assert.strictEqual(mockGetAccessToken.callCount, 1) - - // Credentials should be the same - assert.strictEqual(credentials1, credentials2) - }) - - it('should handle missing access token', async function () { - mockGetAccessToken.resolves('') - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' && err.message.includes('No access token available') - } - ) - }) - - it('should handle HTTP errors from redeem token API', async function () { - fetchStub.resolves({ - ok: false, - status: 401, - statusText: 'Unauthorized', - text: sinon.stub().resolves('Invalid token'), - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' && err.message.includes('401') - } - ) - }) - - it('should handle timeout errors', async function () { - const timeoutError = new Error('Request timeout') - timeoutError.name = 'AbortError' - fetchStub.rejects(timeoutError) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return ( - err.code === 'DerCredentialsFetchFailed' && err.message.includes('timed out after 10 seconds') - ) - } - ) - }) - - it('should handle network errors', async function () { - const networkError = new Error('Network error') - fetchStub.rejects(networkError) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' - } - ) - }) - - it('should handle missing credentials object in response', async function () { - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify({})), - json: sinon.stub().resolves({}), // Missing credentials object - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return ( - err.code === 'DerCredentialsFetchFailed' && err.message.includes('Missing credentials object') - ) - } - ) - }) - - it('should handle invalid accessKeyId in response', async function () { - const invalidResponse = { - credentials: { - accessKeyId: '', // Invalid empty string - secretAccessKey: 'valid-secret', - sessionToken: 'valid-token', - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(invalidResponse)), - json: sinon.stub().resolves(invalidResponse), - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' && err.message.includes('Invalid accessKeyId') - } - ) - }) - - it('should handle invalid secretAccessKey in response', async function () { - const invalidResponse = { - credentials: { - accessKeyId: 'valid-key', - secretAccessKey: undefined, // Invalid null value - sessionToken: 'valid-token', - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(invalidResponse)), - json: sinon.stub().resolves(invalidResponse), - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' && err.message.includes('Invalid secretAccessKey') - } - ) - }) - - it('should handle invalid sessionToken in response', async function () { - const invalidResponse = { - credentials: { - accessKeyId: 'valid-key', - secretAccessKey: 'valid-secret', - sessionToken: undefined, // Invalid undefined value - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(invalidResponse)), - json: sinon.stub().resolves(invalidResponse), - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' && err.message.includes('Invalid sessionToken') - } - ) - }) - - it('should set default expiration when not provided in response', async function () { - const credentials = await derProvider.getCredentials() - - // Should have expiration set to 10 mins from now - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = Date.now() + 10 * 60 * 1000 // 10 minutes - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 5000, 'Expiration should be 10 mins from now') - }) - - it('should use expiration from API response when provided as ISO string', async function () { - const futureExpiration = new Date(Date.now() + 2 * 60 * 60 * 1000) // 2 hours from now - const responseWithExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: futureExpiration.toISOString(), // API returns expiration as ISO string - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithExpiration)), - json: sinon.stub().resolves(responseWithExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should use the expiration from the API response - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = futureExpiration.getTime() - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 1000, 'Should use expiration from API response') - }) - - it('should handle epoch timestamp in seconds from API response', async function () { - const futureTime = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now in seconds - const responseWithEpochExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: futureTime.toString(), // Epoch timestamp in seconds as string - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithEpochExpiration)), - json: sinon.stub().resolves(responseWithEpochExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should correctly parse epoch timestamp and convert to Date - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = futureTime * 1000 // Convert to milliseconds - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 1000, 'Should correctly parse epoch timestamp in seconds') - }) - - it('should handle epoch timestamp as number from API response', async function () { - const futureTime = Math.floor(Date.now() / 1000) + 7200 // 2 hours from now in seconds - const responseWithEpochExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: futureTime, // Epoch timestamp in seconds as number - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithEpochExpiration)), - json: sinon.stub().resolves(responseWithEpochExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should correctly parse epoch timestamp and convert to Date - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = futureTime * 1000 // Convert to milliseconds - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 1000, 'Should correctly parse epoch timestamp as number') - }) - - it('should handle zero epoch timestamp gracefully', async function () { - const responseWithZeroExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: '0', // Zero is not > 0, so treated as ISO string "0" which represents year 0 - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithZeroExpiration)), - json: sinon.stub().resolves(responseWithZeroExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // "0" is parsed as a valid date (year 0), not as an invalid date - // So it should use the parsed date, not the default expiration - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = new Date('0').getTime() // Year 0 - assert.strictEqual(expirationTime, expectedTime, 'Should use parsed date for year 0') - }) - - it('should handle negative epoch timestamp gracefully', async function () { - const responseWithNegativeExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: '-1', // Negative is not > 0, so treated as ISO string "-1" which represents year -1 - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithNegativeExpiration)), - json: sinon.stub().resolves(responseWithNegativeExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // "-1" is parsed as a valid date (year -1), not as an invalid date - // So it should use the parsed date, not the default expiration - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = new Date('-1').getTime() // Year -1 - assert.strictEqual(expirationTime, expectedTime, 'Should use parsed date for year -1') - }) - - it('should handle JSON parsing errors', async function () { - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves('invalid json'), - json: sinon.stub().rejects(new Error('Invalid JSON')), - } as any) - - await assert.rejects( - () => derProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'DerCredentialsFetchFailed' - } - ) - }) - - it('should handle invalid expiration string in response', async function () { - const responseWithInvalidExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: 'invalid-date-string', // Invalid date string - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithInvalidExpiration)), - json: sinon.stub().resolves(responseWithInvalidExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should fall back to default expiration when date parsing fails - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - - // Should be a valid timestamp (not NaN) using the default expiration - assert.ok(!isNaN(expirationTime), 'Should have valid expiration timestamp') - - // Should be close to now + 10 minutes (default expiration) - const expectedTime = Date.now() + 10 * 60 * 1000 - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 5000, 'Should fall back to default expiration for invalid date string') - }) - - it('should handle empty expiration string in response', async function () { - const responseWithEmptyExpiration = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: '', // Empty string - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithEmptyExpiration)), - json: sinon.stub().resolves(responseWithEmptyExpiration), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should fall back to default expiration for empty string - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = Date.now() + 10 * 60 * 1000 // Default 10 minutes - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 5000, 'Should use default expiration for empty string') - }) - - it('should handle non-numeric string that looks like a number', async function () { - const responseWithInvalidNumber = { - credentials: { - accessKeyId: 'AKIA-DER-KEY', - secretAccessKey: 'der-secret-key', - sessionToken: 'der-session-token', - expiration: '123abc', // Non-numeric string - }, - } - - fetchStub.resolves({ - ok: true, - status: 200, - statusText: 'OK', - text: sinon.stub().resolves(JSON.stringify(responseWithInvalidNumber)), - json: sinon.stub().resolves(responseWithInvalidNumber), - } as any) - - const credentials = await derProvider.getCredentials() - - // Should fall back to default expiration for invalid numeric string - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = Date.now() + 10 * 60 * 1000 // Default 10 minutes - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 5000, 'Should use default expiration for invalid numeric string') - }) - }) - - describe('invalidate', function () { - it('should clear cache and force fresh fetch on next call', async function () { - // First call to populate cache - await derProvider.getCredentials() - assert.strictEqual(fetchStub.callCount, 1) - - // Invalidate should clear cache - derProvider.invalidate() - - // Next call should fetch fresh credentials - await derProvider.getCredentials() - assert.strictEqual(fetchStub.callCount, 2) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/model.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/model.test.ts deleted file mode 100644 index e1d030ea82..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/model.test.ts +++ /dev/null @@ -1,345 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { Credentials } from '@aws-sdk/types' -import * as sinon from 'sinon' -import { - SmusSsoConnection, - SmusIamConnection, - isSmusIamConnection, - isSmusSsoConnection, - isValidSmusConnection, - createSmusProfile, - scopeSmus, - getDataZoneSsoScope, -} from '../../../sagemakerunifiedstudio/auth/model' -import { DevSettings } from '../../../shared/settings' - -describe('SMUS Connection Model', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - const mockCredentials: Credentials = { - accessKeyId: 'test-access-key', - secretAccessKey: 'test-secret-key', - } - - const mockCredentialsProvider = async (): Promise => mockCredentials - - const mockGetToken = async () => ({ - accessToken: 'mock-access-token', - expiresAt: new Date(Date.now() + 3600000), // 1 hour from now - }) - - const mockGetRegistration = async () => ({ - clientId: 'mock-client-id', - clientSecret: 'mock-client-secret', - expiresAt: new Date(Date.now() + 86400000), // 24 hours from now - startUrl: 'https://test.sagemaker.us-east-1.on.aws/', - }) - - describe('isSmusIamConnection', function () { - it('should return true for valid SMUS IAM connection', function () { - const connection: SmusIamConnection = { - type: 'iam', - profileName: 'test-profile', - region: 'us-east-1', - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test IAM Connection', - endpointUrl: undefined, - getCredentials: mockCredentialsProvider, - } - - assert.strictEqual(isSmusIamConnection(connection), true) - }) - - it('should return false for SSO connection', function () { - const connection: SmusSsoConnection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [scopeSmus], - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - getToken: mockGetToken, - getRegistration: mockGetRegistration, - } - - assert.strictEqual(isSmusIamConnection(connection), false) - }) - - it('should return false for connection missing required IAM properties', function () { - const connection = { - type: 'iam', - profileName: 'test-profile', - // Missing region, domainUrl, domainId, getCredentials - id: 'test-id', - label: 'Test IAM Connection', - endpointUrl: undefined, - } - - assert.strictEqual(isSmusIamConnection(connection as any), false) - }) - - it('should return false for undefined connection', function () { - assert.strictEqual(isSmusIamConnection(undefined), false) - }) - - it('should return false for connection with wrong type', function () { - const connection = { - type: 'other', - profileName: 'test-profile', - region: 'us-east-1', - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test Connection', - } - - assert.strictEqual(isSmusIamConnection(connection as any), false) - }) - }) - - describe('isSmusSsoConnection', function () { - it('should return true for valid SMUS SSO connection', function () { - const connection: SmusSsoConnection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [scopeSmus], - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - getToken: mockGetToken, - getRegistration: mockGetRegistration, - } - - assert.strictEqual(isSmusSsoConnection(connection), true) - }) - - it('should return false for IAM connection', function () { - const connection: SmusIamConnection = { - type: 'iam', - profileName: 'test-profile', - region: 'us-east-1', - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test IAM Connection', - endpointUrl: undefined, - getCredentials: mockCredentialsProvider, - } - - assert.strictEqual(isSmusSsoConnection(connection), false) - }) - - it('should return false for SSO connection without SMUS scope', function () { - const connection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: ['other:scope'], - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - } - - assert.strictEqual(isSmusSsoConnection(connection as any), false) - }) - - it('should return false for SSO connection missing SMUS properties', function () { - const connection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [scopeSmus], - // Missing domainUrl and domainId - id: 'test-id', - label: 'Test SSO Connection', - } - - assert.strictEqual(isSmusSsoConnection(connection as any), false) - }) - - it('should return false for undefined connection', function () { - assert.strictEqual(isSmusSsoConnection(undefined), false) - }) - }) - - describe('isValidSmusConnection', function () { - it('should return true for valid SMUS SSO connection', function () { - const connection: SmusSsoConnection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [scopeSmus], - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - getToken: mockGetToken, - getRegistration: mockGetRegistration, - } - - assert.strictEqual(isValidSmusConnection(connection), true) - }) - - it('should return true for valid SMUS IAM connection', function () { - const connection: SmusIamConnection = { - type: 'iam', - profileName: 'test-profile', - region: 'us-east-1', - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test IAM Connection', - endpointUrl: undefined, - getCredentials: mockCredentialsProvider, - } - - assert.strictEqual(isValidSmusConnection(connection), true) - }) - - it('should return false for invalid connection', function () { - const connection = { - type: 'other', - id: 'test-id', - label: 'Test Connection', - } - - assert.strictEqual(isValidSmusConnection(connection), false) - }) - - it('should return false for undefined connection', function () { - assert.strictEqual(isValidSmusConnection(undefined), false) - }) - }) - - describe('getDataZoneSsoScope', function () { - it('should return default scope when no custom setting is provided', function () { - // When get() is called with default value, it returns the default (scopeSmus) - // This simulates the behavior when aws.dev.datazoneScope is not set - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(scopeSmus) - - const scope = getDataZoneSsoScope() - - assert.strictEqual(scope, scopeSmus) - }) - - it('should return custom scope when setting is configured', function () { - const customScope = 'custom:datazone:scope' - // When get() is called, it returns the custom value from settings - // This simulates the behavior when aws.dev.datazoneScope is set to customScope - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(customScope) - - const scope = getDataZoneSsoScope() - - assert.strictEqual(scope, customScope) - }) - }) - - describe('createSmusProfile', function () { - it('should create a valid SMUS profile with default scope', function () { - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(scopeSmus) - - const domainUrl = 'https://test.sagemaker.us-east-1.on.aws/' - const domainId = 'test-domain-id' - const startUrl = 'https://test.awsapps.com/start' - const region = 'us-east-1' - - const profile = createSmusProfile(domainUrl, domainId, startUrl, region) - - assert.strictEqual(profile.domainUrl, domainUrl) - assert.strictEqual(profile.domainId, domainId) - assert.strictEqual(profile.startUrl, startUrl) - assert.strictEqual(profile.ssoRegion, region) - assert.strictEqual(profile.type, 'sso') - assert.deepStrictEqual(profile.scopes, [scopeSmus]) - }) - - it('should create a valid SMUS profile with custom scope from settings', function () { - const customScope = 'custom:datazone:scope' - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(customScope) - - const domainUrl = 'https://test.sagemaker.us-east-1.on.aws/' - const domainId = 'test-domain-id' - const startUrl = 'https://test.awsapps.com/start' - const region = 'us-east-1' - - const profile = createSmusProfile(domainUrl, domainId, startUrl, region) - - assert.deepStrictEqual(profile.scopes, [customScope]) - }) - - it('should create a valid SMUS profile with custom scopes parameter', function () { - const domainUrl = 'https://test.sagemaker.us-east-1.on.aws/' - const domainId = 'test-domain-id' - const startUrl = 'https://test.awsapps.com/start' - const region = 'us-east-1' - const customScopes = ['custom:scope1', 'custom:scope2'] - - const profile = createSmusProfile(domainUrl, domainId, startUrl, region, customScopes) - - assert.deepStrictEqual(profile.scopes, customScopes) - }) - }) - - describe('isSmusSsoConnection with custom scope', function () { - it('should return true for connection with custom scope from settings', function () { - const customScope = 'custom:datazone:scope' - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(customScope) - - const connection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [customScope], - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - getToken: mockGetToken, - getRegistration: mockGetRegistration, - } as SmusSsoConnection - - assert.strictEqual(isSmusSsoConnection(connection), true) - }) - - it('should return true for connection with default scope even when custom scope is configured', function () { - const customScope = 'custom:datazone:scope' - sandbox.stub(DevSettings.instance, 'get').withArgs('datazoneScope', scopeSmus).returns(customScope) - - const connection = { - type: 'sso', - startUrl: 'https://test.awsapps.com/start', - ssoRegion: 'us-east-1', - scopes: [scopeSmus], // Using default scope - domainUrl: 'https://test.sagemaker.us-east-1.on.aws/', - domainId: 'test-domain-id', - id: 'test-id', - label: 'Test SSO Connection', - getToken: mockGetToken, - getRegistration: mockGetRegistration, - } as SmusSsoConnection - - // Should still work for backward compatibility - assert.strictEqual(isSmusSsoConnection(connection), true) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.test.ts deleted file mode 100644 index 0654983093..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/preferences/authenticationPreferences.test.ts +++ /dev/null @@ -1,303 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { - SmusAuthenticationPreferencesManager, - SmusAuthenticationPreferences, - SmusIamProfileConfig, -} from '../../../../sagemakerunifiedstudio/auth/preferences/authenticationPreferences' -import { globals } from '../../../../shared' - -describe('SmusAuthenticationPreferencesManager', function () { - let mockContext: any - let sandbox: sinon.SinonSandbox - let mockGlobalState: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - - // Mock the globals.globalState instead of context.globalState directly - mockGlobalState = { - get: sandbox.stub(), - update: sandbox.stub().resolves(), - } - - // Mock VS Code extension context (still needed for the API) - mockContext = { - globalState: mockGlobalState, - } - - // Stub globals.globalState to use our mock - sandbox.stub(globals, 'globalState').value(mockGlobalState) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('getPreferences', function () { - it('should return default preferences when none are stored', function () { - // Setup - mockGlobalState.get.returns(undefined) - - // Act - const preferences = SmusAuthenticationPreferencesManager.getPreferences(mockContext) - - // Assert - assert.deepStrictEqual(preferences, { - rememberChoice: false, - }) - }) - - it('should return stored preferences when available', function () { - // Setup - const storedPreferences: SmusAuthenticationPreferences = { - preferredMethod: 'iam', - rememberChoice: true, - lastUsedSsoConnection: 'conn-123', - lastUsedIamProfile: { - profileName: 'default', - region: 'us-east-1', - lastUsed: new Date('2023-01-01'), - isDefault: true, - }, - } - mockGlobalState.get.returns(storedPreferences) - - // Act - const preferences = SmusAuthenticationPreferencesManager.getPreferences(mockContext) - - // Assert - assert.deepStrictEqual(preferences, storedPreferences) - }) - - it('should merge stored preferences with defaults', function () { - // Setup - const partialPreferences = { - preferredMethod: 'sso' as const, - } - mockGlobalState.get.returns(partialPreferences) - - // Act - const preferences = SmusAuthenticationPreferencesManager.getPreferences(mockContext) - - // Assert - assert.deepStrictEqual(preferences, { - preferredMethod: 'sso', - rememberChoice: false, - }) - }) - }) - - describe('updatePreferences', function () { - it('should update preferences correctly', async function () { - // Setup - const currentPreferences: SmusAuthenticationPreferences = { - preferredMethod: 'sso', - rememberChoice: true, - } - mockGlobalState.get.returns(currentPreferences) - - const updates = { - preferredMethod: 'iam' as const, - lastUsedSsoConnection: 'conn-456', - } - - // Act - await SmusAuthenticationPreferencesManager.updatePreferences(mockContext, updates) - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, updatedPreferences] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - assert.deepStrictEqual(updatedPreferences, { - preferredMethod: 'iam', - rememberChoice: true, - lastUsedSsoConnection: 'conn-456', - }) - }) - }) - - describe('setPreferredMethod', function () { - it('should set preferred method and remember choice', async function () { - // Setup - mockGlobalState.get.returns({}) - - // Act - await SmusAuthenticationPreferencesManager.setPreferredMethod(mockContext, 'iam', true) - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, preferences] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - assert.deepStrictEqual(preferences, { - preferredMethod: 'iam', - rememberChoice: true, - }) - }) - }) - - describe('getPreferredMethod', function () { - it('should return preferred method when remember choice is true', function () { - // Setup - const preferences: SmusAuthenticationPreferences = { - preferredMethod: 'iam', - rememberChoice: true, - } - mockGlobalState.get.returns(preferences) - - // Act - const method = SmusAuthenticationPreferencesManager.getPreferredMethod(mockContext) - - // Assert - assert.strictEqual(method, 'iam') - }) - - it('should return undefined when remember choice is false', function () { - // Setup - const preferences: SmusAuthenticationPreferences = { - preferredMethod: 'iam', - rememberChoice: false, - } - mockGlobalState.get.returns(preferences) - - // Act - const method = SmusAuthenticationPreferencesManager.getPreferredMethod(mockContext) - - // Assert - assert.strictEqual(method, undefined) - }) - - it('should return undefined when no preferred method is set', function () { - // Setup - const preferences: SmusAuthenticationPreferences = { - rememberChoice: true, - } - mockGlobalState.get.returns(preferences) - - // Act - const method = SmusAuthenticationPreferencesManager.getPreferredMethod(mockContext) - - // Assert - assert.strictEqual(method, undefined) - }) - }) - - describe('setLastUsedSsoConnection', function () { - it('should set last used SSO connection', async function () { - // Setup - mockGlobalState.get.returns({}) - - // Act - await SmusAuthenticationPreferencesManager.setLastUsedSsoConnection(mockContext, 'conn-789') - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, preferences] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - assert.deepStrictEqual(preferences, { - rememberChoice: false, - lastUsedSsoConnection: 'conn-789', - }) - }) - }) - - describe('setLastUsedIamProfile', function () { - it('should set last used IAM profile with timestamp', async function () { - // Setup - mockGlobalState.get.returns({}) - const profileConfig: SmusIamProfileConfig = { - profileName: 'production', - region: 'us-west-2', - isDefault: false, - } - - // Act - await SmusAuthenticationPreferencesManager.setLastUsedIamProfile(mockContext, profileConfig) - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, preferences] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - - assert.strictEqual(preferences.lastUsedIamProfile.profileName, 'production') - assert.strictEqual(preferences.lastUsedIamProfile.region, 'us-west-2') - assert.strictEqual(preferences.lastUsedIamProfile.isDefault, false) - assert.ok(preferences.lastUsedIamProfile.lastUsed instanceof Date) - }) - }) - - describe('getLastUsedIamProfile', function () { - it('should return last used IAM profile when available', function () { - // Setup - const profileConfig: SmusIamProfileConfig = { - profileName: 'test-profile', - region: 'eu-west-1', - lastUsed: new Date('2023-06-01'), - isDefault: true, - } - const preferences: SmusAuthenticationPreferences = { - rememberChoice: false, - lastUsedIamProfile: profileConfig, - } - mockGlobalState.get.returns(preferences) - - // Act - const result = SmusAuthenticationPreferencesManager.getLastUsedIamProfile(mockContext) - - // Assert - assert.deepStrictEqual(result, profileConfig) - }) - - it('should return undefined when no IAM profile is stored', function () { - // Setup - mockGlobalState.get.returns({}) - - // Act - const result = SmusAuthenticationPreferencesManager.getLastUsedIamProfile(mockContext) - - // Assert - assert.strictEqual(result, undefined) - }) - }) - - describe('clearPreferences', function () { - it('should clear all preferences', async function () { - // Act - await SmusAuthenticationPreferencesManager.clearPreferences(mockContext) - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, value] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - assert.strictEqual(value, undefined) - }) - }) - - describe('switchAuthenticationMethod', function () { - it('should switch authentication method', async function () { - // Setup - const currentPreferences: SmusAuthenticationPreferences = { - preferredMethod: 'sso', - rememberChoice: true, - } - mockGlobalState.get.returns(currentPreferences) - - // Act - await SmusAuthenticationPreferencesManager.switchAuthenticationMethod(mockContext, 'iam') - - // Assert - assert.strictEqual(mockGlobalState.update.calledOnce, true) - const [key, preferences] = mockGlobalState.update.firstCall.args - assert.strictEqual(key, 'aws.smus.authenticationPreferences') - assert.deepStrictEqual(preferences, { - preferredMethod: 'iam', - rememberChoice: true, - }) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/projectRoleCredentialsProvider.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/projectRoleCredentialsProvider.test.ts deleted file mode 100644 index bef7cc1885..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/projectRoleCredentialsProvider.test.ts +++ /dev/null @@ -1,259 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { ProjectRoleCredentialsProvider } from '../../../sagemakerunifiedstudio/auth/providers/projectRoleCredentialsProvider' -import { DataZoneClient } from '../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { ToolkitError } from '../../../shared/errors' - -describe('ProjectRoleCredentialsProvider', function () { - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockSmusAuthProvider: any - let projectProvider: ProjectRoleCredentialsProvider - let dataZoneClientStub: sinon.SinonStub - - const testProjectId = 'test-project-123' - const testDomainId = 'dzd_testdomain' - const testRegion = 'us-east-2' - - const mockGetEnvironmentCredentialsResponse = { - accessKeyId: 'AKIA-PROJECT-KEY', - secretAccessKey: 'project-secret-key', - sessionToken: 'project-session-token', - expiration: new Date(Date.now() + 14 * 60 * 1000), // 14 minutes as Date object - $metadata: { - httpStatusCode: 200, - requestId: 'test-request-id', - }, - } - - beforeEach(function () { - // Mock SMUS auth provider - mockSmusAuthProvider = { - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns(testRegion), - isConnected: sinon.stub().returns(true), - activeConnection: { - profileName: 'test-profile', - domainId: testDomainId, - ssoRegion: testRegion, - }, - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getCredentialsProviderForIamProfile: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'profile-key', - secretAccessKey: 'profile-secret', - sessionToken: 'profile-token', - }), - }), - } as any - - // Mock DataZone client - mockDataZoneClient = { - getProjectDefaultEnvironmentCreds: sinon.stub().resolves(mockGetEnvironmentCredentialsResponse), - } as any - - dataZoneClientStub = sinon.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - - projectProvider = new ProjectRoleCredentialsProvider(mockSmusAuthProvider, testProjectId) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should initialize with DER provider and project ID', function () { - assert.strictEqual(projectProvider.getProjectId(), testProjectId) - }) - }) - - describe('getCredentialsId', function () { - it('should return correct credentials ID', function () { - const credentialsId = projectProvider.getCredentialsId() - assert.strictEqual(credentialsId.credentialSource, 'temp') - assert.strictEqual(credentialsId.credentialTypeId, `${testDomainId}:${testProjectId}`) - }) - }) - - describe('getProviderType', function () { - it('should return sso provider type', function () { - assert.strictEqual(projectProvider.getProviderType(), 'temp') - }) - }) - - describe('getTelemetryType', function () { - it('should return smusProfile telemetry type', function () { - assert.strictEqual(projectProvider.getTelemetryType(), 'other') - }) - }) - - describe('getDefaultRegion', function () { - it('should return DER provider default region', function () { - assert.strictEqual(projectProvider.getDefaultRegion(), testRegion) - }) - }) - - describe('getHashCode', function () { - it('should return correct hash code', function () { - const hashCode = projectProvider.getHashCode() - assert.strictEqual(hashCode, `smus-project:${testDomainId}:${testProjectId}`) - }) - }) - - describe('canAutoConnect', function () { - it('should return false', async function () { - const result = await projectProvider.canAutoConnect() - assert.strictEqual(result, false) - }) - }) - - describe('isAvailable', function () { - it('should delegate to SMUS auth provider', async function () { - const result = await projectProvider.isAvailable() - assert.strictEqual(result, true) - assert.ok(mockSmusAuthProvider.isConnected.called) - }) - }) - - describe('getCredentials', function () { - it('should fetch and cache project credentials', async function () { - const credentials = await projectProvider.getCredentials() - - // Verify DataZone client createWithCredentials was called with correct parameters - assert.ok(dataZoneClientStub.calledWith(testRegion, testDomainId, sinon.match.any)) - - // Verify getProjectDefaultEnvironmentCreds was called - assert.ok(mockDataZoneClient.getProjectDefaultEnvironmentCreds.called) - assert.ok(mockDataZoneClient.getProjectDefaultEnvironmentCreds.calledWith(testProjectId)) - - // Verify returned credentials - assert.strictEqual(credentials.accessKeyId, mockGetEnvironmentCredentialsResponse.accessKeyId) - assert.strictEqual(credentials.secretAccessKey, mockGetEnvironmentCredentialsResponse.secretAccessKey) - assert.strictEqual(credentials.sessionToken, mockGetEnvironmentCredentialsResponse.sessionToken) - assert.ok(credentials.expiration) - }) - - it('should use cached credentials when available', async function () { - // First call should fetch credentials - const credentials1 = await projectProvider.getCredentials() - - // Second call should use cache - const credentials2 = await projectProvider.getCredentials() - - // DataZone client method should only be called once - assert.strictEqual(mockDataZoneClient.getProjectDefaultEnvironmentCreds.callCount, 1) - - // Credentials should be the same - assert.strictEqual(credentials1, credentials2) - }) - - it('should handle DataZone client errors', async function () { - const error = new Error('DataZone client failed') - mockDataZoneClient.getProjectDefaultEnvironmentCreds.rejects(error) - - await assert.rejects( - () => projectProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'ProjectCredentialsFetchFailed' && err.message.includes(testProjectId) - } - ) - }) - - it('should handle GetEnvironmentCredentials API errors', async function () { - const error = new Error('API call failed') - mockDataZoneClient.getProjectDefaultEnvironmentCreds.rejects(error) - - await assert.rejects( - () => projectProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'ProjectCredentialsFetchFailed' - } - ) - }) - - it('should handle missing credentials in response', async function () { - mockDataZoneClient.getProjectDefaultEnvironmentCreds.resolves({ - accessKeyId: undefined, - $metadata: { - httpStatusCode: 200, - requestId: 'test-request-id', - }, - }) - - await assert.rejects( - () => projectProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'ProjectCredentialsFetchFailed' - } - ) - }) - - it('should handle invalid credential fields', async function () { - const invalidResponse = { - accessKeyId: '', // Invalid empty string - secretAccessKey: 'valid-secret', - sessionToken: 'valid-token', - $metadata: { - httpStatusCode: 200, - requestId: 'test-request-id', - }, - } - mockDataZoneClient.getProjectDefaultEnvironmentCreds.resolves(invalidResponse) - - await assert.rejects( - () => projectProvider.getCredentials(), - (err: ToolkitError) => { - return err.code === 'ProjectCredentialsFetchFailed' - } - ) - }) - - it('should use default expiration when not provided in response', async function () { - const responseWithoutExpiration = { - accessKeyId: 'AKIA-PROJECT-KEY', - secretAccessKey: 'project-secret-key', - sessionToken: 'project-session-token', - // No expiration field - $metadata: { - httpStatusCode: 200, - requestId: 'test-request-id', - }, - } - mockDataZoneClient.getProjectDefaultEnvironmentCreds.resolves(responseWithoutExpiration) - - const credentials = await projectProvider.getCredentials() - - // Should have expiration set to ~10 minutes from now - assert.ok(credentials.expiration) - const expirationTime = credentials.expiration!.getTime() - const expectedTime = Date.now() + 10 * 60 * 1000 - const timeDiff = Math.abs(expirationTime - expectedTime) - assert.ok(timeDiff < 5000, 'Expiration should be ~10 minutes from now') - }) - }) - - describe('invalidate', function () { - it('should clear cache and force fresh fetch on next call', async function () { - // First call to populate cache - await projectProvider.getCredentials() - assert.strictEqual(mockDataZoneClient.getProjectDefaultEnvironmentCreds.callCount, 1) - - // Invalidate should clear cache - projectProvider.invalidate() - - // Next call should fetch fresh credentials - await projectProvider.getCredentials() - assert.strictEqual(mockDataZoneClient.getProjectDefaultEnvironmentCreds.callCount, 2) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/smusAuthenticationProvider.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/smusAuthenticationProvider.test.ts deleted file mode 100644 index 60f8558c9b..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/smusAuthenticationProvider.test.ts +++ /dev/null @@ -1,1846 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import * as setContextModule from '../../../shared/vscode/setContext' -import * as secondaryAuthModule from '../../../auth/secondaryAuth' -import * as sharedCredentialsModule from '../../../auth/credentials/sharedCredentials' -import * as stsClientModule from '../../../shared/clients/stsClient' -import { DataZoneCustomClientHelper } from '../../../sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper' - -import { SmusAuthenticationProvider } from '../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { SmusConnection } from '../../../sagemakerunifiedstudio/auth/model' -import { DataZoneClient } from '../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { SmusUtils } from '../../../sagemakerunifiedstudio/shared/smusUtils' -import * as smusUtils from '../../../sagemakerunifiedstudio/shared/smusUtils' -import { ToolkitError } from '../../../shared/errors' -import * as messages from '../../../shared/utilities/messages' -import * as vscodeSetContext from '../../../shared/vscode/setContext' -import * as resourceMetadataUtils from '../../../sagemakerunifiedstudio/shared/utils/resourceMetadataUtils' -import { DefaultStsClient } from '../../../shared/clients/stsClient' - -describe('SmusAuthenticationProvider', function () { - let mockAuth: any - let mockSecondaryAuth: any - let mockDataZoneClient: sinon.SinonStubbedInstance - let smusAuthProvider: SmusAuthenticationProvider - let extractDomainInfoStub: sinon.SinonStub - let getSsoInstanceInfoStub: sinon.SinonStub - let isInSmusSpaceEnvironmentStub: sinon.SinonStub - let executeCommandStub: sinon.SinonStub - let setContextStubGlobal: sinon.SinonStub - let getResourceMetadataStub: sinon.SinonStub - let mockSecondaryAuthState: { - activeConnection: SmusConnection | undefined - hasSavedConnection: boolean - isConnectionExpired: boolean - } - - const testDomainUrl = 'https://dzd_domainId.sagemaker.us-east-2.on.aws' - const testDomainId = 'dzd_domainId' - const testRegion = 'us-east-2' - const testSsoInstanceInfo = { - issuerUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoInstanceId: 'ssoins-testInstanceId', - clientId: 'arn:aws:sso::123456789:application/ssoins-testInstanceId/apl-testAppId', - region: testRegion, - } - - const mockSmusConnection: SmusConnection = { - id: 'test-connection-id', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: testRegion, - scopes: ['datazone:domain:access'], - label: 'Test SMUS Connection', - domainUrl: testDomainUrl, - domainId: testDomainId, - getToken: sinon.stub().resolves({ accessToken: 'mock-token', expiresAt: new Date() }), - getRegistration: sinon.stub().resolves({ clientId: 'mock-client', expiresAt: new Date() }), - } - - beforeEach(function () { - // Create the setContext stub - setContextStubGlobal = sinon.stub(setContextModule, 'setContext').resolves() - - mockAuth = { - createConnection: sinon.stub().resolves(mockSmusConnection), - listConnections: sinon.stub().resolves([]), - getConnectionState: sinon.stub().returns('valid'), - reauthenticate: sinon.stub().resolves(mockSmusConnection), - } as any - - // Create a mock object with configurable properties - mockSecondaryAuthState = { - activeConnection: mockSmusConnection as SmusConnection | undefined, - hasSavedConnection: false, - isConnectionExpired: false, - } - - mockSecondaryAuth = { - get activeConnection() { - return mockSecondaryAuthState.activeConnection - }, - get hasSavedConnection() { - return mockSecondaryAuthState.hasSavedConnection - }, - get isConnectionExpired() { - return mockSecondaryAuthState.isConnectionExpired - }, - state: { - get: sinon.stub().returns({}), - update: sinon.stub().resolves(), - }, - onDidChangeActiveConnection: sinon.stub().returns({ dispose: sinon.stub() }), - restoreConnection: sinon.stub().resolves(), - useNewConnection: sinon.stub().resolves(mockSmusConnection), - deleteConnection: sinon.stub().resolves(), - } - - mockDataZoneClient = { - // Add any DataZoneClient methods that might be used - } as any - - // Stub static methods - sinon.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - extractDomainInfoStub = sinon - .stub(SmusUtils, 'extractDomainInfoFromUrl') - .returns({ domainId: testDomainId, region: testRegion }) - getSsoInstanceInfoStub = sinon.stub(SmusUtils, 'getSsoInstanceInfo').resolves(testSsoInstanceInfo) - isInSmusSpaceEnvironmentStub = sinon.stub(SmusUtils, 'isInSmusSpaceEnvironment').returns(false) - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand').resolves() - sinon.stub(secondaryAuthModule, 'getSecondaryAuth').returns(mockSecondaryAuth) - - smusAuthProvider = new SmusAuthenticationProvider(mockAuth) - - // Reset the executeCommand stub for clean state - executeCommandStub.resetHistory() - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should initialize with auth and secondary auth', function () { - assert.strictEqual(smusAuthProvider.auth, mockAuth) - assert.strictEqual(smusAuthProvider.secondaryAuth, mockSecondaryAuth) - }) - - it('should register event listeners', function () { - assert.ok(mockSecondaryAuth.onDidChangeActiveConnection.called) - }) - - it('should set initial context', async function () { - // Context should be set during construction (async call) - // Wait a bit for the async call to complete - await new Promise((resolve) => setTimeout(resolve, 0)) - assert.ok(setContextStubGlobal.called) - }) - }) - - describe('activeConnection', function () { - it('should return secondary auth active connection', function () { - assert.strictEqual(smusAuthProvider.activeConnection, mockSmusConnection) - }) - }) - - describe('isUsingSavedConnection', function () { - it('should return secondary auth hasSavedConnection value', function () { - mockSecondaryAuthState.hasSavedConnection = true - assert.strictEqual(smusAuthProvider.isUsingSavedConnection, true) - - mockSecondaryAuthState.hasSavedConnection = false - assert.strictEqual(smusAuthProvider.isUsingSavedConnection, false) - }) - }) - - describe('isConnectionValid', function () { - it('should return true when connection exists and is not expired', function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - mockSecondaryAuthState.isConnectionExpired = false - - assert.strictEqual(smusAuthProvider.isConnectionValid(), true) - }) - - it('should return false when no connection exists', function () { - mockSecondaryAuthState.activeConnection = undefined - - assert.strictEqual(smusAuthProvider.isConnectionValid(), false) - }) - - it('should return false when connection is expired', function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - mockSecondaryAuthState.isConnectionExpired = true - - assert.strictEqual(smusAuthProvider.isConnectionValid(), false) - }) - }) - - describe('isConnected', function () { - it('should return true when active connection exists', function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - assert.strictEqual(smusAuthProvider.isConnected(), true) - }) - - it('should return false when no active connection', function () { - mockSecondaryAuthState.activeConnection = undefined - assert.strictEqual(smusAuthProvider.isConnected(), false) - }) - }) - - describe('restore', function () { - let mockState: any - let loadSharedCredentialsProfilesStub: sinon.SinonStub - let validateIamProfileStub: sinon.SinonStub - beforeEach(function () { - mockState = { - get: sinon.stub(), - update: sinon.stub().resolves(), - } - mockSecondaryAuth.state = mockState - - loadSharedCredentialsProfilesStub = sinon.stub(sharedCredentialsModule, 'loadSharedCredentialsProfiles') - validateIamProfileStub = sinon.stub(smusAuthProvider, 'validateIamProfile') - }) - - it('should call secondary auth restoreConnection when no saved connection ID', async function () { - mockState.get.withArgs('smus.savedConnectionId').returns(undefined) - - await smusAuthProvider.restore() - - assert.ok(mockSecondaryAuth.restoreConnection.called) - assert.ok(loadSharedCredentialsProfilesStub.notCalled) - }) - - it('should validate IAM profile and restore connection', async function () { - const savedConnectionId = 'test-connection-id' - const connectionMetadata = { - profileName: 'test-profile', - domainId: 'old-domain-id', - region: 'us-west-1', - } - const smusConnections = { [savedConnectionId]: connectionMetadata } - - mockState.get.withArgs('smus.savedConnectionId').returns(savedConnectionId) - mockState.get.withArgs('smus.connections').returns(smusConnections) - loadSharedCredentialsProfilesStub.resolves({ 'test-profile': { region: 'us-east-1' } }) - validateIamProfileStub.resolves({ isValid: true }) - - await smusAuthProvider.restore() - - assert.ok(validateIamProfileStub.calledWith('test-profile')) - assert.ok(mockSecondaryAuth.restoreConnection.called) - }) - }) - - describe('connectToSmusWithSso', function () { - it('should create new connection when none exists', async function () { - mockAuth.listConnections.resolves([]) - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(extractDomainInfoStub.calledWith(testDomainUrl)) - assert.ok(getSsoInstanceInfoStub.calledWith(testDomainUrl)) - assert.ok(mockAuth.createConnection.called) - assert.ok(mockSecondaryAuth.useNewConnection.called) - assert.ok(executeCommandStub.calledWith('aws.smus.switchProject')) - }) - - it('should reuse existing valid connection', async function () { - const existingConnection = { ...mockSmusConnection, domainUrl: testDomainUrl.toLowerCase() } - mockAuth.listConnections.resolves([existingConnection]) - mockAuth.getConnectionState.returns('valid') - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(mockAuth.createConnection.notCalled) - assert.ok(mockSecondaryAuth.useNewConnection.calledWith(existingConnection)) - assert.ok(executeCommandStub.calledWith('aws.smus.switchProject')) - }) - - it('should reauthenticate existing invalid connection', async function () { - const existingConnection = { ...mockSmusConnection, domainUrl: testDomainUrl.toLowerCase() } - mockAuth.listConnections.resolves([existingConnection]) - mockAuth.getConnectionState.returns('invalid') - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(mockAuth.reauthenticate.calledWith(existingConnection)) - assert.ok(mockSecondaryAuth.useNewConnection.called) - assert.ok(executeCommandStub.calledWith('aws.smus.switchProject')) - }) - - it('should throw error for invalid domain URL', async function () { - extractDomainInfoStub.returns({ domainId: undefined, region: testRegion }) - - await assert.rejects( - () => smusAuthProvider.connectToSmusWithSso('invalid-url'), - (err: ToolkitError) => { - // The error is wrapped with FailedToConnect, but the original error should be in the cause - return err.code === 'FailedToConnect' && (err.cause as any)?.code === 'InvalidDomainUrl' - } - ) - // Should not trigger project selection on error - assert.ok(executeCommandStub.notCalled) - }) - - it('should handle SmusUtils errors', async function () { - const error = new Error('SmusUtils error') - getSsoInstanceInfoStub.rejects(error) - - await assert.rejects( - () => smusAuthProvider.connectToSmusWithSso(testDomainUrl), - (err: ToolkitError) => err.code === 'FailedToConnect' - ) - // Should not trigger project selection on error - assert.ok(executeCommandStub.notCalled) - }) - - it('should handle auth creation errors', async function () { - const error = new Error('Auth creation failed') - mockAuth.createConnection.rejects(error) - - await assert.rejects( - () => smusAuthProvider.connectToSmusWithSso(testDomainUrl), - (err: ToolkitError) => err.code === 'FailedToConnect' - ) - // Should not trigger project selection on error - assert.ok(executeCommandStub.notCalled) - }) - - it('should not trigger project selection in SMUS space environment', async function () { - isInSmusSpaceEnvironmentStub.returns(true) - mockAuth.listConnections.resolves([]) - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(mockAuth.createConnection.called) - assert.ok(mockSecondaryAuth.useNewConnection.called) - assert.ok(executeCommandStub.notCalled) - }) - - it('should not trigger project selection when reusing connection in SMUS space environment', async function () { - isInSmusSpaceEnvironmentStub.returns(true) - const existingConnection = { ...mockSmusConnection, domainUrl: testDomainUrl.toLowerCase() } - mockAuth.listConnections.resolves([existingConnection]) - mockAuth.getConnectionState.returns('valid') - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(mockSecondaryAuth.useNewConnection.calledWith(existingConnection)) - assert.ok(executeCommandStub.notCalled) - }) - - it('should not trigger project selection when reauthenticating in SMUS space environment', async function () { - isInSmusSpaceEnvironmentStub.returns(true) - const existingConnection = { ...mockSmusConnection, domainUrl: testDomainUrl.toLowerCase() } - mockAuth.listConnections.resolves([existingConnection]) - mockAuth.getConnectionState.returns('invalid') - - const result = await smusAuthProvider.connectToSmusWithSso(testDomainUrl) - - assert.strictEqual(result, mockSmusConnection) - assert.ok(mockAuth.reauthenticate.calledWith(existingConnection)) - assert.ok(mockSecondaryAuth.useNewConnection.called) - assert.ok(executeCommandStub.notCalled) - }) - }) - - describe('reauthenticate', function () { - it('should call auth reauthenticate for SSO connection', async function () { - const result = await smusAuthProvider.reauthenticate(mockSmusConnection) - - // Verify the result has the correct SMUS properties preserved - assert.strictEqual(result.id, mockSmusConnection.id) - assert.strictEqual(result.domainUrl, mockSmusConnection.domainUrl) - assert.strictEqual(result.domainId, mockSmusConnection.domainId) - assert.strictEqual(result.type, mockSmusConnection.type) - assert.strictEqual(result.startUrl, mockSmusConnection.startUrl) - assert.strictEqual(result.label, mockSmusConnection.label) - assert.ok(mockAuth.reauthenticate.calledWith(mockSmusConnection)) - }) - - it('should wrap auth errors in ToolkitError', async function () { - const error = new Error('Reauthentication failed') - mockAuth.reauthenticate.rejects(error) - - await assert.rejects( - () => smusAuthProvider.reauthenticate(mockSmusConnection), - (err: ToolkitError) => err.message.includes('Unable to reauthenticate') - ) - }) - }) - - describe('showReauthenticationPrompt', function () { - it('should show reauthentication message', async function () { - const showReauthenticateMessageStub = sinon.stub(messages, 'showReauthenticateMessage').resolves() - - await smusAuthProvider.showReauthenticationPrompt(mockSmusConnection) - - assert.ok(showReauthenticateMessageStub.called) - const callArgs = showReauthenticateMessageStub.firstCall.args[0] - assert.ok(callArgs.message.includes('SageMaker Unified Studio')) - assert.strictEqual(callArgs.suppressId, 'smusConnectionExpired') - }) - }) - - describe('getAccessToken', function () { - beforeEach(function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - mockAuth.getSsoAccessToken = sinon.stub().resolves('mock-access-token') - mockAuth.invalidateConnection = sinon.stub() - }) - - it('should return access token when successful', async function () { - const token = await smusAuthProvider.getAccessToken() - - assert.strictEqual(token, 'mock-access-token') - assert.ok(mockAuth.getSsoAccessToken.calledWith(mockSmusConnection)) - }) - - it('should throw error when no active connection', async function () { - mockSecondaryAuthState.activeConnection = undefined - - await assert.rejects( - () => smusAuthProvider.getAccessToken(), - (err: ToolkitError) => err.code === 'NoActiveConnection' - ) - }) - - it('should handle InvalidGrantException and mark connection for reauthentication', async function () { - const invalidGrantError = new Error('UnknownError') - invalidGrantError.name = 'InvalidGrantException' - mockAuth.getSsoAccessToken.rejects(invalidGrantError) - - await assert.rejects( - () => smusAuthProvider.getAccessToken(), - (err: ToolkitError) => { - return ( - err.code === 'RedeemAccessTokenFailed' && - err.message.includes('Failed to retrieve SSO access token for connection') - ) - } - ) - - // Verify connection was NOT invalidated (current implementation doesn't handle InvalidGrantException specially) - assert.ok(mockAuth.invalidateConnection.notCalled) - }) - - it('should handle other errors normally', async function () { - const genericError = new Error('Network error') - mockAuth.getSsoAccessToken.rejects(genericError) - - await assert.rejects( - () => smusAuthProvider.getAccessToken(), - (err: ToolkitError) => - err.message.includes('Failed to retrieve SSO access token for connection') && - err.code === 'RedeemAccessTokenFailed' - ) - - // Verify connection was NOT invalidated for generic errors - assert.ok(mockAuth.invalidateConnection.notCalled) - }) - }) - - describe('fromContext', function () { - it('should return singleton instance', function () { - const instance1 = SmusAuthenticationProvider.fromContext() - const instance2 = SmusAuthenticationProvider.fromContext() - - assert.strictEqual(instance1, instance2) - }) - - it('should return instance property', function () { - const instance = SmusAuthenticationProvider.fromContext() - assert.strictEqual(SmusAuthenticationProvider.instance, instance) - }) - }) - - describe('getDomainAccountId', function () { - let getContextStub: sinon.SinonStub - let getResourceMetadataStub: sinon.SinonStub - let getDerCredentialsProviderStub: sinon.SinonStub - let getDomainRegionStub: sinon.SinonStub - let mockStsClient: any - let mockCredentialsProvider: any - - beforeEach(function () { - // Mock dependencies - getContextStub = sinon.stub(vscodeSetContext, 'getContext') - getResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'getResourceMetadata') - - // Mock STS client - mockStsClient = { - getCallerIdentity: sinon.stub(), - } - sinon - .stub(DefaultStsClient.prototype, 'getCallerIdentity') - .callsFake(() => mockStsClient.getCallerIdentity()) - - // Mock credentials provider - mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - // Stub methods on the provider instance - getDerCredentialsProviderStub = sinon - .stub(smusAuthProvider, 'getDerCredentialsProvider') - .resolves(mockCredentialsProvider) - getDomainRegionStub = sinon.stub(smusAuthProvider, 'getDomainRegion').returns('us-east-1') - - // Reset cached value - smusAuthProvider['cachedDomainAccountId'] = undefined - }) - - afterEach(function () { - sinon.restore() - }) - - describe('when cached value exists', function () { - it('should return cached account ID without making any calls', async function () { - const cachedAccountId = '123456789012' - smusAuthProvider['cachedDomainAccountId'] = cachedAccountId - - const result = await smusAuthProvider.getDomainAccountId() - - assert.strictEqual(result, cachedAccountId) - assert.ok(getContextStub.notCalled) - assert.ok(getResourceMetadataStub.notCalled) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - }) - - describe('in SMUS space environment', function () { - let extractAccountIdFromResourceMetadataStub: sinon.SinonStub - - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(true) - extractAccountIdFromResourceMetadataStub = sinon - .stub(smusUtils, 'extractAccountIdFromResourceMetadata') - .resolves('123456789012') - }) - - it('should extract account from resource metadata and cache result', async function () { - const testAccountId = '123456789012' - - const result = await smusAuthProvider.getDomainAccountId() - - assert.strictEqual(result, testAccountId) - assert.strictEqual(smusAuthProvider['cachedDomainAccountId'], testAccountId) - assert.ok(extractAccountIdFromResourceMetadataStub.called) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should throw error when extractAccountIdFromResourceMetadata fails', async function () { - extractAccountIdFromResourceMetadataStub.rejects(new ToolkitError('Metadata extraction failed')) - - await assert.rejects( - () => smusAuthProvider.getDomainAccountId(), - (err: ToolkitError) => err.message.includes('Metadata extraction failed') - ) - - assert.strictEqual(smusAuthProvider['cachedDomainAccountId'], undefined) - }) - }) - - describe('in non-SMUS space environment', function () { - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(false) - mockSecondaryAuthState.activeConnection = mockSmusConnection - }) - - it('should use STS GetCallerIdentity to get account ID and cache it', async function () { - const testAccountId = '123456789012' - mockStsClient.getCallerIdentity.resolves({ - Account: testAccountId, - UserId: 'test-user-id', - Arn: 'arn:aws:sts::123456789012:assumed-role/test-role/test-session', - }) - - const result = await smusAuthProvider.getDomainAccountId() - - assert.strictEqual(result, testAccountId) - assert.strictEqual(smusAuthProvider['cachedDomainAccountId'], testAccountId) - assert.ok(getDerCredentialsProviderStub.called) - assert.ok(getDomainRegionStub.called) - assert.ok(mockCredentialsProvider.getCredentials.called) - assert.ok(mockStsClient.getCallerIdentity.called) - }) - - it('should throw error when no active connection exists', async function () { - mockSecondaryAuthState.activeConnection = undefined - - await assert.rejects( - () => smusAuthProvider.getDomainAccountId(), - (err: ToolkitError) => { - return ( - err.code === 'NoActiveConnection' && - err.message.includes('No active SMUS connection available') - ) - } - ) - - assert.strictEqual(smusAuthProvider['cachedDomainAccountId'], undefined) - assert.ok(getDerCredentialsProviderStub.notCalled) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should throw error when STS GetCallerIdentity fails', async function () { - mockStsClient.getCallerIdentity.rejects(new Error('STS call failed')) - - await assert.rejects( - () => smusAuthProvider.getDomainAccountId(), - (err: ToolkitError) => { - return ( - err.code === 'GetDomainAccountIdFailed' && - err.message.includes('Failed to retrieve AWS account ID for active domain connection') - ) - } - ) - - assert.strictEqual(smusAuthProvider['cachedDomainAccountId'], undefined) - }) - }) - }) - - describe('getProjectAccountId', function () { - let getContextStub: sinon.SinonStub - let extractAccountIdFromResourceMetadataStub: sinon.SinonStub - let getProjectCredentialProviderStub: sinon.SinonStub - let mockProjectCredentialsProvider: any - let mockStsClient: any - let mockDataZoneClientForProject: any - - const testProjectId = 'test-project-id' - const testAccountId = '123456789012' - const testRegion = 'us-east-1' - - beforeEach(function () { - // Mock dependencies - getContextStub = sinon.stub(vscodeSetContext, 'getContext') - extractAccountIdFromResourceMetadataStub = sinon - .stub(smusUtils, 'extractAccountIdFromResourceMetadata') - .resolves(testAccountId) - - // Mock project credentials provider - mockProjectCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - getProjectCredentialProviderStub = sinon - .stub(smusAuthProvider, 'getProjectCredentialProvider') - .resolves(mockProjectCredentialsProvider) - - // Update the existing mockDataZoneClient to include getToolingEnvironment - mockDataZoneClientForProject = { - getToolingEnvironment: sinon.stub().resolves({ - awsAccountRegion: testRegion, - projectId: testProjectId, - domainId: testDomainId, - createdBy: 'test-user', - name: 'test-environment', - id: 'test-env-id', - status: 'ACTIVE', - }), - } - // Update the existing mockDataZoneClient instead of creating a new stub - Object.assign(mockDataZoneClient, mockDataZoneClientForProject) - - // Mock STS client - mockStsClient = { - getCallerIdentity: sinon.stub().resolves({ - Account: testAccountId, - UserId: 'test-user-id', - Arn: 'arn:aws:sts::123456789012:assumed-role/test-role/test-session', - }), - } - - // Clear cache - smusAuthProvider['cachedProjectAccountIds'].clear() - mockSecondaryAuthState.activeConnection = mockSmusConnection - }) - - afterEach(function () { - sinon.restore() - }) - - describe('when cached value exists', function () { - it('should return cached project account ID without making any calls', async function () { - smusAuthProvider['cachedProjectAccountIds'].set(testProjectId, testAccountId) - - const result = await smusAuthProvider.getProjectAccountId(testProjectId) - - assert.strictEqual(result, testAccountId) - assert.ok(getContextStub.notCalled) - assert.ok(extractAccountIdFromResourceMetadataStub.notCalled) - assert.ok(getProjectCredentialProviderStub.notCalled) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - }) - - describe('in SMUS space environment', function () { - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(true) - }) - - it('should extract account ID from resource metadata and cache it', async function () { - const result = await smusAuthProvider.getProjectAccountId(testProjectId) - - assert.strictEqual(result, testAccountId) - assert.strictEqual(smusAuthProvider['cachedProjectAccountIds'].get(testProjectId), testAccountId) - assert.ok(extractAccountIdFromResourceMetadataStub.called) - assert.ok(getProjectCredentialProviderStub.notCalled) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should throw error when extractAccountIdFromResourceMetadata fails', async function () { - extractAccountIdFromResourceMetadataStub.rejects(new ToolkitError('Metadata extraction failed')) - - await assert.rejects( - () => smusAuthProvider.getProjectAccountId(testProjectId), - (err: ToolkitError) => err.message.includes('Metadata extraction failed') - ) - - assert.ok(!smusAuthProvider['cachedProjectAccountIds'].has(testProjectId)) - }) - }) - - describe('in non-SMUS space environment', function () { - let stsConstructorStub: sinon.SinonStub - - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(false) - // Stub the DefaultStsClient constructor to return our mock instance - // stsClientModule imported at top - stsConstructorStub = sinon.stub(stsClientModule, 'DefaultStsClient').callsFake(() => mockStsClient) - }) - - afterEach(function () { - if (stsConstructorStub) { - stsConstructorStub.restore() - } - }) - - it('should use project credentials with STS to get account ID and cache it', async function () { - const result = await smusAuthProvider.getProjectAccountId(testProjectId) - - assert.strictEqual(result, testAccountId) - assert.strictEqual(smusAuthProvider['cachedProjectAccountIds'].get(testProjectId), testAccountId) - assert.ok(getProjectCredentialProviderStub.calledWith(testProjectId)) - assert.ok(mockProjectCredentialsProvider.getCredentials.called) - assert.ok((DataZoneClient.createWithCredentials as sinon.SinonStub).called) - assert.ok(mockDataZoneClientForProject.getToolingEnvironment.calledWith(testProjectId)) - assert.ok(mockStsClient.getCallerIdentity.called) - }) - - it('should throw error when no active connection exists', async function () { - mockSecondaryAuthState.activeConnection = undefined - - await assert.rejects( - () => smusAuthProvider.getProjectAccountId(testProjectId), - (err: ToolkitError) => { - return ( - err.code === 'NoActiveConnection' && - err.message.includes('No active SMUS connection available') - ) - } - ) - - assert.ok(!smusAuthProvider['cachedProjectAccountIds'].has(testProjectId)) - }) - - it('should throw error when tooling environment has no region', async function () { - mockDataZoneClientForProject.getToolingEnvironment.resolves({ - id: 'env-123', - awsAccountRegion: undefined, - projectId: undefined, - domainId: undefined, - createdBy: undefined, - name: undefined, - provider: undefined, - $metadata: {}, - }) - - await assert.rejects( - () => smusAuthProvider.getProjectAccountId(testProjectId), - (err: ToolkitError) => { - return ( - err.message.includes('Failed to get project account ID') && - err.message.includes('No AWS account region found in tooling environment') - ) - } - ) - - assert.ok(!smusAuthProvider['cachedProjectAccountIds'].has(testProjectId)) - }) - - it('should throw error when STS GetCallerIdentity fails', async function () { - mockStsClient.getCallerIdentity.rejects(new Error('STS call failed')) - - await assert.rejects( - () => smusAuthProvider.getProjectAccountId(testProjectId), - (err: ToolkitError) => { - return ( - err.message.includes('Failed to get project account ID') && - err.message.includes('STS call failed') - ) - } - ) - - assert.ok(!smusAuthProvider['cachedProjectAccountIds'].has(testProjectId)) - }) - }) - }) - - describe('signOut', function () { - let mockState: any - - beforeEach(function () { - mockState = { - get: sinon.stub(), - update: sinon.stub().resolves(), - } - mockSecondaryAuth.state = mockState - mockSecondaryAuth.forgetConnection = sinon.stub().resolves() - }) - - it('should do nothing when no active connection exists', async function () { - mockSecondaryAuthState.activeConnection = undefined - - await smusAuthProvider.signOut() - - assert.ok(mockState.get.notCalled) - assert.ok(mockState.update.notCalled) - assert.ok(mockSecondaryAuth.deleteConnection.notCalled) - assert.ok(mockSecondaryAuth.forgetConnection.notCalled) - }) - - it('should delete SSO connection and clear metadata', async function () { - const ssoConnection = { - ...mockSmusConnection, - type: 'sso' as const, - id: 'sso-connection-id', - } - mockSecondaryAuthState.activeConnection = ssoConnection - - const smusConnections = { - 'sso-connection-id': { - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockState.get.withArgs('smus.connections').returns(smusConnections) - - await smusAuthProvider.signOut() - - assert.ok(mockState.get.calledWith('smus.connections')) - assert.ok(mockState.update.calledWith('smus.connections', {})) - assert.ok(mockSecondaryAuth.deleteConnection.called) - assert.ok(mockSecondaryAuth.forgetConnection.notCalled) - }) - - it('should forget IAM connection without deleting and clear metadata', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - const smusConnections = { - 'profile:test-profile': { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockState.get.withArgs('smus.connections').returns(smusConnections) - - await smusAuthProvider.signOut() - - assert.ok(mockState.get.calledWith('smus.connections')) - assert.ok(mockState.update.calledWith('smus.connections', {})) - assert.ok(mockSecondaryAuth.forgetConnection.called) - assert.ok(mockSecondaryAuth.deleteConnection.notCalled) - }) - - it('should handle mock connection in SMUS space environment', async function () { - const mockConnection = { - id: 'mock-connection-id', - // No 'type' property - simulates mock connection - } - mockSecondaryAuthState.activeConnection = mockConnection as any - - const smusConnections = { - 'mock-connection-id': { - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockState.get.withArgs('smus.connections').returns(smusConnections) - - await smusAuthProvider.signOut() - - assert.ok(mockState.get.calledWith('smus.connections')) - assert.ok(mockState.update.calledWith('smus.connections', {})) - assert.ok(mockSecondaryAuth.deleteConnection.notCalled) - assert.ok(mockSecondaryAuth.forgetConnection.notCalled) - }) - - it('should handle missing metadata gracefully', async function () { - const ssoConnection = { - ...mockSmusConnection, - type: 'sso' as const, - id: 'sso-connection-id', - } - mockSecondaryAuthState.activeConnection = ssoConnection - - mockState.get.withArgs('smus.connections').returns({}) - - await smusAuthProvider.signOut() - - assert.ok(mockState.get.calledWith('smus.connections')) - // When there's no metadata to delete, update should not be called - assert.ok(mockState.update.notCalled) - assert.ok(mockSecondaryAuth.deleteConnection.called) - }) - - it('should throw ToolkitError when deleteConnection fails', async function () { - const ssoConnection = { - ...mockSmusConnection, - type: 'sso' as const, - id: 'sso-connection-id', - } - mockSecondaryAuthState.activeConnection = ssoConnection - - mockState.get.withArgs('smus.connections').returns({}) - mockSecondaryAuth.deleteConnection.rejects(new Error('Delete failed')) - - await assert.rejects( - () => smusAuthProvider.signOut(), - (err: ToolkitError) => { - return ( - err.code === 'SignOutFailed' && - err.message.includes('Failed to sign out from SageMaker Unified Studio') - ) - } - ) - }) - - it('should throw ToolkitError when forgetConnection fails', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - mockState.get.withArgs('smus.connections').returns({}) - mockSecondaryAuth.forgetConnection.rejects(new Error('Forget failed')) - - await assert.rejects( - () => smusAuthProvider.signOut(), - (err: ToolkitError) => { - return ( - err.code === 'SignOutFailed' && - err.message.includes('Failed to sign out from SageMaker Unified Studio') - ) - } - ) - }) - }) - - describe('connectWithIamProfile', function () { - let mockState: any - const testProfileName = 'test-profile' - const testIamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - - beforeEach(function () { - mockState = { - get: sinon.stub(), - update: sinon.stub().resolves(), - } - mockSecondaryAuth.state = mockState - mockAuth.getConnection = sinon.stub() - mockAuth.refreshConnectionState = sinon.stub().resolves() - }) - - it('should connect with existing IAM profile and store metadata', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(testIamConnection) - mockState.get.withArgs('smus.connections').returns({}) - - const result = await smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl) - - assert.strictEqual(result.id, testIamConnection.id) - assert.strictEqual(result.type, 'iam') - assert.strictEqual(result.profileName, testProfileName) - assert.strictEqual(result.region, testRegion) - assert.strictEqual(result.domainUrl, testDomainUrl) - assert.strictEqual(result.domainId, testDomainId) - - assert.ok(mockAuth.getConnection.calledWith({ id: `profile:${testProfileName}` })) - assert.ok(mockSecondaryAuth.useNewConnection.calledWith(testIamConnection)) - assert.ok(mockAuth.refreshConnectionState.calledWith(testIamConnection)) - assert.ok( - mockState.update.calledWith('smus.connections', { - [testIamConnection.id]: { - profileName: testProfileName, - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - isIamDomain: false, - }, - }) - ) - }) - - it('should merge with existing SMUS connections metadata', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(testIamConnection) - - const existingConnections = { - 'other-connection-id': { - domainUrl: 'https://other-domain.sagemaker.us-west-2.on.aws', - domainId: 'other-domain-id', - }, - } - mockState.get.withArgs('smus.connections').returns(existingConnections) - - await smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl) - - assert.ok( - mockState.update.calledWith('smus.connections', { - 'other-connection-id': existingConnections['other-connection-id'], - [testIamConnection.id]: { - profileName: testProfileName, - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - isIamDomain: false, - }, - }) - ) - }) - - it('should throw error for invalid domain URL', async function () { - extractDomainInfoStub.returns({ domainId: undefined, region: testRegion }) - - await assert.rejects( - () => smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, 'invalid-url'), - (err: ToolkitError) => { - return ( - err.code === 'FailedToConnect' && - err.message.includes('Failed to connect to SageMaker Unified Studio with IAM profile') - ) - } - ) - - assert.ok(mockAuth.getConnection.notCalled) - assert.ok(mockSecondaryAuth.useNewConnection.notCalled) - }) - - it('should throw error when IAM connection not found', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(undefined) - - await assert.rejects( - () => smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl), - (err: ToolkitError) => { - return ( - err.code === 'FailedToConnect' && - err.message.includes('Failed to connect to SageMaker Unified Studio with IAM profile') && - (err.cause as any)?.code === 'ConnectionNotFound' - ) - } - ) - - assert.ok(mockSecondaryAuth.useNewConnection.notCalled) - }) - - it('should throw error when connection is not IAM type', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - const nonIamConnection = { - id: 'profile:test-profile', - type: 'sso' as const, - label: 'Test SSO Connection', - } - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(nonIamConnection) - - await assert.rejects( - () => smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl), - (err: ToolkitError) => { - return ( - err.code === 'FailedToConnect' && - err.message.includes('Failed to connect to SageMaker Unified Studio with IAM profile') - ) - } - ) - }) - - it('should handle useNewConnection failure', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(testIamConnection) - mockState.get.withArgs('smus.connections').returns({}) - mockSecondaryAuth.useNewConnection.rejects(new Error('Failed to use connection')) - - await assert.rejects( - () => smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl), - (err: ToolkitError) => { - return ( - err.code === 'FailedToConnect' && - err.message.includes('Failed to connect to SageMaker Unified Studio with IAM profile') - ) - } - ) - }) - - it('should handle refreshConnectionState failure', async function () { - extractDomainInfoStub.returns({ domainId: testDomainId, region: testRegion }) - mockAuth.getConnection.withArgs({ id: `profile:${testProfileName}` }).resolves(testIamConnection) - mockState.get.withArgs('smus.connections').returns({}) - mockAuth.refreshConnectionState.rejects(new Error('Failed to refresh state')) - - await assert.rejects( - () => smusAuthProvider.connectWithIamProfile(testProfileName, testRegion, testDomainUrl), - (err: ToolkitError) => { - return ( - err.code === 'FailedToConnect' && - err.message.includes('Failed to connect to SageMaker Unified Studio with IAM profile') - ) - } - ) - }) - }) - - describe('activeConnection with IAM metadata', function () { - let mockState: any - - beforeEach(function () { - mockState = { - get: sinon.stub(), - update: sinon.stub().resolves(), - } - mockSecondaryAuth.state = mockState - }) - - it('should return IAM connection with SMUS metadata when available', function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - const smusConnections = { - 'profile:test-profile': { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockState.get.withArgs('smus.connections').returns(smusConnections) - - const result = smusAuthProvider.activeConnection - - assert.strictEqual(result?.id, iamConnection.id) - assert.strictEqual((result as any)?.type, 'iam') - assert.strictEqual((result as any).profileName, 'test-profile') - assert.strictEqual((result as any).region, testRegion) - assert.strictEqual((result as any).domainUrl, testDomainUrl) - assert.strictEqual((result as any).domainId, testDomainId) - }) - - it('should return SSO connection with SMUS metadata when available', function () { - const ssoConnection = { - ...mockSmusConnection, - type: 'sso' as const, - } - mockSecondaryAuthState.activeConnection = ssoConnection - - const smusConnections = { - [ssoConnection.id]: { - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockState.get.withArgs('smus.connections').returns(smusConnections) - - const result = smusAuthProvider.activeConnection - - assert.strictEqual(result?.id, ssoConnection.id) - assert.strictEqual((result as any)?.type, 'sso') - assert.strictEqual((result as any)?.domainUrl, testDomainUrl) - assert.strictEqual((result as any)?.domainId, testDomainId) - }) - - it('should return base connection when no metadata available', function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - mockState.get.withArgs('smus.connections').returns({}) - - const result = smusAuthProvider.activeConnection - - assert.strictEqual(result?.id, iamConnection.id) - assert.strictEqual((result as any)?.type, 'iam') - assert.strictEqual((result as any).profileName, undefined) - assert.strictEqual((result as any).domainUrl, undefined) - }) - - it('should return undefined when no active connection', function () { - mockSecondaryAuthState.activeConnection = undefined - - const result = smusAuthProvider.activeConnection - - assert.strictEqual(result, undefined) - }) - - it('should handle missing smus.connections state gracefully', function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - mockState.get.withArgs('smus.connections').returns(undefined) - - const result = smusAuthProvider.activeConnection - - assert.strictEqual(result?.id, iamConnection.id) - assert.strictEqual((result as any)?.type, 'iam') - }) - }) - - describe('getDerCredentialsProvider', function () { - let getContextStub: sinon.SinonStub - - beforeEach(function () { - getContextStub = sinon.stub(vscodeSetContext, 'getContext') - - // Clear cache - smusAuthProvider['credentialsProviderCache'].clear() - }) - - describe('in SMUS space environment', function () { - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(true) - - // Mock resource metadata for SMUS space environment - getResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'getResourceMetadata').returns({ - ResourceArn: 'arn:aws:sagemaker:us-east-2:123456789012:app/dzd_domainId/test-app', - AdditionalMetadata: { - DataZoneDomainId: testDomainId, - DataZoneDomainRegion: testRegion, - }, - } as any) - }) - - afterEach(function () { - getResourceMetadataStub?.restore() - }) - - it('should return a credentials provider that can retrieve credentials', async function () { - // In SMUS space environment, the method should return a provider - // We can't easily test the internal branching logic without stubbing ES modules - // So we test that it returns a valid provider structure - const provider = await smusAuthProvider.getDerCredentialsProvider() - - assert.ok(provider, 'Provider should be returned') - assert.ok(typeof provider.getCredentials === 'function', 'Provider should have getCredentials method') - }) - - it('should not cache providers in SMUS space environment', async function () { - // Get provider twice - const provider1 = await smusAuthProvider.getDerCredentialsProvider() - const provider2 = await smusAuthProvider.getDerCredentialsProvider() - - // In SMUS space, providers are not cached (new provider each time) - // This is because the logic returns early before caching - assert.ok(provider1) - assert.ok(provider2) - }) - }) - - describe('in non-SMUS space environment', function () { - let getAccessTokenStub: sinon.SinonStub - - beforeEach(function () { - getContextStub.withArgs('aws.smus.inSmusSpaceEnvironment').returns(false) - mockSecondaryAuthState.activeConnection = mockSmusConnection - getAccessTokenStub = sinon.stub(smusAuthProvider, 'getAccessToken').resolves('mock-access-token') - }) - - it('should create and cache DomainExecRoleCredentialsProvider for SSO connection', async function () { - const provider = await smusAuthProvider.getDerCredentialsProvider() - - assert.ok(provider) - assert.ok(getAccessTokenStub.notCalled) // Not called until getCredentials is invoked - - // Verify caching - const cachedProvider = await smusAuthProvider.getDerCredentialsProvider() - assert.strictEqual(provider, cachedProvider) - }) - - it('should throw error when no active connection', async function () { - mockSecondaryAuthState.activeConnection = undefined - - await assert.rejects( - () => smusAuthProvider.getDerCredentialsProvider(), - (err: ToolkitError) => { - return ( - err.code === 'NoActiveConnection' && - err.message.includes('No active SMUS connection available') - ) - } - ) - }) - - it('should throw error for non-SSO connection', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - await assert.rejects( - () => smusAuthProvider.getDerCredentialsProvider(), - (err: ToolkitError) => { - return ( - err.code === 'InvalidConnectionType' && - err.message.includes( - 'Domain Execution Role credentials are only available for SSO connections' - ) - ) - } - ) - }) - - it('should use cached provider for same connection', async function () { - const provider1 = await smusAuthProvider.getDerCredentialsProvider() - const provider2 = await smusAuthProvider.getDerCredentialsProvider() - - assert.strictEqual(provider1, provider2) - }) - - it('should create different providers for different connections', async function () { - const provider1 = await smusAuthProvider.getDerCredentialsProvider() - - // Change connection - const differentConnection = { - ...mockSmusConnection, - id: 'different-connection-id', - domainId: 'different-domain-id', - } - mockSecondaryAuthState.activeConnection = differentConnection - - const provider2 = await smusAuthProvider.getDerCredentialsProvider() - - assert.notStrictEqual(provider1, provider2) - }) - }) - }) - - describe('initIamModeContextInSpaceEnvironment', function () { - let getResourceMetadataStub: sinon.SinonStub - let getDerCredentialsProviderStub: sinon.SinonStub - let getInstanceStub: sinon.SinonStub - let mockCredentialsProvider: any - let mockClientHelper: any - - const testResourceMetadata = { - AdditionalMetadata: { - DataZoneDomainId: 'test-domain-id', - DataZoneDomainRegion: 'us-east-1', - DataZoneProjectId: 'test-project-id', - }, - } - - beforeEach(function () { - getResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'getResourceMetadata') - - // Reset the global setContext stub history for clean test state - setContextStubGlobal.resetHistory() - - mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - getDerCredentialsProviderStub = sinon - .stub(smusAuthProvider, 'getDerCredentialsProvider') - .resolves(mockCredentialsProvider) - - // Mock DataZoneCustomClientHelper - const getDomainStub = sinon.stub() - mockClientHelper = { - getDomain: getDomainStub, - } - - getInstanceStub = sinon.stub(DataZoneCustomClientHelper, 'getInstance').returns(mockClientHelper) - - // Setup getDomain to return domain details - getDomainStub.resolves({ - id: testResourceMetadata.AdditionalMetadata.DataZoneDomainId, - domainVersion: 'V2', - iamSignIns: ['IAM_ROLE', 'IAM_USER'], - }) - }) - - afterEach(function () { - sinon.restore() - }) - - it('should set IAM mode context to true when domain is IAM mode', async function () { - getResourceMetadataStub.returns(testResourceMetadata) - - await smusAuthProvider['initIamModeContextInSpaceEnvironment']() - - assert.ok(getResourceMetadataStub.called) - assert.ok(getDerCredentialsProviderStub.called) - assert.ok( - getInstanceStub.calledWith( - mockCredentialsProvider, - testResourceMetadata.AdditionalMetadata.DataZoneDomainRegion - ) - ) - assert.ok(setContextStubGlobal.calledWith('aws.smus.isIamMode', true)) - }) - - it('should set IAM mode context to false when domain is not IAM mode', async function () { - getResourceMetadataStub.returns(testResourceMetadata) - - // Override getDomain to return a non-IAM domain - mockClientHelper.getDomain = sinon.stub().resolves({ - id: testResourceMetadata.AdditionalMetadata.DataZoneDomainId, - domainVersion: 'V2', - }) - - await smusAuthProvider['initIamModeContextInSpaceEnvironment']() - - assert.ok(getResourceMetadataStub.called) - assert.ok(getDerCredentialsProviderStub.called) - assert.ok( - getInstanceStub.calledWith( - mockCredentialsProvider, - testResourceMetadata.AdditionalMetadata.DataZoneDomainRegion - ) - ) - assert.ok(setContextStubGlobal.calledWith('aws.smus.isIamMode', false)) - }) - - it('should not call IAM mode check when resource metadata is missing', async function () { - getResourceMetadataStub.returns(undefined) - - await smusAuthProvider['initIamModeContextInSpaceEnvironment']() - - assert.ok(getResourceMetadataStub.called) - assert.ok(getDerCredentialsProviderStub.notCalled) - assert.ok(getInstanceStub.notCalled) - assert.ok(setContextStubGlobal.notCalled) - }) - - it('should handle error when getDerCredentialsProvider fails', async function () { - getResourceMetadataStub.returns(testResourceMetadata) - const testError = new Error('Failed to get credentials provider') - getDerCredentialsProviderStub.rejects(testError) - - await smusAuthProvider['initIamModeContextInSpaceEnvironment']() - - assert.ok(getResourceMetadataStub.called) - assert.ok(getDerCredentialsProviderStub.called) - assert.ok(getInstanceStub.notCalled) - assert.ok(setContextStubGlobal.calledWith('aws.smus.isIamMode', false)) - }) - }) - - describe('getSessionName', function () { - let mockStsClient: any - let mockCredentialsProvider: any - - beforeEach(function () { - // Mock STS client - mockStsClient = { - getCallerIdentity: sinon.stub(), - } - sinon - .stub(DefaultStsClient.prototype, 'getCallerIdentity') - .callsFake(() => mockStsClient.getCallerIdentity()) - - // Mock credentials provider - mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-access-key', - secretAccessKey: 'test-secret-key', - sessionToken: 'test-session-token', - }), - } - - sinon - .stub(smusAuthProvider as any, 'getCredentialsForIamProfile') - .resolves(mockCredentialsProvider.getCredentials()) - }) - - afterEach(function () { - sinon.restore() - }) - - it('should return session name for IAM connection with assumed role', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - // Mock STS response with assumed role ARN - const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name' - mockStsClient.getCallerIdentity.resolves({ - Arn: assumedRoleArn, - Account: '123456789012', - UserId: 'AIDAI1234567890EXAMPLE:my-session-name', - }) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, 'my-session-name') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) - }) - - it('should return undefined for IAM connection without assumed role (IAM user)', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - // Mock STS response with IAM user ARN (no session name) - const iamUserArn = 'arn:aws:iam::123456789012:user/my-user' - mockStsClient.getCallerIdentity.resolves({ - Arn: iamUserArn, - Account: '123456789012', - UserId: 'AIDAI1234567890EXAMPLE', - }) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, undefined) - assert.ok(mockStsClient.getCallerIdentity.calledOnce) - }) - - it('should return undefined for SSO connection', async function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, undefined) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should return undefined when not connected', async function () { - mockSecondaryAuthState.activeConnection = undefined - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, undefined) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should cache and reuse caller identity ARN', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name' - mockStsClient.getCallerIdentity.resolves({ - Arn: assumedRoleArn, - Account: '123456789012', - UserId: 'AIDAI1234567890EXAMPLE:my-session-name', - }) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - // First call - should fetch from STS - const sessionName1 = await smusAuthProvider.getSessionName() - assert.strictEqual(sessionName1, 'my-session-name') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) - - // Second call - should use cached value - const sessionName2 = await smusAuthProvider.getSessionName() - assert.strictEqual(sessionName2, 'my-session-name') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) // Still only called once - }) - - it('should handle STS errors gracefully', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - mockStsClient.getCallerIdentity.rejects(new Error('STS call failed')) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, undefined) - }) - - it('should return undefined when connection metadata is missing', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - // No connection metadata - mockSecondaryAuth.state.get.withArgs('smus.connections').returns({}) - - const sessionName = await smusAuthProvider.getSessionName() - - assert.strictEqual(sessionName, undefined) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - }) - - describe('getRoleArn', function () { - let mockStsClient: any - let mockCredentialsProvider: any - - beforeEach(function () { - // Mock STS client - mockStsClient = { - getCallerIdentity: sinon.stub(), - } - sinon - .stub(DefaultStsClient.prototype, 'getCallerIdentity') - .callsFake(() => mockStsClient.getCallerIdentity()) - - // Mock credentials provider - mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-access-key', - secretAccessKey: 'test-secret-key', - sessionToken: 'test-session-token', - }), - } - - sinon - .stub(smusAuthProvider as any, 'getCredentialsForIamProfile') - .resolves(mockCredentialsProvider.getCredentials()) - }) - - afterEach(function () { - sinon.restore() - }) - - it('should return IAM role ARN for IAM connection with assumed role', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - // Mock STS response with assumed role ARN - const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name' - mockStsClient.getCallerIdentity.resolves({ - Arn: assumedRoleArn, - Account: '123456789012', - UserId: 'AIDAI1234567890EXAMPLE:my-session-name', - }) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - const roleArn = await smusAuthProvider.getIamPrincipalArn() - - // Should convert assumed role ARN to IAM role ARN - assert.strictEqual(roleArn, 'arn:aws:iam::123456789012:role/MyRole') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) - }) - - it('should return undefined for SSO connection', async function () { - mockSecondaryAuthState.activeConnection = mockSmusConnection - - const roleArn = await smusAuthProvider.getIamPrincipalArn() - - assert.strictEqual(roleArn, undefined) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should return undefined when not connected', async function () { - mockSecondaryAuthState.activeConnection = undefined - - const roleArn = await smusAuthProvider.getIamPrincipalArn() - - assert.strictEqual(roleArn, undefined) - assert.ok(mockStsClient.getCallerIdentity.notCalled) - }) - - it('should use cached caller identity ARN', async function () { - const iamConnection = { - id: 'profile:test-profile', - type: 'iam' as const, - label: 'Test IAM Profile', - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - endpointUrl: undefined, - getCredentials: sinon.stub().resolves(), - } - mockSecondaryAuthState.activeConnection = iamConnection as any - - const assumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name' - mockStsClient.getCallerIdentity.resolves({ - Arn: assumedRoleArn, - Account: '123456789012', - UserId: 'AIDAI1234567890EXAMPLE:my-session-name', - }) - - // Mock connection metadata - const smusConnections = { - [iamConnection.id]: { - profileName: 'test-profile', - region: testRegion, - domainUrl: testDomainUrl, - domainId: testDomainId, - }, - } - mockSecondaryAuth.state.get.withArgs('smus.connections').returns(smusConnections) - - // First call - should fetch from STS - const roleArn1 = await smusAuthProvider.getIamPrincipalArn() - assert.strictEqual(roleArn1, 'arn:aws:iam::123456789012:role/MyRole') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) - - // Second call - should use cached value - const roleArn2 = await smusAuthProvider.getIamPrincipalArn() - assert.strictEqual(roleArn2, 'arn:aws:iam::123456789012:role/MyRole') - assert.ok(mockStsClient.getCallerIdentity.calledOnce) // Still only called once - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.test.ts deleted file mode 100644 index e0332326be..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/authenticationMethodSelection.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { SmusAuthenticationMethodSelector } from '../../../../sagemakerunifiedstudio/auth/ui/authenticationMethodSelection' - -describe('SmusAuthenticationMethodSelector', function () { - // Note: Due to AWS Toolkit test framework restrictions on mocking vscode.window, - // these tests focus on the interface and behavior rather than deep mocking. - // The actual QuickPick functionality is tested through integration tests. - - describe('showAuthenticationMethodSelection', function () { - it('should export the correct interface', function () { - // Verify the class exists and has the expected static method - assert.ok('showAuthenticationMethodSelection' in SmusAuthenticationMethodSelector) - assert.strictEqual(typeof SmusAuthenticationMethodSelector.showAuthenticationMethodSelection, 'function') - }) - - it('should handle authentication method types correctly', function () { - // Test that the types are properly defined - const testMethod1: 'sso' | 'iam' = 'sso' - const testMethod2: 'sso' | 'iam' = 'iam' - - assert.strictEqual(testMethod1, 'sso') - assert.strictEqual(testMethod2, 'iam') - }) - - // The actual UI testing would be done manually or through E2E tests - it('should be callable without throwing', function () { - // Verify the method exists and is accessible - assert.doesNotThrow(() => { - // Just verify the method exists without calling it - assert.ok('showAuthenticationMethodSelection' in SmusAuthenticationMethodSelector) - }) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/iamProfileSelection.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/ui/iamProfileSelection.test.ts deleted file mode 100644 index 2a56b2baf0..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/iamProfileSelection.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SmusIamProfileSelector } from '../../../../sagemakerunifiedstudio/auth/ui/iamProfileSelection' - -describe('SmusIamProfileSelector', function () { - describe('showRegionSelection', function () { - it('should be a static method', function () { - assert.strictEqual(typeof SmusIamProfileSelector.showRegionSelection, 'function') - }) - }) - - describe('showIamProfileSelection', function () { - it('should be a static method', function () { - assert.strictEqual(typeof SmusIamProfileSelector.showIamProfileSelection, 'function') - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/ssoAuthentication.test.ts b/packages/core/src/test/sagemakerunifiedstudio/auth/ui/ssoAuthentication.test.ts deleted file mode 100644 index 6a4221ac7a..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/auth/ui/ssoAuthentication.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import { SmusSsoAuthenticationUI } from '../../../../sagemakerunifiedstudio/auth/ui/ssoAuthentication' - -describe('SmusSsoAuthenticationUI', function () { - // Note: Due to AWS Toolkit test framework restrictions on mocking vscode.window, - // these tests focus on the interface and behavior rather than deep mocking. - // The actual QuickPick functionality is tested through integration tests. - - describe('showDomainUrlInput', function () { - it('should export the correct interface', function () { - // Verify the class exists and has the expected static method - assert.ok('showDomainUrlInput' in SmusSsoAuthenticationUI) - assert.strictEqual(typeof SmusSsoAuthenticationUI.showDomainUrlInput, 'function') - }) - - it('should be callable without throwing', function () { - // Verify the method exists and is accessible - assert.doesNotThrow(() => { - // Just verify the method exists without calling it - assert.ok('showDomainUrlInput' in SmusSsoAuthenticationUI) - }) - }) - - it('should handle return type union correctly', function () { - // Test that the return types are properly defined - const testResult1: string | 'BACK' | undefined = 'https://example.com' - const testResult2: string | 'BACK' | undefined = 'BACK' - const testResult3: string | 'BACK' | undefined = undefined - - assert.strictEqual(testResult1, 'https://example.com') - assert.strictEqual(testResult2, 'BACK') - assert.strictEqual(testResult3, undefined) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/connectionMagicsSelector/activation.test.ts b/packages/core/src/test/sagemakerunifiedstudio/connectionMagicsSelector/activation.test.ts deleted file mode 100644 index 86e37c7644..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/connectionMagicsSelector/activation.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' - -describe('Connection magic selector test', function () { - it('example test', function () { - assert.ok(true) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/activation.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/activation.test.ts deleted file mode 100644 index eb770f6bf8..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/activation.test.ts +++ /dev/null @@ -1,579 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { activate } from '../../../sagemakerunifiedstudio/explorer/activation' -import { - SmusAuthenticationProvider, - setSmusConnectedContext, -} from '../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { ResourceTreeDataProvider } from '../../../shared/treeview/resourceTreeDataProvider' -import { SageMakerUnifiedStudioRootNode } from '../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode' -import { getLogger } from '../../../shared/logger/logger' -import { getTestWindow } from '../../shared/vscode/window' -import { SeverityLevel } from '../../shared/vscode/message' -import * as extensionUtilities from '../../../shared/extensionUtilities' -import { createMockSpaceNode } from '../testUtils' -import { DataZoneClient } from '../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import * as model from '../../../sagemakerunifiedstudio/auth/model' - -describe('SMUS Explorer Activation', function () { - let mockExtensionContext: vscode.ExtensionContext - let mockSmusAuthProvider: sinon.SinonStubbedInstance - let mockTreeView: sinon.SinonStubbedInstance> - let mockTreeDataProvider: sinon.SinonStubbedInstance - let mockSmusRootNode: sinon.SinonStubbedInstance - let mockProjectNode: any - let createTreeViewStub: sinon.SinonStub - let registerCommandStub: sinon.SinonStub - let executeCommandSpy: sinon.SinonSpy - let dataZoneDisposeStub: sinon.SinonStub - let setupUserActivityMonitoringStub: sinon.SinonStub - - beforeEach(async function () { - mockExtensionContext = { - subscriptions: [], - } as any - - mockSmusAuthProvider = { - restore: sinon.stub().resolves(), - isConnected: sinon.stub().returns(true), - reauthenticate: sinon.stub().resolves(), - onDidChange: sinon.stub().callsFake((_listener: () => void) => ({ dispose: sinon.stub() })), - onDidChangeActiveConnection: sinon.stub().callsFake((_listener: () => void) => ({ dispose: sinon.stub() })), - activeConnection: { - id: 'test-connection', - domainId: 'test-domain', - ssoRegion: 'us-east-1', - }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - } as any - - mockTreeView = { - dispose: sinon.stub(), - } as any - - mockTreeDataProvider = { - refresh: sinon.stub(), - } as any - - mockProjectNode = { - getProject: sinon.stub().returns({ id: 'test-project', name: 'Test Project' }), - refreshNode: sinon.stub().resolves(), - } - - mockSmusRootNode = { - getChildren: sinon.stub().resolves([]), - getProjectSelectNode: sinon.stub().returns(mockProjectNode), - } as any - - // Stub vscode APIs - createTreeViewStub = sinon.stub(vscode.window, 'createTreeView').returns(mockTreeView as any) - registerCommandStub = sinon.stub(vscode.commands, 'registerCommand').returns({ dispose: sinon.stub() } as any) - executeCommandSpy = sinon.spy(vscode.commands, 'executeCommand') - - // Stub SmusAuthenticationProvider - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockSmusAuthProvider as any) - - // Stub DataZoneClient.dispose - dataZoneDisposeStub = sinon.stub(DataZoneClient, 'dispose') - - // Stub SageMakerUnifiedStudioRootNode constructor - sinon.stub(SageMakerUnifiedStudioRootNode.prototype, 'getChildren').returns(mockSmusRootNode.getChildren()) - sinon - .stub(SageMakerUnifiedStudioRootNode.prototype, 'getProjectSelectNode') - .returns(mockSmusRootNode.getProjectSelectNode()) - - // Stub ResourceTreeDataProvider constructor - sinon.stub(ResourceTreeDataProvider.prototype, 'refresh').value(mockTreeDataProvider.refresh) - - // Stub logger - sinon.stub({ getLogger }, 'getLogger').returns({ - debug: sinon.stub(), - info: sinon.stub(), - error: sinon.stub(), - } as any) - - // Stub setSmusConnectedContext - sinon.stub({ setSmusConnectedContext }, 'setSmusConnectedContext').resolves() - - // Stub setupUserActivityMonitoring - setupUserActivityMonitoringStub = sinon - .stub(require('../../../awsService/sagemaker/sagemakerSpace'), 'setupUserActivityMonitoring') - .resolves() - - // Stub isSageMaker to return true for SMUS - sinon.stub(extensionUtilities, 'isSageMaker').returns(true) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('activate', function () { - it('should initialize SMUS authentication provider and call restore', async function () { - await activate(mockExtensionContext) - - assert.ok((SmusAuthenticationProvider.fromContext as sinon.SinonStub).called) - assert.ok(mockSmusAuthProvider.restore.called) - }) - - it('should create tree view with correct configuration', async function () { - await activate(mockExtensionContext) - - assert.ok(createTreeViewStub.calledWith('aws.smus.rootView')) - const createTreeViewArgs = createTreeViewStub.firstCall.args[1] - assert.ok('treeDataProvider' in createTreeViewArgs) - }) - - it('should register all required commands', async function () { - await activate(mockExtensionContext) - - // Check that commands are registered - const registeredCommands = registerCommandStub.getCalls().map((call) => call.args[0]) - - assert.ok(registeredCommands.includes('aws.smus.rootView.refresh')) - assert.ok(registeredCommands.includes('aws.smus.projectView')) - assert.ok(registeredCommands.includes('aws.smus.refreshProject')) - assert.ok(registeredCommands.includes('aws.smus.switchProject')) - assert.ok(registeredCommands.includes('aws.smus.stopSpace')) - assert.ok(registeredCommands.includes('aws.smus.openRemoteConnection')) - assert.ok(registeredCommands.includes('aws.smus.reauthenticate')) - }) - - it('should add all disposables to extension context subscriptions', async function () { - await activate(mockExtensionContext) - - // Should have multiple subscriptions added - assert.ok(mockExtensionContext.subscriptions.length > 0) - }) - - it('should refresh tree data provider on initialization', async function () { - await activate(mockExtensionContext) - - assert.ok(mockTreeDataProvider.refresh.called) - }) - - it('should register DataZone client disposal', async function () { - await activate(mockExtensionContext) - - // Find the DataZone dispose subscription - const subscriptions = mockExtensionContext.subscriptions - assert.ok(subscriptions.length > 0) - - // The DataZone dispose subscription should be among the subscriptions - let dataZoneDisposeFound = false - for (const subscription of subscriptions) { - if (subscription && typeof subscription.dispose === 'function') { - // Try calling dispose and see if it calls DataZoneClient.dispose - const callCountBefore = dataZoneDisposeStub.callCount - subscription.dispose() - if (dataZoneDisposeStub.callCount > callCountBefore) { - dataZoneDisposeFound = true - break - } - } - } - - assert.ok(dataZoneDisposeFound, 'Should register DataZone client disposal') - }) - - describe('command handlers', function () { - beforeEach(async function () { - await activate(mockExtensionContext) - }) - - it('should handle aws.smus.rootView.refresh command', async function () { - const refreshCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.rootView.refresh') - - assert.ok(refreshCommand) - - // Execute the command handler - await refreshCommand.args[1]() - - assert.ok(mockTreeDataProvider.refresh.called) - }) - - it('should handle aws.smus.reauthenticate command with connection', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - const mockConnection = { - id: 'test-connection', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: 'us-east-1', - scopes: ['datazone:domain:access'], - label: 'Test Connection', - } as any - - const testWindow = getTestWindow() - - // Execute the command handler with connection - await reauthCommand.args[1](mockConnection) - - assert.ok(mockSmusAuthProvider.reauthenticate.calledWith(mockConnection)) - assert.ok(mockSmusRootNode.getProjectSelectNode.called) - assert.ok((mockProjectNode.getProject as sinon.SinonStub).called) - assert.ok(executeCommandSpy.neverCalledWith('aws.smus.switchProject')) - assert.ok(mockTreeDataProvider.refresh.called) - - // Check that an information message was shown - const infoMessages = testWindow.shownMessages.filter( - (msg) => msg.severity === SeverityLevel.Information - ) - assert.ok(infoMessages.length > 0, 'Should show information message') - assert.ok(infoMessages.some((msg) => msg.message.includes('Successfully reauthenticated'))) - }) - - it('should handle aws.smus.reauthenticate command without connection', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - // Execute the command handler without connection - await reauthCommand.args[1]() - - assert.ok(mockSmusAuthProvider.reauthenticate.notCalled) - }) - - it('should handle reauthentication errors', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - const mockConnection = { - id: 'test-connection', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: 'us-east-1', - scopes: ['datazone:domain:access'], - label: 'Test Connection', - } as any - const error = new Error('Reauthentication failed') - mockSmusAuthProvider.reauthenticate.rejects(error) - - const testWindow = getTestWindow() - - // Execute the command handler - await reauthCommand.args[1](mockConnection) - - // Check that an error message was shown - const errorMessages = testWindow.shownMessages.filter((msg) => msg.severity === SeverityLevel.Error) - assert.ok(errorMessages.length > 0, 'Should show error message') - assert.ok(errorMessages.some((msg) => msg.message.includes('Reauthentication failed'))) - }) - - it('should extract detailed error message from ToolkitError cause chain', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - const mockConnection = { - id: 'test-connection', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: 'us-east-1', - scopes: ['datazone:domain:access'], - label: 'Test Connection', - } as any - - // Create a ToolkitError with a cause chain - const detailedError = new Error('Invalid profile - The security token is expired') - const wrapperError = new Error('Unable to reauthenticate SageMaker Unified Studio connection.') - ;(wrapperError as any).cause = detailedError - mockSmusAuthProvider.reauthenticate.rejects(wrapperError) - - const testWindow = getTestWindow() - - // Execute the command handler - await reauthCommand.args[1](mockConnection) - - // Check that the detailed error message from the cause was shown - const errorMessages = testWindow.shownMessages.filter((msg) => msg.severity === SeverityLevel.Error) - assert.ok(errorMessages.length > 0, 'Should show error message') - const hasDetailedError = errorMessages.some((msg) => - msg.message.includes('Invalid profile - The security token is expired') - ) - assert.ok(hasDetailedError, 'Should show detailed error from cause chain') - }) - - it('should not show success message for IAM connection reauthentication', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - // Create an IAM connection - const mockIamConnection = { - id: 'test-iam-connection', - type: 'iam', - profileName: 'test-profile', - region: 'us-east-1', - label: 'Test IAM Connection', - } as any - - // Stub isSmusIamConnection to return true for IAM connection - sinon.stub(model, 'isSmusIamConnection').returns(true) - - // Mock the return value to return the connection (IAM connection handled its own message) - mockSmusAuthProvider.reauthenticate.resolves(mockIamConnection) - - const testWindow = getTestWindow() - - // Execute the command handler - await reauthCommand.args[1](mockIamConnection) - - assert.ok(mockSmusAuthProvider.reauthenticate.calledWith(mockIamConnection)) - assert.ok(mockTreeDataProvider.refresh.called) - - // Check that NO information message was shown (IAM handles its own) - const infoMessages = testWindow.shownMessages.filter( - (msg) => msg.severity === SeverityLevel.Information - ) - assert.ok( - !infoMessages.some((msg) => msg.message.includes('Successfully reauthenticated')), - 'Should not show success message for IAM connection' - ) - }) - - it('should show success message for SSO connection reauthentication', async function () { - const reauthCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.reauthenticate') - - assert.ok(reauthCommand) - - const mockSsoConnection = { - id: 'test-sso-connection', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: 'us-east-1', - scopes: ['datazone:domain:access'], - label: 'Test SSO Connection', - } as any - - // Stub isSmusIamConnection to return false for SSO connection - sinon.stub(model, 'isSmusIamConnection').returns(false) - - // Mock the return value to indicate SSO connection (returns connection object) - mockSmusAuthProvider.reauthenticate.resolves(mockSsoConnection) - - const testWindow = getTestWindow() - - // Execute the command handler - await reauthCommand.args[1](mockSsoConnection) - - assert.ok(mockSmusAuthProvider.reauthenticate.calledWith(mockSsoConnection)) - assert.ok(mockTreeDataProvider.refresh.called) - - // Check that an information message was shown for SSO - const infoMessages = testWindow.shownMessages.filter( - (msg) => msg.severity === SeverityLevel.Information - ) - assert.ok(infoMessages.length > 0, 'Should show information message for SSO') - assert.ok( - infoMessages.some((msg) => msg.message.includes('Successfully reauthenticated')), - 'Should show success message for SSO connection' - ) - }) - - it('should handle aws.smus.refreshProject command', async function () { - const refreshProjectCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.refreshProject') - - assert.ok(refreshProjectCommand) - - // Execute the command handler - await refreshProjectCommand.args[1]() - - // Verify that getProjectSelectNode was called and refreshNode was called on the returned node - assert.ok(mockSmusRootNode.getProjectSelectNode.called) - const projectNode = mockSmusRootNode.getProjectSelectNode() - assert.ok((projectNode.refreshNode as sinon.SinonStub).called) - }) - - it('should handle aws.smus.stopSpace command with valid node', async function () { - const stopSpaceCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.stopSpace') - - assert.ok(stopSpaceCommand) - - const mockSpaceNode = createMockSpaceNode() - - // Mock the stopSpace function - const stopSpaceStub = sinon.stub() - sinon.stub(require('../../../awsService/sagemaker/commands'), 'stopSpace').value(stopSpaceStub) - - // Execute the command handler - await stopSpaceCommand.args[1](mockSpaceNode) - - assert.ok( - stopSpaceStub.calledWith( - mockSpaceNode.resource, - mockExtensionContext, - mockSpaceNode.resource.sageMakerClient - ) - ) - }) - - it('should handle aws.smus.stopSpace command with invalid node', async function () { - const stopSpaceCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.stopSpace') - - assert.ok(stopSpaceCommand) - - const testWindow = getTestWindow() - - // Execute the command handler with undefined node - await stopSpaceCommand.args[1](undefined) - - // Check that a warning message was shown - const warningMessages = testWindow.shownMessages.filter((msg) => msg.severity === SeverityLevel.Warning) - assert.ok(warningMessages.length > 0, 'Should show warning message') - assert.ok(warningMessages.some((msg) => msg.message.includes('Space information is being refreshed'))) - }) - - it('should handle aws.smus.openRemoteConnection command with valid node', async function () { - const openRemoteCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.openRemoteConnection') - - assert.ok(openRemoteCommand) - - const mockSpaceNode = createMockSpaceNode() - - // Mock the openRemoteConnect function - const openRemoteConnectStub = sinon.stub() - sinon - .stub(require('../../../awsService/sagemaker/commands'), 'openRemoteConnect') - .value(openRemoteConnectStub) - - // Execute the command handler - await openRemoteCommand.args[1](mockSpaceNode) - - assert.ok( - openRemoteConnectStub.calledWith( - mockSpaceNode.resource, - mockExtensionContext, - mockSpaceNode.resource.sageMakerClient - ) - ) - }) - - it('should handle aws.smus.openRemoteConnection command with invalid node', async function () { - const openRemoteCommand = registerCommandStub - .getCalls() - .find((call) => call.args[0] === 'aws.smus.openRemoteConnection') - - assert.ok(openRemoteCommand) - - const testWindow = getTestWindow() - - // Execute the command handler with undefined node - await openRemoteCommand.args[1](undefined) - - // Check that a warning message was shown - const warningMessages = testWindow.shownMessages.filter((msg) => msg.severity === SeverityLevel.Warning) - assert.ok(warningMessages.length > 0, 'Should show warning message') - assert.ok(warningMessages.some((msg) => msg.message.includes('Space information is being refreshed'))) - }) - }) - - it('should propagate auth provider initialization errors', async function () { - const error = new Error('Auth provider initialization failed') - mockSmusAuthProvider.restore.rejects(error) - - // Should throw the error since there's no error handling in activate() - await assert.rejects(() => activate(mockExtensionContext), /Auth provider initialization failed/) - }) - - it('should create root node with auth provider', async function () { - await activate(mockExtensionContext) - - // Verify that SageMakerUnifiedStudioRootNode was created with the auth provider - assert.ok(createTreeViewStub.called) - const treeDataProvider = createTreeViewStub.firstCall.args[1].treeDataProvider - assert.ok(treeDataProvider) - }) - - // TODO: Fix the activation test - it.skip('should setup user activity monitoring', async function () { - await activate(mockExtensionContext) - - assert.ok(setupUserActivityMonitoringStub.called) - }) - }) - - describe('command registration', function () { - it('should register commands with correct names', async function () { - await activate(mockExtensionContext) - - const expectedCommands = [ - 'aws.smus.rootView.refresh', - 'aws.smus.projectView', - 'aws.smus.refreshProject', - 'aws.smus.switchProject', - 'aws.smus.stopSpace', - 'aws.smus.openRemoteConnection', - 'aws.smus.reauthenticate', - ] - - const registeredCommands = registerCommandStub.getCalls().map((call) => call.args[0]) - - for (const command of expectedCommands) { - assert.ok(registeredCommands.includes(command), `Command ${command} should be registered`) - } - }) - - it('should register commands that return disposables', async function () { - await activate(mockExtensionContext) - - for (const call of registerCommandStub.getCalls()) { - const disposable = call.returnValue - assert.ok(disposable && typeof disposable.dispose === 'function') - } - }) - }) - - describe('resource cleanup', function () { - it('should dispose DataZone client on extension deactivation', async function () { - await activate(mockExtensionContext) - - // Find and execute the DataZone dispose subscription - const disposeSubscription = mockExtensionContext.subscriptions.find( - (sub) => sub.dispose && sub.dispose.toString().includes('DataZoneClient.dispose') - ) - - if (disposeSubscription) { - disposeSubscription.dispose() - assert.ok(dataZoneDisposeStub.called) - } - }) - - it('should add tree view to subscriptions for disposal', async function () { - await activate(mockExtensionContext) - - assert.ok(mockExtensionContext.subscriptions.includes(mockTreeView)) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.test.ts deleted file mode 100644 index 9aadf68d44..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { createFederatedConnectionNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/federatedConnectionStrategy' -import { GlueClient, ListEntitiesCommand, DescribeEntityCommand } from '@aws-sdk/client-glue' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('FederatedConnectionStrategy', function () { - let sandbox: sinon.SinonSandbox - let mockGlueClient: sinon.SinonStubbedInstance - let mockCredentialsProvider: ConnectionCredentialsProvider - - const mockConnection = { - connectionId: 'federated-conn-123', - name: 'test-federated-connection', - glueConnectionName: 'test-glue-connection', - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockCredentialsProvider = { - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - logger: {} as any, - smusAuthProvider: {} as any, - connectionId: 'test-connection', - projectId: 'test-project', - } as any - - mockGlueClient = sandbox.createStubInstance(GlueClient) - sandbox.stub(GlueClient.prototype, 'send').callsFake(mockGlueClient.send) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('createFederatedConnectionNode', function () { - it('should create connection node with correct properties', async function () { - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - assert.strictEqual(node.id, 'federated-federated-conn-123') - assert.strictEqual(node.resource, mockConnection) - - const treeItem = await node.getTreeItem() - assert.strictEqual(treeItem.label, 'test-federated-connection') - assert.strictEqual(treeItem.contextValue, 'federatedConnection') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - - it('should return error when no glue connection name', async function () { - const connectionWithoutGlue = { ...mockConnection, glueConnectionName: undefined } - - const node = await createFederatedConnectionNode( - connectionWithoutGlue as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.includes('error')) - }) - - it('should return placeholder when no entities found', async function () { - mockGlueClient.send.resolves({ Entities: [] }) - - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].resource, '[No data found]') - }) - - it('should group tables under Tables container', async function () { - mockGlueClient.send.resolves({ - Entities: [ - { EntityName: 'table1', Category: 'TABLE', Label: 'Table 1' }, - { EntityName: 'table2', Category: 'TABLE', Label: 'Table 2' }, - ], - }) - - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - assert.strictEqual(children.length, 1) - - const tablesContainer = children[0] - assert.ok(tablesContainer.id.includes('tables')) - - const tableChildren = await tablesContainer.getChildren!() - assert.strictEqual(tableChildren.length, 2) - }) - - it('should handle mixed entity types correctly', async function () { - mockGlueClient.send.resolves({ - Entities: [ - { EntityName: 'schema1', Category: 'SCHEMA', Label: 'Schema 1' }, - { EntityName: 'table1', Category: 'TABLE', Label: 'Table 1' }, - ], - }) - - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - assert.strictEqual(children.length, 2) // schema + tables container - }) - - it('should handle table columns', async function () { - const mockEntity = { EntityName: 'test-table', Category: 'TABLE' } - - mockGlueClient.send.callsFake((command) => { - if (command instanceof DescribeEntityCommand) { - return Promise.resolve({ - Fields: [ - { FieldName: 'col1', FieldType: 'string', Label: 'Column 1' }, - { FieldName: 'col2', FieldType: 'int', Label: 'Column 2' }, - ], - }) - } - if (command instanceof ListEntitiesCommand) { - return Promise.resolve({ - Entities: [mockEntity], - }) - } - return Promise.resolve({}) - }) - - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - const tablesContainer = children[0] - const tableNodes = await tablesContainer.getChildren!() - const tableNode = tableNodes[0] - - const columns = await tableNode.getChildren!() - assert.strictEqual(columns.length, 2) - - const columnTreeItem = await columns[0].getTreeItem() - assert.strictEqual(columnTreeItem.description, 'string') - }) - - it('should handle API errors gracefully', async function () { - mockGlueClient.send.rejects(new Error('API Error')) - - const node = await createFederatedConnectionNode( - mockConnection as any, - mockCredentialsProvider, - 'us-east-1' - ) - - const children = await node.getChildren!() - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.includes('error')) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.test.ts deleted file mode 100644 index ebd4780c14..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy.test.ts +++ /dev/null @@ -1,540 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { - LakehouseNode, - createLakehouseConnectionNode, -} from '../../../../sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy' -import { GlueCatalog } from '@amzn/glue-catalog-client' -import { GlueClient } from '../../../../sagemakerunifiedstudio/shared/client/glueClient' -import { ConnectionClientStore } from '../../../../sagemakerunifiedstudio/shared/client/connectionClientStore' -import { NodeType } from '../../../../sagemakerunifiedstudio/explorer/nodes/types' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('LakehouseStrategy', function () { - let sandbox: sinon.SinonSandbox - let mockGlueCatalogClient: sinon.SinonStubbedInstance - let mockGlueClient: sinon.SinonStubbedInstance - - const mockConnection = { - connectionId: 'lakehouse-conn-123', - name: 'test-lakehouse-connection', - type: 'ATHENA', - domainId: 'domain-123', - projectId: 'project-123', - } - - const mockCredentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - getDomainAccountId: async () => '123456789012', - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockGlueCatalogClient = { - getCatalogs: sandbox.stub(), - } as any - - mockGlueClient = { - getDatabases: sandbox.stub(), - getTables: sandbox.stub(), - getTable: sandbox.stub(), - } as any - - // Stub the GlueCatalog constructor to return our mock - sandbox.stub(GlueCatalog.prototype, 'getCatalogs').callsFake(mockGlueCatalogClient.getCatalogs) - sandbox.stub(GlueClient.prototype, 'getDatabases').callsFake(mockGlueClient.getDatabases) - sandbox.stub(GlueClient.prototype, 'getTables').callsFake(mockGlueClient.getTables) - sandbox.stub(GlueClient.prototype, 'getTable').callsFake(mockGlueClient.getTable) - - const mockClientStore = { - getGlueClient: sandbox.stub().returns(mockGlueClient), - getGlueCatalogClient: sandbox.stub().returns(mockGlueCatalogClient), - } - sandbox.stub(ConnectionClientStore, 'getInstance').returns(mockClientStore as any) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('LakehouseNode', function () { - it('should initialize with correct properties', function () { - const nodeData = { - id: 'test-node', - nodeType: NodeType.CONNECTION, - value: { test: 'value' }, - } - - const node = new LakehouseNode(nodeData) - - assert.strictEqual(node.id, 'test-node') - assert.deepStrictEqual(node.resource, { test: 'value' }) - }) - - it('should return empty array for leaf nodes', async function () { - const nodeData = { - id: 'leaf-node', - nodeType: NodeType.REDSHIFT_COLUMN, - value: {}, - } - - const node = new LakehouseNode(nodeData) - const children = await node.getChildren() - - assert.strictEqual(children.length, 0) - }) - - it('should return error node when children provider fails', async function () { - const nodeData = { - id: 'error-node', - nodeType: NodeType.CONNECTION, - value: {}, - } - - const failingProvider = async () => { - throw new Error('Provider failed') - } - - const node = new LakehouseNode(nodeData, failingProvider) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.startsWith('error-node-error-getChildren-')) - }) - - it('should create correct tree item for column node', async function () { - const nodeData = { - id: 'column-node', - nodeType: NodeType.REDSHIFT_COLUMN, - value: { name: 'test_column', type: 'varchar' }, - } - - const node = new LakehouseNode(nodeData) - const treeItem = await node.getTreeItem() - - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.strictEqual(treeItem.description, 'varchar') - }) - - it('should cache children after first load', async function () { - const provider = sandbox - .stub() - .resolves([new LakehouseNode({ id: 'child', nodeType: NodeType.GLUE_DATABASE })]) - const node = new LakehouseNode({ id: 'parent', nodeType: NodeType.CONNECTION }, provider) - - await node.getChildren() - await node.getChildren() - - assert.ok(provider.calledOnce) - }) - }) - - describe('createLakehouseConnectionNode', function () { - it('should create connection node with correct structure', function () { - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - - assert.strictEqual(node.id, 'lakehouse-conn-123') - assert.strictEqual(node.data.nodeType, NodeType.CONNECTION) - assert.strictEqual(node.data.path?.connection, 'test-lakehouse-connection') - }) - - it('should create AWS Data Catalog node for default connections', async function () { - const defaultConnection = { - ...mockConnection, - name: 'project.default_lakehouse', - } - - mockGlueCatalogClient.getCatalogs.resolves({ catalogs: [], nextToken: undefined }) - mockGlueClient.getDatabases.resolves({ - databases: [{ Name: 'default-db' }], - nextToken: undefined, - }) - - const node = createLakehouseConnectionNode( - defaultConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - const awsDataCatalogNode = children.find((child) => child.id.includes('AwsDataCatalog')) as LakehouseNode - assert.ok(awsDataCatalogNode) - assert.strictEqual(awsDataCatalogNode.data.nodeType, NodeType.GLUE_CATALOG) - }) - - it('should not create AWS Data Catalog node for non-default connections', async function () { - mockGlueCatalogClient.getCatalogs.resolves({ catalogs: [], nextToken: undefined }) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - const awsDataCatalogNode = children.find((child) => child.id.includes('AwsDataCatalog')) - assert.strictEqual(awsDataCatalogNode, undefined) - }) - - it('should handle errors gracefully', async function () { - mockGlueCatalogClient.getCatalogs.rejects(new Error('Catalog error')) - mockGlueClient.getDatabases.rejects(new Error('Database error')) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - assert.ok(children.length > 0) - assert.ok(children.some((child) => child.id.startsWith('lakehouse-conn-123-error-'))) - }) - - it('should create placeholder when no catalogs found', async function () { - // Mock getCatalogs to return empty array in the correct format - mockGlueCatalogClient.getCatalogs.resolves({ catalogs: [], nextToken: undefined }) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - // Check if placeholder exists - it should be a TreeItem with label '[No data found]' - const hasPlaceholder = children.some((child) => { - const treeItem = (child as any).data?.value - return treeItem === '[No data found]' || child.id.includes('placeholder') - }) - assert.ok(hasPlaceholder, 'Should have placeholder node when no catalogs found') - }) - }) - - describe('Catalog nodes', function () { - it('should create regular catalog nodes and load databases', async function () { - const regularCatalog = { - CatalogId: 'regular-catalog', - Name: 'regular-catalog', - CatalogType: 'HIVE', - } - - mockGlueCatalogClient.getCatalogs.resolves({ - catalogs: [regularCatalog], - nextToken: undefined, - }) - mockGlueClient.getDatabases.resolves({ - databases: [{ Name: 'test-db' }], - nextToken: undefined, - }) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - const catalogNode = children.find((child) => child.id === 'regular-catalog') as LakehouseNode - assert.ok(catalogNode) - assert.ok(mockGlueCatalogClient.getCatalogs.called) - - const catalogChildren = await catalogNode.getChildren() - assert.strictEqual(catalogChildren.length, 1) - assert.strictEqual((catalogChildren[0] as LakehouseNode).data.nodeType, NodeType.GLUE_DATABASE) - }) - - it('should handle catalog database pagination', async function () { - const catalogNode = new LakehouseNode( - { - id: 'catalog-node', - nodeType: NodeType.GLUE_CATALOG, - path: { catalog: 'test-catalog' }, - }, - async () => { - const allDatabases = [] - let nextToken: string | undefined - do { - const { databases, nextToken: token } = await mockGlueClient.getDatabases( - 'test-catalog', - undefined, - undefined, - nextToken - ) - allDatabases.push(...databases) - nextToken = token - } while (nextToken) - return allDatabases.map( - (db) => new LakehouseNode({ id: db.Name || '', nodeType: NodeType.GLUE_DATABASE }) - ) - } - ) - - mockGlueClient.getDatabases - .onFirstCall() - .resolves({ databases: [{ Name: 'db1' }], nextToken: 'token1' }) - .onSecondCall() - .resolves({ databases: [{ Name: 'db2' }], nextToken: undefined }) - - const children = await catalogNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.ok(mockGlueClient.getDatabases.calledTwice) - }) - - it('should treat RedLake catalogs as parent catalogs', async function () { - const redLakeCatalog = { - CatalogId: 'redlake-catalog', - Name: 'redlake-catalog', - FederatedCatalog: { - ConnectionName: 'aws:redshift', - }, - } - - mockGlueCatalogClient.getCatalogs.resolves({ - catalogs: [redLakeCatalog], - nextToken: undefined, - }) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - const catalogNode = children.find((child) => child.id === 'redlake-catalog') as LakehouseNode - assert.ok(catalogNode) - - const catalogChildren = await catalogNode.getChildren() - assert.strictEqual(catalogChildren.length, 1) - assert.ok(catalogChildren[0].resource === '[No data found]') - }) - - it('should treat S3Tables catalogs as parent catalogs', async function () { - const s3TablesCatalog = { - CatalogId: 's3tables-catalog', - Name: 's3tables-catalog', - FederatedCatalog: { - ConnectionName: 'aws:s3tables', - }, - } - - mockGlueCatalogClient.getCatalogs.resolves({ - catalogs: [s3TablesCatalog], - nextToken: undefined, - }) - - const node = createLakehouseConnectionNode( - mockConnection as any, - mockCredentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - const catalogNode = children.find((child) => child.id === 's3tables-catalog') as LakehouseNode - assert.ok(catalogNode) - - const catalogChildren = await catalogNode.getChildren() - assert.strictEqual(catalogChildren.length, 1) - assert.ok(catalogChildren[0].resource === '[No data found]') - }) - }) - - describe('Database nodes', function () { - it('should handle table pagination', async function () { - const databaseNode = new LakehouseNode( - { - id: 'database-node', - nodeType: NodeType.GLUE_DATABASE, - path: { catalog: 'test-catalog', database: 'test-db' }, - }, - async () => { - const allTables = [] - let nextToken: string | undefined - do { - const { tables, nextToken: token } = await mockGlueClient.getTables( - 'test-db', - 'test-catalog', - undefined, - nextToken - ) - allTables.push(...tables) - nextToken = token - } while (nextToken) - return allTables.map( - (table) => new LakehouseNode({ id: table.Name || '', nodeType: NodeType.GLUE_TABLE }) - ) - } - ) - - mockGlueClient.getTables - .onFirstCall() - .resolves({ tables: [{ Name: 'table1' }], nextToken: 'token1' }) - .onSecondCall() - .resolves({ tables: [{ Name: 'table2' }], nextToken: undefined }) - - const children = await databaseNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.ok(mockGlueClient.getTables.calledTwice) - }) - - it('should handle AWS Data Catalog database queries', async function () { - const databaseNode = new LakehouseNode( - { - id: 'database-node', - nodeType: NodeType.GLUE_DATABASE, - path: { catalog: 'aws-data-catalog', database: 'test-db' }, - }, - async () => { - const catalogId = undefined - const { tables } = await mockGlueClient.getTables('test-db', catalogId) - return tables.map( - (table) => new LakehouseNode({ id: table.Name || '', nodeType: NodeType.GLUE_TABLE }) - ) - } - ) - - mockGlueClient.getTables.resolves({ tables: [{ Name: 'aws-table' }], nextToken: undefined }) - - const children = await databaseNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(mockGlueClient.getTables.calledWith('test-db', undefined)) - }) - }) - - describe('Table nodes', function () { - it('should create table node and load columns', async function () { - const tableNode = new LakehouseNode( - { - id: 'table-node', - nodeType: NodeType.GLUE_TABLE, - path: { database: 'test-db', table: 'test-table' }, - }, - async () => { - const tableDetails = await mockGlueClient.getTable('test-db', 'test-table') - const columns = tableDetails?.StorageDescriptor?.Columns || [] - const partitions = tableDetails?.PartitionKeys || [] - return [...columns, ...partitions].map( - (col) => - new LakehouseNode({ - id: `column-${col.Name}`, - nodeType: NodeType.REDSHIFT_COLUMN, - value: { name: col.Name, type: col.Type }, - }) - ) - } - ) - - mockGlueClient.getTable.resolves({ - StorageDescriptor: { - Columns: [{ Name: 'col1', Type: 'string' }], - }, - PartitionKeys: [{ Name: 'partition_col', Type: 'date' }], - Name: undefined, - }) - - const children = await tableNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.ok(mockGlueClient.getTable.calledWith('test-db', 'test-table')) - }) - - it('should handle table with no columns', async function () { - const tableNode = new LakehouseNode( - { - id: 'empty-table-node', - nodeType: NodeType.GLUE_TABLE, - path: { database: 'test-db', table: 'empty-table' }, - }, - async () => { - const tableDetails = await mockGlueClient.getTable('test-db', 'empty-table') - const columns = tableDetails?.StorageDescriptor?.Columns || [] - const partitions = tableDetails?.PartitionKeys || [] - return [...columns, ...partitions].map( - (col) => - new LakehouseNode({ - id: `column-${col.Name}`, - nodeType: NodeType.REDSHIFT_COLUMN, - value: { name: col.Name, type: col.Type }, - }) - ) - } - ) - - mockGlueClient.getTable.resolves({ - StorageDescriptor: { Columns: [] }, - PartitionKeys: [], - Name: undefined, - }) - - const children = await tableNode.getChildren() - - assert.strictEqual(children.length, 0) - }) - - it('should handle table getTable errors gracefully', async function () { - const tableNode = new LakehouseNode( - { - id: 'error-table-node', - nodeType: NodeType.GLUE_TABLE, - path: { database: 'test-db', table: 'error-table' }, - }, - async () => { - try { - await mockGlueClient.getTable('test-db', 'error-table') - return [] - } catch (err) { - return [] - } - } - ) - - mockGlueClient.getTable.rejects(new Error('Table not found')) - - const children = await tableNode.getChildren() - - assert.strictEqual(children.length, 0) - }) - }) - - describe('Column nodes', function () { - it('should create column node with correct properties', function () { - const parentNode = new LakehouseNode({ - id: 'parent-table', - nodeType: NodeType.GLUE_TABLE, - path: { database: 'test-db', table: 'test-table' }, - }) - - const columnNode = new LakehouseNode({ - id: 'parent-table/test-column', - nodeType: NodeType.REDSHIFT_COLUMN, - value: { name: 'test-column', type: 'varchar' }, - path: { database: 'test-db', table: 'test-table', column: 'test-column' }, - parent: parentNode, - }) - - assert.strictEqual(columnNode.id, 'parent-table/test-column') - assert.strictEqual(columnNode.resource.name, 'test-column') - assert.strictEqual(columnNode.resource.type, 'varchar') - assert.strictEqual(columnNode.getParent(), parentNode) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.test.ts deleted file mode 100644 index 50b5e36e25..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/redshiftStrategy.test.ts +++ /dev/null @@ -1,359 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { - RedshiftNode, - createRedshiftConnectionNode, -} from '../../../../sagemakerunifiedstudio/explorer/nodes/redshiftStrategy' -import { SQLWorkbenchClient } from '../../../../sagemakerunifiedstudio/shared/client/sqlWorkbenchClient' -import * as sqlWorkbenchClient from '../../../../sagemakerunifiedstudio/shared/client/sqlWorkbenchClient' -import { ConnectionClientStore } from '../../../../sagemakerunifiedstudio/shared/client/connectionClientStore' -import { NodeType } from '../../../../sagemakerunifiedstudio/explorer/nodes/types' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('redshiftStrategy', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('RedshiftNode', function () { - describe('constructor', function () { - it('should create node with correct properties', function () { - const nodeData = { - id: 'test-id', - nodeType: NodeType.REDSHIFT_CLUSTER, - value: { clusterName: 'test-cluster' }, - } - - const node = new RedshiftNode(nodeData) - - assert.strictEqual(node.id, 'test-id') - assert.strictEqual(node.data.nodeType, NodeType.REDSHIFT_CLUSTER) - assert.deepStrictEqual(node.resource, { clusterName: 'test-cluster' }) - }) - }) - - describe('getChildren', function () { - it('should return cached children if available', async function () { - const nodeData = { - id: 'test-id', - nodeType: NodeType.REDSHIFT_CLUSTER, - } - - const node = new RedshiftNode(nodeData) - // Simulate cached children - ;(node as any).childrenNodes = [{ id: 'cached-child' }] - - const children = await node.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual((children[0] as any).id, 'cached-child') - }) - - it('should return empty array for leaf nodes', async function () { - const nodeData = { - id: 'test-id', - nodeType: NodeType.REDSHIFT_COLUMN, - } - - const node = new RedshiftNode(nodeData) - const children = await node.getChildren() - assert.strictEqual(children.length, 0) - }) - }) - - describe('getTreeItem', function () { - it('should return correct tree item for regular nodes', async function () { - const nodeData = { - id: 'test-cluster', - nodeType: NodeType.REDSHIFT_CLUSTER, - value: { clusterName: 'test-cluster' }, - } - - const node = new RedshiftNode(nodeData) - const treeItem = await node.getTreeItem() - - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(treeItem.contextValue, NodeType.REDSHIFT_CLUSTER) - }) - - it('should return column tree item for column nodes', async function () { - const nodeData = { - id: 'test-column', - nodeType: NodeType.REDSHIFT_COLUMN, - value: { type: 'VARCHAR(255)' }, - } - - const node = new RedshiftNode(nodeData) - const treeItem = await node.getTreeItem() - - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.strictEqual(treeItem.description, 'VARCHAR(255)') - }) - - it('should return leaf tree item for leaf nodes', async function () { - const nodeData = { - id: 'test-column', - nodeType: NodeType.REDSHIFT_COLUMN, - } - - const node = new RedshiftNode(nodeData) - const treeItem = await node.getTreeItem() - - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - }) - - describe('getParent', function () { - it('should return parent node', function () { - const parentData = { id: 'parent', nodeType: NodeType.REDSHIFT_CLUSTER } - const parent = new RedshiftNode(parentData) - - const nodeData = { - id: 'child', - nodeType: NodeType.REDSHIFT_DATABASE, - parent: parent, - } - - const node = new RedshiftNode(nodeData) - assert.strictEqual(node.getParent(), parent) - }) - }) - }) - - describe('createRedshiftConnectionNode', function () { - let mockSQLClient: sinon.SinonStubbedInstance - - beforeEach(function () { - mockSQLClient = { - executeQuery: sandbox.stub(), - getResources: sandbox.stub(), - } as any - - sandbox.stub(SQLWorkbenchClient, 'createWithCredentials').returns(mockSQLClient as any) - sandbox.stub(sqlWorkbenchClient, 'createRedshiftConnectionConfig').resolves({ - id: 'test-connection-id', - type: '4', - databaseType: 'REDSHIFT', - connectableResourceIdentifier: 'test-cluster', - connectableResourceType: 'CLUSTER', - database: 'test-db', - }) - - const mockClientStore = { - getSQLWorkbenchClient: sandbox.stub().returns(mockSQLClient), - } - sandbox.stub(ConnectionClientStore, 'getInstance').returns(mockClientStore as any) - }) - - it.skip('should create Redshift connection node with JDBC URL', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test Redshift Connection', - type: 'RedshiftConnection', - props: { - jdbcConnection: { - jdbcUrl: 'jdbc:redshift://test-cluster.123456789012.us-east-1.redshift.amazonaws.com:5439/dev', - dbname: 'test-db', - }, - redshiftProperties: {}, - }, - location: { - awsAccountId: '', - awsRegion: 'us-east-1', - }, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - mockSQLClient.executeQuery.resolves('query-id') - mockSQLClient.getResources.resolves({ - resources: [ - { - displayName: 'test-db', - type: 'DATABASE', - identifier: '', - childObjectTypes: [], - }, - ], - }) - - const node = createRedshiftConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider - ) - - assert.strictEqual(node.data.nodeType, NodeType.CONNECTION) - assert.strictEqual(node.data.value.connection.name, 'Test Redshift Connection') - - // Test children provider - now creates database nodes directly - const children = await node.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual((children[0] as RedshiftNode).data.nodeType, NodeType.REDSHIFT_DATABASE) - }) - - it.skip('should create connection node with host from jdbcConnection', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test Connection', - type: 'RedshiftConnection', - props: { - jdbcConnection: { - host: 'test-host.redshift.amazonaws.com', - dbname: 'test-db', - }, - redshiftProperties: {}, - }, - location: { - awsAccountId: '', - awsRegion: 'us-east-1', - }, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - mockSQLClient.executeQuery.resolves('query-id') - mockSQLClient.getResources.resolves({ resources: [] }) - - const node = createRedshiftConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider - ) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual((children[0] as RedshiftNode).data.nodeType, NodeType.REDSHIFT_DATABASE) - }) - - it('should return placeholder when connection params are missing', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test Connection', - type: 'RedshiftConnection', - props: { - jdbcConnection: {}, - redshiftProperties: {}, - }, - location: {}, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - getDomainAccountId: async () => '123456789012', - } - - const node = createRedshiftConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider - ) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].resource, '[No data found]') - }) - - it.skip('should handle workgroup name in host', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test Connection', - type: 'RedshiftConnection', - props: { - jdbcConnection: { - host: 'test-host.redshift-serverless.amazonaws.com', - dbname: 'test-db', - }, - redshiftProperties: { - storage: { - workgroupName: 'test-workgroup', - }, - }, - }, - location: { - awsAccountId: '', - awsRegion: 'us-east-1', - }, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - mockSQLClient.executeQuery.resolves('query-id') - mockSQLClient.getResources.resolves({ resources: [] }) - - const node = createRedshiftConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider - ) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - }) - - it.skip('should handle connection errors gracefully', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test Connection', - type: 'RedshiftConnection', - props: { - jdbcConnection: { - host: 'test-host.redshift.amazonaws.com', - dbname: 'test-db', - }, - }, - location: { - awsAccountId: '', - awsRegion: 'us-east-1', - }, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - // Make createRedshiftConnectionConfig throw an error - ;(sqlWorkbenchClient.createRedshiftConnectionConfig as sinon.SinonStub).rejects( - new Error('Connection config failed') - ) - - const node = createRedshiftConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider - ) - - // The error should be handled gracefully and return an error node - const children = await node.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual((children[0] as any).id.includes('error'), true) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/s3Strategy.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/s3Strategy.test.ts deleted file mode 100644 index f6838ab483..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/s3Strategy.test.ts +++ /dev/null @@ -1,253 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { S3Node, createS3ConnectionNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/s3Strategy' -import { S3Client } from '../../../../sagemakerunifiedstudio/shared/client/s3Client' -import { ConnectionClientStore } from '../../../../sagemakerunifiedstudio/shared/client/connectionClientStore' -import { NodeType, ConnectionType } from '../../../../sagemakerunifiedstudio/explorer/nodes/types' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' -import { createMockS3Connection, createMockCredentialsProvider } from '../../testUtils' - -describe('s3Strategy', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('S3Node', function () { - describe('constructor', function () { - it('should create node with correct properties', function () { - const node = new S3Node({ - id: 'test-id', - nodeType: NodeType.S3_BUCKET, - connectionType: ConnectionType.S3, - value: { bucket: 'test-bucket' }, - path: { bucket: 'test-bucket' }, - }) - - assert.strictEqual(node.id, 'test-id') - assert.strictEqual(node.data.nodeType, NodeType.S3_BUCKET) - assert.strictEqual(node.data.connectionType, ConnectionType.S3) - }) - }) - - describe('getChildren', function () { - it('should return empty array for leaf nodes', async function () { - const node = new S3Node({ - id: 'test-id', - nodeType: NodeType.S3_FILE, - connectionType: ConnectionType.S3, - }) - - const children = await node.getChildren() - assert.strictEqual(children.length, 0) - }) - - it('should handle children provider errors', async function () { - const errorProvider = async () => { - throw new Error('Provider error') - } - - const node = new S3Node( - { - id: 'test-id', - nodeType: NodeType.S3_BUCKET, - connectionType: ConnectionType.S3, - }, - errorProvider - ) - - const children = await node.getChildren() - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.startsWith('test-id-error-getChildren-')) - }) - }) - - describe('getTreeItem', function () { - it('should return correct tree item for non-leaf node', async function () { - const node = new S3Node({ - id: 'test-id', - nodeType: NodeType.S3_BUCKET, - connectionType: ConnectionType.S3, - }) - - const treeItem = await node.getTreeItem() - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(treeItem.contextValue, NodeType.S3_BUCKET) - }) - - it('should return correct tree item for leaf node', async function () { - const node = new S3Node({ - id: 'test-id', - nodeType: NodeType.S3_FILE, - connectionType: ConnectionType.S3, - }) - - const treeItem = await node.getTreeItem() - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - }) - }) - - describe('createS3ConnectionNode', function () { - let mockS3Client: sinon.SinonStubbedInstance - - beforeEach(function () { - mockS3Client = { - listPaths: sandbox.stub(), - } as any - - sandbox.stub(S3Client.prototype, 'constructor' as any) - sandbox.stub(S3Client.prototype, 'listPaths').callsFake(mockS3Client.listPaths) - - const mockClientStore = { - getS3Client: sandbox.stub().returns(mockS3Client), - } - sandbox.stub(ConnectionClientStore, 'getInstance').returns(mockClientStore as any) - }) - - it('should create S3 connection node successfully for non-default connection', function () { - const connection = { - connectionId: 'conn-123', - name: 'Test S3 Connection', - type: 'S3Connection', - props: { - s3Properties: { - s3Uri: 's3://test-bucket/prefix/', - }, - }, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - const node = createS3ConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - - assert.strictEqual(node.data.nodeType, NodeType.CONNECTION) - assert.strictEqual(node.data.connectionType, ConnectionType.S3) - }) - - it('should create S3 connection node for default connection with full path', function () { - const connection = createMockS3Connection() - const credentialsProvider = createMockCredentialsProvider() - - const node = createS3ConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - - assert.strictEqual(node.data.nodeType, NodeType.CONNECTION) - assert.strictEqual(node.data.connectionType, ConnectionType.S3) - }) - - it('should return error node when no S3 URI found', function () { - const connection = { - connectionId: 'conn-123', - name: 'Test S3 Connection', - type: 'S3Connection', - props: {}, - } - - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - } - - const node = createS3ConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - - assert.ok(node.id.startsWith('conn-123-error-connection-')) - }) - - it('should handle bucket listing for non-default connection', async function () { - const connection = { - connectionId: 'conn-123', - name: 'Test S3 Connection', - type: 'S3Connection', - props: { - s3Properties: { - s3Uri: 's3://test-bucket/', - }, - }, - } - - const credentialsProvider = createMockCredentialsProvider() - - mockS3Client.listPaths.resolves({ - paths: [ - { - bucket: 'test-bucket', - prefix: 'file.txt', - displayName: 'file.txt', - isFolder: false, - }, - ], - nextToken: undefined, - }) - - const node = createS3ConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual((children[0] as S3Node).data.nodeType, NodeType.S3_BUCKET) - }) - - it('should handle bucket listing for default connection with full path display', async function () { - const connection = createMockS3Connection() - const credentialsProvider = createMockCredentialsProvider() - - mockS3Client.listPaths.resolves({ - paths: [ - { - bucket: 'test-bucket', - prefix: 'domain/project/dev/', - displayName: 'dev', - isFolder: true, - }, - ], - nextToken: undefined, - }) - - const node = createS3ConnectionNode( - connection as any, - credentialsProvider as ConnectionCredentialsProvider, - 'us-east-1' - ) - const children = await node.getChildren() - - assert.strictEqual(children.length, 1) - const bucketNode = children[0] as S3Node - assert.strictEqual(bucketNode.data.nodeType, NodeType.S3_BUCKET) - // For default connection, should show full path - assert.strictEqual(bucketNode.data.path?.label, 'test-bucket/domain/project/') - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.test.ts deleted file mode 100644 index 52d1d04540..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode.test.ts +++ /dev/null @@ -1,426 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioAuthInfoNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { SmusConnection, SmusSsoConnection } from '../../../../sagemakerunifiedstudio/auth/model' - -describe('SageMakerUnifiedStudioAuthInfoNode', function () { - let authInfoNode: SageMakerUnifiedStudioAuthInfoNode - let mockAuthProvider: any - let mockConnection: SmusSsoConnection - let currentActiveConnection: SmusConnection | undefined - - beforeEach(function () { - mockConnection = { - id: 'test-connection-id', - type: 'sso', - startUrl: 'https://identitycenter.amazonaws.com/ssoins-testInstanceId', - ssoRegion: 'us-east-2', - scopes: ['datazone:domain:access'], - label: 'Test SMUS Connection', - domainUrl: 'https://dzd_domainId.sagemaker.us-east-2.on.aws', - domainId: 'dzd_domainId', - // Mock the required methods from SsoConnection - getToken: sinon.stub().resolves(), - getRegistration: sinon.stub().resolves(), - } as any - - // Initialize the current active connection - currentActiveConnection = mockConnection - - // Create mock auth provider with getter for activeConnection - mockAuthProvider = { - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(true), - onDidChange: sinon.stub().callsFake((listener: () => void) => ({ dispose: sinon.stub() })), - onDidChangeActiveConnection: sinon.stub().callsFake((listener: () => void) => ({ dispose: sinon.stub() })), - getDomainId: sinon.stub().callsFake(() => { - return currentActiveConnection?.domainId - }), - getDomainRegion: sinon.stub().callsFake(() => { - if (currentActiveConnection?.type === 'sso') { - return (currentActiveConnection as any).ssoRegion - } else if (currentActiveConnection?.type === 'iam') { - return (currentActiveConnection as any).region - } - return undefined - }), - getSessionName: sinon.stub().resolves(undefined), - getRoleArn: sinon.stub().resolves(undefined), - get activeConnection() { - return currentActiveConnection - }, - set activeConnection(value: SmusConnection | undefined) { - currentActiveConnection = value - }, - } - - // Stub getContext to return false for IAM mode by default (SSO connections) - sinon.stub(require('../../../../shared/vscode/setContext'), 'getContext').returns(false) - - // Stub SmusAuthenticationProvider.fromContext - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - authInfoNode = new SageMakerUnifiedStudioAuthInfoNode() - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should initialize with correct properties', function () { - assert.strictEqual(authInfoNode.id, 'smusAuthInfoNode') - assert.strictEqual(authInfoNode.resource, authInfoNode) - }) - - it('should register for auth provider changes', function () { - assert.ok(mockAuthProvider.onDidChange.called) - }) - - it('should have onDidChangeTreeItem event', function () { - assert.ok(typeof authInfoNode.onDidChangeTreeItem === 'function') - }) - }) - - describe('getTreeItem', function () { - describe('when connected and valid', function () { - beforeEach(function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.activeConnection = mockConnection - }) - - it('should return connected tree item', async function () { - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Domain: dzd_domainId') - assert.strictEqual(treeItem.description, 'us-east-2') - assert.strictEqual(treeItem.contextValue, 'smusAuthInfo') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - - // Check icon - assert.ok(treeItem.iconPath instanceof vscode.ThemeIcon) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'key') - - // Check tooltip - const tooltip = treeItem.tooltip as string - assert.ok(tooltip?.includes('Connected to SageMaker Unified Studio')) - assert.ok(tooltip?.includes('dzd_domainId')) - assert.ok(tooltip?.includes('us-east-2')) - assert.ok(tooltip?.includes('Status: Connected')) - - // Should not have command when valid - assert.strictEqual(treeItem.command, undefined) - }) - }) - - describe('when connected but expired', function () { - beforeEach(function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(false) - mockAuthProvider.activeConnection = mockConnection - }) - - it('should return expired tree item with reauthenticate command', async function () { - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Domain: dzd_domainId (Expired) - Click to reauthenticate') - assert.strictEqual(treeItem.description, 'us-east-2') - assert.strictEqual(treeItem.contextValue, 'smusAuthInfo') - - // Check icon - assert.ok(treeItem.iconPath instanceof vscode.ThemeIcon) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'warning') - - // Check tooltip - const tooltip = treeItem.tooltip as string - assert.ok(tooltip?.includes('Connection to SageMaker Unified Studio has expired')) - assert.ok(tooltip?.includes('Status: Expired - Click to reauthenticate')) - - // Should have reauthenticate command - assert.ok(treeItem.command) - assert.strictEqual(treeItem.command.command, 'aws.smus.reauthenticate') - assert.strictEqual(treeItem.command.title, 'Reauthenticate') - assert.deepStrictEqual(treeItem.command.arguments, [mockConnection]) - }) - }) - - describe('when not connected', function () { - beforeEach(function () { - mockAuthProvider.isConnected.returns(false) - mockAuthProvider.isConnectionValid.returns(false) - mockAuthProvider.activeConnection = undefined - }) - - it('should return not connected tree item', async function () { - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Not Connected') - assert.strictEqual(treeItem.description, undefined) - assert.strictEqual(treeItem.contextValue, 'smusAuthInfo') - - // Check icon - assert.ok(treeItem.iconPath instanceof vscode.ThemeIcon) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'circle-slash') - - // Check tooltip - const tooltip = treeItem.tooltip as string - assert.ok(tooltip?.includes('Not connected to SageMaker Unified Studio')) - assert.ok(tooltip?.includes('Please sign in to access your projects')) - - // Should not have command when not connected - assert.strictEqual(treeItem.command, undefined) - }) - }) - - describe('with missing connection details', function () { - beforeEach(function () { - const incompleteConnection = { - ...mockConnection, - domainId: undefined, - ssoRegion: undefined, - } as any - - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.activeConnection = incompleteConnection - }) - - it('should handle missing domain ID and region gracefully', async function () { - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Domain: Unknown') - assert.strictEqual(treeItem.description, 'Unknown') - - const tooltip = treeItem.tooltip as string - assert.ok(tooltip?.includes('Domain ID: Unknown')) - assert.ok(tooltip?.includes('Region: Unknown')) - }) - }) - }) - - describe('getParent', function () { - it('should return undefined', function () { - assert.strictEqual(authInfoNode.getParent(), undefined) - }) - }) - - describe('event handling', function () { - it('should fire onDidChangeTreeItem when auth provider changes', function () { - const eventSpy = sinon.spy() - authInfoNode.onDidChangeTreeItem(eventSpy) - - // Simulate auth provider change - const onDidChangeCallback = mockAuthProvider.onDidChange.firstCall.args[0] - onDidChangeCallback() - - assert.ok(eventSpy.called) - }) - - it('should dispose event listener properly', function () { - const disposeSpy = sinon.spy() - mockAuthProvider.onDidChange.returns({ dispose: disposeSpy }) - - // Create new node to trigger event listener registration - new SageMakerUnifiedStudioAuthInfoNode() - - // The dispose should be available for cleanup - assert.ok(mockAuthProvider.onDidChange.called) - }) - }) - - describe('theme icon colors', function () { - it('should use green color for connected state', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - - const treeItem = await authInfoNode.getTreeItem() - const icon = treeItem.iconPath as vscode.ThemeIcon - - assert.ok(icon.color instanceof vscode.ThemeColor) - assert.strictEqual((icon.color as any).id, 'charts.green') - }) - - it('should use yellow color for expired state', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(false) - - const treeItem = await authInfoNode.getTreeItem() - const icon = treeItem.iconPath as vscode.ThemeIcon - - assert.ok(icon.color instanceof vscode.ThemeColor) - assert.strictEqual((icon.color as any).id, 'charts.yellow') - }) - - it('should use red color for not connected state', async function () { - mockAuthProvider.isConnected.returns(false) - - const treeItem = await authInfoNode.getTreeItem() - const icon = treeItem.iconPath as vscode.ThemeIcon - - assert.ok(icon.color instanceof vscode.ThemeColor) - assert.strictEqual((icon.color as any).id, 'charts.red') - }) - }) - - describe('tooltip content', function () { - it('should include all relevant information for connected state', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - - const treeItem = await authInfoNode.getTreeItem() - const tooltip = treeItem.tooltip as string - - assert.ok(tooltip.includes('Connected to SageMaker Unified Studio')) - assert.ok(tooltip.includes(`Domain ID: ${mockConnection.domainId}`)) - assert.ok(tooltip.includes(`Region: ${mockConnection.ssoRegion}`)) - assert.ok(tooltip.includes('Status: Connected')) - }) - - it('should include expiration information for expired state', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(false) - - const treeItem = await authInfoNode.getTreeItem() - const tooltip = treeItem.tooltip as string - - assert.ok(tooltip.includes('Connection to SageMaker Unified Studio has expired')) - assert.ok(tooltip.includes('Status: Expired - Click to reauthenticate')) - }) - - it('should include sign-in prompt for not connected state', async function () { - mockAuthProvider.isConnected.returns(false) - - const treeItem = await authInfoNode.getTreeItem() - const tooltip = treeItem.tooltip as string - - assert.ok(tooltip.includes('Not connected to SageMaker Unified Studio')) - assert.ok(tooltip.includes('Please sign in to access your projects')) - }) - }) - - describe('IAM connections in IAM mode', function () { - let mockIamConnection: any - - beforeEach(function () { - mockIamConnection = { - id: 'profile:test-profile', - type: 'iam', - label: 'Test IAM Profile', - profileName: 'test-profile', - region: 'us-west-2', - domainUrl: 'https://dzd_domainId.sagemaker.us-west-2.on.aws', - domainId: 'dzd_domainId', - getCredentials: sinon.stub().resolves(), - } - - currentActiveConnection = mockIamConnection - - // Override getContext stub to return true for IAM mode - const getContextModule = require('../../../../shared/vscode/setContext') - const existingStub = getContextModule.getContext as sinon.SinonStub - existingStub.withArgs('aws.smus.isIamMode').returns(true) - }) - - it('should display profile name with session name for IAM connection', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.getSessionName = sinon.stub().resolves('my-session-name') - mockAuthProvider.getIamPrincipalArn = sinon - .stub() - .resolves('arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name') - - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Connected with profile: test-profile (session: my-session-name)') - assert.strictEqual(treeItem.description, 'us-west-2') - }) - - it('should display profile name without session name when unavailable', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.getSessionName = sinon.stub().resolves(undefined) - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(undefined) - - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Connected with profile: test-profile') - assert.strictEqual(treeItem.description, 'us-west-2') - }) - - it('should include session name and role ARN in tooltip when available', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.getSessionName = sinon.stub().resolves('my-session-name') - mockAuthProvider.getIamPrincipalArn = sinon - .stub() - .resolves('arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name') - - const treeItem = await authInfoNode.getTreeItem() - const tooltip = treeItem.tooltip as string - - assert.ok(tooltip.includes('Connected to SageMaker Unified Studio')) - assert.ok(tooltip.includes('Profile: test-profile')) - assert.ok(tooltip.includes('Region: us-west-2')) - assert.ok(tooltip.includes('Session: my-session-name')) - assert.ok(tooltip.includes('Role ARN: arn:aws:sts::123456789012:assumed-role/MyRole/my-session-name')) - assert.ok(tooltip.includes('Status: Connected')) - }) - - it('should not include session name or role ARN in tooltip when unavailable', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.getSessionName = sinon.stub().resolves(undefined) - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(undefined) - - const treeItem = await authInfoNode.getTreeItem() - const tooltip = treeItem.tooltip as string - - assert.ok(tooltip.includes('Connected to SageMaker Unified Studio')) - assert.ok(tooltip.includes('Profile: test-profile')) - assert.ok(tooltip.includes('Region: us-west-2')) - assert.ok(!tooltip.includes('Session:')) - assert.ok(!tooltip.includes('Role ARN:')) - assert.ok(tooltip.includes('Status: Connected')) - }) - - it('should handle getSessionName errors gracefully', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(true) - mockAuthProvider.getSessionName = sinon.stub().resolves(undefined) // Return undefined instead of rejecting - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(undefined) - - // Should not throw, just display without session name - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Connected with profile: test-profile') - assert.strictEqual(treeItem.description, 'us-west-2') - }) - - it('should display expired IAM connection with profile name', async function () { - mockAuthProvider.isConnected.returns(true) - mockAuthProvider.isConnectionValid.returns(false) - mockAuthProvider.getSessionName = sinon.stub().resolves('my-session-name') - - const treeItem = await authInfoNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Profile: test-profile (Expired) - Click to reauthenticate') - assert.strictEqual(treeItem.description, 'us-west-2') - - // Check icon - assert.ok(treeItem.iconPath instanceof vscode.ThemeIcon) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'warning') - - // Should have reauthenticate command - assert.ok(treeItem.command) - assert.strictEqual(treeItem.command.command, 'aws.smus.reauthenticate') - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.test.ts deleted file mode 100644 index d1b05a547e..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode.test.ts +++ /dev/null @@ -1,110 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioComputeNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode' -import { SageMakerUnifiedStudioProjectNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' -import { SagemakerClient } from '../../../../shared/clients/sagemaker' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import * as setContext from '../../../../shared/vscode/setContext' - -describe('SageMakerUnifiedStudioComputeNode', function () { - let computeNode: SageMakerUnifiedStudioComputeNode - let mockParent: SageMakerUnifiedStudioProjectNode - let mockExtensionContext: vscode.ExtensionContext - let mockAuthProvider: SmusAuthenticationProvider - let mockSagemakerClient: SagemakerClient - - beforeEach(function () { - mockParent = { - getProject: sinon.stub(), - } as any - - mockExtensionContext = { - subscriptions: [], - extensionUri: vscode.Uri.file('/test'), - } as any - - mockAuthProvider = {} as any - mockSagemakerClient = {} as any - - computeNode = new SageMakerUnifiedStudioComputeNode( - mockParent, - mockExtensionContext, - mockAuthProvider, - mockSagemakerClient - ) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties', function () { - assert.strictEqual(computeNode.id, 'smusComputeNode') - assert.strictEqual(computeNode.resource, computeNode) - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item', async function () { - const treeItem = await computeNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Compute') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'smusComputeNode') - assert.ok(treeItem.iconPath) - }) - }) - - describe('getChildren', function () { - it('returns empty array when no project is selected', async function () { - ;(mockParent.getProject as sinon.SinonStub).returns(undefined) - - const children = await computeNode.getChildren() - - assert.deepStrictEqual(children, []) - }) - - it('returns connection nodes and spaces node when project is selected (non-IAM mode)', async function () { - const mockProject = { id: 'project-123', name: 'Test Project' } - ;(mockParent.getProject as sinon.SinonStub).returns(mockProject) - - // Mock IAM mode to be false - sinon.stub(setContext, 'getContext').returns(false) - - const children = await computeNode.getChildren() - - assert.strictEqual(children.length, 3) - assert.strictEqual(children[0].id, 'Data warehouse') - assert.strictEqual(children[1].id, 'Data processing') - assert.ok(children[2] instanceof SageMakerUnifiedStudioSpacesParentNode) - }) - - it('returns only spaces node when project is selected (IAM mode)', async function () { - const mockProject = { id: 'project-123', name: 'Test Project' } - ;(mockParent.getProject as sinon.SinonStub).returns(mockProject) - - // Mock IAM mode to be true - sinon.stub(setContext, 'getContext').returns(true) - - const children = await computeNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0] instanceof SageMakerUnifiedStudioSpacesParentNode) - }) - }) - - describe('getParent', function () { - it('returns parent node', function () { - const parent = computeNode.getParent() - assert.strictEqual(parent, mockParent) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.test.ts deleted file mode 100644 index a85d63302a..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioConnectionNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode' -import { SageMakerUnifiedStudioConnectionParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode' -import { ConnectionType, ConnectionSummary } from '@aws-sdk/client-datazone' -import { getLogger } from '../../../../shared/logger/logger' - -describe('SageMakerUnifiedStudioConnectionNode', function () { - let connectionNode: SageMakerUnifiedStudioConnectionNode - let mockParent: sinon.SinonStubbedInstance - - const mockRedshiftConnection: ConnectionSummary = { - connectionId: 'conn-1', - name: 'Test Redshift Connection', - type: ConnectionType.REDSHIFT, - environmentId: 'env-1', - domainId: 'domain-1', - domainUnitId: 'unit-1', - physicalEndpoints: [], - props: { - redshiftProperties: { - jdbcUrl: 'jdbc:redshift://test-cluster:5439/testdb', - }, - }, - } - - const mockSparkConnection: ConnectionSummary = { - connectionId: 'conn-2', - name: 'Test Spark Connection', - type: ConnectionType.SPARK, - environmentId: 'env-2', - domainId: 'domain-2', - domainUnitId: 'unit-2', - physicalEndpoints: [], - props: { - sparkGlueProperties: { - glueVersion: '4.0', - workerType: 'G.1X', - numberOfWorkers: 2, - idleTimeout: 30, - }, - }, - } - - beforeEach(function () { - mockParent = {} as any - sinon.stub(getLogger(), 'debug') - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties for Redshift connection', function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection) - - assert.strictEqual(connectionNode.id, 'Test Redshift Connection') - assert.strictEqual(connectionNode.resource, connectionNode) - assert.strictEqual(connectionNode.contextValue, 'SageMakerUnifiedStudioConnectionNode') - }) - - it('creates instance with empty id when connection name is undefined', function () { - const connectionWithoutName = { ...mockRedshiftConnection, name: undefined } - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, connectionWithoutName) - - assert.strictEqual(connectionNode.id, '') - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item for Redshift connection', async function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection) - - const treeItem = await connectionNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Test Redshift Connection') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.strictEqual(treeItem.contextValue, 'SageMakerUnifiedStudioConnectionNode') - assert.ok(treeItem.tooltip instanceof vscode.MarkdownString) - }) - - it('returns correct tree item for Spark connection', async function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockSparkConnection) - - const treeItem = await connectionNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Test Spark Connection') - assert.ok(treeItem.tooltip instanceof vscode.MarkdownString) - }) - }) - - describe('tooltip generation', function () { - it('generates correct tooltip for Redshift connection', async function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection) - - const treeItem = await connectionNode.getTreeItem() - const tooltip = (treeItem.tooltip as vscode.MarkdownString).value - - assert(tooltip.includes('REDSHIFT')) - assert(tooltip.includes('env-1')) - assert(tooltip.includes('jdbc:redshift://test-cluster:5439/testdb')) - }) - - it('generates correct tooltip for Spark connection', async function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockSparkConnection) - - const treeItem = await connectionNode.getTreeItem() - const tooltip = (treeItem.tooltip as vscode.MarkdownString).value - - assert(tooltip.includes('SPARK')) - assert(tooltip.includes('4.0')) - assert(tooltip.includes('G.1X')) - assert(tooltip.includes('2')) - assert(tooltip.includes('30')) - }) - - it('generates empty tooltip for unknown connection type', async function () { - const unknownConnection = { ...mockRedshiftConnection, type: 'UNKNOWN' as ConnectionType } - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, unknownConnection) - - const treeItem = await connectionNode.getTreeItem() - const tooltip = (treeItem.tooltip as vscode.MarkdownString).value - - assert.strictEqual(tooltip, '') - }) - }) - - describe('getParent', function () { - it('returns the parent node', function () { - connectionNode = new SageMakerUnifiedStudioConnectionNode(mockParent as any, mockRedshiftConnection) - - const parent = connectionNode.getParent() - - assert.strictEqual(parent, mockParent) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.test.ts deleted file mode 100644 index 18778c5266..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode.test.ts +++ /dev/null @@ -1,254 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioConnectionParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionParentNode' -import { SageMakerUnifiedStudioComputeNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode' -import { SageMakerUnifiedStudioConnectionNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioConnectionNode' -import { DataZoneClient } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' - -import { ConnectionType, ListConnectionsCommandOutput, ConnectionSummary } from '@aws-sdk/client-datazone' -import { getLogger } from '../../../../shared/logger/logger' - -describe('SageMakerUnifiedStudioConnectionParentNode', function () { - let connectionParentNode: SageMakerUnifiedStudioConnectionParentNode - let mockComputeNode: sinon.SinonStubbedInstance - - let mockDataZoneClient: sinon.SinonStubbedInstance - - const mockProject = { - id: 'project-123', - domainId: 'domain-123', - } - - const mockConnectionsOutput: ListConnectionsCommandOutput = { - items: [ - { - connectionId: 'conn-1', - name: 'Test Connection 1', - type: ConnectionType.REDSHIFT, - environmentId: 'env-1', - } as ConnectionSummary, - { - connectionId: 'conn-2', - name: 'Test Connection 2', - type: ConnectionType.REDSHIFT, - environmentId: 'env-2', - } as ConnectionSummary, - ], - $metadata: {}, - } - - beforeEach(function () { - // Create mock objects - mockDataZoneClient = { - fetchConnections: sinon.stub(), - } as any - - mockComputeNode = { - authProvider: { - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getDomainId: sinon.stub().returns('domain-123'), - getDomainRegion: sinon.stub().returns('us-east-1'), - } as any, - parent: { - project: mockProject, - } as any, - } as any - - // Stub static methods - sinon.stub(DataZoneClient, 'createWithCredentials').resolves(mockDataZoneClient as any) - sinon.stub(getLogger(), 'debug') - - connectionParentNode = new SageMakerUnifiedStudioConnectionParentNode( - mockComputeNode as any, - ConnectionType.REDSHIFT, - 'Data warehouse' - ) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties', function () { - assert.strictEqual(connectionParentNode.id, 'Data warehouse') - assert.strictEqual(connectionParentNode.resource, connectionParentNode) - assert.strictEqual(connectionParentNode.contextValue, 'SageMakerUnifiedStudioConnectionParentNode') - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item', async function () { - const treeItem = await connectionParentNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Data warehouse') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(treeItem.contextValue, 'SageMakerUnifiedStudioConnectionParentNode') - }) - }) - - describe('getChildren', function () { - it('returns connection nodes when connections exist', async function () { - mockDataZoneClient.fetchConnections.resolves(mockConnectionsOutput) - - const children = await connectionParentNode.getChildren() - - assert.strictEqual(children.length, 2) - assert(children[0] instanceof SageMakerUnifiedStudioConnectionNode) - assert(children[1] instanceof SageMakerUnifiedStudioConnectionNode) - - // Verify fetchConnections was called with correct parameters - assert( - mockDataZoneClient.fetchConnections.calledOnceWith( - mockProject.domainId, - mockProject.id, - ConnectionType.REDSHIFT - ) - ) - }) - - it('returns no connections node when no connections exist', async function () { - const emptyOutput: ListConnectionsCommandOutput = { items: [], $metadata: {} } - mockDataZoneClient.fetchConnections.resolves(emptyOutput) - - const children = await connectionParentNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoConnections') - const treeItem = await children[0].getTreeItem() - assert.strictEqual(treeItem.label, '[No connections found]') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - - it('returns no connections node when connections items is undefined', async function () { - const undefinedOutput: ListConnectionsCommandOutput = { items: undefined, $metadata: {} } - mockDataZoneClient.fetchConnections.resolves(undefinedOutput) - - const children = await connectionParentNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoConnections') - }) - - it('handles missing project information gracefully', async function () { - const nodeWithoutProject = new SageMakerUnifiedStudioConnectionParentNode( - { - authProvider: { - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getDomainId: sinon.stub().returns('domain-123'), - getDomainRegion: sinon.stub().returns('us-east-1'), - } as any, - parent: { - project: undefined, - } as any, - } as any, - ConnectionType.SPARK, - 'Data processing' - ) - - mockDataZoneClient.fetchConnections.resolves({ items: [], $metadata: {} }) - - const children = await nodeWithoutProject.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoConnections') - assert(mockDataZoneClient.fetchConnections.calledOnceWith(undefined, undefined, ConnectionType.SPARK)) - }) - }) - - describe('getParent', function () { - it('returns the parent compute node', function () { - const parent = connectionParentNode.getParent() - assert.strictEqual(parent, mockComputeNode) - }) - }) - - describe('error handling', function () { - it('handles DataZoneClient.getInstance error', async function () { - sinon.restore() - sinon.stub(DataZoneClient, 'createWithCredentials').rejects(new Error('Client error')) - sinon.stub(getLogger(), 'debug') - - try { - await connectionParentNode.getChildren() - assert.fail('Expected error to be thrown') - } catch (error) { - assert.strictEqual((error as Error).message, 'Client error') - } - }) - - it('handles fetchConnections error', async function () { - mockDataZoneClient.fetchConnections.rejects(new Error('Fetch error')) - - try { - await connectionParentNode.getChildren() - assert.fail('Expected error to be thrown') - } catch (error) { - assert.strictEqual((error as Error).message, 'Fetch error') - } - }) - }) - - describe('connections property', function () { - it('sets connections property after getChildren call', async function () { - mockDataZoneClient.fetchConnections.resolves(mockConnectionsOutput) - - await connectionParentNode.getChildren() - - assert.strictEqual(connectionParentNode.connections, mockConnectionsOutput) - }) - }) - - describe('different connection types', function () { - it('works with SPARK connection type', async function () { - const sparkNode = new SageMakerUnifiedStudioConnectionParentNode( - mockComputeNode as any, - ConnectionType.SPARK, - 'Spark connections' - ) - - const sparkOutput = { - items: [ - { - connectionId: 'spark-1', - name: 'Spark Connection', - type: ConnectionType.SPARK, - environmentId: 'env-spark', - } as ConnectionSummary, - ], - $metadata: {}, - } - - mockDataZoneClient.fetchConnections.resolves(sparkOutput) - - const children = await sparkNode.getChildren() - - assert.strictEqual(children.length, 1) - assert( - mockDataZoneClient.fetchConnections.calledWith( - mockProject.domainId, - mockProject.id, - ConnectionType.SPARK - ) - ) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.test.ts deleted file mode 100644 index 3d3ad01108..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioDataNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode' -import { SageMakerUnifiedStudioProjectNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode' -import { DataZoneClient, DataZoneProject } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import * as s3Strategy from '../../../../sagemakerunifiedstudio/explorer/nodes/s3Strategy' -import * as redshiftStrategy from '../../../../sagemakerunifiedstudio/explorer/nodes/redshiftStrategy' -import * as lakehouseStrategy from '../../../../sagemakerunifiedstudio/explorer/nodes/lakehouseStrategy' -import * as setContext from '../../../../shared/vscode/setContext' - -describe('SageMakerUnifiedStudioDataNode', function () { - let sandbox: sinon.SinonSandbox - let dataNode: SageMakerUnifiedStudioDataNode - let mockParent: sinon.SinonStubbedInstance - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockAuthProvider: sinon.SinonStubbedInstance - let mockProjectCredentialProvider: any - - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - domainId: 'domain-123', - } - - const mockCredentials = { - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - $metadata: {}, - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockParent = { - getProject: sandbox.stub().returns(mockProject), - } as any - - mockProjectCredentialProvider = { - getCredentials: sandbox.stub().resolves(mockCredentials), - } - - mockAuthProvider = { - getProjectCredentialProvider: sandbox.stub().resolves(mockProjectCredentialProvider), - getConnectionCredentialsProvider: sandbox.stub().resolves(mockProjectCredentialProvider), - getDomainRegion: sandbox.stub().returns('us-east-1'), - getDomainId: sandbox.stub().returns('domain-123'), - getDerCredentialsProvider: sandbox.stub().resolves({ - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - } as any - - mockDataZoneClient = { - getInstance: sandbox.stub(), - getProjectDefaultEnvironmentCreds: sandbox.stub(), - listConnections: sandbox.stub(), - getConnection: sandbox.stub(), - getRegion: sandbox.stub().returns('us-east-1'), - } as any - - sandbox.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - sandbox.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - sandbox.stub(s3Strategy, 'createS3ConnectionNode').returns({ - id: 's3-node', - getChildren: () => Promise.resolve([]), - getTreeItem: () => ({}) as any, - getParent: () => undefined, - } as any) - sandbox.stub(s3Strategy, 'createS3AccessGrantNodes').resolves([]) - sandbox.stub(redshiftStrategy, 'createRedshiftConnectionNode').returns({ - id: 'redshift-node', - getChildren: () => Promise.resolve([]), - getTreeItem: () => ({}) as any, - getParent: () => undefined, - } as any) - sandbox.stub(lakehouseStrategy, 'createLakehouseConnectionNode').returns({ - id: 'lakehouse-node', - getChildren: () => Promise.resolve([]), - getTreeItem: () => ({}) as any, - getParent: () => undefined, - } as any) - - dataNode = new SageMakerUnifiedStudioDataNode(mockParent as any) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('constructor', function () { - it('should initialize with correct properties', function () { - assert.strictEqual(dataNode.id, 'smusDataExplorer') - assert.deepStrictEqual(dataNode.resource, {}) - }) - - it('should initialize with provided children', function () { - const initialChildren = [{ id: 'child1' } as any] - const nodeWithChildren = new SageMakerUnifiedStudioDataNode(mockParent as any, initialChildren) - // Children should be cached - assert.ok(nodeWithChildren) - }) - }) - - describe('getTreeItem', function () { - it('should return correct tree item', function () { - const treeItem = dataNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Data') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(treeItem.contextValue, 'dataFolder') - }) - }) - - describe('getParent', function () { - it('should return parent node', function () { - assert.strictEqual(dataNode.getParent(), mockParent) - }) - }) - - describe('getChildren', function () { - it('should return cached children if available', async function () { - const initialChildren = [{ id: 'cached' } as any] - const nodeWithCache = new SageMakerUnifiedStudioDataNode(mockParent as any, initialChildren) - - const children = await nodeWithCache.getChildren() - assert.strictEqual(children, initialChildren) - }) - - it('should return error node when no project available', async function () { - mockParent.getProject.returns(undefined) - - const children = await dataNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.startsWith('smusDataExplorer-error-project-')) - }) - - it('should return error node when credentials are missing', async function () { - mockProjectCredentialProvider.getCredentials.resolves(undefined) - - const children = await dataNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.startsWith('smusDataExplorer-error-connections-')) - }) - - it('should return placeholder when no connections found', async function () { - mockDataZoneClient.listConnections.resolves([]) - - const children = await dataNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].resource, '[No data found]') - }) - - it('should create Bucket parent node and Redshift nodes for connections', async function () { - const mockConnections = [ - { connectionId: 's3-conn', type: 'S3', name: 's3-connection' }, - { connectionId: 'redshift-conn', type: 'REDSHIFT', name: 'redshift-connection' }, - ] - - // Mock IAM mode to be false so Redshift connections are included - sandbox.stub(setContext, 'getContext').returns(false) - - mockDataZoneClient.listConnections.resolves(mockConnections as any) - mockDataZoneClient.getConnection - .onFirstCall() - .resolves({ - location: { awsRegion: 'us-east-1', awsAccountId: '' }, - connectionCredentials: mockCredentials, - connectionId: '', - name: '', - type: '', - domainId: '', - projectId: '', - }) - .onSecondCall() - .resolves({ - location: { awsRegion: 'us-east-1', awsAccountId: '' }, - connectionCredentials: mockCredentials, - connectionId: '', - name: '', - type: '', - domainId: '', - projectId: '', - }) - - const children = await dataNode.getChildren() - - // Should have Bucket parent node and Redshift node - assert.strictEqual(children.length, 2) - - // Check for Bucket parent node - const bucketNode = children.find((child) => child.id === 'bucket-parent') - assert.ok(bucketNode, 'Should have bucket parent node') - - // Verify Bucket node has correct tree item - const bucketTreeItem = await bucketNode!.getTreeItem() - assert.strictEqual(bucketTreeItem.label, 'Buckets') - assert.strictEqual(bucketTreeItem.contextValue, 'bucketFolder') - - // Verify S3 nodes are created when Bucket node is expanded - await bucketNode!.getChildren!() - assert.ok((s3Strategy.createS3ConnectionNode as sinon.SinonStub).calledOnce) - - assert.ok((redshiftStrategy.createRedshiftConnectionNode as sinon.SinonStub).calledOnce) - }) - - it('should handle connection detail errors gracefully', async function () { - const mockConnections = [{ connectionId: 's3-conn', type: 'S3', name: 's3-connection' }] - - mockDataZoneClient.listConnections.resolves(mockConnections as any) - - const children = await dataNode.getChildren() - - // Should have Bucket parent node even with connection errors - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'bucket-parent') - - // Mock connection credentials provider to reject when bucket is expanded - mockAuthProvider.getConnectionCredentialsProvider.rejects(new Error('Connection error')) - - // Error should occur when expanding the Bucket node - const bucketChildren = await children[0].getChildren!() - assert.strictEqual(bucketChildren.length, 1) - assert.ok(bucketChildren[0].id.startsWith('smusDataExplorer-error-s3-')) - }) - - it('should return error node when general error occurs', async function () { - mockAuthProvider.getProjectCredentialProvider.rejects(new Error('General error')) - - const children = await dataNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0].id.startsWith('smusDataExplorer-error-connections-')) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.test.ts deleted file mode 100644 index 8c71b10206..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode.test.ts +++ /dev/null @@ -1,344 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioProjectNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode' -import { DataZoneClient, DataZoneProject } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { getLogger } from '../../../../shared/logger/logger' -import { telemetry } from '../../../../shared/telemetry/telemetry' -import { SagemakerClient } from '../../../../shared/clients/sagemaker' -import { SageMakerUnifiedStudioDataNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioDataNode' -import { SageMakerUnifiedStudioComputeNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode' -import * as vscodeUtils from '../../../../shared/vscode/setContext' -import { createMockExtensionContext } from '../../testUtils' - -describe('SageMakerUnifiedStudioProjectNode', function () { - let projectNode: SageMakerUnifiedStudioProjectNode - let mockDataZoneClient: sinon.SinonStubbedInstance - - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - description: 'Test Description', - domainId: 'domain-123', - } - - beforeEach(function () { - // Create mock parent - const mockParent = {} as any - - // Create mock auth provider - const mockAuthProvider = { - activeConnection: { domainId: 'test-domain', ssoRegion: 'us-west-2' }, - invalidateAllProjectCredentialsInCache: sinon.stub(), - getProjectCredentialProvider: sinon.stub(), - getDomainRegion: sinon.stub().returns('us-west-2'), - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns('test-domain'), - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - } as any - - // Create mock extension context - const mockExtensionContext = createMockExtensionContext() - - projectNode = new SageMakerUnifiedStudioProjectNode(mockParent, mockAuthProvider, mockExtensionContext) - - sinon.stub(getLogger(), 'info') - sinon.stub(getLogger(), 'warn') - - // Stub telemetry - sinon.stub(telemetry, 'record') - - // Create mock DataZone client - mockDataZoneClient = { - getProjectDefaultEnvironmentCreds: sinon.stub(), - getUserId: sinon.stub(), - fetchAllProjectMemberships: sinon.stub(), - getDomainId: sinon.stub().returns('test-domain-id'), - getToolingEnvironmentId: sinon.stub(), - getEnvironmentDetails: sinon.stub(), - getToolingEnvironment: sinon.stub(), - } as any - - // Stub DataZoneClient static methods - sinon.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - - // Stub SagemakerClient constructor - sinon.stub(SagemakerClient.prototype, 'dispose') - - // Stub child node constructors to prevent actual instantiation - sinon.stub(SageMakerUnifiedStudioDataNode.prototype, 'constructor' as any).returns({}) - sinon.stub(SageMakerUnifiedStudioComputeNode.prototype, 'constructor' as any).returns({}) - - // Stub getContext to return false for SMUS space environment - sinon.stub(vscodeUtils, 'getContext').returns(false) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties', function () { - assert.strictEqual(projectNode.id, 'smusProjectNode') - assert.strictEqual(projectNode.resource, projectNode) - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item when no project is selected', async function () { - const treeItem = await projectNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Select a project') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'smusProjectSelectPicker') - assert.ok(treeItem.command) - assert.strictEqual(treeItem.command?.command, 'aws.smus.projectView') - }) - - it('returns correct tree item when project is selected', async function () { - await projectNode.setProject(mockProject) - const treeItem = await projectNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Project: ' + mockProject.name) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'smusSelectedProject') - assert.strictEqual(treeItem.tooltip, `Project: ${mockProject.name}\nID: ${mockProject.id}`) - }) - }) - - describe('getParent', function () { - it('returns parent node', function () { - const parent = projectNode.getParent() - assert.ok(parent) - }) - }) - - describe('setProject', function () { - it('updates the project and calls cleanupProjectResources', async function () { - const cleanupSpy = sinon.spy(projectNode as any, 'cleanupProjectResources') - await projectNode.setProject(mockProject) - assert.strictEqual(projectNode['project'], mockProject) - assert(cleanupSpy.calledOnce) - }) - }) - - describe('clearProject', function () { - it('clears the project, calls cleanupProjectResources and fires change event', async function () { - await projectNode.setProject(mockProject) - const cleanupSpy = sinon.spy(projectNode as any, 'cleanupProjectResources') - const emitterSpy = sinon.spy(projectNode['onDidChangeEmitter'], 'fire') - - await projectNode.clearProject() - - assert.strictEqual(projectNode['project'], undefined) - assert(cleanupSpy.calledOnce) - assert(emitterSpy.calledOnce) - }) - }) - - describe('getProject', function () { - it('returns undefined when no project is set', function () { - assert.strictEqual(projectNode.getProject(), undefined) - }) - - it('returns project when set', async function () { - await projectNode.setProject(mockProject) - assert.strictEqual(projectNode.getProject(), mockProject) - }) - }) - - describe('refreshNode', function () { - it('fires change event', async function () { - const emitterSpy = sinon.spy(projectNode['onDidChangeEmitter'], 'fire') - await projectNode.refreshNode() - assert(emitterSpy.calledOnce) - }) - }) - - describe('getChildren', function () { - it('returns empty array when no project is selected', async function () { - const children = await projectNode.getChildren() - assert.deepStrictEqual(children, []) - }) - - it('returns data and compute nodes when project is selected and user has access', async function () { - await projectNode.setProject(mockProject) - const mockCredProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().resolves(mockCredProvider) - - // Mock getToolingEnvironment method - mockDataZoneClient.getToolingEnvironment.resolves({ - id: 'env-123', - awsAccountRegion: 'us-east-1', - projectId: undefined, - domainId: undefined, - createdBy: undefined, - name: undefined, - provider: undefined, - $metadata: {}, - }) - - const children = await projectNode.getChildren() - assert.strictEqual(children.length, 2) - }) - - it('returns access denied message when user does not have project access', async function () { - await projectNode.setProject(mockProject) - - // Mock access check to return false by throwing AccessDeniedException - const accessError = new Error('Access denied') - accessError.name = 'AccessDeniedException' - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().rejects(accessError) - - const children = await projectNode.getChildren() - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusProjectAccessDenied') - - const treeItem = await children[0].getTreeItem() - assert.strictEqual(treeItem.label, 'You do not have access to this project. Contact your administrator.') - }) - - it('throws error when initializeSagemakerClient fails', async function () { - await projectNode.setProject(mockProject) - const credError = new Error('Failed to initialize SageMaker client') - - // First call succeeds for access check, second call fails for initializeSagemakerClient - const mockCredProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - projectNode['authProvider'].getProjectCredentialProvider = sinon - .stub() - .onFirstCall() - .resolves(mockCredProvider) - .onSecondCall() - .rejects(credError) - - // Mock getToolingEnvironment method - mockDataZoneClient.getToolingEnvironment.resolves({ - id: 'env-123', - awsAccountRegion: 'us-east-1', - projectId: undefined, - domainId: undefined, - createdBy: undefined, - name: undefined, - provider: undefined, - $metadata: {}, - }) - - await assert.rejects(async () => await projectNode.getChildren(), credError) - }) - }) - - describe('initializeSagemakerClient', function () { - it('throws error when no project is selected', async function () { - await assert.rejects( - async () => await projectNode['initializeSagemakerClient']('us-east-1'), - /No project selected for initializing SageMaker client/ - ) - }) - - it('creates SagemakerClient with project credentials', async function () { - await projectNode.setProject(mockProject) - const mockCredProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().resolves(mockCredProvider) - - const client = await projectNode['initializeSagemakerClient']('us-east-1') - assert.ok(client instanceof SagemakerClient) - assert( - (projectNode['authProvider'].getProjectCredentialProvider as sinon.SinonStub).calledWith(mockProject.id) - ) - }) - }) - - describe('checkProjectCredsAccess', function () { - it('returns true when user has project access', async function () { - const mockCredProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().resolves(mockCredProvider) - - const hasAccess = await projectNode['checkProjectCredsAccess']('project-123') - assert.strictEqual(hasAccess, true) - }) - - it('returns false when user does not have project access', async function () { - const accessError = new Error('Access denied') - accessError.name = 'AccessDeniedException' - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().rejects(accessError) - - const hasAccess = await projectNode['checkProjectCredsAccess']('project-123') - assert.strictEqual(hasAccess, false) - }) - - it('throws error when getCredentials fails', async function () { - const mockCredProvider = { - getCredentials: sinon.stub().rejects(new Error('Credentials error')), - } - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().resolves(mockCredProvider) - - await assert.rejects( - async () => await projectNode['checkProjectCredsAccess']('project-123'), - /Credentials error/ - ) - }) - - it('throws error when access check throws non-AccessDeniedException error', async function () { - projectNode['authProvider'].getProjectCredentialProvider = sinon.stub().rejects(new Error('Other error')) - - await assert.rejects(async () => await projectNode['checkProjectCredsAccess']('project-123'), /Other error/) - }) - }) - - describe('cleanupProjectResources', function () { - it('invalidates credentials and disposes existing sagemaker client', async function () { - // Set up existing sagemaker client with mock - const mockClient = { dispose: sinon.stub() } as any - projectNode['sagemakerClient'] = mockClient - - await projectNode['cleanupProjectResources']() - - assert((projectNode['authProvider'].invalidateAllProjectCredentialsInCache as sinon.SinonStub).calledOnce) - assert(mockClient.dispose.calledOnce) - assert.strictEqual(projectNode['sagemakerClient'], undefined) - }) - - it('handles case when no sagemaker client exists', async function () { - projectNode['sagemakerClient'] = undefined - - await projectNode['cleanupProjectResources']() - - assert((projectNode['authProvider'].invalidateAllProjectCredentialsInCache as sinon.SinonStub).calledOnce) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.test.ts deleted file mode 100644 index fc4c25ffa5..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode.test.ts +++ /dev/null @@ -1,1174 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { - SageMakerUnifiedStudioRootNode, - selectSMUSProject, -} from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioRootNode' -import { SageMakerUnifiedStudioProjectNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioProjectNode' -import { DataZoneClient, DataZoneProject } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { SageMakerUnifiedStudioAuthInfoNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioAuthInfoNode' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import * as pickerPrompter from '../../../../shared/ui/pickerPrompter' -import { getTestWindow } from '../../../shared/vscode/window' -import { assertTelemetry } from '../../../../../src/test/testUtil' -import { createMockExtensionContext, createMockUnauthenticatedAuthProvider } from '../../testUtils' -import { DataZoneCustomClientHelper } from '../../../../sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper' -import { DefaultStsClient } from '../../../../shared/clients/stsClient' -import { SmusUtils, SmusErrorCodes } from '../../../../sagemakerunifiedstudio/shared/smusUtils' -import { ToolkitError } from '../../../../shared/errors' -import * as utils from '../../../../sagemakerunifiedstudio/explorer/nodes/utils' -import * as setContextModule from '../../../../shared/vscode/setContext' - -describe('SmusRootNode', function () { - let rootNode: SageMakerUnifiedStudioRootNode - let mockDataZoneClient: sinon.SinonStubbedInstance - - const testDomainId = 'test-domain-123' - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - description: 'Test Description', - domainId: testDomainId, - } - - /** - * Helper function to verify login and learn more nodes - */ - async function verifyLoginAndLearnMoreNodes(children: any[]) { - assert.strictEqual(children.length, 2) - assert.strictEqual(children[0].id, 'smusLogin') - assert.strictEqual(children[1].id, 'smusLearnMore') - - // Check login node - const loginTreeItem = await children[0].getTreeItem() - assert.strictEqual(loginTreeItem.label, 'Sign in to get started') - assert.strictEqual(loginTreeItem.contextValue, 'sageMakerUnifiedStudioLogin') - assert.deepStrictEqual(loginTreeItem.command, { - command: 'aws.smus.login', - title: 'Sign in to SageMaker Unified Studio', - }) - - // Check learn more node - const learnMoreTreeItem = await children[1].getTreeItem() - assert.strictEqual(learnMoreTreeItem.label, 'Learn more about SageMaker Unified Studio') - assert.strictEqual(learnMoreTreeItem.contextValue, 'sageMakerUnifiedStudioLearnMore') - assert.deepStrictEqual(learnMoreTreeItem.command, { - command: 'aws.smus.learnMore', - title: 'Learn more about SageMaker Unified Studio', - }) - } - - beforeEach(function () { - // Create mock extension context - const mockExtensionContext = createMockExtensionContext() - - // Create a mock auth provider - const mockAuthProvider = { - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(true), - activeConnection: { domainId: testDomainId, ssoRegion: 'us-west-2' }, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - } as any - - rootNode = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - - // Mock domain ID is handled by the mock auth provider - - // Create mock DataZone client - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - listProjects: sinon.stub(), - } as any - - // Stub DataZoneClient static methods - sinon.stub(DataZoneClient, 'createWithCredentials').returns(mockDataZoneClient as any) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('should initialize id and resource properties', function () { - // Create a mock auth provider - const mockAuthProvider = { - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(true), - activeConnection: { domainId: testDomainId, ssoRegion: 'us-west-2' }, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - } as any - - const mockExtensionContext = createMockExtensionContext() - - const node = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - assert.strictEqual(node.id, 'smusRootNode') - assert.strictEqual(node.resource, node) - assert.ok(node.getAuthInfoNode() instanceof SageMakerUnifiedStudioAuthInfoNode) - assert.ok(node.getProjectSelectNode() instanceof SageMakerUnifiedStudioProjectNode) - assert.strictEqual(typeof node.onDidChangeTreeItem, 'function') - assert.strictEqual(typeof node.onDidChangeChildren, 'function') - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item when authenticated', async function () { - const treeItem = rootNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'SageMaker Unified Studio') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'sageMakerUnifiedStudioRoot') - assert.strictEqual(treeItem.description, 'Connected') - assert.ok(treeItem.iconPath) - }) - - it('returns correct tree item when not authenticated', async function () { - // Create a mock auth provider for unauthenticated state - const mockAuthProvider = createMockUnauthenticatedAuthProvider() - const mockExtensionContext = createMockExtensionContext() - - const unauthenticatedNode = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - const treeItem = unauthenticatedNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'SageMaker Unified Studio') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'sageMakerUnifiedStudioRoot') - assert.strictEqual(treeItem.description, 'Not authenticated') - assert.ok(treeItem.iconPath) - }) - }) - - describe('getChildren', function () { - it('returns login node when not authenticated (empty domain ID)', async function () { - // Create a mock auth provider for unauthenticated state - const mockAuthProvider = createMockUnauthenticatedAuthProvider() - const mockExtensionContext = createMockExtensionContext() - - const unauthenticatedNode = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - const children = await unauthenticatedNode.getChildren() - await verifyLoginAndLearnMoreNodes(children) - }) - - it('returns login node when DataZone client throws error', async function () { - // Create a mock auth provider that throws an error - const mockAuthProvider = { - isConnected: sinon.stub().throws(new Error('Auth provider error')), - isConnectionValid: sinon.stub().returns(false), - activeConnection: undefined, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - } as any - - const mockExtensionContext = createMockExtensionContext() - - const errorNode = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - const children = await errorNode.getChildren() - await verifyLoginAndLearnMoreNodes(children) - }) - - it('returns root nodes when authenticated', async function () { - mockDataZoneClient.listProjects.resolves({ projects: [mockProject], nextToken: undefined }) - - const children = await rootNode.getChildren() - - assert.strictEqual(children.length, 2) - assert.ok(children[0] instanceof SageMakerUnifiedStudioAuthInfoNode) - assert.ok(children[1] instanceof SageMakerUnifiedStudioProjectNode) - // The first child is the auth info node, the second is the project node - assert.strictEqual(children[0].id, 'smusAuthInfoNode') - assert.strictEqual(children[1].id, 'smusProjectNode') - - assert.strictEqual(children.length, 2) - assert.strictEqual(children[1].id, 'smusProjectNode') - - const treeItem = await children[1].getTreeItem() - assert.strictEqual(treeItem.label, 'Select a project') - assert.strictEqual(treeItem.contextValue, 'smusProjectSelectPicker') - assert.deepStrictEqual(treeItem.command, { - command: 'aws.smus.projectView', - title: 'Select Project', - arguments: [children[1]], - }) - }) - - it('returns auth info node when connection is expired', async function () { - // Create a mock auth provider with expired connection - const mockAuthProvider = { - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(false), - activeConnection: { - type: 'sso', - domainId: testDomainId, - ssoRegion: 'us-west-2', - domainUrl: 'https://test-domain.datazone.aws.amazon.com', - scopes: ['datazone:domain:access'], - }, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - showReauthenticationPrompt: sinon.stub(), - } as any - - const mockExtensionContext = createMockExtensionContext() - - const expiredNode = new SageMakerUnifiedStudioRootNode(mockAuthProvider, mockExtensionContext) - const children = await expiredNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.ok(children[0] instanceof SageMakerUnifiedStudioAuthInfoNode) - assert.ok(mockAuthProvider.showReauthenticationPrompt.calledOnce) - }) - }) - - describe('refresh', function () { - it('fires change events', function () { - const onDidChangeTreeItemSpy = sinon.spy() - const onDidChangeChildrenSpy = sinon.spy() - - rootNode.onDidChangeTreeItem(onDidChangeTreeItemSpy) - rootNode.onDidChangeChildren(onDidChangeChildrenSpy) - - rootNode.refresh() - - assert(onDidChangeTreeItemSpy.calledOnce) - assert(onDidChangeChildrenSpy.calledOnce) - }) - }) -}) - -describe('SelectSMUSProject', function () { - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockProjectNode: sinon.SinonStubbedInstance - let createQuickPickStub: sinon.SinonStub - let executeCommandStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - let createDZClientStub: sinon.SinonStub - - const testDomainId = 'test-domain-123' - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - description: 'Test Description', - domainId: testDomainId, - updatedAt: new Date(), - } - - const mockProject2: DataZoneProject = { - id: 'project-456', - name: 'Another Project', - description: 'Another Description', - domainId: testDomainId, - updatedAt: new Date(Date.now() - 86400000), // 1 day ago - } - - beforeEach(function () { - // Create mock DataZone client - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - listProjects: sinon.stub(), - fetchAllProjects: sinon.stub(), - } as any - - // Create mock project node - mockProjectNode = { - setProject: sinon.stub(), - getProject: sinon.stub().returns(undefined), - project: undefined, - } as any - - // Stub createDZClientBaseOnDomainMode to return our mock client - createDZClientStub = sinon.stub() - createDZClientStub.resolves(mockDataZoneClient) - sinon.replace(utils, 'createDZClientBaseOnDomainMode', createDZClientStub) - - // Stub SmusAuthenticationProvider - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns({ - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(true), - activeConnection: { domainId: testDomainId, ssoRegion: 'us-west-2' }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - } as any) - - // Stub getContext to return false for IAM mode by default (non-IAM mode) - getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(false) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - - // Stub quickPick - return the project directly (not wrapped in an item) - const mockQuickPick = { - prompt: sinon.stub().resolves(mockProject), - } - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - - // Stub vscode.commands.executeCommand - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sinon.restore() - }) - - it('fetches all projects and sets the project for first time', async function () { - mockDataZoneClient.fetchAllProjects.resolves([mockProject, mockProject2]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, mockProject) - assert.ok(mockDataZoneClient.fetchAllProjects.calledOnce) - assert.ok(mockDataZoneClient.fetchAllProjects.calledWith()) - assert.ok(createQuickPickStub.calledOnce) - assert.ok(mockProjectNode.setProject.calledOnce) - assert.ok(executeCommandStub.calledWith('aws.smus.rootView.refresh')) - assertTelemetry('smus_accessProject', { - result: 'Succeeded', - smusProjectId: mockProject.id, - }) - }) - - it('filters out GenerativeAIModelGovernanceProject', async function () { - const governanceProject: DataZoneProject = { - id: 'governance-123', - name: 'GenerativeAIModelGovernanceProject', - description: 'Governance project', - domainId: testDomainId, - updatedAt: new Date(), - } - - mockDataZoneClient.fetchAllProjects.resolves([mockProject, governanceProject, mockProject2]) - - await selectSMUSProject(mockProjectNode as any) - - // Verify that the governance project is filtered out - const quickPickCall = createQuickPickStub.getCall(0) - const items = quickPickCall.args[0] - assert.strictEqual(items.length, 2) // Should only have mockProject and mockProject2 - assert.ok(!items.some((item: any) => item.data.name === 'GenerativeAIModelGovernanceProject')) - }) - - it('handles no active connection', async function () { - sinon.restore() - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns({ - activeConnection: undefined, - getDomainId: sinon.stub().returns(undefined), - } as any) - - const result = await selectSMUSProject(mockProjectNode as any) - assert.strictEqual(result, undefined) - - assertTelemetry('smus_accessProject', { - result: 'Succeeded', - }) - }) - - it('fetches all projects and switches the current project', async function () { - mockProjectNode = { - setProject: sinon.stub(), - getProject: sinon.stub().returns(mockProject), - project: mockProject, - } as any - mockDataZoneClient.fetchAllProjects.resolves([mockProject, mockProject2]) - - // Stub quickPick to return mockProject2 for the second test - const mockQuickPick = { - prompt: sinon.stub().resolves(mockProject2), - } - createQuickPickStub.restore() // Remove the previous stub - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, mockProject2) - assert.ok(mockDataZoneClient.fetchAllProjects.calledOnce) - assert.ok(mockDataZoneClient.fetchAllProjects.calledWith()) - assert.ok(createQuickPickStub.calledOnce) - assert.ok(mockProjectNode.setProject.calledOnce) - assert.ok(executeCommandStub.calledWith('aws.smus.rootView.refresh')) - assertTelemetry('smus_accessProject', { - result: 'Succeeded', - smusProjectId: mockProject2.id, - }) - }) - - it('shows message when no projects found', async function () { - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok(!mockProjectNode.setProject.called) - }) - - it('handles API errors gracefully', async function () { - const error = new Error('API error') - mockDataZoneClient.fetchAllProjects.rejects(error) - - const result = await selectSMUSProject(mockProjectNode as any) - assert.strictEqual(result, undefined) - - assert.ok(!mockProjectNode.setProject.called) - assertTelemetry('smus_accessProject', { - result: 'Succeeded', - }) - }) - - it('handles case when user cancels project selection', async function () { - mockDataZoneClient.fetchAllProjects.resolves([mockProject, mockProject2]) - - // Make quickPick return undefined (user cancelled) - const mockQuickPick = { - prompt: sinon.stub().resolves(undefined), - } - createQuickPickStub.returns(mockQuickPick as any) - - const result = await selectSMUSProject(mockProjectNode as any) - - // Should return undefined - assert.strictEqual(result, undefined) - - // Verify project was not set - assert.ok(!mockProjectNode.setProject.called) - - // Verify refresh command was not called - assert.ok(!executeCommandStub.called) - }) - - it('handles empty projects list correctly', async function () { - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok(mockDataZoneClient.fetchAllProjects.calledOnce) - assert.ok(!mockProjectNode.setProject.called) - assert.ok(!executeCommandStub.called) - }) -}) - -describe('selectSMUSProject - Additional Tests', function () { - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockProjectNode: sinon.SinonStubbedInstance - let createQuickPickStub: sinon.SinonStub - let executeCommandStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - let createDZClientStub: sinon.SinonStub - - const testDomainId = 'test-domain-123' - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - description: 'Test Description', - domainId: testDomainId, - updatedAt: new Date(), - } - - beforeEach(function () { - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - fetchAllProjects: sinon.stub(), - } as any - - mockProjectNode = { - setProject: sinon.stub(), - } as any - - // Stub createDZClientBaseOnDomainMode to return our mock client - createDZClientStub = sinon.stub() - createDZClientStub.resolves(mockDataZoneClient) - sinon.replace(utils, 'createDZClientBaseOnDomainMode', createDZClientStub) - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns({ - activeConnection: { domainId: testDomainId, ssoRegion: 'us-west-2' }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - } as any) - - // Stub getContext to return false for IAM mode by default (non-IAM mode) - getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(false) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - - const mockQuickPick = { - prompt: sinon.stub().resolves(mockProject), - } - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand') - }) - - afterEach(function () { - sinon.restore() - }) - - it('handles access denied error gracefully', async function () { - const accessDeniedError = new Error('Access denied') - accessDeniedError.name = 'AccessDeniedError' - mockDataZoneClient.fetchAllProjects.rejects(accessDeniedError) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok( - createQuickPickStub.calledWith([ - { - label: '$(error)', - description: "You don't have permissions to view projects. Please contact your administrator", - }, - ]) - ) - }) - - it('shows "No projects found" message when projects list is empty', async function () { - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - const testWindow = getTestWindow() - assert.ok(testWindow.shownMessages.some((msg) => msg.message === 'No projects found in the domain')) - // When no projects are found, createQuickPick should not be called - assert.ok(!createQuickPickStub.called) - }) - - it('handles invalid selected project object', async function () { - mockDataZoneClient.fetchAllProjects.resolves([mockProject]) - - // Mock quickPick to return an object with 'type' property (invalid selection) - const mockQuickPick = { - prompt: sinon.stub().resolves({ type: 'invalid', data: mockProject }), - } - createQuickPickStub.returns(mockQuickPick as any) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.deepStrictEqual(result, { type: 'invalid', data: mockProject }) - assert.ok(!mockProjectNode.setProject.called) - assert.ok(!executeCommandStub.called) - }) -}) - -describe('selectSMUSProject - Express Mode', function () { - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockProjectNode: sinon.SinonStubbedInstance - let createQuickPickStub: sinon.SinonStub - let executeCommandStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - let getInstanceStub: sinon.SinonStub - let createDZClientStub: sinon.SinonStub - - const testDomainId = 'test-domain-123' - const testUserProfileId = 'user-profile-123' - - const userProject: DataZoneProject = { - id: 'project-123', - name: 'User Project', - description: 'Project created by user', - domainId: testDomainId, - createdBy: testUserProfileId, - updatedAt: new Date(), - } - - const otherUserProject: DataZoneProject = { - id: 'project-456', - name: 'Other User Project', - description: 'Project created by another user', - domainId: testDomainId, - createdBy: 'other-user-profile-456', - updatedAt: new Date(Date.now() - 86400000), - } - - beforeEach(function () { - const mockGroupProfileId = 'group-profile-123' - - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - fetchAllProjects: sinon.stub(), - } as any - - mockProjectNode = { - setProject: sinon.stub(), - } as any - - // Stub createDZClientBaseOnDomainMode to return our mock client - createDZClientStub = sinon.stub() - createDZClientStub.resolves(mockDataZoneClient) - sinon.replace(utils, 'createDZClientBaseOnDomainMode', createDZClientStub) - - // Mock credentials provider - const mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - const mockAuthProvider = { - activeConnection: { - type: 'iam' as const, - profileName: 'test-profile', - region: 'us-west-2', - domainId: testDomainId, - domainUrl: `https://${testDomainId}.sagemaker.us-west-2.on.aws/`, - }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getCredentialsProviderForIamProfile: sinon.stub().resolves(mockCredentialsProvider), - getIamPrincipalArn: sinon.stub().resolves('arn:aws:sts::123456789012:assumed-role/TestRole/test-session'), - } - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - // Mock DataZoneCustomClientHelper - const mockDataZoneCustomClientHelper = { - getGroupProfileId: sinon.stub().resolves(mockGroupProfileId), - } - getInstanceStub = sinon - .stub(DataZoneCustomClientHelper, 'getInstance') - .returns(mockDataZoneCustomClientHelper as any) - - // Mock STS client - sinon.stub(DefaultStsClient.prototype, 'getCallerIdentity').resolves({ - Arn: 'arn:aws:sts::123456789012:assumed-role/TestRole/test-session', - UserId: 'AIDAI123456789EXAMPLE:test-session', - Account: '123456789012', - }) - - // Mock SmusUtils - simulate IAM role session (not IAM user) - sinon.stub(SmusUtils, 'isIamUserArn').returns(false) - sinon.stub(SmusUtils, 'convertAssumedRoleArnToIamRoleArn').returns('arn:aws:iam::123456789012:role/TestRole') - - const mockQuickPick = { - prompt: sinon.stub().resolves(userProject), - } - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand') - - // Stub getContext to simulate IAM mode - getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - }) - - afterEach(function () { - sinon.restore() - }) - - it('filters projects to show only user-created projects in IAM mode', async function () { - mockDataZoneClient.fetchAllProjects.resolves([userProject, otherUserProject]) - - const result = await selectSMUSProject(mockProjectNode as any) - - // Verify DataZoneCustomClientHelper.getInstance was called - assert.ok(getInstanceStub.called) - - // Verify projects were fetched with group identifier - assert.ok(mockDataZoneClient.fetchAllProjects.calledOnce) - const fetchCallArgs = mockDataZoneClient.fetchAllProjects.getCall(0).args[0] - assert.ok(fetchCallArgs?.groupIdentifier) - - // Verify the project was selected and set - assert.strictEqual(result, userProject) - assert.ok(mockProjectNode.setProject.calledOnce) - assert.ok(executeCommandStub.calledWith('aws.smus.rootView.refresh')) - }) - - it('shows message when no user-created projects found in IAM mode', async function () { - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - // Verify DataZoneCustomClientHelper.getInstance was called - assert.ok(getInstanceStub.called) - - // Verify no projects were shown in quick pick - assert.ok(!createQuickPickStub.called) - - // Verify appropriate message was shown - const testWindow = getTestWindow() - assert.ok( - testWindow.shownMessages.some( - (msg) => msg.message === 'No accessible projects found for your IAM principal' - ) - ) - - // Verify no project was set - assert.strictEqual(result, undefined) - assert.ok(!mockProjectNode.setProject.called) - }) - - it('shows all user-created projects when multiple exist in IAM mode', async function () { - const userProject2: DataZoneProject = { - id: 'project-789', - name: 'Another User Project', - description: 'Another project created by user', - domainId: testDomainId, - createdBy: testUserProfileId, - updatedAt: new Date(Date.now() - 172800000), // 2 days ago - } - - // In IAM mode, fetchAllProjects is called with groupIdentifier filter - // So the API returns only projects for that group (already filtered) - mockDataZoneClient.fetchAllProjects.resolves([userProject, userProject2]) - - await selectSMUSProject(mockProjectNode as any) - - // Verify projects were fetched with group identifier - assert.ok(mockDataZoneClient.fetchAllProjects.calledOnce) - const fetchCallArgs = mockDataZoneClient.fetchAllProjects.getCall(0).args[0] - assert.ok(fetchCallArgs?.groupIdentifier) - - // Verify all returned projects are shown in quick pick - const quickPickCall = createQuickPickStub.getCall(0) - const items = quickPickCall.args[0] - assert.strictEqual(items.length, 2) - assert.ok(items.some((item: any) => item.data.id === userProject.id)) - assert.ok(items.some((item: any) => item.data.id === userProject2.id)) - }) - - it('does not filter projects in non-IAM mode', async function () { - // Stub getContext to return false for IAM mode - getContextStub.withArgs('aws.smus.isIamMode').returns(false) - - mockDataZoneClient.fetchAllProjects.resolves([userProject, otherUserProject]) - - await selectSMUSProject(mockProjectNode as any) - - // Verify DataZoneCustomClientHelper.getInstance was NOT called in non-IAM mode - assert.ok(!getInstanceStub.called) - - // Verify all projects are shown in quick pick - const quickPickCall = createQuickPickStub.getCall(0) - const items = quickPickCall.args[0] - assert.strictEqual(items.length, 2) - assert.ok(items.some((item: any) => item.data.id === userProject.id)) - assert.ok(items.some((item: any) => item.data.id === otherUserProject.id)) - }) -}) - -describe('selectSMUSProject - Error Handling', function () { - let mockDataZoneClient: sinon.SinonStubbedInstance - let mockProjectNode: sinon.SinonStubbedInstance - let createQuickPickStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - let createDZClientStub: sinon.SinonStub - - const testDomainId = 'test-domain-123' - const testUserProfileId = 'user-profile-123' - - const mockProject: DataZoneProject = { - id: 'project-123', - name: 'Test Project', - description: 'Test Description', - domainId: testDomainId, - createdBy: testUserProfileId, - updatedAt: new Date(), - } - - beforeEach(function () { - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - fetchAllProjects: sinon.stub(), - getUserProfileId: sinon.stub().resolves(testUserProfileId), - } as any - - mockProjectNode = { - setProject: sinon.stub(), - } as any - - createDZClientStub = sinon.stub() - createDZClientStub.resolves(mockDataZoneClient) - sinon.replace(utils, 'createDZClientBaseOnDomainMode', createDZClientStub) - - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns({ - activeConnection: { domainId: testDomainId, ssoRegion: 'us-west-2' }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - } as any) - - getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(false) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - - const mockQuickPick = { - prompt: sinon.stub().resolves(mockProject), - } - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - }) - - afterEach(function () { - sinon.restore() - }) - - describe('No projects scenario', function () { - it('displays "No projects found in the domain" message when user has no projects', async function () { - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - const testWindow = getTestWindow() - assert.ok(testWindow.shownMessages.some((msg) => msg.message === 'No projects found in the domain')) - // createQuickPick should NOT be called when there are no projects - assert.ok(!createQuickPickStub.called) - assert.ok(!mockProjectNode.setProject.called) - }) - }) - - describe('No accessible projects in IAM mode', function () { - beforeEach(function () { - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - - // Override the SSO connection with IAM connection for IAM mode tests - sinon.restore() - - // Re-setup mocks with IAM connection - mockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - fetchAllProjects: sinon.stub(), - getUserProfileId: sinon.stub().resolves(testUserProfileId), - } as any - - mockProjectNode = { - setProject: sinon.stub(), - } as any - - createDZClientStub = sinon.stub() - createDZClientStub.resolves(mockDataZoneClient) - sinon.replace(utils, 'createDZClientBaseOnDomainMode', createDZClientStub) - - // Mock credentials provider - const mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - const mockAuthProvider = { - activeConnection: { - type: 'iam' as const, - profileName: 'test-profile', - region: 'us-west-2', - domainId: testDomainId, - domainUrl: `https://${testDomainId}.sagemaker.us-west-2.on.aws/`, - }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getCredentialsProviderForIamProfile: sinon.stub().resolves(mockCredentialsProvider), - getIamPrincipalArn: sinon - .stub() - .resolves('arn:aws:sts::123456789012:assumed-role/TestRole/test-session'), - } - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - // Mock DataZoneCustomClientHelper - const mockDataZoneCustomClientHelper = { - getGroupProfileId: sinon.stub().resolves('group-profile-123'), - } - sinon.stub(DataZoneCustomClientHelper, 'getInstance').returns(mockDataZoneCustomClientHelper as any) - - // Mock STS client - sinon.stub(DefaultStsClient.prototype, 'getCallerIdentity').resolves({ - Arn: 'arn:aws:sts::123456789012:assumed-role/TestRole/test-session', - UserId: 'AIDAI123456789EXAMPLE:test-session', - Account: '123456789012', - }) - - // Mock SmusUtils - simulate IAM role session (not IAM user) - sinon.stub(SmusUtils, 'isIamUserArn').returns(false) - sinon - .stub(SmusUtils, 'convertAssumedRoleArnToIamRoleArn') - .returns('arn:aws:iam::123456789012:role/TestRole') - - const mockQuickPick = { - prompt: sinon.stub().resolves(mockProject), - } - createQuickPickStub = sinon.stub(pickerPrompter, 'createQuickPick').returns(mockQuickPick as any) - - getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - }) - - it('displays "No accessible projects found" when user has no projects they created', async function () { - // In IAM mode, fetchAllProjects is called with groupIdentifier filter - // which should return empty array when no projects match - mockDataZoneClient.fetchAllProjects.resolves([]) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - const testWindow = getTestWindow() - assert.ok( - testWindow.shownMessages.some( - (msg) => msg.message === 'No accessible projects found for your IAM principal' - ) - ) - assert.ok(!mockProjectNode.setProject.called) - }) - - it('handles getGroupProfileId failure with appropriate error message', async function () { - const testRoleArn = 'arn:aws:iam::123456789012:role/TestRole' - - // Mock getGroupProfileId to throw a ToolkitError with NoGroupProfileFound code - const groupProfileError = new ToolkitError(`No group profile found for IAM role: ${testRoleArn}`, { - code: SmusErrorCodes.NoGroupProfileFound, - name: 'ToolkitError', - }) - const mockDataZoneCustomClientHelper = { - getGroupProfileId: sinon.stub().rejects(groupProfileError), - } - sinon.restore() - sinon.stub(DataZoneCustomClientHelper, 'getInstance').returns(mockDataZoneCustomClientHelper as any) - - // Re-stub other dependencies - const mockAuthProvider = { - activeConnection: { - type: 'iam' as const, - profileName: 'test-profile', - region: 'us-west-2', - domainId: testDomainId, - domainUrl: `https://${testDomainId}.sagemaker.us-west-2.on.aws/`, - }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getCredentialsProviderForIamProfile: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getIamPrincipalArn: sinon.stub().resolves(testRoleArn), - } - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - sinon.stub(DefaultStsClient.prototype, 'getCallerIdentity').resolves({ - Arn: 'arn:aws:sts::123456789012:assumed-role/TestRole/test-session', - UserId: 'AIDAI123456789EXAMPLE:test-session', - Account: '123456789012', - }) - - const getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - - sinon.replace(utils, 'createDZClientBaseOnDomainMode', sinon.stub().resolves(mockDataZoneClient)) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.ok(result instanceof Error) - const testWindow = getTestWindow() - assert.ok( - testWindow.shownMessages.some((msg) => - msg.message.includes(`No resources found for IAM principal: ${testRoleArn}`) - ) - ) - }) - - it('handles getUserProfileIdForIamPrincipal failure with appropriate error message', async function () { - const testUserArn = 'arn:aws:iam::123456789012:user/test-user' - - // Mock getUserProfileIdForIamPrincipal to throw a ToolkitError with NoUserProfileFound code - const userProfileError = new ToolkitError(`No user profile found for IAM principal: ${testUserArn}`, { - code: SmusErrorCodes.NoUserProfileFound, - name: 'ToolkitError', - }) - - sinon.restore() - - // Re-stub dependencies for IAM user flow - const mockAuthProvider = { - activeConnection: { - type: 'iam' as const, - profileName: 'test-profile', - region: 'us-west-2', - domainId: testDomainId, - domainUrl: `https://${testDomainId}.sagemaker.us-west-2.on.aws/`, - }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - getCredentialsProviderForIamProfile: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getIamPrincipalArn: sinon.stub().resolves(testUserArn), - } - sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - // Mock SmusUtils to indicate this is an IAM user - sinon.stub(SmusUtils, 'isIamUserArn').returns(true) - - const getContextStub = sinon.stub() - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - getContextStub.callThrough() - sinon.replace(setContextModule, 'getContext', getContextStub) - - // Create a new mock client with the failing method - const failingMockDataZoneClient = { - getDomainId: sinon.stub().returns(testDomainId), - fetchAllProjects: sinon.stub(), - getUserProfileIdForIamPrincipal: sinon.stub().rejects(userProfileError), - } as any - - sinon.replace(utils, 'createDZClientBaseOnDomainMode', sinon.stub().resolves(failingMockDataZoneClient)) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.ok(result instanceof Error) - const testWindow = getTestWindow() - assert.ok( - testWindow.shownMessages.some((msg) => - msg.message.includes(`No resources found for IAM principal: ${testUserArn}`) - ) - ) - }) - }) - - describe('Access denied scenarios', function () { - it('displays appropriate error message when user lacks permissions to view projects', async function () { - const accessDeniedError = new Error('Access denied to list projects') - accessDeniedError.name = 'AccessDeniedException' - mockDataZoneClient.fetchAllProjects.rejects(accessDeniedError) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok( - createQuickPickStub.calledWith([ - { - label: '$(error)', - description: "You don't have permissions to view projects. Please contact your administrator", - }, - ]) - ) - assert.ok(!mockProjectNode.setProject.called) - }) - - it('handles AccessDenied error name variations', async function () { - const accessDeniedError = new Error('Access denied') - accessDeniedError.name = 'AccessDeniedError' - mockDataZoneClient.fetchAllProjects.rejects(accessDeniedError) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok( - createQuickPickStub.calledWith([ - { - label: '$(error)', - description: "You don't have permissions to view projects. Please contact your administrator", - }, - ]) - ) - }) - - it('handles UnauthorizedOperation error as access denied', async function () { - const unauthorizedError = new Error('Unauthorized operation') - unauthorizedError.name = 'UnauthorizedOperationAccessDenied' - mockDataZoneClient.fetchAllProjects.rejects(unauthorizedError) - - const result = await selectSMUSProject(mockProjectNode as any) - - assert.strictEqual(result, undefined) - assert.ok( - createQuickPickStub.calledWith([ - { - label: '$(error)', - description: "You don't have permissions to view projects. Please contact your administrator", - }, - ]) - ) - }) - }) -}) - -describe('smusSignOutCommand', function () { - let mockAuthProvider: sinon.SinonStubbedInstance - let executeCommandStub: sinon.SinonStub - let fromContextStub: sinon.SinonStub - - beforeEach(function () { - const testDomainId = 'test-domain-123' - - mockAuthProvider = { - dispose: sinon.stub(), - isConnected: sinon.stub().returns(true), - isConnectionValid: sinon.stub().returns(true), - signOut: sinon.stub().resolves(), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns('us-west-2'), - activeConnection: { - type: 'sso', - domainId: testDomainId, - ssoRegion: 'us-west-2', - domainUrl: 'https://test-domain.datazone.aws.amazon.com', - scopes: ['datazone:domain:access'], - }, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - } as any - - // Stub SmusAuthenticationProvider.fromContext to return our mock - fromContextStub = sinon.stub(SmusAuthenticationProvider, 'fromContext').returns(mockAuthProvider as any) - - // Stub executeCommand to avoid actually calling refresh - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand') - executeCommandStub.withArgs('aws.smus.rootView.refresh').resolves() - executeCommandStub.withArgs('aws.smus.signOut').callThrough() - }) - - afterEach(function () { - sinon.restore() - }) - - it('disposes auth provider when signing out', async function () { - await vscode.commands.executeCommand('aws.smus.signOut') - - assert.ok(fromContextStub.calledOnce, 'fromContext should be called once') - - assert.ok(mockAuthProvider.signOut.calledOnce, 'signOut should be called once') - - assert.ok(mockAuthProvider.dispose.calledOnce, 'dispose should be called once') - - const testWindow = getTestWindow() - assert.ok( - testWindow.shownMessages.some((msg) => - msg.message.includes('Successfully signed out from SageMaker Unified Studio.') - ), - 'success message should be shown' - ) - - assert.ok(executeCommandStub.calledWith('aws.smus.rootView.refresh'), 'refresh command should be called') - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.test.ts deleted file mode 100644 index a44b2ec3e7..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode.test.ts +++ /dev/null @@ -1,280 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SagemakerUnifiedStudioSpaceNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' -import { SagemakerClient, SagemakerSpaceApp } from '../../../../shared/clients/sagemaker' -import { SagemakerSpace } from '../../../../awsService/sagemaker/sagemakerSpace' - -describe('SagemakerUnifiedStudioSpaceNode', function () { - let spaceNode: SagemakerUnifiedStudioSpaceNode - let mockParent: SageMakerUnifiedStudioSpacesParentNode - let mockSagemakerClient: SagemakerClient - let mockSpaceApp: SagemakerSpaceApp - let mockSagemakerSpace: sinon.SinonStubbedInstance - let trackPendingNodeStub: sinon.SinonStub - - beforeEach(function () { - trackPendingNodeStub = sinon.stub() - mockParent = { - trackPendingNode: trackPendingNodeStub, - } as any - - mockSagemakerClient = { - describeApp: sinon.stub(), - describeSpace: sinon.stub(), - } as any - - mockSpaceApp = { - SpaceName: 'test-space', - DomainId: 'domain-123', - Status: 'InService', - DomainSpaceKey: 'domain-123:test-space', - App: { - AppName: 'test-app', - Status: 'InService', - }, - } as any - - mockSagemakerSpace = { - label: 'test-space (Running)', - description: 'Private space', - tooltip: new vscode.MarkdownString('Space tooltip'), - iconPath: { light: 'light-icon', dark: 'dark-icon' }, - contextValue: 'smusSpaceNode', - updateSpace: sinon.stub(), - setSpaceStatus: sinon.stub(), - isPending: sinon.stub().returns(false), - getStatus: sinon.stub().returns('Running'), - getAppStatus: sinon.stub().resolves('InService'), - name: 'test-space', - arn: 'arn:aws:sagemaker:us-west-2:123456789012:space/test-space', - getAppArn: sinon.stub().resolves('arn:aws:sagemaker:us-west-2:123456789012:app/test-app'), - getSpaceArn: sinon.stub().resolves('arn:aws:sagemaker:us-west-2:123456789012:space/test-space'), - updateSpaceAppStatus: sinon.stub().resolves(), - buildTooltip: sinon.stub().returns('Space tooltip'), - getAppIcon: sinon.stub().returns({ light: 'light-icon', dark: 'dark-icon' }), - DomainSpaceKey: 'domain-123:test-space', - } as any - - sinon.stub(SagemakerSpace.prototype, 'constructor' as any).returns(mockSagemakerSpace) - - spaceNode = new SagemakerUnifiedStudioSpaceNode( - mockParent, - mockSagemakerClient, - 'us-west-2', - mockSpaceApp, - true - ) - - // Replace the internal smSpace with our mock - ;(spaceNode as any).smSpace = mockSagemakerSpace - }) - - afterEach(function () { - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties', function () { - assert.strictEqual(spaceNode.id, 'smusSpaceNodetest-space') - assert.strictEqual(spaceNode.resource, spaceNode) - assert.strictEqual(spaceNode.regionCode, 'us-west-2') - assert.strictEqual(spaceNode.spaceApp, mockSpaceApp) - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item', function () { - const treeItem = spaceNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'test-space (Running)') - assert.strictEqual(treeItem.description, 'Private space') - assert.strictEqual(treeItem.contextValue, 'smusSpaceNode') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.ok(treeItem.iconPath) - assert.ok(treeItem.tooltip) - }) - }) - - describe('getChildren', function () { - it('returns empty array', function () { - const children = spaceNode.getChildren() - assert.deepStrictEqual(children, []) - }) - }) - - describe('getParent', function () { - it('returns parent node', function () { - const parent = spaceNode.getParent() - assert.strictEqual(parent, mockParent) - }) - }) - - describe('refreshNode', function () { - it('fires change event', async function () { - const emitterSpy = sinon.spy(spaceNode['onDidChangeEmitter'], 'fire') - await spaceNode.refreshNode() - assert(emitterSpy.calledOnce) - }) - }) - - describe('updateSpace', function () { - it('updates space and tracks pending node when pending', function () { - mockSagemakerSpace.isPending.returns(true) - const newSpaceApp = { ...mockSpaceApp, Status: 'Pending' } - - spaceNode.updateSpace(newSpaceApp) - - assert(mockSagemakerSpace.updateSpace.calledWith(newSpaceApp)) - assert(trackPendingNodeStub.calledWith('domain-123:test-space')) - }) - - it('updates space without tracking when not pending', function () { - mockSagemakerSpace.isPending.returns(false) - const newSpaceApp = { ...mockSpaceApp, Status: 'InService' } - - spaceNode.updateSpace(newSpaceApp) - - assert(mockSagemakerSpace.updateSpace.calledWith(newSpaceApp)) - assert(trackPendingNodeStub.notCalled) - }) - }) - - describe('setSpaceStatus', function () { - it('delegates to SagemakerSpace', function () { - spaceNode.setSpaceStatus('InService', 'Running') - assert(mockSagemakerSpace.setSpaceStatus.calledWith('InService', 'Running')) - }) - }) - - describe('isPending', function () { - it('delegates to SagemakerSpace', function () { - const result = spaceNode.isPending() - assert(mockSagemakerSpace.isPending.called) - assert.strictEqual(result, false) - }) - }) - - describe('getStatus', function () { - it('delegates to SagemakerSpace', function () { - const result = spaceNode.getStatus() - assert(mockSagemakerSpace.getStatus.called) - assert.strictEqual(result, 'Running') - }) - }) - - describe('getAppStatus', function () { - it('delegates to SagemakerSpace', async function () { - const result = await spaceNode.getAppStatus() - assert(mockSagemakerSpace.getAppStatus.called) - assert.strictEqual(result, 'InService') - }) - }) - - describe('name property', function () { - it('returns space name', function () { - assert.strictEqual(spaceNode.name, 'test-space') - }) - }) - - describe('arn property', function () { - it('returns space arn', function () { - assert.strictEqual(spaceNode.arn, 'arn:aws:sagemaker:us-west-2:123456789012:space/test-space') - }) - }) - - describe('getAppArn', function () { - it('delegates to SagemakerSpace', async function () { - const result = await spaceNode.getAppArn() - assert(mockSagemakerSpace.getAppArn.called) - assert.strictEqual(result, 'arn:aws:sagemaker:us-west-2:123456789012:app/test-app') - }) - }) - - describe('getSpaceArn', function () { - it('delegates to SagemakerSpace', async function () { - const result = await spaceNode.getSpaceArn() - assert(mockSagemakerSpace.getSpaceArn.called) - assert.strictEqual(result, 'arn:aws:sagemaker:us-west-2:123456789012:space/test-space') - }) - }) - - describe('updateSpaceAppStatus', function () { - it('updates status and tracks pending node when pending', async function () { - mockSagemakerSpace.isPending.returns(true) - - await spaceNode.updateSpaceAppStatus() - - assert(mockSagemakerSpace.updateSpaceAppStatus.called) - assert(trackPendingNodeStub.calledWith('domain-123:test-space')) - }) - - it('updates status without tracking when not pending', async function () { - mockSagemakerSpace.isPending.returns(false) - - await spaceNode.updateSpaceAppStatus() - - assert(mockSagemakerSpace.updateSpaceAppStatus.called) - assert(trackPendingNodeStub.notCalled) - }) - }) - - describe('buildTooltip', function () { - it('delegates to SagemakerSpace', function () { - const result = spaceNode.buildTooltip() - assert(mockSagemakerSpace.buildTooltip.called) - assert.strictEqual(result, 'Space tooltip') - }) - }) - - describe('getAppIcon', function () { - it('delegates to SagemakerSpace', function () { - const result = spaceNode.getAppIcon() - assert(mockSagemakerSpace.getAppIcon.called) - assert.deepStrictEqual(result, { light: 'light-icon', dark: 'dark-icon' }) - }) - }) - - describe('DomainSpaceKey property', function () { - it('returns domain space key', function () { - assert.strictEqual(spaceNode.DomainSpaceKey, 'domain-123:test-space') - }) - }) - - describe('SagemakerSpace getContext for SMUS', function () { - it('returns awsSagemakerSpaceRunningNode for running SMUS space with undefined RemoteAccess', function () { - // Create a space app without RemoteAccess setting (undefined) - const smusSpaceApp = { - SpaceName: 'test-space', - DomainId: 'domain-123', - Status: 'InService', - DomainSpaceKey: 'domain-123:test-space', - App: { - AppName: 'test-app', - Status: 'InService', - }, - SpaceSettingsSummary: { - // RemoteAccess is undefined - }, - } as any - - // Create a real SagemakerSpace instance for SMUS to test the actual getContext logic - const realSagemakerSpace = new SagemakerSpace( - mockSagemakerClient, - 'us-west-2', - smusSpaceApp, - true // isSMUSSpace = true - ) - - const context = realSagemakerSpace.getContext() - - assert.strictEqual(context, 'awsSagemakerSpaceRunningNode') - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.test.ts deleted file mode 100644 index 6ac3d7e014..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode.test.ts +++ /dev/null @@ -1,618 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import { SageMakerUnifiedStudioSpacesParentNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpacesParentNode' -import { SageMakerUnifiedStudioComputeNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioComputeNode' -import { SagemakerUnifiedStudioSpaceNode } from '../../../../sagemakerunifiedstudio/explorer/nodes/sageMakerUnifiedStudioSpaceNode' -import { DataZoneClient } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { SagemakerClient } from '../../../../shared/clients/sagemaker' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { getLogger } from '../../../../shared/logger/logger' -import { SmusUtils, SmusErrorCodes } from '../../../../sagemakerunifiedstudio/shared/smusUtils' -import { ToolkitError } from '../../../../shared/errors' -import * as vscodeUtils from '../../../../shared/vscode/setContext' -import * as utils from '../../../../sagemakerunifiedstudio/explorer/nodes/utils' -import { DataZoneCustomClientHelper } from '../../../../sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper' - -describe('SageMakerUnifiedStudioSpacesParentNode', function () { - let spacesNode: SageMakerUnifiedStudioSpacesParentNode - let mockParent: SageMakerUnifiedStudioComputeNode - let mockExtensionContext: vscode.ExtensionContext - let mockAuthProvider: SmusAuthenticationProvider - let mockSagemakerClient: sinon.SinonStubbedInstance - let mockDataZoneClient: sinon.SinonStubbedInstance - - beforeEach(function () { - mockParent = {} as any - mockExtensionContext = { - extensionUri: vscode.Uri.file('/test'), - } as any - mockAuthProvider = { - activeConnection: { domainId: 'test-domain', ssoRegion: 'us-west-2', profileName: 'test-profile' }, - getDomainId: sinon.stub().returns('test-domain'), - getDomainRegion: sinon.stub().returns('us-west-2'), - getIamPrincipalArn: sinon.stub().resolves(undefined), - getDerCredentialsProvider: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - getCredentialsProviderForIamProfile: sinon.stub().resolves({ - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - }), - } as any - mockSagemakerClient = sinon.createStubInstance(SagemakerClient) - mockSagemakerClient.fetchSpaceAppsAndDomains.resolves([new Map(), new Map()]) - - mockDataZoneClient = { - getInstance: sinon.stub(), - getUserId: sinon.stub(), - getDomainId: sinon.stub(), - getRegion: sinon.stub(), - getToolingEnvironmentId: sinon.stub(), - getEnvironmentDetails: sinon.stub(), - getToolingEnvironment: sinon.stub(), - } as any - - sinon.stub(DataZoneClient, 'createWithCredentials').resolves(mockDataZoneClient as any) - sinon.stub(getLogger(), 'debug') - sinon.stub(getLogger(), 'error') - sinon.stub(SmusUtils, 'extractSSOIdFromUserId').returns('user-12345') - sinon.stub(vscodeUtils, 'getContext').returns(false) - - spacesNode = new SageMakerUnifiedStudioSpacesParentNode( - mockParent, - 'project-123', - mockExtensionContext, - mockAuthProvider, - mockSagemakerClient as any - ) - }) - - afterEach(function () { - spacesNode.pollingSet.clear() - sinon.restore() - }) - - describe('constructor', function () { - it('creates instance with correct properties', function () { - assert.strictEqual(spacesNode.id, 'smusSpacesParentNode') - assert.strictEqual(spacesNode.resource, spacesNode) - }) - }) - - describe('getTreeItem', function () { - it('returns correct tree item', async function () { - const treeItem = await spacesNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Spaces') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Expanded) - assert.strictEqual(treeItem.contextValue, 'smusSpacesNode') - assert.ok(treeItem.iconPath) - }) - }) - - describe('getParent', function () { - it('returns parent node', function () { - const parent = spacesNode.getParent() - assert.strictEqual(parent, mockParent) - }) - }) - - describe('getProjectId', function () { - it('returns project ID', function () { - assert.strictEqual(spacesNode.getProjectId(), 'project-123') - }) - }) - - describe('getAuthProvider', function () { - it('returns auth provider', function () { - assert.strictEqual(spacesNode.getAuthProvider(), mockAuthProvider) - }) - }) - - describe('refreshNode', function () { - it('fires change event', async function () { - const emitterSpy = sinon.spy(spacesNode['onDidChangeEmitter'], 'fire') - await spacesNode.refreshNode() - assert(emitterSpy.calledOnce) - }) - }) - - describe('trackPendingNode', function () { - it('adds node to polling set', function () { - const addSpy = sinon.spy(spacesNode.pollingSet, 'add') - spacesNode.trackPendingNode('test-key') - assert(addSpy.calledWith('test-key')) - }) - }) - - describe('getSpaceNodes', function () { - it('returns space node when found', function () { - const mockSpaceNode = {} as SagemakerUnifiedStudioSpaceNode - spacesNode['sagemakerSpaceNodes'].set('test-key', mockSpaceNode) - - const result = spacesNode.getSpaceNodes('test-key') - assert.strictEqual(result, mockSpaceNode) - }) - - it('throws error when node not found', function () { - assert.throws( - () => spacesNode.getSpaceNodes('non-existent'), - /Node with id non-existent from polling set not found/ - ) - }) - }) - - describe('getSageMakerDomainId', function () { - it('throws error when no active connection', async function () { - const mockAuthProviderNoConnection = { - activeConnection: undefined, - } as any - - const spacesNodeNoConnection = new SageMakerUnifiedStudioSpacesParentNode( - mockParent, - 'project-123', - mockExtensionContext, - mockAuthProviderNoConnection, - mockSagemakerClient as any - ) - - await assert.rejects( - async () => await spacesNodeNoConnection.getSageMakerDomainId(), - /No active connection found to get SageMaker domain ID/ - ) - }) - - it('throws error when DataZone client not initialized', async function () { - ;(DataZoneClient.createWithCredentials as sinon.SinonStub).resolves(undefined) - - await assert.rejects( - async () => await spacesNode.getSageMakerDomainId(), - /DataZone client is not initialized/ - ) - }) - - it('throws error when tooling environment ID not found', async function () { - mockDataZoneClient.getDomainId.returns('domain-123') - const error = new Error('Failed to get tooling environment ID: Environment not found') - mockDataZoneClient.getToolingEnvironment.rejects(error) - - await assert.rejects( - async () => await spacesNode.getSageMakerDomainId(), - /Failed to get tooling environment ID: Environment not found/ - ) - }) - - it('throws error when no default environment found', async function () { - mockDataZoneClient.getDomainId.returns('domain-123') - const error = new Error('No default environment found for project') - mockDataZoneClient.getToolingEnvironment.rejects(error) - - await assert.rejects( - async () => await spacesNode.getSageMakerDomainId(), - /No default environment found for project/ - ) - }) - - it('throws error when SageMaker domain ID not found in resources', async function () { - mockDataZoneClient.getDomainId.returns('domain-123') - mockDataZoneClient.getToolingEnvironment.resolves({ - projectId: 'project-123', - domainId: 'domain-123', - createdBy: 'user', - name: 'test-env', - awsAccountRegion: 'us-west-2', - provisionedResources: [{ name: 'otherResource', value: 'value', type: 'OTHER' }], - } as any) - - await assert.rejects( - async () => await spacesNode.getSageMakerDomainId(), - /No SageMaker domain found in the tooling environment/ - ) - }) - - it('returns SageMaker domain ID when found', async function () { - mockDataZoneClient.getDomainId.returns('domain-123') - mockDataZoneClient.getToolingEnvironment.resolves({ - projectId: 'project-123', - domainId: 'domain-123', - createdBy: 'user', - name: 'test-env', - awsAccountRegion: 'us-west-2', - provisionedResources: [ - { - name: 'sageMakerDomainId', - value: 'sagemaker-domain-123', - type: 'SAGEMAKER_DOMAIN', - }, - ], - } as any) - - const result = await spacesNode.getSageMakerDomainId() - assert.strictEqual(result, 'sagemaker-domain-123') - }) - }) - - describe('getChildren', function () { - let updateChildrenStub: sinon.SinonStub - let mockSpaceNode1: SagemakerUnifiedStudioSpaceNode - let mockSpaceNode2: SagemakerUnifiedStudioSpaceNode - - beforeEach(function () { - updateChildrenStub = sinon.stub(spacesNode as any, 'updateChildren').resolves() - mockSpaceNode1 = { id: 'space1' } as any - mockSpaceNode2 = { id: 'space2' } as any - }) - - it('returns space nodes when spaces exist', async function () { - spacesNode['sagemakerSpaceNodes'].set('space1', mockSpaceNode1) - spacesNode['sagemakerSpaceNodes'].set('space2', mockSpaceNode2) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 2) - assert(children.includes(mockSpaceNode1)) - assert(children.includes(mockSpaceNode2)) - assert(updateChildrenStub.calledOnce) - }) - - it('returns no spaces found node when no spaces exist', async function () { - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - const noSpacesNode = children[0] - assert.strictEqual(noSpacesNode.id, 'smusNoSpaces') - - const treeItem = await noSpacesNode.getTreeItem() - assert.strictEqual(treeItem.label, '[No Spaces found]') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - - it('returns no spaces found node when updateChildren throws error', async function () { - updateChildrenStub.rejects(new Error('Update failed')) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoSpaces') - }) - - it('returns access denied node when AccessDeniedException is thrown', async function () { - const accessDeniedError = new Error('Access denied') - accessDeniedError.name = 'AccessDeniedException' - updateChildrenStub.rejects(accessDeniedError) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - const accessDeniedNode = children[0] - assert.strictEqual(accessDeniedNode.id, 'smusAccessDenied') - - const treeItem = await accessDeniedNode.getTreeItem() - assert.ok(treeItem) - assert.strictEqual( - treeItem.label, - "You don't have permission to view spaces. Please contact your administrator." - ) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.ok(treeItem.iconPath) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'error') - }) - }) - - describe('updatePendingNodes', function () { - it('updates pending space nodes and removes from polling set when not pending', async function () { - const mockSpaceNode = { - DomainSpaceKey: 'test-key', - updateSpaceAppStatus: sinon.stub().resolves(), - isPending: sinon.stub().returns(false), - refreshNode: sinon.stub().resolves(), - } as any - - spacesNode['sagemakerSpaceNodes'].set('test-key', mockSpaceNode) - spacesNode.pollingSet.add('test-key') - - await spacesNode['updatePendingNodes']() - - assert(mockSpaceNode.updateSpaceAppStatus.calledOnce) - assert(mockSpaceNode.refreshNode.calledOnce) - assert(!spacesNode.pollingSet.has('test-key')) - }) - - it('keeps pending nodes in polling set', async function () { - const mockSpaceNode = { - DomainSpaceKey: 'test-key', - updateSpaceAppStatus: sinon.stub().resolves(), - isPending: sinon.stub().returns(true), - refreshNode: sinon.stub().resolves(), - } as any - - spacesNode['sagemakerSpaceNodes'].set('test-key', mockSpaceNode) - spacesNode.pollingSet.add('test-key') - - await spacesNode['updatePendingNodes']() - - assert(mockSpaceNode.updateSpaceAppStatus.calledOnce) - assert(mockSpaceNode.refreshNode.notCalled) - assert(spacesNode.pollingSet.has('test-key')) - }) - }) - - describe('getAccessDeniedChildren', function () { - it('returns access denied tree node with error icon', async function () { - const accessDeniedChildren = spacesNode['getAccessDeniedChildren']() - - assert.strictEqual(accessDeniedChildren.length, 1) - const accessDeniedNode = accessDeniedChildren[0] - assert.strictEqual(accessDeniedNode.id, 'smusAccessDenied') - - const treeItem = await accessDeniedNode.getTreeItem() - assert.ok(treeItem) - assert.strictEqual( - treeItem.label, - "You don't have permission to view spaces. Please contact your administrator." - ) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.ok(treeItem.iconPath) - assert.strictEqual((treeItem.iconPath as vscode.ThemeIcon).id, 'error') - }) - }) - - describe('updateChildren', function () { - beforeEach(function () { - mockDataZoneClient.getUserId.resolves('ABCA4NU3S7PEOLDQPLXYZ:user-12345678-d061-70a4-0bf2-eeee67a6ab12') - mockDataZoneClient.getDomainId.returns('domain-123') - mockDataZoneClient.getRegion.returns('us-west-2') - mockDataZoneClient.getToolingEnvironment.resolves({ - awsAccountRegion: 'us-west-2', - provisionedResources: [{ name: 'sageMakerDomainId', value: 'sagemaker-domain-123' }], - } as any) - }) - - it('filters spaces by current user ownership', async function () { - const spaceApps = new Map([ - [ - 'space1', - { - DomainId: 'domain-123', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user-12345' }, - DomainSpaceKey: 'space1', - }, - ], - [ - 'space2', - { - DomainId: 'domain-123', - OwnershipSettingsSummary: { OwnerUserProfileName: 'other-user' }, - DomainSpaceKey: 'space2', - }, - ], - ]) - const domains = new Map([['domain-123', { DomainId: 'domain-123' }]]) - - mockSagemakerClient.fetchSpaceAppsAndDomains.resolves([spaceApps, domains]) - - await spacesNode['updateChildren']() - - assert.strictEqual(spacesNode['spaceApps'].size, 1) - assert(spacesNode['spaceApps'].has('space1')) - assert(!spacesNode['spaceApps'].has('space2')) - }) - - it('creates space nodes for filtered spaces', async function () { - const spaceApps = new Map([ - [ - 'space1', - { - DomainId: 'domain-123', - OwnershipSettingsSummary: { OwnerUserProfileName: 'user-12345' }, - DomainSpaceKey: 'space1', - }, - ], - ]) - const domains = new Map([['domain-123', { DomainId: 'domain-123' }]]) - - mockSagemakerClient.fetchSpaceAppsAndDomains.resolves([spaceApps, domains]) - - await spacesNode['updateChildren']() - - assert.strictEqual(spacesNode['sagemakerSpaceNodes'].size, 1) - assert(spacesNode['sagemakerSpaceNodes'].has('space1')) - }) - - it('throws AccessDeniedException when fetchSpaceAppsAndDomains fails with access denied', async function () { - const accessDeniedError = new Error('Access denied to spaces') - accessDeniedError.name = 'AccessDeniedException' - mockSagemakerClient.fetchSpaceAppsAndDomains.rejects(accessDeniedError) - - await assert.rejects(async () => await spacesNode['updateChildren'](), /Access denied to spaces/) - }) - }) - - describe('IAM mode error handling', function () { - beforeEach(function () { - // Add getIamPrincipalArn stub to mockAuthProvider - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves('arn:aws:iam::123456789012:user/test-user') - }) - - it('should return no user profile error node when NoUserProfileFound error is thrown', async function () { - const noProfileError = new ToolkitError('No user profile found for IAM principal', { - code: SmusErrorCodes.NoUserProfileFound, - }) - const updateChildrenStub = sinon.stub(spacesNode as any, 'updateChildren') - updateChildrenStub.rejects(noProfileError) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoUserProfile') - - const treeItem = await children[0].getTreeItem() - assert.strictEqual(treeItem.label, 'No spaces found for IAM principal') - }) - - it('should return no user profile error node when NoGroupProfileFound error is thrown', async function () { - const noProfileError = new ToolkitError('No group profile found for IAM role', { - code: SmusErrorCodes.NoGroupProfileFound, - }) - const updateChildrenStub = sinon.stub(spacesNode as any, 'updateChildren') - updateChildrenStub.rejects(noProfileError) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusNoUserProfile') - - const treeItem = await children[0].getTreeItem() - assert.strictEqual(treeItem.label, 'No spaces found for IAM principal') - }) - - it('should return access denied error node when IAM mode returns AccessDeniedException', async function () { - const accessDeniedError = new Error("You don't have permissions to access this resource") - accessDeniedError.name = 'AccessDeniedException' - const updateChildrenStub = sinon.stub(spacesNode as any, 'updateChildren') - updateChildrenStub.rejects(accessDeniedError) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusAccessDenied') - }) - - it('should return user profile error node when IAM mode returns generic error', async function () { - const genericError = new Error('Failed to retrieve user profile information') - const updateChildrenStub = sinon.stub(spacesNode as any, 'updateChildren') - updateChildrenStub.rejects(genericError) - - const children = await spacesNode.getChildren() - - assert.strictEqual(children.length, 1) - assert.strictEqual(children[0].id, 'smusUserProfileError') - - const treeItem = await children[0].getTreeItem() - assert.strictEqual(treeItem.label, 'Failed to retrieve spaces. Please try again.') - }) - }) - - describe('getUserProfileIdForIamAuthMode - IAM user flow', function () { - let createDZClientStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - - beforeEach(function () { - getContextStub = vscodeUtils.getContext as sinon.SinonStub - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - createDZClientStub = sinon.stub(utils, 'createDZClientBaseOnDomainMode') - }) - - afterEach(function () { - createDZClientStub.restore() - }) - - it('should use GetUserProfile API for IAM user', async function () { - const mockUserArn = 'arn:aws:iam::123456789012:user/test-user' - const mockUserProfileId = 'up_user123' - - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(mockUserArn) - mockAuthProvider.getDomainId = sinon.stub().returns('domain-123') - - const mockGetUserProfileId = sinon.stub().resolves(mockUserProfileId) - mockDataZoneClient.getUserProfileIdForIamPrincipal = mockGetUserProfileId as any - createDZClientStub.resolves(mockDataZoneClient) - - const result = await spacesNode['getUserProfileIdForIamAuthMode']() - - assert.strictEqual(result, mockUserProfileId) - assert(mockGetUserProfileId.calledWith(mockUserArn, 'domain-123')) - }) - - it('should throw error when IAM user profile not found', async function () { - const mockUserArn = 'arn:aws:iam::123456789012:user/test-user' - - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(mockUserArn) - mockAuthProvider.getDomainId = sinon.stub().returns('domain-123') - - mockDataZoneClient.getUserProfileIdForIamPrincipal = sinon.stub().resolves(undefined) as any - createDZClientStub.resolves(mockDataZoneClient) - - await assert.rejects( - async () => await spacesNode['getUserProfileIdForIamAuthMode'](), - /No user profile found for IAM user/ - ) - }) - - it('should throw error when caller ARN cannot be retrieved', async function () { - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(undefined) - - await assert.rejects( - async () => await spacesNode['getUserProfileIdForIamAuthMode'](), - /Unable to retrieve caller identity ARN/ - ) - }) - }) - - describe('getUserProfileIdForIamAuthMode - IAM role session flow', function () { - let mockDataZoneCustomClientHelper: any - let getInstanceStub: sinon.SinonStub - let getContextStub: sinon.SinonStub - - beforeEach(function () { - getContextStub = vscodeUtils.getContext as sinon.SinonStub - getContextStub.withArgs('aws.smus.isIamMode').returns(true) - - mockDataZoneCustomClientHelper = { - getUserProfileIdForSession: sinon.stub(), - } - - // Mock the DataZoneCustomClientHelper.getInstance - getInstanceStub = sinon - .stub(DataZoneCustomClientHelper, 'getInstance') - .returns(mockDataZoneCustomClientHelper) - }) - - afterEach(function () { - getInstanceStub.restore() - }) - - it('should use SearchUserProfile API for IAM role session', async function () { - const mockRoleArn = 'arn:aws:iam::123456789012:role/TestRole' - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/TestRole/test-session' - const mockUserProfileId = 'up_session123' - - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(mockRoleArn) - mockAuthProvider.getCachedIamCallerIdentityArn = sinon.stub().resolves(mockAssumedRoleArn) - mockAuthProvider.getDomainId = sinon.stub().returns('domain-123') - mockDataZoneCustomClientHelper.getUserProfileIdForSession.resolves(mockUserProfileId) - - const result = await spacesNode['getUserProfileIdForIamAuthMode']() - - assert.strictEqual(result, mockUserProfileId) - assert( - mockDataZoneCustomClientHelper.getUserProfileIdForSession.calledWith('domain-123', mockAssumedRoleArn) - ) - }) - - it('should throw error when assumed role ARN cannot be retrieved', async function () { - const mockRoleArn = 'arn:aws:iam::123456789012:role/TestRole' - - mockAuthProvider.getIamPrincipalArn = sinon.stub().resolves(mockRoleArn) - mockAuthProvider.getCachedIamCallerIdentityArn = sinon.stub().resolves(undefined) - - await assert.rejects( - async () => await spacesNode['getUserProfileIdForIamAuthMode'](), - /Unable to retrieve assumed role ARN with session/ - ) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/utils.test.ts b/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/utils.test.ts deleted file mode 100644 index bc2785315f..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/explorer/nodes/utils.test.ts +++ /dev/null @@ -1,313 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as vscode from 'vscode' -import { - getLabel, - isLeafNode, - getIconForNodeType, - createTreeItem, - createColumnTreeItem, - createErrorTreeItem, - isRedLakeDatabase, - getTooltip, - getRedshiftTypeFromHost, - isRedLakeCatalog, - isS3TablesCatalog, -} from '../../../../sagemakerunifiedstudio/explorer/nodes/utils' -import { NodeType, ConnectionType, RedshiftType } from '../../../../sagemakerunifiedstudio/explorer/nodes/types' - -describe('utils', function () { - describe('getLabel', function () { - it('should return container labels for container nodes', function () { - assert.strictEqual(getLabel({ id: 'test', nodeType: NodeType.REDSHIFT_TABLE, isContainer: true }), 'Tables') - assert.strictEqual(getLabel({ id: 'test', nodeType: NodeType.REDSHIFT_VIEW, isContainer: true }), 'Views') - assert.strictEqual( - getLabel({ id: 'test', nodeType: NodeType.REDSHIFT_FUNCTION, isContainer: true }), - 'Functions' - ) - assert.strictEqual( - getLabel({ id: 'test', nodeType: NodeType.REDSHIFT_STORED_PROCEDURE, isContainer: true }), - 'Stored Procedures' - ) - }) - - it('should return path label when available', function () { - assert.strictEqual( - getLabel({ id: 'test', nodeType: NodeType.S3_FILE, path: { label: 'custom-label' } }), - 'custom-label' - ) - }) - - it('should return S3 folder name with trailing slash', function () { - assert.strictEqual( - getLabel({ id: 'test', nodeType: NodeType.S3_FOLDER, path: { key: 'folder/subfolder/' } }), - 'subfolder/' - ) - }) - - it('should return S3 file name', function () { - assert.strictEqual( - getLabel({ id: 'test', nodeType: NodeType.S3_FILE, path: { key: 'folder/file.txt' } }), - 'file.txt' - ) - }) - - it('should return last part of ID as fallback', function () { - assert.strictEqual(getLabel({ id: 'parent/child/node', nodeType: NodeType.CONNECTION }), 'node') - }) - }) - - describe('isLeafNode', function () { - it('should return false for container nodes', function () { - assert.strictEqual(isLeafNode({ nodeType: NodeType.REDSHIFT_TABLE, isContainer: true }), false) - }) - - it('should return true for leaf node types', function () { - assert.strictEqual(isLeafNode({ nodeType: NodeType.S3_FILE }), true) - assert.strictEqual(isLeafNode({ nodeType: NodeType.REDSHIFT_COLUMN }), true) - assert.strictEqual(isLeafNode({ nodeType: NodeType.ERROR }), true) - assert.strictEqual(isLeafNode({ nodeType: NodeType.LOADING }), true) - assert.strictEqual(isLeafNode({ nodeType: NodeType.EMPTY }), true) - }) - - it('should return false for non-leaf node types', function () { - assert.strictEqual(isLeafNode({ nodeType: NodeType.CONNECTION }), false) - assert.strictEqual(isLeafNode({ nodeType: NodeType.REDSHIFT_CLUSTER }), false) - }) - }) - - describe('getIconForNodeType', function () { - it('should return correct icons for different node types', function () { - const errorIcon = getIconForNodeType(NodeType.ERROR) - const loadingIcon = getIconForNodeType(NodeType.LOADING) - - assert.ok(errorIcon instanceof vscode.ThemeIcon) - assert.strictEqual((errorIcon as vscode.ThemeIcon).id, 'error') - assert.ok(loadingIcon instanceof vscode.ThemeIcon) - assert.strictEqual((loadingIcon as vscode.ThemeIcon).id, 'loading~spin') - }) - - it('should return different icons for container vs non-container nodes', function () { - const containerIcon = getIconForNodeType(NodeType.REDSHIFT_TABLE, true) - const nonContainerIcon = getIconForNodeType(NodeType.REDSHIFT_TABLE, false) - - assert.ok(containerIcon instanceof vscode.ThemeIcon) - assert.ok(nonContainerIcon instanceof vscode.ThemeIcon) - assert.strictEqual((containerIcon as vscode.ThemeIcon).id, 'table') - assert.strictEqual((nonContainerIcon as vscode.ThemeIcon).id, 'aws-redshift-table') - }) - - it('should return custom icon for GLUE_CATALOG', function () { - const catalogIcon = getIconForNodeType(NodeType.GLUE_CATALOG) - - // The catalog icon should be a custom icon, not a ThemeIcon - assert.ok(catalogIcon) - // We can't easily test the exact icon path in unit tests, but we can verify it's not a ThemeIcon - assert.ok( - !(catalogIcon instanceof vscode.ThemeIcon) || - (catalogIcon as any).id === 'aws-sagemakerunifiedstudio-catalog' - ) - }) - }) - - describe('createTreeItem', function () { - it('should create tree item with correct properties', function () { - const item = createTreeItem('Test Label', NodeType.CONNECTION, false, false, 'Test Tooltip') - - assert.strictEqual(item.label, 'Test Label') - assert.strictEqual(item.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(item.contextValue, NodeType.CONNECTION) - assert.strictEqual(item.tooltip, 'Test Tooltip') - }) - - it('should create leaf node with None collapsible state', function () { - const item = createTreeItem('Leaf Node', NodeType.S3_FILE, true) - - assert.strictEqual(item.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - }) - - describe('createColumnTreeItem', function () { - it('should create column tree item with type description', function () { - const item = createColumnTreeItem('column_name', 'VARCHAR(255)', NodeType.REDSHIFT_COLUMN) - - assert.strictEqual(item.label, 'column_name') - assert.strictEqual(item.description, 'VARCHAR(255)') - assert.strictEqual(item.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.strictEqual(item.contextValue, NodeType.REDSHIFT_COLUMN) - assert.strictEqual(item.tooltip, 'column_name: VARCHAR(255)') - }) - }) - - describe('createErrorTreeItem', function () { - it('should create error tree item', function () { - const item = createErrorTreeItem('Error message') - - assert.strictEqual(item.label, 'Error message') - assert.strictEqual(item.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.ok(item.iconPath instanceof vscode.ThemeIcon) - assert.strictEqual((item.iconPath as vscode.ThemeIcon).id, 'error') - }) - }) - - describe('isRedLakeDatabase', function () { - it('should return true for RedLake database names', function () { - assert.strictEqual(isRedLakeDatabase('database@catalog'), true) - assert.strictEqual(isRedLakeDatabase('my-db@my-catalog'), true) - assert.strictEqual(isRedLakeDatabase('test_db@test_catalog'), true) - }) - - it('should return false for regular database names', function () { - assert.strictEqual(isRedLakeDatabase('regular_database'), false) - assert.strictEqual(isRedLakeDatabase('dev'), false) - assert.strictEqual(isRedLakeDatabase(''), false) - assert.strictEqual(isRedLakeDatabase(undefined), false) - }) - }) - - describe('getTooltip', function () { - it('should return correct tooltip for connection nodes', function () { - const redshiftData = { - id: 'conn1', - nodeType: NodeType.CONNECTION, - connectionType: ConnectionType.REDSHIFT, - } - const s3Data = { - id: 'conn2', - nodeType: NodeType.CONNECTION, - connectionType: ConnectionType.S3, - } - - assert.strictEqual(getTooltip(redshiftData), 'Redshift Connection: conn1') - assert.strictEqual(getTooltip(s3Data), 'Connection: conn2\nType: S3') - }) - - it('should return correct tooltip for S3 nodes', function () { - const bucketData = { - id: 'bucket1', - nodeType: NodeType.S3_BUCKET, - path: { bucket: 'my-bucket' }, - } - const fileData = { - id: 'file1', - nodeType: NodeType.S3_FILE, - path: { bucket: 'my-bucket', key: 'folder/file.txt' }, - } - - assert.strictEqual(getTooltip(bucketData), 'S3 Bucket: my-bucket') - assert.strictEqual(getTooltip(fileData), 'File: file.txt\nBucket: my-bucket') - }) - - it('should return correct tooltip for Redshift container nodes', function () { - const containerData = { - id: 'tables', - nodeType: NodeType.REDSHIFT_TABLE, - isContainer: true, - path: { schema: 'public' }, - } - - assert.strictEqual(getTooltip(containerData), 'Tables in public') - }) - - it('should return correct tooltip for Redshift object nodes', function () { - const tableData = { - id: 'table1', - nodeType: NodeType.REDSHIFT_TABLE, - path: { schema: 'public' }, - } - - assert.strictEqual(getTooltip(tableData), 'Table: public.table1') - }) - }) - - describe('getRedshiftTypeFromHost', function () { - it('should return undefined for invalid hosts', function () { - assert.strictEqual(getRedshiftTypeFromHost(undefined), undefined) - assert.strictEqual(getRedshiftTypeFromHost(''), undefined) - assert.strictEqual(getRedshiftTypeFromHost('invalid-host'), undefined) - }) - - it('should identify serverless hosts', function () { - const serverlessHost = 'workgroup.123456789012.us-east-1.redshift-serverless.amazonaws.com' - assert.strictEqual(getRedshiftTypeFromHost(serverlessHost), RedshiftType.Serverless) - }) - - it('should identify cluster hosts', function () { - const clusterHost = 'cluster.123456789012.us-east-1.redshift.amazonaws.com' - assert.strictEqual(getRedshiftTypeFromHost(clusterHost), RedshiftType.Cluster) - }) - - it('should handle hosts with port numbers', function () { - const hostWithPort = 'cluster.123456789012.us-east-1.redshift.amazonaws.com:5439' - assert.strictEqual(getRedshiftTypeFromHost(hostWithPort), RedshiftType.Cluster) - }) - - it('should return undefined for unrecognized domains', function () { - const unknownHost = 'host.example.com' - assert.strictEqual(getRedshiftTypeFromHost(unknownHost), undefined) - }) - }) - - describe('isRedLakeCatalog', function () { - it('should return true for RedLake catalogs with FederatedCatalog connection', function () { - const catalog = { - FederatedCatalog: { - ConnectionName: 'aws:redshift', - }, - } - assert.strictEqual(isRedLakeCatalog(catalog), true) - }) - - it('should return true for RedLake catalogs with CatalogProperties', function () { - const catalog = { - CatalogProperties: { - DataLakeAccessProperties: { - CatalogType: 'aws:redshift', - }, - }, - } - assert.strictEqual(isRedLakeCatalog(catalog), true) - }) - - it('should return false for non-RedLake catalogs', function () { - const catalog = { - FederatedCatalog: { - ConnectionName: 'aws:s3tables', - }, - } - assert.strictEqual(isRedLakeCatalog(catalog), false) - }) - - it('should return false for undefined catalog', function () { - assert.strictEqual(isRedLakeCatalog(undefined), false) - }) - }) - - describe('isS3TablesCatalog', function () { - it('should return true for S3 Tables catalogs', function () { - const catalog = { - FederatedCatalog: { - ConnectionName: 'aws:s3tables', - }, - } - assert.strictEqual(isS3TablesCatalog(catalog), true) - }) - - it('should return false for non-S3 Tables catalogs', function () { - const catalog = { - FederatedCatalog: { - ConnectionName: 'aws:redshift', - }, - } - assert.strictEqual(isS3TablesCatalog(catalog), false) - }) - - it('should return false for undefined catalog', function () { - assert.strictEqual(isS3TablesCatalog(undefined), false) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/clientStore.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/clientStore.test.ts deleted file mode 100644 index e2c14ace96..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/clientStore.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { ConnectionClientStore } from '../../../../sagemakerunifiedstudio/shared/client/connectionClientStore' -import { S3Client } from '../../../../sagemakerunifiedstudio/shared/client/s3Client' -import { SQLWorkbenchClient } from '../../../../sagemakerunifiedstudio/shared/client/sqlWorkbenchClient' -import { GlueClient } from '../../../../sagemakerunifiedstudio/shared/client/glueClient' -import { GlueCatalogClient } from '../../../../sagemakerunifiedstudio/shared/client/glueCatalogClient' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('ClientStore', function () { - let sandbox: sinon.SinonSandbox - let clientStore: ConnectionClientStore - - const mockCredentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - clientStore = ConnectionClientStore.getInstance() - }) - - afterEach(function () { - sandbox.restore() - clientStore.clearAll() - }) - - describe('getInstance', function () { - it('should return singleton instance', function () { - const instance1 = ConnectionClientStore.getInstance() - const instance2 = ConnectionClientStore.getInstance() - assert.strictEqual(instance1, instance2) - }) - }) - - describe('getClient', function () { - it('should create and cache client', function () { - const factory = sandbox.stub().returns({ test: 'client' }) - - const client1 = clientStore.getClient('conn-1', 'TestClient', factory) - const client2 = clientStore.getClient('conn-1', 'TestClient', factory) - - assert.strictEqual(client1, client2) - assert.ok(factory.calledOnce) - }) - - it('should create separate clients for different connections', function () { - const factory = sandbox.stub() - factory.onFirstCall().returns({ test: 'client1' }) - factory.onSecondCall().returns({ test: 'client2' }) - - const client1 = clientStore.getClient('conn-1', 'TestClient', factory) - const client2 = clientStore.getClient('conn-2', 'TestClient', factory) - - assert.notStrictEqual(client1, client2) - assert.ok(factory.calledTwice) - }) - }) - - describe('getS3Client', function () { - it('should create S3Client with credentials provider', function () { - sandbox.stub(S3Client.prototype, 'constructor' as any) - - const client = clientStore.getS3Client( - 'conn-1', - 'us-east-1', - mockCredentialsProvider as ConnectionCredentialsProvider - ) - - assert.ok(client instanceof S3Client) - }) - }) - - describe('getSQLWorkbenchClient', function () { - it('should create SQLWorkbenchClient with credentials provider', function () { - const stub = sandbox.stub(SQLWorkbenchClient, 'createWithCredentials').returns({} as any) - - clientStore.getSQLWorkbenchClient( - 'conn-1', - 'us-east-1', - mockCredentialsProvider as ConnectionCredentialsProvider - ) - - assert.ok(stub.calledOnce) - }) - }) - - describe('getGlueClient', function () { - it('should create GlueClient with credentials provider', function () { - sandbox.stub(GlueClient.prototype, 'constructor' as any) - - const client = clientStore.getGlueClient( - 'conn-1', - 'us-east-1', - mockCredentialsProvider as ConnectionCredentialsProvider - ) - - assert.ok(client instanceof GlueClient) - }) - }) - - describe('getGlueCatalogClient', function () { - it('should create GlueCatalogClient with credentials provider', function () { - const stub = sandbox.stub(GlueCatalogClient, 'createWithCredentials').returns({} as any) - - clientStore.getGlueCatalogClient( - 'conn-1', - 'us-east-1', - mockCredentialsProvider as ConnectionCredentialsProvider - ) - - assert.ok(stub.calledOnce) - }) - }) - - describe('clearConnection', function () { - it('should clear cached clients for specific connection', function () { - const factory = sandbox.stub().returns({ test: 'client' }) - - clientStore.getClient('conn-1', 'TestClient', factory) - clientStore.clearConnection('conn-1') - clientStore.getClient('conn-1', 'TestClient', factory) - - assert.strictEqual(factory.callCount, 2) - }) - }) - - describe('clearAll', function () { - it('should clear all cached clients', function () { - const factory = sandbox.stub().returns({ test: 'client' }) - - clientStore.getClient('conn-1', 'TestClient', factory) - clientStore.clearAll() - clientStore.getClient('conn-1', 'TestClient', factory) - - assert.strictEqual(factory.callCount, 2) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/credentialsAdapter.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/credentialsAdapter.test.ts deleted file mode 100644 index cfb2cfbce6..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/credentialsAdapter.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import * as AWS from 'aws-sdk' -import { adaptConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/shared/client/credentialsAdapter' - -describe('credentialsAdapter', function () { - let sandbox: sinon.SinonSandbox - let mockConnectionCredentialsProvider: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - mockConnectionCredentialsProvider = { - getCredentials: sandbox.stub(), - } - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('adaptConnectionCredentialsProvider', function () { - it('should create CredentialProviderChain', function () { - const chain = adaptConnectionCredentialsProvider(mockConnectionCredentialsProvider) - assert.ok(chain instanceof AWS.CredentialProviderChain) - }) - - it('should create credentials with provider function', function () { - const chain = adaptConnectionCredentialsProvider(mockConnectionCredentialsProvider) - assert.ok(chain.providers) - assert.strictEqual(chain.providers.length, 1) - assert.strictEqual(typeof chain.providers[0], 'function') - }) - - it('should create AWS Credentials object', function () { - const chain = adaptConnectionCredentialsProvider(mockConnectionCredentialsProvider) - const provider = chain.providers[0] as () => AWS.Credentials - const credentials = provider() - assert.ok(credentials instanceof AWS.Credentials) - }) - - it('should set needsRefresh to always return true', function () { - const chain = adaptConnectionCredentialsProvider(mockConnectionCredentialsProvider) - const provider = chain.providers[0] as () => AWS.Credentials - const credentials = provider() - assert.strictEqual(credentials.needsRefresh(), true) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneClient.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneClient.test.ts deleted file mode 100644 index 91a6a092bd..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneClient.test.ts +++ /dev/null @@ -1,636 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { DataZoneClient } from '../../../../sagemakerunifiedstudio/shared/client/datazoneClient' -import { SmusAuthenticationProvider } from '../../../../sagemakerunifiedstudio/auth/providers/smusAuthenticationProvider' -import { GetEnvironmentCommandOutput } from '@aws-sdk/client-datazone/dist-types/commands/GetEnvironmentCommand' -import { DefaultStsClient } from '../../../../shared/clients/stsClient' -import { SmusUtils, SmusErrorCodes } from '../../../../sagemakerunifiedstudio/shared/smusUtils' -import { ToolkitError } from '../../../../shared/errors' - -describe('DataZoneClient', () => { - let dataZoneClient: DataZoneClient - let mockAuthProvider: any - const testDomainId = 'dzd_domainId' - const testRegion = 'us-east-2' - - beforeEach(async () => { - // Create mock connection object - const mockConnection = { - id: 'connection-id', - domainId: testDomainId, - ssoRegion: testRegion, - } - - // Create mock auth provider - mockAuthProvider = { - isConnected: sinon.stub().returns(true), - getDomainId: sinon.stub().returns(testDomainId), - getDomainRegion: sinon.stub().returns(testRegion), - activeConnection: mockConnection, - onDidChangeActiveConnection: sinon.stub().returns({ - dispose: sinon.stub(), - }), - secondaryAuth: { - state: { - get: sinon.stub().returns({ - 'connection-id': { - profileName: 'test-profile', - }, - }), - }, - }, - getCredentialsProviderForIamProfile: sinon.stub(), - } as any - - // Create mock credentials provider - const mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - getCredentialsId: () => ({ credentialSource: 'temp' as const, credentialTypeId: 'test' }), - getProviderType: () => 'temp' as const, - getTelemetryType: () => 'other' as any, - getDefaultRegion: () => testRegion, - getHashCode: () => 'test-hash', - canAutoConnect: () => Promise.resolve(false), - isAvailable: () => Promise.resolve(true), - } - - // Set up the DataZoneClient using createWithCredentials - dataZoneClient = DataZoneClient.createWithCredentials(testRegion, testDomainId, mockCredentialsProvider) - }) - - afterEach(() => { - sinon.restore() - }) - - describe('createWithCredentials', () => { - it('should create new instance with credentials', () => { - const mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - getCredentialsId: () => ({ credentialSource: 'temp' as const, credentialTypeId: 'test' }), - getProviderType: () => 'temp' as const, - getTelemetryType: () => 'other' as any, - getDefaultRegion: () => testRegion, - getHashCode: () => 'test-hash', - canAutoConnect: () => Promise.resolve(false), - isAvailable: () => Promise.resolve(true), - } - - const instance = DataZoneClient.createWithCredentials(testRegion, testDomainId, mockCredentialsProvider) - assert.ok(instance) - assert.strictEqual(instance.getRegion(), testRegion) - assert.strictEqual(instance.getDomainId(), testDomainId) - }) - }) - - describe('getRegion', () => { - it('should return configured region', () => { - const result = dataZoneClient.getRegion() - assert.strictEqual(typeof result, 'string') - assert.ok(result.length > 0) - }) - }) - - describe('listProjects', () => { - it('should list projects with pagination', async () => { - const mockDataZone = { - listProjects: sinon.stub().resolves({ - items: [ - { - id: 'project-1', - name: 'Project 1', - description: 'First project', - createdAt: new Date('2023-01-01'), - updatedAt: new Date('2023-01-02'), - }, - ], - nextToken: 'next-token', - }), - } - - // Mock the getDataZoneClient method - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.listProjects({ - maxResults: 10, - }) - - assert.strictEqual(result.projects.length, 1) - assert.strictEqual(result.projects[0].id, 'project-1') - assert.strictEqual(result.projects[0].name, 'Project 1') - assert.strictEqual(result.projects[0].domainId, testDomainId) - assert.strictEqual(result.nextToken, 'next-token') - }) - - it('should handle empty results', async () => { - const mockDataZone = { - listProjects: sinon.stub().resolves({ - items: [], - nextToken: undefined, - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.listProjects() - - assert.strictEqual(result.projects.length, 0) - assert.strictEqual(result.nextToken, undefined) - }) - - it('should handle API errors', async () => { - const error = new Error('API Error') - sinon.stub(dataZoneClient as any, 'getDataZoneClient').rejects(error) - - await assert.rejects(() => dataZoneClient.listProjects(), error) - }) - }) - - describe('getProjectDefaultEnvironmentCreds', () => { - it('should get environment credentials for project', async () => { - const mockCredentials = { - accessKeyId: 'AKIATEST', - secretAccessKey: 'secret', - sessionToken: 'token', - } - - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().resolves({ - items: [{ id: 'env-1', name: 'Tooling' }], - }), - getEnvironmentCredentials: sinon.stub().resolves(mockCredentials), - } - - // Mock getToolingBlueprintName to return 'Tooling' - sinon.stub(dataZoneClient as any, 'getToolingBlueprintName').returns('Tooling') - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.getProjectDefaultEnvironmentCreds('project-1') - - assert.deepStrictEqual(result, mockCredentials) - assert.ok( - mockDataZone.listEnvironmentBlueprints.calledWith({ - domainIdentifier: testDomainId, - managed: true, - name: 'Tooling', - }) - ) - assert.ok( - mockDataZone.listEnvironments.calledWith({ - domainIdentifier: testDomainId, - projectIdentifier: 'project-1', - environmentBlueprintIdentifier: 'blueprint-1', - provider: 'Amazon SageMaker', - }) - ) - assert.ok( - mockDataZone.getEnvironmentCredentials.calledWith({ - domainIdentifier: testDomainId, - environmentIdentifier: 'env-1', - }) - ) - }) - - it('should throw error when tooling blueprint not found', async () => { - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [], - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects( - () => dataZoneClient.getProjectDefaultEnvironmentCreds('project-1'), - /Failed to get tooling blueprint/ - ) - }) - - it('should throw error when default environment not found', async () => { - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().resolves({ - items: [], - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects( - () => dataZoneClient.getProjectDefaultEnvironmentCreds('project-1'), - /Failed to find default Tooling environment/ - ) - }) - }) - - describe('fetchAllProjects', function () { - it('fetches all projects by handling pagination', async function () { - // Create a stub for listProjects that returns paginated resultssults - const listProjectsStub = sinon.stub() - - // First call returns first page with nextToken - listProjectsStub.onFirstCall().resolves({ - projects: [ - { - id: 'project-1', - name: 'Project 1', - description: 'First project', - domainId: testDomainId, - }, - ], - nextToken: 'next-page-token', - }) - - // Second call returns second page with no nextToken - listProjectsStub.onSecondCall().resolves({ - projects: [ - { - id: 'project-2', - name: 'Project 2', - description: 'Second project', - domainId: testDomainId, - }, - ], - nextToken: undefined, - }) - - // Replace the listProjects method with our stub - dataZoneClient.listProjects = listProjectsStub - - // Call fetchAllProjects - const result = await dataZoneClient.fetchAllProjects() - - // Verify results - assert.strictEqual(result.length, 2) - assert.strictEqual(result[0].id, 'project-1') - assert.strictEqual(result[1].id, 'project-2') - - // Verify listProjects was called correctly - assert.strictEqual(listProjectsStub.callCount, 2) - assert.deepStrictEqual(listProjectsStub.firstCall.args[0], { - maxResults: 50, - nextToken: undefined, - }) - assert.deepStrictEqual(listProjectsStub.secondCall.args[0], { - maxResults: 50, - nextToken: 'next-page-token', - }) - }) - - it('returns empty array when no projects found', async function () { - // Create a stub for listProjects that returns empty results - const listProjectsStub = sinon.stub().resolves({ - projects: [], - nextToken: undefined, - }) - - // Replace the listProjects method with our stub - dataZoneClient.listProjects = listProjectsStub - - // Call fetchAllProjects - const result = await dataZoneClient.fetchAllProjects() - - // Verify results - assert.strictEqual(result.length, 0) - assert.strictEqual(listProjectsStub.callCount, 1) - }) - - it('handles errors gracefully', async function () { - // Create a stub for listProjects that throws an error - const listProjectsStub = sinon.stub().rejects(new Error('API error')) - - // Replace the listProjects method with our stub - dataZoneClient.listProjects = listProjectsStub - - // Call fetchAllProjects and expect it to throw - await assert.rejects(() => dataZoneClient.fetchAllProjects(), /API error/) - }) - }) - - describe('getToolingEnvironmentId', () => { - it('should get tooling environment ID successfully', async () => { - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().resolves({ - items: [{ id: 'env-1', name: 'Tooling' }], - }), - } - - // Mock getToolingBlueprintName to return 'Tooling' - sinon.stub(dataZoneClient as any, 'getToolingBlueprintName').returns('Tooling') - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.getToolingEnvironmentId('domain-1', 'project-1') - - assert.strictEqual(result, 'env-1') - }) - - it('should handle listEnvironmentBlueprints error', async () => { - const error = new Error('Blueprint API Error') - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().rejects(error), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects(() => dataZoneClient.getToolingEnvironmentId('domain-1', 'project-1'), error) - }) - - it('should handle listEnvironments error', async () => { - const error = new Error('Environment API Error') - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().rejects(error), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects(() => dataZoneClient.getToolingEnvironmentId('domain-1', 'project-1'), error) - }) - }) - - describe('getToolingEnvironment', () => { - beforeEach(() => { - mockAuthProvider = {} as SmusAuthenticationProvider - }) - - it('should return environment details when successful', async () => { - const mockEnvironment: GetEnvironmentCommandOutput = { - id: 'env-123', - awsAccountRegion: 'us-east-1', - projectId: undefined, - domainId: undefined, - createdBy: undefined, - name: undefined, - provider: undefined, - $metadata: {}, - } - - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().resolves({ - items: [{ id: 'env-1', name: 'Tooling' }], - }), - getEnvironment: sinon.stub().resolves(mockEnvironment), - } - - // Mock getToolingBlueprintName to return 'Tooling' - sinon.stub(dataZoneClient as any, 'getToolingBlueprintName').returns('Tooling') - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.getToolingEnvironment('project-123') - - assert.strictEqual(result, mockEnvironment) - }) - - it('should throw error when no tooling environment ID found', async () => { - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().resolves({ - items: [{ id: 'blueprint-1', name: 'Tooling' }], - }), - listEnvironments: sinon.stub().resolves({ - items: [], - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects( - () => dataZoneClient.getToolingEnvironment('project-123'), - /No default Tooling environment found for project/ - ) - }) - - it('should throw error when getToolingEnvironmentId fails', async () => { - const mockDataZone = { - listEnvironmentBlueprints: sinon.stub().rejects(new Error('API error')), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects(() => dataZoneClient.getToolingEnvironment('project-123'), /API error/) - }) - }) - - describe('fetchAllProjectMemberships', () => { - it('should fetch all project memberships with pagination', async () => { - const mockDataZone = { - listProjectMemberships: sinon.stub(), - } - - // First call returns first page with nextToken - mockDataZone.listProjectMemberships.onFirstCall().resolves({ - members: [ - { - memberDetails: { - user: { - userId: 'user-1', - }, - }, - }, - ], - nextToken: 'next-token', - }) - - // Second call returns second page without nextToken - mockDataZone.listProjectMemberships.onSecondCall().resolves({ - members: [ - { - memberDetails: { - user: { - userId: 'user-2', - }, - }, - }, - ], - nextToken: undefined, - }) - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.fetchAllProjectMemberships('project-1') - - assert.strictEqual(result.length, 2) - assert.strictEqual(result[0].memberDetails?.user?.userId, 'user-1') - assert.strictEqual(result[1].memberDetails?.user?.userId, 'user-2') - assert.strictEqual(mockDataZone.listProjectMemberships.callCount, 2) - }) - - it('should handle empty memberships', async () => { - const mockDataZone = { - listProjectMemberships: sinon.stub().resolves({ - members: [], - nextToken: undefined, - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.fetchAllProjectMemberships('project-1') - - assert.strictEqual(result.length, 0) - }) - - it('should handle API errors', async () => { - const error = new Error('Membership API Error') - const mockDataZone = { - listProjectMemberships: sinon.stub().rejects(error), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects(() => dataZoneClient.fetchAllProjectMemberships('project-1'), error) - }) - }) - - describe('getUserProfileId', () => { - let stsClientStub: sinon.SinonStub - let convertAssumedRoleArnStub: sinon.SinonStub - let mockCredentialsProvider: any - - beforeEach(() => { - // Mock connection with ID - mockAuthProvider.activeConnection = { id: 'connection-id' } - - // Mock credentials provider - mockCredentialsProvider = { - getCredentials: sinon.stub().resolves({ - accessKeyId: 'id', - secretAccessKey: 'secret', - sessionToken: 'token', - }), - } - - mockAuthProvider.getCredentialsProviderForIamProfile.resolves(mockCredentialsProvider) - - // Stub STS client - stsClientStub = sinon.stub(DefaultStsClient.prototype, 'getCallerIdentity') - - // Stub SmusUtils method - convertAssumedRoleArnStub = sinon.stub(SmusUtils as any, 'convertAssumedRoleArnToIamRoleArn') - }) - - afterEach(() => { - stsClientStub.restore() - convertAssumedRoleArnStub.restore() - }) - - it('should successfully get user profile ID with role ARN', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/service-role/MyRole' - const mockUserProfileId = 'user-profile-123' - - const mockDataZone = { - getUserProfile: sinon.stub().resolves({ - id: mockUserProfileId, - userIdentifier: mockRoleArn, - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.getUserProfileIdForIamPrincipal(mockRoleArn) - - assert.strictEqual(result, mockUserProfileId) - assert.ok( - mockDataZone.getUserProfile.calledWith({ - domainIdentifier: testDomainId, - userIdentifier: mockRoleArn, - }) - ) - }) - - it('should handle DataZone getUserProfile API failure', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/service-role/MyRole' - const datazoneError = new Error('DataZone API Error') - - const mockDataZone = { - getUserProfile: sinon.stub().rejects(datazoneError), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects( - async () => { - await dataZoneClient.getUserProfileIdForIamPrincipal(mockRoleArn) - }, - (error: Error) => { - assert.ok(error instanceof ToolkitError) - assert.ok(error.message.includes('Failed to get user profile ID')) - return true - } - ) - }) - - it('should get user profile ID for IAM user ARN', async () => { - const mockUserArn = 'arn:aws:iam::123456789012:user/test-user' - const mockUserProfileId = 'user-profile-456' - - const mockDataZone = { - getUserProfile: sinon.stub().resolves({ - id: mockUserProfileId, - userIdentifier: mockUserArn, - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - const result = await dataZoneClient.getUserProfileIdForIamPrincipal(mockUserArn) - - assert.strictEqual(result, mockUserProfileId) - assert.ok( - mockDataZone.getUserProfile.calledWith({ - domainIdentifier: testDomainId, - userIdentifier: mockUserArn, - }) - ) - }) - - it('should throw error when user profile ID is not returned', async () => { - const mockUserArn = 'arn:aws:iam::123456789012:user/test-user' - - const mockDataZone = { - getUserProfile: sinon.stub().resolves({ - // No id field - }), - } - - sinon.stub(dataZoneClient as any, 'getDataZoneClient').resolves(mockDataZone) - - await assert.rejects( - async () => { - await dataZoneClient.getUserProfileIdForIamPrincipal(mockUserArn) - }, - (error: Error) => { - assert.ok(error instanceof ToolkitError) - assert.strictEqual((error as ToolkitError).code, SmusErrorCodes.NoUserProfileFound) - return true - } - ) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.test.ts deleted file mode 100644 index 59dfd0c7d9..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper.test.ts +++ /dev/null @@ -1,1221 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { DataZoneCustomClientHelper } from '../../../../sagemakerunifiedstudio/shared/client/datazoneCustomClientHelper' -import * as DataZoneCustomClient from '../../../../sagemakerunifiedstudio/shared/client/datazonecustomclient' - -type DataZoneDomain = DataZoneCustomClient.Types.DomainSummary - -describe('DataZoneCustomClientHelper', () => { - let client: DataZoneCustomClientHelper - let mockAuthProvider: any - const testRegion = 'us-east-1' - - beforeEach(() => { - // Create mock auth provider - mockAuthProvider = { - isConnected: sinon.stub().returns(true), - onDidChangeActiveConnection: sinon.stub().returns({ - dispose: sinon.stub(), - }), - } as any - - // Clear instances and create new client - DataZoneCustomClientHelper.dispose() - client = DataZoneCustomClientHelper.getInstance(mockAuthProvider, testRegion) - }) - - afterEach(() => { - sinon.restore() - DataZoneCustomClientHelper.dispose() - }) - - describe('getInstance', () => { - it('should return singleton instance for same region', () => { - const instance1 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, testRegion) - const instance2 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, testRegion) - - assert.strictEqual(instance1, instance2) - }) - - it('should create different instances for different regions', () => { - const instance1 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-east-1') - const instance2 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-west-2') - - assert.notStrictEqual(instance1, instance2) - }) - - it('should create new instance after dispose', () => { - const instance1 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, testRegion) - DataZoneCustomClientHelper.dispose() - const instance2 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, testRegion) - - assert.notStrictEqual(instance1, instance2) - }) - }) - - describe('dispose', () => { - it('should clear all instances', () => { - const instance1 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-east-1') - const instance2 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-west-2') - - DataZoneCustomClientHelper.dispose() - - // Should create new instance after dispose - const newInstance1 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-east-1') - const newInstance2 = DataZoneCustomClientHelper.getInstance(mockAuthProvider, 'us-west-2') - - assert.notStrictEqual(instance1, newInstance1) - assert.notStrictEqual(instance2, newInstance2) - }) - }) - - describe('getRegion', () => { - it('should return configured region', () => { - const result = client.getRegion() - assert.strictEqual(result, testRegion) - }) - }) - - describe('listDomains', () => { - it('should list domains with pagination', async () => { - const mockResponse = { - items: [ - { - id: 'dzd_domain1', - name: 'Test Domain 1', - description: 'First test domain', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_domain1', - managedAccountId: '123456789012', - status: 'AVAILABLE', - portalUrl: 'https://domain1.datazone.aws', - createdAt: new Date('2023-01-01T00:00:00Z'), - lastUpdatedAt: new Date('2023-01-02T00:00:00Z'), - domainVersion: '1.0', - preferences: { DOMAIN_MODE: 'STANDARD' }, - }, - ], - nextToken: 'next-token', - } - - const mockDataZoneClient = { - listDomains: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.listDomains({ - maxResults: 10, - status: 'AVAILABLE', - }) - - assert.strictEqual(result.domains.length, 1) - assert.strictEqual(result.domains[0].id, 'dzd_domain1') - assert.strictEqual(result.domains[0].name, 'Test Domain 1') - assert.strictEqual(result.domains[0].arn, 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_domain1') - assert.strictEqual(result.domains[0].managedAccountId, '123456789012') - assert.strictEqual(result.domains[0].status, 'AVAILABLE') - assert.strictEqual(result.nextToken, 'next-token') - assert.ok(result.domains[0].createdAt instanceof Date) - assert.ok(result.domains[0].lastUpdatedAt instanceof Date) - }) - - it('should handle empty results', async () => { - const mockResponse = { - items: [], - nextToken: undefined, - } - - const mockDataZoneClient = { - listDomains: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.listDomains() - - assert.strictEqual(result.domains.length, 0) - assert.strictEqual(result.nextToken, undefined) - }) - - it('should handle API errors', async () => { - const error = new Error('API Error') - sinon.stub(client as any, 'getDataZoneCustomClient').rejects(error) - - await assert.rejects(() => client.listDomains(), error) - }) - }) - - describe('fetchAllDomains', () => { - it('should fetch all domains by handling pagination', async () => { - const listDomainsStub = sinon.stub() - - // First call returns first page with nextToken - listDomainsStub.onFirstCall().resolves({ - domains: [ - { - id: 'dzd_domain1', - name: 'Domain 1', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_domain1', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - } as DataZoneDomain, - ], - nextToken: 'next-page-token', - }) - - // Second call returns second page with no nextToken - listDomainsStub.onSecondCall().resolves({ - domains: [ - { - id: 'dzd_domain2', - name: 'Domain 2', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_domain2', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - } as DataZoneDomain, - ], - nextToken: undefined, - }) - - // Replace the listDomains method with our stub - client.listDomains = listDomainsStub - - const result = await client.fetchAllDomains({ status: 'AVAILABLE' }) - - assert.strictEqual(result.length, 2) - assert.strictEqual(result[0].id, 'dzd_domain1') - assert.strictEqual(result[1].id, 'dzd_domain2') - - // Verify listDomains was called correctly - assert.strictEqual(listDomainsStub.callCount, 2) - assert.deepStrictEqual(listDomainsStub.firstCall.args[0], { - status: 'AVAILABLE', - maxResults: 25, - nextToken: undefined, - }) - assert.deepStrictEqual(listDomainsStub.secondCall.args[0], { - status: 'AVAILABLE', - maxResults: 25, - nextToken: 'next-page-token', - }) - }) - - it('should return empty array when no domains found', async () => { - const listDomainsStub = sinon.stub().resolves({ - domains: [], - nextToken: undefined, - }) - - client.listDomains = listDomainsStub - - const result = await client.fetchAllDomains() - - assert.strictEqual(result.length, 0) - assert.strictEqual(listDomainsStub.callCount, 1) - }) - - it('should handle errors gracefully', async () => { - const listDomainsStub = sinon.stub().rejects(new Error('API error')) - - client.listDomains = listDomainsStub - - await assert.rejects(() => client.fetchAllDomains(), /API error/) - }) - }) - - describe('getDomain', () => { - it('should find EXPRESS domain', async () => { - const listDomainsStub = sinon.stub() - - listDomainsStub.onFirstCall().resolves({ - domains: [ - { - id: 'dzd_standard', - name: 'Standard Domain', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_standard', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - domainVersion: 'V2', - iamSignIns: ['IAM_ROLE'], - preferences: { DOMAIN_MODE: 'STANDARD' }, - }, - { - id: 'dzd_express', - name: 'Express Domain', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_express', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - domainVersion: 'V2', - iamSignIns: ['IAM_ROLE', 'IAM_USER'], - preferences: { DOMAIN_MODE: 'EXPRESS' }, - }, - ] as DataZoneDomain[], - nextToken: 'next-token', - }) - - client.listDomains = listDomainsStub - - const result = await client.getIamDomain() - - assert.ok(result) - assert.strictEqual(result.id, 'dzd_express') - assert.strictEqual(result.name, 'Express Domain') - assert.strictEqual(result.preferences?.DOMAIN_MODE, 'EXPRESS') - - // Should only call once since EXPRESS domain found on first page - assert.strictEqual(listDomainsStub.callCount, 1) - }) - - it('should return undefined when no EXPRESS domain found', async () => { - const listDomainsStub = sinon.stub() - - listDomainsStub.onFirstCall().resolves({ - domains: [ - { - id: 'dzd_standard', - name: 'Standard Domain', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_standard', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - preferences: { DOMAIN_MODE: 'STANDARD' }, - }, - ] as DataZoneDomain[], - nextToken: undefined, - }) - - client.listDomains = listDomainsStub - - const result = await client.getIamDomain() - - assert.strictEqual(result, undefined) - assert.strictEqual(listDomainsStub.callCount, 1) - }) - - it('should return undefined when no domains found', async () => { - const listDomainsStub = sinon.stub().resolves({ - domains: [], - nextToken: undefined, - }) - - client.listDomains = listDomainsStub - - const result = await client.getIamDomain() - - assert.strictEqual(result, undefined) - assert.strictEqual(listDomainsStub.callCount, 1) - }) - - it('should handle domains without preferences', async () => { - const listDomainsStub = sinon.stub() - - listDomainsStub.onFirstCall().resolves({ - domains: [ - { - id: 'dzd_no_prefs', - name: 'Domain Without Preferences', - arn: 'arn:aws:datazone:us-east-1:123456789012:domain/dzd_no_prefs', - managedAccountId: '123456789012', - status: 'AVAILABLE', - createdAt: new Date(), - // No preferences field - }, - ] as DataZoneDomain[], - nextToken: undefined, - }) - - client.listDomains = listDomainsStub - - const result = await client.getIamDomain() - - assert.strictEqual(result, undefined) - }) - - it('should handle API errors', async () => { - const listDomainsStub = sinon.stub().rejects(new Error('API error')) - - client.listDomains = listDomainsStub - - await assert.rejects(() => client.getIamDomain(), /Failed to get domain info: API error/) - }) - }) - - describe('getDomain', () => { - it('should get domain by ID successfully', async () => { - const mockDomainId = 'dzd_test123' - const mockResponse = { - id: mockDomainId, - name: 'Test Domain', - description: 'A test domain', - arn: `arn:aws:datazone:us-east-1:123456789012:domain/${mockDomainId}`, - status: 'AVAILABLE', - portalUrl: 'https://test.datazone.aws', - createdAt: '2023-01-01T00:00:00Z', - lastUpdatedAt: '2023-01-02T00:00:00Z', - domainVersion: '1.0', - preferences: { DOMAIN_MODE: 'EXPRESS' }, - } - const mockDataZoneClient = { - getDomain: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.getDomain(mockDomainId) - - assert.strictEqual(result.id, mockDomainId) - assert.strictEqual(result.name, 'Test Domain') - assert.strictEqual(result.description, 'A test domain') - assert.strictEqual(result.arn, `arn:aws:datazone:us-east-1:123456789012:domain/${mockDomainId}`) - assert.strictEqual(result.status, 'AVAILABLE') - assert.strictEqual(result.portalUrl, 'https://test.datazone.aws') - assert.strictEqual(result.domainVersion, '1.0') - assert.deepStrictEqual(result.preferences, { DOMAIN_MODE: 'EXPRESS' }) - - // Verify the API was called with correct parameters - assert.ok(mockDataZoneClient.getDomain.calledOnce) - assert.deepStrictEqual(mockDataZoneClient.getDomain.firstCall.args[0], { - identifier: mockDomainId, - }) - }) - - it('should handle API errors when getting domain', async () => { - const mockDomainId = 'dzd_test123' - const error = new Error('Domain not found') - - const mockDataZoneClient = { - getDomain: sinon.stub().returns({ - promise: () => Promise.reject(error), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - await assert.rejects(() => client.getDomain(mockDomainId), error) - - // Verify the API was called with correct parameters - assert.ok(mockDataZoneClient.getDomain.calledOnce) - assert.deepStrictEqual(mockDataZoneClient.getDomain.firstCall.args[0], { - identifier: mockDomainId, - }) - }) - }) - - describe('searchGroupProfiles', () => { - const mockDomainId = 'dzd_test123' - - it('should search group profiles successfully', async () => { - const mockResponse = { - items: [ - { - domainId: mockDomainId, - id: 'gp_profile1', - status: 'ACTIVATED', - groupName: 'AdminGroup', - rolePrincipalArn: 'arn:aws:iam::123456789012:role/AdminRole', - rolePrincipalId: 'AIDAI123456789EXAMPLE', - }, - { - domainId: mockDomainId, - id: 'gp_profile2', - status: 'ACTIVATED', - groupName: 'DeveloperGroup', - rolePrincipalArn: 'arn:aws:iam::123456789012:role/DeveloperRole', - rolePrincipalId: 'AIDAI987654321EXAMPLE', - }, - ], - nextToken: 'next-token', - } - - const mockDataZoneClient = { - searchGroupProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchGroupProfiles(mockDomainId, { - groupType: 'IAM_ROLE_SESSION_GROUP', - maxResults: 50, - }) - - assert.strictEqual(result.items.length, 2) - assert.strictEqual(result.items[0].id, 'gp_profile1') - assert.strictEqual(result.items[0].rolePrincipalArn, 'arn:aws:iam::123456789012:role/AdminRole') - assert.strictEqual(result.items[1].id, 'gp_profile2') - assert.strictEqual(result.nextToken, 'next-token') - - // Verify API was called with correct parameters - assert.ok(mockDataZoneClient.searchGroupProfiles.calledOnce) - const callArgs = mockDataZoneClient.searchGroupProfiles.firstCall.args[0] - assert.strictEqual(callArgs.domainIdentifier, mockDomainId) - assert.strictEqual(callArgs.groupType, 'IAM_ROLE_SESSION_GROUP') - assert.strictEqual(callArgs.maxResults, 50) - }) - - it('should handle empty results', async () => { - const mockResponse = { - items: [], - nextToken: undefined, - } - - const mockDataZoneClient = { - searchGroupProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchGroupProfiles(mockDomainId) - - assert.strictEqual(result.items.length, 0) - assert.strictEqual(result.nextToken, undefined) - }) - - it('should handle API errors', async () => { - const error = new Error('API Error') - const mockDataZoneClient = { - searchGroupProfiles: sinon.stub().returns({ - promise: () => Promise.reject(error), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - await assert.rejects(() => client.searchGroupProfiles(mockDomainId), error) - }) - - it('should support pagination with nextToken', async () => { - const mockResponse = { - items: [ - { - domainId: mockDomainId, - id: 'gp_profile3', - status: 'ACTIVATED', - groupName: 'TestGroup', - rolePrincipalArn: 'arn:aws:iam::123456789012:role/TestRole', - rolePrincipalId: 'AIDAI111111111EXAMPLE', - }, - ], - nextToken: undefined, - } - - const mockDataZoneClient = { - searchGroupProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchGroupProfiles(mockDomainId, { - nextToken: 'previous-token', - }) - - assert.strictEqual(result.items.length, 1) - assert.strictEqual(result.nextToken, undefined) - - // Verify nextToken was passed - const callArgs = mockDataZoneClient.searchGroupProfiles.firstCall.args[0] - assert.strictEqual(callArgs.nextToken, 'previous-token') - }) - }) - - describe('searchUserProfiles', () => { - const mockDomainId = 'dzd_test123' - - it('should search user profiles successfully', async () => { - const mockResponse = { - items: [ - { - domainId: mockDomainId, - id: 'up_user1', - type: 'IAM', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI123456789EXAMPLE:session1', - }, - }, - }, - { - domainId: mockDomainId, - id: 'up_user2', - type: 'IAM', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/DeveloperRole', - principalId: 'AIDAI987654321EXAMPLE:session2', - }, - }, - }, - ], - nextToken: 'next-token', - } - - const mockDataZoneClient = { - searchUserProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchUserProfiles(mockDomainId, { - userType: 'DATAZONE_IAM_USER', - maxResults: 50, - }) - - assert.strictEqual(result.items.length, 2) - assert.strictEqual(result.items[0].id, 'up_user1') - assert.strictEqual(result.items[0].details?.iam?.principalId, 'AIDAI123456789EXAMPLE:session1') - assert.strictEqual(result.items[1].id, 'up_user2') - assert.strictEqual(result.nextToken, 'next-token') - - // Verify API was called with correct parameters - assert.ok(mockDataZoneClient.searchUserProfiles.calledOnce) - const callArgs = mockDataZoneClient.searchUserProfiles.firstCall.args[0] - assert.strictEqual(callArgs.domainIdentifier, mockDomainId) - assert.strictEqual(callArgs.userType, 'DATAZONE_IAM_USER') - assert.strictEqual(callArgs.maxResults, 50) - }) - - it('should handle SSO user profiles', async () => { - const mockResponse = { - items: [ - { - domainId: mockDomainId, - id: 'up_sso_user', - type: 'SSO', - status: 'ACTIVATED', - details: { - sso: { - firstName: 'John', - lastName: 'Doe', - username: 'jdoe', - }, - }, - }, - ], - nextToken: undefined, - } - - const mockDataZoneClient = { - searchUserProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchUserProfiles(mockDomainId, { - userType: 'SSO_USER', - }) - - assert.strictEqual(result.items.length, 1) - assert.strictEqual(result.items[0].details?.sso?.username, 'jdoe') - assert.strictEqual(result.items[0].details?.sso?.firstName, 'John') - }) - - it('should handle empty results', async () => { - const mockResponse = { - items: [], - nextToken: undefined, - } - - const mockDataZoneClient = { - searchUserProfiles: sinon.stub().returns({ - promise: () => Promise.resolve(mockResponse), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - const result = await client.searchUserProfiles(mockDomainId, { - userType: 'DATAZONE_IAM_USER', - }) - - assert.strictEqual(result.items.length, 0) - assert.strictEqual(result.nextToken, undefined) - }) - - it('should handle API errors', async () => { - const error = new Error('API Error') - const mockDataZoneClient = { - searchUserProfiles: sinon.stub().returns({ - promise: () => Promise.reject(error), - }), - } - - sinon.stub(client as any, 'getDataZoneCustomClient').resolves(mockDataZoneClient) - - await assert.rejects( - () => - client.searchUserProfiles(mockDomainId, { - userType: 'DATAZONE_IAM_USER', - }), - error - ) - }) - }) - - describe('getGroupProfileId', () => { - const mockDomainId = 'dzd_test123' - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - - it('should find matching group profile on first page', async () => { - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.onFirstCall().resolves({ - items: [ - { - id: 'gp_profile1', - rolePrincipalArn: mockRoleArn, - status: 'ACTIVATED', - }, - ], - nextToken: undefined, - }) - - const result = await client.getGroupProfileId(mockDomainId, mockRoleArn) - - assert.strictEqual(result, 'gp_profile1') - assert.ok(searchStub.calledOnce) - assert.strictEqual(searchStub.firstCall.args[0], mockDomainId) - assert.strictEqual(searchStub.firstCall.args[1]?.groupType, 'IAM_ROLE_SESSION_GROUP') - }) - - it('should throw ToolkitError when no matching profile found', async () => { - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.resolves({ - items: [ - { - id: 'gp_profile1', - rolePrincipalArn: 'arn:aws:iam::123456789012:role/OtherRole', - status: 'ACTIVATED', - }, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('No group profile found')) - assert.strictEqual(err.code, 'NoGroupProfileFound') - return true - } - ) - }) - - it('should handle API errors', async () => { - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.rejects(new Error('API Error')) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get group profile ID')) - return true - } - ) - }) - }) - - describe('getUserProfileIdForSession', () => { - const mockDomainId = 'dzd_test123' - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - - it('should find matching user profile by role ARN and session name', async () => { - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.onFirstCall().resolves({ - items: [ - { - id: 'up_user1', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI123456789EXAMPLE:my-session', - }, - }, - }, - ], - nextToken: undefined, - }) - - const result = await client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn) - - assert.strictEqual(result, 'up_user1') - assert.ok(searchStub.calledOnce) - assert.strictEqual(searchStub.firstCall.args[0], mockDomainId) - assert.strictEqual(searchStub.firstCall.args[1].userType, 'DATAZONE_IAM_USER') - assert.strictEqual(searchStub.firstCall.args[1].searchText, 'arn:aws:iam::123456789012:role/AdminRole') - }) - - it('should find matching user profile across multiple pages', async () => { - const searchStub = sinon.stub(client, 'searchUserProfiles') - - // First page - no match (different session name) - searchStub.onFirstCall().resolves({ - items: [ - { - id: 'up_user1', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI123456789EXAMPLE:other-session', - }, - }, - }, - ], - nextToken: 'next-token', - }) - - // Second page - match found - searchStub.onSecondCall().resolves({ - items: [ - { - id: 'up_user2', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI987654321EXAMPLE:my-session', - }, - }, - }, - ], - nextToken: undefined, - }) - - const result = await client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn) - - assert.strictEqual(result, 'up_user2') - assert.strictEqual(searchStub.callCount, 2) - }) - - it('should throw ToolkitError when session name cannot be extracted', async () => { - const invalidArn = 'arn:aws:iam::123456789012:role/AdminRole' - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, invalidArn), - (err: any) => { - assert.ok(err.message.includes('Unable to extract session name')) - assert.strictEqual(err.code, 'NoUserProfileFound') - return true - } - ) - }) - - it('should throw ToolkitError when no matching profile found', async () => { - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - id: 'up_user1', - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI123456789EXAMPLE:other-session', - }, - }, - }, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('No user profile found')) - assert.strictEqual(err.code, 'NoUserProfileFound') - return true - } - ) - }) - - it('should handle profiles without IAM details', async () => { - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - id: 'up_user1', - status: 'ACTIVATED', - details: { - // No iam field - }, - }, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('No user profile found')) - return true - } - ) - }) - - it('should handle API errors', async () => { - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.rejects(new Error('API Error')) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get user profile ID')) - return true - } - ) - }) - - it('should handle various role ARN formats', async () => { - const testCases = [ - { - arn: 'arn:aws:sts::123456789012:assumed-role/MyRole/session-123', - expectedSession: 'session-123', - }, - { - arn: 'arn:aws:sts::123456789012:assumed-role/DeveloperRole/user-session-name', - expectedSession: 'user-session-name', - }, - { - arn: 'arn:aws:sts::999888777666:assumed-role/AdminRole/admin-session', - expectedSession: 'admin-session', - }, - ] - - for (const testCase of testCases) { - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - id: 'up_test', - status: 'ACTIVATED', - details: { - iam: { - principalId: `PRINCIPAL:${testCase.expectedSession}`, - }, - }, - }, - ], - nextToken: undefined, - }) - - const result = await client.getUserProfileIdForSession(mockDomainId, testCase.arn) - assert.strictEqual(result, 'up_test') - - searchStub.restore() - } - }) - }) - - describe('Project and Space Filtering', () => { - const mockDomainId = 'dzd_test123' - - describe('Project filtering by group profile', () => { - it('should filter projects when group profile is found', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - const mockGroupProfileId = 'gp_profile1' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.resolves({ - items: [ - { - id: mockGroupProfileId, - rolePrincipalArn: mockRoleArn, - status: 'ACTIVATED', - }, - ], - nextToken: undefined, - }) - - const result = await client.getGroupProfileId(mockDomainId, mockRoleArn) - - assert.strictEqual(result, mockGroupProfileId) - assert.ok(searchStub.calledOnce) - }) - - it('should handle empty project list for group profile', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.resolves({ - items: [], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('No group profile found')) - assert.strictEqual(err.code, 'NoGroupProfileFound') - return true - } - ) - }) - - it('should handle API errors during project filtering', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.rejects(new Error('API Error')) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get group profile ID')) - return true - } - ) - }) - - it('should handle AccessDeniedException during project filtering', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - const accessDeniedError = new Error('Access denied') - accessDeniedError.name = 'AccessDeniedException' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.rejects(accessDeniedError) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get group profile ID')) - return true - } - ) - }) - }) - - describe('Space filtering by user profile', () => { - it('should filter spaces when user profile is found', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - const mockUserProfileId = 'up_user1' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - id: mockUserProfileId, - status: 'ACTIVATED', - details: { - iam: { - arn: 'arn:aws:iam::123456789012:role/AdminRole', - principalId: 'AIDAI123456789EXAMPLE:my-session', - }, - }, - }, - ], - nextToken: undefined, - }) - - const result = await client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn) - - assert.strictEqual(result, mockUserProfileId) - assert.ok(searchStub.calledOnce) - }) - - it('should handle empty space list for user profile', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('No user profile found')) - assert.strictEqual(err.code, 'NoUserProfileFound') - return true - } - ) - }) - - it('should handle API errors during space filtering', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.rejects(new Error('API Error')) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get user profile ID')) - return true - } - ) - }) - - it('should handle AccessDeniedException during space filtering', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - const accessDeniedError = new Error('Access denied') - accessDeniedError.name = 'AccessDeniedException' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.rejects(accessDeniedError) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get user profile ID')) - return true - } - ) - }) - - it('should handle profiles with missing principalId', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - id: 'up_user1', - status: 'ACTIVATED', - details: { - iam: { - // Missing principalId - }, - }, - }, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('No user profile found')) - return true - } - ) - }) - }) - - describe('Error scenarios in filtering logic', () => { - it('should handle network errors during group profile search', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - const networkError = new Error('Network error') - networkError.name = 'NetworkError' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.rejects(networkError) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get group profile ID')) - return true - } - ) - }) - - it('should handle network errors during user profile search', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - const networkError = new Error('Network error') - networkError.name = 'NetworkError' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.rejects(networkError) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get user profile ID')) - return true - } - ) - }) - - it('should handle timeout errors during group profile search', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - const timeoutError = new Error('Request timeout') - timeoutError.name = 'TimeoutError' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.rejects(timeoutError) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('Failed to get group profile ID')) - return true - } - ) - }) - - it('should handle malformed response during group profile search', async () => { - const mockRoleArn = 'arn:aws:iam::123456789012:role/AdminRole' - - const searchStub = sinon.stub(client, 'searchGroupProfiles') - searchStub.resolves({ - items: [ - { - // Missing required fields - status: 'ACTIVATED', - } as any, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getGroupProfileId(mockDomainId, mockRoleArn), - (err: any) => { - assert.ok(err.message.includes('No group profile found')) - return true - } - ) - }) - - it('should handle malformed response during user profile search', async () => { - const mockAssumedRoleArn = 'arn:aws:sts::123456789012:assumed-role/AdminRole/my-session' - - const searchStub = sinon.stub(client, 'searchUserProfiles') - searchStub.resolves({ - items: [ - { - // Missing required fields - status: 'ACTIVATED', - } as any, - ], - nextToken: undefined, - }) - - await assert.rejects( - () => client.getUserProfileIdForSession(mockDomainId, mockAssumedRoleArn), - (err: any) => { - assert.ok(err.message.includes('No user profile found')) - return true - } - ) - }) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/glueClient.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/glueClient.test.ts deleted file mode 100644 index cd34fe7703..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/glueClient.test.ts +++ /dev/null @@ -1,202 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { GlueClient } from '../../../../sagemakerunifiedstudio/shared/client/glueClient' -import { Glue, GetDatabasesCommand, GetTablesCommand, GetTableCommand } from '@aws-sdk/client-glue' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('GlueClient', function () { - let sandbox: sinon.SinonSandbox - let glueClient: GlueClient - let mockGlue: sinon.SinonStubbedInstance - - const mockCredentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockGlue = { - send: sandbox.stub(), - } as any - - sandbox.stub(Glue.prototype, 'send').callsFake(mockGlue.send) - - glueClient = new GlueClient('us-east-1', mockCredentialsProvider as ConnectionCredentialsProvider) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('getDatabases', function () { - it('should get databases successfully', async function () { - const mockResponse = { - DatabaseList: [ - { Name: 'database1', Description: 'Test database 1' }, - { Name: 'database2', Description: 'Test database 2' }, - ], - NextToken: 'next-token', - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getDatabases('test-catalog', undefined, undefined, 'start-token') - - assert.strictEqual(result.databases.length, 2) - assert.strictEqual(result.databases[0].Name, 'database1') - assert.strictEqual(result.nextToken, 'next-token') - - const sendCall = mockGlue.send.getCall(0) - const command = sendCall.args[0] as GetDatabasesCommand - assert.ok(command instanceof GetDatabasesCommand) - }) - - it('should get databases without catalog ID', async function () { - const mockResponse = { - DatabaseList: [{ Name: 'default-db' }], - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getDatabases() - - assert.strictEqual(result.databases.length, 1) - assert.strictEqual(result.databases[0].Name, 'default-db') - assert.strictEqual(result.nextToken, undefined) - }) - - it('should handle errors when getting databases', async function () { - const error = new Error('Access denied') - mockGlue.send.rejects(error) - - await assert.rejects( - async () => { - await glueClient.getDatabases('test-catalog') - }, - { - message: 'Access denied', - } - ) - }) - }) - - describe('getTables', function () { - it('should get tables successfully', async function () { - const mockResponse = { - TableList: [ - { Name: 'table1', DatabaseName: 'test-db' }, - { Name: 'table2', DatabaseName: 'test-db' }, - ], - NextToken: 'next-token', - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getTables('test-db', 'test-catalog', undefined, 'start-token') - - assert.strictEqual(result.tables.length, 2) - assert.strictEqual(result.tables[0].Name, 'table1') - assert.strictEqual(result.nextToken, 'next-token') - - const sendCall = mockGlue.send.getCall(0) - const command = sendCall.args[0] as GetTablesCommand - assert.ok(command instanceof GetTablesCommand) - }) - - it('should get tables without catalog ID', async function () { - const mockResponse = { - TableList: [{ Name: 'default-table' }], - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getTables('test-db') - - assert.strictEqual(result.tables.length, 1) - assert.strictEqual(result.tables[0].Name, 'default-table') - }) - - it('should handle errors when getting tables', async function () { - const error = new Error('Database not found') - mockGlue.send.rejects(error) - - await assert.rejects( - async () => { - await glueClient.getTables('nonexistent-db') - }, - { - message: 'Database not found', - } - ) - }) - }) - - describe('getTable', function () { - it('should get table details successfully', async function () { - const mockResponse = { - Table: { - Name: 'test-table', - DatabaseName: 'test-db', - StorageDescriptor: { - Columns: [ - { Name: 'col1', Type: 'string' }, - { Name: 'col2', Type: 'int' }, - ], - }, - PartitionKeys: [{ Name: 'partition_col', Type: 'date' }], - }, - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getTable('test-db', 'test-table', 'test-catalog') - - assert.strictEqual(result?.Name, 'test-table') - assert.strictEqual(result?.StorageDescriptor?.Columns?.length, 2) - assert.strictEqual(result?.PartitionKeys?.length, 1) - - const sendCall = mockGlue.send.getCall(0) - const command = sendCall.args[0] as GetTableCommand - assert.ok(command instanceof GetTableCommand) - }) - - it('should get table without catalog ID', async function () { - const mockResponse = { - Table: { - Name: 'default-table', - DatabaseName: 'default-db', - }, - } - - mockGlue.send.resolves(mockResponse) - - const result = await glueClient.getTable('default-db', 'default-table') - - assert.strictEqual(result?.Name, 'default-table') - }) - - it('should handle errors when getting table', async function () { - const error = new Error('Table not found') - mockGlue.send.rejects(error) - - await assert.rejects( - async () => { - await glueClient.getTable('test-db', 'nonexistent-table') - }, - { - message: 'Table not found', - } - ) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/gluePrivateClient.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/gluePrivateClient.test.ts deleted file mode 100644 index 049d9e256d..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/gluePrivateClient.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { GlueCatalogClient } from '../../../../sagemakerunifiedstudio/shared/client/glueCatalogClient' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' -import { GlueCatalog } from '@amzn/glue-catalog-client' - -describe('GlueCatalogClient', function () { - let sandbox: sinon.SinonSandbox - let mockGlueCatalogService: any - let glueCatalogConstructorStub: sinon.SinonStub - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockGlueCatalogService = { - getCatalogs: sandbox.stub().resolves({ - CatalogList: [ - { - Name: 'test-catalog', - CatalogType: 'HIVE', - Parameters: { key1: 'value1' }, - }, - ], - }), - } - - // Stub the GlueCatalog constructor - glueCatalogConstructorStub = sandbox.stub(GlueCatalog.prototype, 'getCatalogs') - glueCatalogConstructorStub.callsFake(mockGlueCatalogService.getCatalogs) - }) - - afterEach(function () { - sandbox.restore() - // Reset singleton instance - ;(GlueCatalogClient as any).instance = undefined - }) - - describe('getInstance', function () { - it('should create singleton instance', function () { - const client1 = GlueCatalogClient.getInstance('us-east-1') - const client2 = GlueCatalogClient.getInstance('us-east-1') - - assert.strictEqual(client1, client2) - }) - - it('should return region correctly', function () { - const client = GlueCatalogClient.getInstance('us-west-2') - assert.strictEqual(client.getRegion(), 'us-west-2') - }) - }) - - describe('createWithCredentials', function () { - it('should create client with credentials', function () { - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - const client = GlueCatalogClient.createWithCredentials( - 'us-east-1', - credentialsProvider as ConnectionCredentialsProvider - ) - assert.strictEqual(client.getRegion(), 'us-east-1') - }) - }) - - describe('getCatalogs', function () { - it('should return catalogs successfully', async function () { - const client = GlueCatalogClient.getInstance('us-east-1') - const catalogs = await client.getCatalogs() - - assert.strictEqual(catalogs.catalogs.length, 1) - assert.strictEqual(catalogs.catalogs[0].Name, 'test-catalog') - assert.strictEqual(catalogs.catalogs[0].CatalogType, 'HIVE') - assert.deepStrictEqual(catalogs.catalogs[0].Parameters, { key1: 'value1' }) - }) - - it('should return empty array when no catalogs found', async function () { - mockGlueCatalogService.getCatalogs.resolves({ CatalogList: [] }) - - const client = GlueCatalogClient.getInstance('us-east-1') - const catalogs = await client.getCatalogs() - - assert.strictEqual(catalogs.catalogs.length, 0) - }) - - it('should handle API errors', async function () { - const error = new Error('API Error') - mockGlueCatalogService.getCatalogs.rejects(error) - - const client = GlueCatalogClient.getInstance('us-east-1') - - await assert.rejects(async () => await client.getCatalogs(), error) - }) - - it('should create client with credentials when provided', async function () { - const credentialsProvider = { - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - expiration: new Date('2025-12-31'), - }), - } as any - - const client = GlueCatalogClient.createWithCredentials('us-east-1', credentialsProvider) - const result = await client.getCatalogs() - - // Verify the API method was called and returned expected results - assert.ok(glueCatalogConstructorStub.called) - assert.strictEqual(result.catalogs.length, 1) - assert.strictEqual(client.getRegion(), 'us-east-1') - }) - - it('should handle errors when creating client with credentials', async function () { - const credentialsProvider = { - getCredentials: sandbox.stub().resolves({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } as any - - const client = GlueCatalogClient.createWithCredentials('us-east-1', credentialsProvider) - - // Make getCatalogs fail - const error = new Error('Credentials error') - mockGlueCatalogService.getCatalogs.rejects(error) - - await assert.rejects(async () => await client.getCatalogs(), error) - }) - - it('should create client without credentials when not provided', async function () { - const client = GlueCatalogClient.getInstance('us-east-1') - const result = await client.getCatalogs() - - // Verify the method was called - assert.ok(glueCatalogConstructorStub.called) - assert.strictEqual(result.catalogs.length, 1) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/s3Client.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/s3Client.test.ts deleted file mode 100644 index 714ced3d44..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/s3Client.test.ts +++ /dev/null @@ -1,306 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { S3Client } from '../../../../sagemakerunifiedstudio/shared/client/s3Client' -import { S3 } from '@aws-sdk/client-s3' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('S3Client', function () { - let sandbox: sinon.SinonSandbox - let mockS3: sinon.SinonStubbedInstance - let s3Client: S3Client - - const mockCredentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockS3 = { - listObjectsV2: sandbox.stub(), - } as any - - sandbox.stub(S3.prototype, 'constructor' as any) - sandbox.stub(S3.prototype, 'listObjectsV2').callsFake(mockS3.listObjectsV2) - - s3Client = new S3Client('us-east-1', mockCredentialsProvider as ConnectionCredentialsProvider) - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('constructor', function () { - it('should create client with correct properties', function () { - const client = new S3Client('us-west-2', mockCredentialsProvider as ConnectionCredentialsProvider) - assert.ok(client) - }) - }) - - describe('listPaths', function () { - it('should list folders and files successfully', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'folder1/' }, { Prefix: 'folder2/' }], - Contents: [ - { - Key: 'file1.txt', - Size: 1024, - LastModified: new Date('2023-01-01'), - }, - { - Key: 'file2.txt', - Size: 2048, - LastModified: new Date('2023-01-02'), - }, - ], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket') - - assert.strictEqual(result.paths.length, 4) - const paths = result.paths - - // Check folders - assert.strictEqual(paths[0].displayName, 'folder1') - assert.strictEqual(paths[0].isFolder, true) - assert.strictEqual(paths[0].bucket, 'test-bucket') - assert.strictEqual(paths[0].prefix, 'folder1/') - - assert.strictEqual(paths[1].displayName, 'folder2') - assert.strictEqual(paths[1].isFolder, true) - - // Check files - assert.strictEqual(paths[2].displayName, 'file1.txt') - assert.strictEqual(paths[2].isFolder, false) - assert.strictEqual(paths[2].size, 1024) - assert.deepStrictEqual(paths[2].lastModified, new Date('2023-01-01')) - - assert.strictEqual(paths[3].displayName, 'file2.txt') - assert.strictEqual(paths[3].isFolder, false) - assert.strictEqual(paths[3].size, 2048) - }) - - it('should list paths with prefix', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'prefix/subfolder/' }], - Contents: [ - { - Key: 'prefix/file.txt', - Size: 512, - LastModified: new Date('2023-01-01'), - }, - ], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket', 'prefix/') - - assert.strictEqual(result.paths.length, 2) - const paths = result.paths - assert.strictEqual(paths[0].displayName, 'subfolder') - assert.strictEqual(paths[0].isFolder, true) - assert.strictEqual(paths[1].displayName, 'file.txt') - assert.strictEqual(paths[1].isFolder, false) - - // Verify API call - assert.ok(mockS3.listObjectsV2.calledOnce) - const callArgs = mockS3.listObjectsV2.getCall(0).args[0] - assert.strictEqual(callArgs.Bucket, 'test-bucket') - assert.strictEqual(callArgs.Prefix, 'prefix/') - assert.strictEqual(callArgs.Delimiter, '/') - assert.strictEqual(callArgs.ContinuationToken, undefined) - }) - - it('should return empty array when no objects found', async function () { - const mockResponse = { - CommonPrefixes: [], - Contents: [], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('empty-bucket') - - assert.strictEqual(result.paths.length, 0) - }) - - it('should handle response with only folders', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'folder1/' }, { Prefix: 'folder2/' }], - Contents: undefined, - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket') - - assert.strictEqual(result.paths.length, 2) - const paths = result.paths - assert.strictEqual(paths[0].isFolder, true) - assert.strictEqual(paths[1].isFolder, true) - }) - - it('should handle response with only files', async function () { - const mockResponse = { - CommonPrefixes: undefined, - Contents: [ - { - Key: 'file1.txt', - Size: 1024, - LastModified: new Date('2023-01-01'), - }, - ], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket') - - assert.strictEqual(result.paths.length, 1) - const paths = result.paths - assert.strictEqual(paths[0].isFolder, false) - assert.strictEqual(paths[0].displayName, 'file1.txt') - }) - - it('should filter out folder markers and prefix matches', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'folder/' }], - Contents: [ - { - Key: 'prefix/', - Size: 0, - LastModified: new Date('2023-01-01'), - }, - { - Key: 'prefix/file.txt', - Size: 1024, - LastModified: new Date('2023-01-01'), - }, - { - Key: 'prefix/folder/', - Size: 0, - LastModified: new Date('2023-01-01'), - }, - ], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket', 'prefix/') - - // Should only include the folder from CommonPrefixes and the file (not folder markers) - assert.strictEqual(result.paths.length, 2) - const paths = result.paths - assert.strictEqual(paths[0].displayName, 'folder') - assert.strictEqual(paths[0].isFolder, true) - assert.strictEqual(paths[1].displayName, 'file.txt') - assert.strictEqual(paths[1].isFolder, false) - }) - - it('should handle API errors', async function () { - const error = new Error('S3 API Error') - mockS3.listObjectsV2.rejects(error) - - await assert.rejects(async () => await s3Client.listPaths('test-bucket'), error) - }) - - it('should handle missing object properties gracefully', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: undefined }, { Prefix: 'valid-folder/' }], - Contents: [ - { - Key: undefined, - Size: 1024, - }, - { - Key: 'valid-file.txt', - Size: undefined, - LastModified: undefined, - }, - ], - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket') - - // Should only include valid entries - assert.strictEqual(result.paths.length, 2) - const paths = result.paths - assert.strictEqual(paths[0].displayName, 'valid-folder') - assert.strictEqual(paths[0].isFolder, true) - assert.strictEqual(paths[1].displayName, 'valid-file.txt') - assert.strictEqual(paths[1].isFolder, false) - assert.strictEqual(paths[1].size, undefined) - assert.strictEqual(paths[1].lastModified, undefined) - }) - - it('should create S3 client on first use', async function () { - const mockResponse = { CommonPrefixes: [], Contents: [] } - mockS3.listObjectsV2.resolves(mockResponse) - - await s3Client.listPaths('test-bucket') - - // Verify S3 client was created with correct parameters - assert.ok(S3.prototype.constructor) - }) - - it('should reuse existing S3 client on subsequent calls', async function () { - const mockResponse = { CommonPrefixes: [], Contents: [] } - mockS3.listObjectsV2.resolves(mockResponse) - - // Make multiple calls - await s3Client.listPaths('test-bucket') - await s3Client.listPaths('test-bucket') - - // S3 constructor should only be called once (during first call) - assert.ok(mockS3.listObjectsV2.calledTwice) - }) - - it('should handle ContinuationToken for pagination', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'folder1/' }], - Contents: [{ Key: 'file1.txt', Size: 1024 }], - NextContinuationToken: 'next-token-123', - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket', 'prefix/', 'continuation-token') - - assert.strictEqual(result.paths.length, 2) - assert.strictEqual(result.nextToken, 'next-token-123') - - // Verify ContinuationToken was passed - const callArgs = mockS3.listObjectsV2.getCall(0).args[0] - assert.strictEqual(callArgs.ContinuationToken, 'continuation-token') - }) - - it('should return undefined nextToken when no more pages', async function () { - const mockResponse = { - CommonPrefixes: [{ Prefix: 'folder1/' }], - Contents: [{ Key: 'file1.txt', Size: 1024 }], - NextContinuationToken: undefined, - } - - mockS3.listObjectsV2.resolves(mockResponse) - - const result = await s3Client.listPaths('test-bucket') - - assert.strictEqual(result.paths.length, 2) - assert.strictEqual(result.nextToken, undefined) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.test.ts deleted file mode 100644 index e4b1dc50a8..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/client/sqlWorkbenchClient.test.ts +++ /dev/null @@ -1,249 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { - SQLWorkbenchClient, - generateSqlWorkbenchArn, - createRedshiftConnectionConfig, -} from '../../../../sagemakerunifiedstudio/shared/client/sqlWorkbenchClient' -import { STSClient } from '@aws-sdk/client-sts' -import globals from '../../../../shared/extensionGlobals' -import { ConnectionCredentialsProvider } from '../../../../sagemakerunifiedstudio/auth/providers/connectionCredentialsProvider' - -describe('SQLWorkbenchClient', function () { - let sandbox: sinon.SinonSandbox - let mockSqlClient: any - let mockSdkClientBuilder: any - - beforeEach(function () { - sandbox = sinon.createSandbox() - - mockSqlClient = { - getResources: sandbox.stub().returns({ - promise: sandbox.stub().resolves({ - resources: [{ name: 'test-resource' }], - nextToken: 'next-token', - }), - }), - executeQuery: sandbox.stub().returns({ - promise: sandbox.stub().resolves({ - queryExecutions: [{ queryExecutionId: 'test-execution-id' }], - }), - }), - } - - mockSdkClientBuilder = { - createAwsService: sandbox.stub().resolves(mockSqlClient), - } - - sandbox.stub(globals, 'sdkClientBuilder').value(mockSdkClientBuilder) - }) - - afterEach(function () { - sandbox.restore() - // Reset singleton instance - ;(SQLWorkbenchClient as any).instance = undefined - }) - - describe('getInstance', function () { - it('should create singleton instance', function () { - const client1 = SQLWorkbenchClient.getInstance('us-east-1') - const client2 = SQLWorkbenchClient.getInstance('us-east-1') - - assert.strictEqual(client1, client2) - }) - - it('should return region correctly', function () { - const client = SQLWorkbenchClient.getInstance('us-west-2') - assert.strictEqual(client.getRegion(), 'us-west-2') - }) - }) - - describe('createWithCredentials', function () { - it('should create client with credentials', function () { - const credentialsProvider = { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - sessionToken: 'test-token', - }), - } - - const client = SQLWorkbenchClient.createWithCredentials( - 'us-east-1', - credentialsProvider as ConnectionCredentialsProvider - ) - assert.strictEqual(client.getRegion(), 'us-east-1') - }) - }) - - describe('getResources', function () { - it('should get resources with connection', async function () { - const client = SQLWorkbenchClient.getInstance('us-east-1') - const connectionConfig = { - id: 'test-id', - type: 'test-type', - databaseType: 'REDSHIFT', - connectableResourceIdentifier: 'test-identifier', - connectableResourceType: 'CLUSTER', - database: 'test-db', - } - - const result = await client.getResources({ - connection: connectionConfig, - resourceType: 'TABLE', - maxItems: 50, - }) - - assert.deepStrictEqual(result.resources, [{ name: 'test-resource' }]) - assert.strictEqual(result.nextToken, 'next-token') - }) - - it('should handle API errors', async function () { - const error = new Error('API Error') - mockSqlClient.getResources.returns({ - promise: sandbox.stub().rejects(error), - }) - - const client = SQLWorkbenchClient.getInstance('us-east-1') - - await assert.rejects( - async () => - await client.getResources({ - connection: { - id: '', - type: '', - databaseType: '', - connectableResourceIdentifier: '', - connectableResourceType: '', - database: '', - }, - resourceType: '', - }), - error - ) - }) - }) - - describe('executeQuery', function () { - it('should execute query successfully', async function () { - const client = SQLWorkbenchClient.getInstance('us-east-1') - const connectionConfig = { - id: 'test-id', - type: 'test-type', - databaseType: 'REDSHIFT', - connectableResourceIdentifier: 'test-identifier', - connectableResourceType: 'CLUSTER', - database: 'test-db', - } - - const result = await client.executeQuery(connectionConfig, 'SELECT 1') - - assert.strictEqual(result, 'test-execution-id') - }) - - it('should handle query execution errors', async function () { - const error = new Error('Query Error') - mockSqlClient.executeQuery.returns({ - promise: sandbox.stub().rejects(error), - }) - - const client = SQLWorkbenchClient.getInstance('us-east-1') - const connectionConfig = { - id: 'test-id', - type: 'test-type', - databaseType: 'REDSHIFT', - connectableResourceIdentifier: 'test-identifier', - connectableResourceType: 'CLUSTER', - database: 'test-db', - } - - await assert.rejects(async () => await client.executeQuery(connectionConfig, 'SELECT 1'), error) - }) - }) -}) - -describe('generateSqlWorkbenchArn', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should generate ARN with provided account ID', async function () { - const arn = await generateSqlWorkbenchArn('us-east-1', '123456789012') - - assert.ok(arn.startsWith('arn:aws:sqlworkbench:us-east-1:123456789012:connection/')) - assert.ok(arn.includes('-')) - }) -}) - -describe('createRedshiftConnectionConfig', function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - sandbox.stub(STSClient.prototype, 'send').resolves({ Account: '123456789012' }) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should create serverless connection config', async function () { - const config = await createRedshiftConnectionConfig( - 'test-workgroup.123456789012.us-east-1.redshift-serverless.amazonaws.com', - 'test-db', - '123456789012', - 'us-east-1', - '', - false - ) - - assert.strictEqual(config.databaseType, 'REDSHIFT') - assert.strictEqual(config.connectableResourceType, 'WORKGROUP') - assert.strictEqual(config.connectableResourceIdentifier, 'test-workgroup') - assert.strictEqual(config.database, 'test-db') - assert.strictEqual(config.type, '4') // FEDERATED - }) - - it('should create cluster connection config', async function () { - const config = await createRedshiftConnectionConfig( - 'test-cluster.123456789012.us-east-1.redshift.amazonaws.com', - 'test-db', - '123456789012', - 'us-east-1', - '', - false - ) - - assert.strictEqual(config.databaseType, 'REDSHIFT') - assert.strictEqual(config.connectableResourceType, 'CLUSTER') - assert.strictEqual(config.connectableResourceIdentifier, 'test-cluster') - assert.strictEqual(config.database, 'test-db') - assert.strictEqual(config.type, '5') // TEMPORARY_CREDENTIALS_WITH_IAM - }) - - it('should create config with secret authentication', async function () { - const config = await createRedshiftConnectionConfig( - 'test-cluster.123456789012.us-east-1.redshift.amazonaws.com', - 'test-db', - '123456789012', - 'us-east-1', - 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret', - false - ) - - assert.strictEqual(config.type, '6') // SECRET - assert.ok(config.auth) - assert.strictEqual(config.auth.secretArn, 'arn:aws:secretsmanager:us-east-1:123456789012:secret:test-secret') - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/devSettingsEndpointConfiguration.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/devSettingsEndpointConfiguration.test.ts deleted file mode 100644 index c532128301..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/devSettingsEndpointConfiguration.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { DevSettings } from '../../../shared/settings' - -describe('Endpoint Configuration from Settings', () => { - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('DataZone endpoint configuration', () => { - it('should return custom DataZone endpoint when configured', () => { - const customEndpoint = 'https://custom-datazone.example.com' - const getStub = sandbox.stub(DevSettings.instance, 'get') - getStub.withArgs('endpoints', {}).returns({ datazone: customEndpoint }) - - const endpoints = DevSettings.instance.get('endpoints', {}) - const datazoneEndpoint = endpoints['datazone'] - - assert.strictEqual(datazoneEndpoint, customEndpoint) - }) - }) - - describe('SageMaker endpoint configuration', () => { - it('should return custom SageMaker endpoint when configured', () => { - const customEndpoint = 'https://custom-sagemaker.example.com' - const getStub = sandbox.stub(DevSettings.instance, 'get') - getStub.withArgs('endpoints', {}).returns({ sagemaker: customEndpoint }) - - const endpoints = DevSettings.instance.get('endpoints', {}) - const sagemakerEndpoint = endpoints['sagemaker'] - - assert.strictEqual(sagemakerEndpoint, customEndpoint) - }) - }) - - describe('Endpoint fallback behavior', () => { - it('should construct default DataZone endpoint when custom endpoint is not set', () => { - const getStub = sandbox.stub(DevSettings.instance, 'get') - getStub.withArgs('endpoints', {}).returns({}) - - const region = 'us-west-2' - const endpoints = DevSettings.instance.get('endpoints', {}) - const customEndpoint = endpoints['datazone'] - const endpoint = customEndpoint || `https://datazone.${region}.api.aws` - - assert.strictEqual(endpoint, 'https://datazone.us-west-2.api.aws') - }) - - it('should construct default SageMaker endpoint when custom endpoint is not set', () => { - const getStub = sandbox.stub(DevSettings.instance, 'get') - getStub.withArgs('endpoints', {}).returns({}) - - const region = 'us-east-1' - const endpoints = DevSettings.instance.get('endpoints', {}) - const customEndpoint = endpoints['sagemaker'] - const endpoint = customEndpoint || `https://sagemaker.${region}.amazonaws.com` - - assert.strictEqual(endpoint, 'https://sagemaker.us-east-1.amazonaws.com') - }) - - it('should handle multiple endpoints in configuration', () => { - const customEndpoints = { - datazone: 'https://custom-datazone.example.com', - sagemaker: 'https://custom-sagemaker.example.com', - } - const getStub = sandbox.stub(DevSettings.instance, 'get') - getStub.withArgs('endpoints', {}).returns(customEndpoints) - - const endpoints = DevSettings.instance.get('endpoints', {}) - - assert.strictEqual(endpoints['datazone'], customEndpoints.datazone) - assert.strictEqual(endpoints['sagemaker'], customEndpoints.sagemaker) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/smusUtils.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/smusUtils.test.ts deleted file mode 100644 index a0b556ca66..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/smusUtils.test.ts +++ /dev/null @@ -1,797 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { - SmusUtils, - SmusErrorCodes, - SmusTimeouts, - SmusCredentialExpiry, - validateCredentialFields, - extractAccountIdFromSageMakerArn, - extractAccountIdFromResourceMetadata, - isCredentialExpirationError, - isIamDomain, - IamSignInRole, - IamSignInUser, - DomainVersionV1, - DomainVersionV2, -} from '../../../sagemakerunifiedstudio/shared/smusUtils' -import { ToolkitError } from '../../../shared/errors' -import * as extensionUtilities from '../../../shared/extensionUtilities' -import * as resourceMetadataUtils from '../../../sagemakerunifiedstudio/shared/utils/resourceMetadataUtils' -import fetch from 'node-fetch' - -describe('SmusUtils', () => { - const testDomainUrl = 'https://dzd_domainId.sagemaker.us-east-2.on.aws' - const testDomainIdLowercase = 'dzd_domainid' // Domain IDs get lowercased by URL parsing - const testRegion = 'us-east-2' - - afterEach(() => { - sinon.restore() - }) - - describe('extractDomainIdFromUrl', () => { - it('should extract domain ID from valid URL', () => { - const result = SmusUtils.extractDomainIdFromUrl(testDomainUrl) - assert.strictEqual(result, testDomainIdLowercase) - }) - - it('should return undefined for invalid URL', () => { - const result = SmusUtils.extractDomainIdFromUrl('invalid-url') - assert.strictEqual(result, undefined) - }) - - it('should handle URLs with dzd- prefix', () => { - const urlWithDash = 'https://dzd-domainId.sagemaker.us-east-2.on.aws' - const result = SmusUtils.extractDomainIdFromUrl(urlWithDash) - assert.strictEqual(result, 'dzd-domainid') - }) - - it('should handle URLs with dzd_ prefix', () => { - const urlWithUnderscore = 'https://dzd_domainId.sagemaker.us-east-2.on.aws' - const result = SmusUtils.extractDomainIdFromUrl(urlWithUnderscore) - assert.strictEqual(result, testDomainIdLowercase) - }) - }) - - describe('extractRegionFromUrl', () => { - it('should extract region from valid URL', () => { - const result = SmusUtils.extractRegionFromUrl(testDomainUrl) - assert.strictEqual(result, testRegion) - }) - - it('should return fallback region for invalid URL', () => { - const result = SmusUtils.extractRegionFromUrl('invalid-url', 'us-west-2') - assert.strictEqual(result, 'us-west-2') - }) - - it('should return default fallback region when not specified', () => { - const result = SmusUtils.extractRegionFromUrl('invalid-url') - assert.strictEqual(result, 'us-east-1') - }) - - it('should handle different regions', () => { - const urlWithDifferentRegion = 'https://dzd_test.sagemaker.eu-west-1.on.aws' - const result = SmusUtils.extractRegionFromUrl(urlWithDifferentRegion) - assert.strictEqual(result, 'eu-west-1') - }) - - it('should handle non-prod stages', () => { - const urlWithStage = 'https://dzd_test.sagemaker-gamma.us-west-2.on.aws' - const result = SmusUtils.extractRegionFromUrl(urlWithStage) - assert.strictEqual(result, 'us-west-2') - }) - }) - - describe('extractDomainInfoFromUrl', () => { - it('should extract both domain ID and region', () => { - const result = SmusUtils.extractDomainInfoFromUrl(testDomainUrl) - assert.strictEqual(result.domainId, testDomainIdLowercase) - assert.strictEqual(result.region, testRegion) - }) - - it('should use fallback region when extraction fails', () => { - const result = SmusUtils.extractDomainInfoFromUrl('invalid-url', 'us-west-2') - assert.strictEqual(result.domainId, undefined) - assert.strictEqual(result.region, 'us-west-2') - }) - }) - - describe('validateDomainUrl', () => { - it('should return undefined for valid URL', () => { - const result = SmusUtils.validateDomainUrl(testDomainUrl) - assert.strictEqual(result, undefined) - }) - - it('should return error for empty URL', () => { - const result = SmusUtils.validateDomainUrl('') - assert.strictEqual(result, 'Domain URL is required') - }) - - it('should return error for whitespace-only URL', () => { - const result = SmusUtils.validateDomainUrl(' ') - assert.strictEqual(result, 'Domain URL is required') - }) - - it('should return error for non-HTTPS URL', () => { - const result = SmusUtils.validateDomainUrl('http://dzd_test.sagemaker.us-east-1.on.aws') - assert.strictEqual(result, 'Domain URL must use HTTPS (https://)') - }) - - it('should return error for non-SageMaker domain', () => { - const result = SmusUtils.validateDomainUrl('https://example.com') - assert.strictEqual( - result, - 'URL must be a valid SageMaker Unified Studio domain (e.g., https://dzd_xxxxxxxxx.sagemaker.us-east-1.on.aws)' - ) - }) - - it('should return error for URL without domain ID', () => { - const result = SmusUtils.validateDomainUrl('https://invalid.sagemaker.us-east-1.on.aws') - assert.strictEqual(result, 'URL must contain a valid domain ID (starting with dzd- or dzd_)') - }) - - it('should return error for invalid URL format', () => { - const result = SmusUtils.validateDomainUrl('not-a-url') - assert.strictEqual(result, 'Domain URL must use HTTPS (https://)') - }) - - it('should handle URLs with dzd- prefix', () => { - const urlWithDash = 'https://dzd-domainId.sagemaker.us-east-2.on.aws' - const result = SmusUtils.validateDomainUrl(urlWithDash) - assert.strictEqual(result, undefined) - }) - - it('should handle URLs with dzd_ prefix', () => { - const urlWithUnderscore = 'https://dzd_domainId.sagemaker.us-east-2.on.aws' - const result = SmusUtils.validateDomainUrl(urlWithUnderscore) - assert.strictEqual(result, undefined) - }) - - it('should trim whitespace from URL', () => { - const urlWithWhitespace = ' https://dzd_domainId.sagemaker.us-east-2.on.aws ' - const result = SmusUtils.validateDomainUrl(urlWithWhitespace) - assert.strictEqual(result, undefined) - }) - }) - - describe('constants', () => { - it('should export SmusErrorCodes with correct values', () => { - assert.strictEqual(SmusErrorCodes.NoActiveConnection, 'NoActiveConnection') - assert.strictEqual(SmusErrorCodes.ApiTimeout, 'ApiTimeout') - assert.strictEqual(SmusErrorCodes.SmusLoginFailed, 'SmusLoginFailed') - assert.strictEqual(SmusErrorCodes.RedeemAccessTokenFailed, 'RedeemAccessTokenFailed') - }) - - it('should export SmusTimeouts with correct values', () => { - assert.strictEqual(SmusTimeouts.apiCallTimeoutMs, 10 * 1000) - }) - - it('should export SmusCredentialExpiry with correct values', () => { - assert.strictEqual(SmusCredentialExpiry.derExpiryMs, 10 * 60 * 1000) - assert.strictEqual(SmusCredentialExpiry.projectExpiryMs, 10 * 60 * 1000) - assert.strictEqual(SmusCredentialExpiry.connectionExpiryMs, 10 * 60 * 1000) - }) - }) - - describe('getSsoInstanceInfo', () => { - let fetchStub: sinon.SinonStub - - beforeEach(() => { - fetchStub = sinon.stub(fetch, 'default' as any) - }) - - afterEach(() => { - fetchStub.restore() - }) - - it('should throw error for invalid domain URL', async () => { - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo('invalid-url'), - (error: any) => { - assert.strictEqual(error.code, 'InvalidDomainUrl') - return true - } - ) - }) - - it('should throw error for URL without domain ID', async () => { - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo('https://invalid.sagemaker.us-east-1.on.aws'), - (error: any) => { - assert.strictEqual(error.code, 'InvalidDomainUrl') - return true - } - ) - }) - - it('should handle timeout errors', async () => { - const timeoutError = new Error('Request timeout') - timeoutError.name = 'AbortError' - fetchStub.rejects(timeoutError) - - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo(testDomainUrl), - (error: any) => { - assert.strictEqual(error.code, SmusErrorCodes.ApiTimeout) - assert.ok(error.message.includes('timed out after 10 seconds')) - return true - } - ) - }) - - it('should handle login failure errors', async () => { - fetchStub.resolves({ - ok: false, - status: 401, - statusText: 'Unauthorized', - }) - - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo(testDomainUrl), - (error: any) => { - assert.strictEqual(error.code, SmusErrorCodes.SmusLoginFailed) - assert.ok(error.message.includes('401')) - return true - } - ) - }) - - it('should successfully extract SSO instance info', async () => { - const mockResponse = { - ok: true, - json: sinon.stub().resolves({ - redirectUrl: - 'https://example.com/oauth/authorize?client_id=arn%3Aaws%3Asso%3A%3A123456789%3Aapplication%2Fssoins-123%2Fapl-456', - }), - } - fetchStub.resolves(mockResponse) - - const result = await SmusUtils.getSsoInstanceInfo(testDomainUrl) - - assert.strictEqual(result.ssoInstanceId, 'ssoins-123') - assert.strictEqual(result.issuerUrl, 'https://identitycenter.amazonaws.com/ssoins-123') - assert.strictEqual(result.clientId, 'arn:aws:sso::123456789:application/ssoins-123/apl-456') - assert.strictEqual(result.region, testRegion) - }) - - it('should throw error for missing redirect URL', async () => { - const mockResponse = { - ok: true, - json: sinon.stub().resolves({}), - } - fetchStub.resolves(mockResponse) - - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo(testDomainUrl), - (error: any) => { - assert.strictEqual(error.code, 'InvalidLoginResponse') - return true - } - ) - }) - - it('should throw error for missing client_id in redirect URL', async () => { - const mockResponse = { - ok: true, - json: sinon.stub().resolves({ - redirectUrl: 'https://example.com/oauth/authorize', - }), - } - fetchStub.resolves(mockResponse) - - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo(testDomainUrl), - (error: any) => { - assert.strictEqual(error.code, 'InvalidRedirectUrl') - return true - } - ) - }) - - it('should throw error for invalid ARN format', async () => { - const mockResponse = { - ok: true, - json: sinon.stub().resolves({ - redirectUrl: 'https://example.com/oauth/authorize?client_id=invalid-arn', - }), - } - fetchStub.resolves(mockResponse) - - await assert.rejects( - () => SmusUtils.getSsoInstanceInfo(testDomainUrl), - (error: any) => { - assert.strictEqual(error.code, 'InvalidArnFormat') - return true - } - ) - }) - }) - - describe('extractSSOIdFromUserId', () => { - it('should extract SSO ID from valid user ID', () => { - const result = SmusUtils.extractSSOIdFromUserId('user-12345678-abcd-efgh-ijkl-123456789012') - assert.strictEqual(result, '12345678-abcd-efgh-ijkl-123456789012') - }) - - it('should throw error for invalid user ID format', () => { - assert.throws( - () => SmusUtils.extractSSOIdFromUserId('invalid-format'), - /Invalid UserId format: invalid-format/ - ) - }) - - it('should throw error for empty user ID', () => { - assert.throws(() => SmusUtils.extractSSOIdFromUserId(''), /Invalid UserId format: /) - }) - - it('should throw error for user ID without prefix', () => { - assert.throws( - () => SmusUtils.extractSSOIdFromUserId('12345678-abcd-efgh-ijkl-123456789012'), - /Invalid UserId format: 12345678-abcd-efgh-ijkl-123456789012/ - ) - }) - }) - - describe('validateCredentialFields', () => { - it('should not throw for valid credentials', () => { - const validCredentials = { - accessKeyId: 'AKIAIOSFODNN7EXAMPLE', - secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - sessionToken: - 'AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE', - } - - assert.doesNotThrow(() => { - validateCredentialFields(validCredentials, 'TestError', 'test context') - }) - }) - - it('should throw for missing accessKeyId', () => { - const invalidCredentials = { - secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - sessionToken: 'token', - } - - assert.throws( - () => validateCredentialFields(invalidCredentials, 'TestError', 'test context'), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.strictEqual(error.code, 'TestError') - assert.ok(error.message.includes('Invalid accessKeyId in test context')) - return true - } - ) - }) - - it('should throw for invalid accessKeyId type', () => { - const invalidCredentials = { - accessKeyId: 123, - secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - sessionToken: 'token', - } - - assert.throws( - () => validateCredentialFields(invalidCredentials, 'TestError', 'test context'), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.strictEqual(error.code, 'TestError') - assert.ok(error.message.includes('Invalid accessKeyId in test context: number')) - return true - } - ) - }) - - it('should throw for missing secretAccessKey', () => { - const invalidCredentials = { - accessKeyId: 'AKIAIOSFODNN7EXAMPLE', - sessionToken: 'token', - } - - assert.throws( - () => validateCredentialFields(invalidCredentials, 'TestError', 'test context'), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.strictEqual(error.code, 'TestError') - assert.ok(error.message.includes('Invalid secretAccessKey in test context')) - return true - } - ) - }) - - it('should throw for missing sessionToken', () => { - const invalidCredentials = { - accessKeyId: 'AKIAIOSFODNN7EXAMPLE', - secretAccessKey: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', - } - - assert.throws( - () => validateCredentialFields(invalidCredentials, 'TestError', 'test context'), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.strictEqual(error.code, 'TestError') - assert.ok(error.message.includes('Invalid sessionToken in test context')) - return true - } - ) - }) - }) - - describe('isInSmusSpaceEnvironment', () => { - let isSageMakerStub: sinon.SinonStub - let getResourceMetadataStub: sinon.SinonStub - - beforeEach(() => { - isSageMakerStub = sinon.stub(extensionUtilities, 'isSageMaker') - getResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'getResourceMetadata') - }) - - it('should return true when in SMUS space with DataZone domain ID', () => { - isSageMakerStub.withArgs('SMUS').returns(true) - getResourceMetadataStub.returns({ - AdditionalMetadata: { - DataZoneDomainId: 'dz-domain-123', - }, - }) - - const result = SmusUtils.isInSmusSpaceEnvironment() - assert.strictEqual(result, true) - }) - - it('should return false when not in SMUS space', () => { - isSageMakerStub.withArgs('SMUS').returns(false) - isSageMakerStub.withArgs('SMUS-SPACE-REMOTE-ACCESS').returns(false) - - const result = SmusUtils.isInSmusSpaceEnvironment() - assert.strictEqual(result, false) - }) - - it('should return false when in SMUS space but no resource metadata', () => { - isSageMakerStub.withArgs('SMUS').returns(true) - getResourceMetadataStub.returns(undefined) - - const result = SmusUtils.isInSmusSpaceEnvironment() - assert.strictEqual(result, false) - }) - - it('should return false when in SMUS space but no DataZone domain ID', () => { - isSageMakerStub.withArgs('SMUS').returns(true) - getResourceMetadataStub.returns({ - AdditionalMetadata: {}, - }) - - const result = SmusUtils.isInSmusSpaceEnvironment() - assert.strictEqual(result, false) - }) - }) - - describe('isIamUserArn', () => { - it('should return true for IAM user ARN', () => { - const iamUserArn = 'arn:aws:iam::619071339486:user/vabharga-test' - const result = SmusUtils.isIamUserArn(iamUserArn) - assert.strictEqual(result, true) - }) - - it('should return false for IAM role session ARN', () => { - const roleSessionArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/MySession' - const result = SmusUtils.isIamUserArn(roleSessionArn) - assert.strictEqual(result, false) - }) - - it('should return false for IAM role ARN', () => { - const roleArn = 'arn:aws:iam::123456789012:role/MyRole' - const result = SmusUtils.isIamUserArn(roleArn) - assert.strictEqual(result, false) - }) - - it('should return false for undefined ARN', () => { - const result = SmusUtils.isIamUserArn(undefined) - assert.strictEqual(result, false) - }) - - it('should return false for empty string', () => { - const result = SmusUtils.isIamUserArn('') - assert.strictEqual(result, false) - }) - - it('should return false for invalid ARN format', () => { - const result = SmusUtils.isIamUserArn('not-an-arn') - assert.strictEqual(result, false) - }) - - it('should return false for non-IAM ARN', () => { - const s3Arn = 'arn:aws:s3:::my-bucket' - const result = SmusUtils.isIamUserArn(s3Arn) - assert.strictEqual(result, false) - }) - }) - - describe('convertAssumedRoleArnToIamRoleArn', () => { - it('should convert basic assumed role ARN to IAM role ARN', () => { - const stsArn = 'arn:aws:sts::123456789012:assumed-role/MyRole/MySession' - const expected = 'arn:aws:iam::123456789012:role/MyRole' - - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(stsArn) - assert.strictEqual(result, expected) - }) - - it('should convert assumed role ARN with aws-cn partition', () => { - const stsArn = 'arn:aws-cn:sts::123456789012:assumed-role/MyRole/MySession' - const expected = 'arn:aws-cn:iam::123456789012:role/MyRole' - - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(stsArn) - assert.strictEqual(result, expected) - }) - - it('should convert assumed role ARN with aws-us-gov partition', () => { - const stsArn = 'arn:aws-us-gov:sts::123456789012:assumed-role/MyRole/MySession' - const expected = 'arn:aws-us-gov:iam::123456789012:role/MyRole' - - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(stsArn) - assert.strictEqual(result, expected) - }) - - it('should return IAM user ARN as-is', () => { - const iamUserArn = 'arn:aws:iam::619071339486:user/vabharga-test' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamUserArn) - assert.strictEqual(result, iamUserArn) - }) - - it('should return IAM user ARN with aws-cn partition as-is', () => { - const iamUserArn = 'arn:aws-cn:iam::123456789012:user/my-user' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamUserArn) - assert.strictEqual(result, iamUserArn) - }) - - it('should return IAM user ARN with aws-us-gov partition as-is', () => { - const iamUserArn = 'arn:aws-us-gov:iam::123456789012:user/my-user' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamUserArn) - assert.strictEqual(result, iamUserArn) - }) - - it('should return IAM role ARN as-is', () => { - const iamRoleArn = 'arn:aws:iam::123456789012:role/MyRole' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamRoleArn) - assert.strictEqual(result, iamRoleArn) - }) - - it('should return IAM role ARN with aws-cn partition as-is', () => { - const iamRoleArn = 'arn:aws-cn:iam::123456789012:role/MyRole' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamRoleArn) - assert.strictEqual(result, iamRoleArn) - }) - - it('should handle IAM user ARN with special characters', () => { - const iamUserArn = 'arn:aws:iam::123456789012:user/path/to/user-name_123' - const result = SmusUtils.convertAssumedRoleArnToIamRoleArn(iamUserArn) - assert.strictEqual(result, iamUserArn) - }) - - it('should throw error for invalid ARN format - missing components', () => { - const invalidArn = 'arn:aws:sts::123456789012:assumed-role/MyRole' - - assert.throws( - () => SmusUtils.convertAssumedRoleArnToIamRoleArn(invalidArn), - (error: Error) => { - assert.ok(error.message.includes('Invalid STS ARN format')) - assert.ok(error.message.includes(invalidArn)) - return true - } - ) - }) - - it('should throw error for empty string', () => { - assert.throws( - () => SmusUtils.convertAssumedRoleArnToIamRoleArn(''), - (error: Error) => { - assert.ok(error.message.includes('Invalid STS ARN format')) - return true - } - ) - }) - }) -}) - -describe('extractAccountIdFromSageMakerArn', () => { - describe('valid ARN formats', () => { - it('should extract account ID from valid ARN', () => { - const arn = 'arn:aws:sagemaker:us-west-2:123456789012:app/domain-id/ce/CodeEditor/default' - const result = extractAccountIdFromSageMakerArn(arn) - - assert.strictEqual(result, '123456789012') - }) - }) - - describe('invalid ARN formats', () => { - it('should throw error for empty ARN', () => { - assert.throws( - () => extractAccountIdFromSageMakerArn(''), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.ok(error.message.includes('Invalid SageMaker ARN format')) - return true - } - ) - }) - - it('should throw error for non-ARN string', () => { - assert.throws( - () => extractAccountIdFromSageMakerArn('not-an-arn'), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.ok(error.message.includes('Invalid SageMaker ARN format')) - return true - } - ) - }) - - it('should throw error for wrong service', () => { - const arn = 'arn:aws:s3:us-east-1:123456789012:bucket/my-bucket' - assert.throws( - () => extractAccountIdFromSageMakerArn(arn), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.ok(error.message.includes('Invalid SageMaker ARN format')) - return true - } - ) - }) - - it('should throw error for missing account ID', () => { - const arn = 'arn:aws:sagemaker:us-east-1::space/domain/space' - assert.throws( - () => extractAccountIdFromSageMakerArn(arn), - (error: any) => { - assert.ok(error instanceof ToolkitError) - assert.ok(error.message.includes('Invalid SageMaker ARN format')) - return true - } - ) - }) - }) -}) - -describe('extractAccountIdFromResourceMetadata', () => { - let getResourceMetadataStub: sinon.SinonStub - - beforeEach(() => { - getResourceMetadataStub = sinon.stub(resourceMetadataUtils, 'getResourceMetadata') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should extract account ID from ResourceArn successfully', async () => { - const testAccountId = '123456789012' - const testResourceArn = `arn:aws:sagemaker:us-east-1:${testAccountId}:app/domain-id/appName/CodeEditor/default` - - getResourceMetadataStub.returns({ - ResourceArn: testResourceArn, - }) - - const result = await extractAccountIdFromResourceMetadata() - - assert.strictEqual(result, testAccountId) - assert.ok(getResourceMetadataStub.called) - }) - - it('should throw error when ResourceArn is missing', async () => { - getResourceMetadataStub.returns({}) - - await assert.rejects( - () => extractAccountIdFromResourceMetadata(), - (err: Error) => { - return err.message.includes( - 'Failed to extract AWS account ID from ResourceArn in SMUS space environment' - ) - } - ) - }) - - it('should throw error when extractAccountIdFromSageMakerArn fails', async () => { - const testResourceArn = 'invalid-arn' - getResourceMetadataStub.returns({ - ResourceArn: testResourceArn, - }) - - await assert.rejects( - () => extractAccountIdFromResourceMetadata(), - (err: Error) => { - return err.message.includes( - 'Failed to extract AWS account ID from ResourceArn in SMUS space environment' - ) - } - ) - }) -}) - -describe('isCredentialExpirationError', () => { - describe('should return true for credential expiration errors', () => { - it('should detect ExpiredTokenException by error name (exact match)', () => { - const error = { - name: 'ExpiredTokenException', - message: 'Token has expired', - } - - const result = isCredentialExpirationError(error) - assert.strictEqual(result, true) - }) - - it('should detect ExpiredTokenException in error message', () => { - const error = { - name: 'SomeOtherError', - message: 'Request failed with ExpiredTokenException: Token has expired', - } - - const result = isCredentialExpirationError(error) - assert.strictEqual(result, true) - }) - }) - - describe('should return false for non-expiration errors', () => { - it('should return false for different error names', () => { - const error = { - name: 'AccessDeniedException', - message: 'Access denied', - } - - const result = isCredentialExpirationError(error) - assert.strictEqual(result, false) - }) - }) -}) - -describe('isIamDomain', () => { - it('should return false for V1 domains regardless of IamSignIns', () => { - const result = isIamDomain({ - domainVersion: DomainVersionV1, - iamSignIns: [IamSignInRole, IamSignInUser], - }) - assert.strictEqual(result, false) - }) - - it('should return true for V2 domain with both IAM_ROLE and IAM_USER', () => { - const result = isIamDomain({ - domainVersion: DomainVersionV2, - iamSignIns: [IamSignInRole, IamSignInUser], - }) - assert.strictEqual(result, true) - }) - - it('should return false for V2 domain with only IAM_ROLE', () => { - const result = isIamDomain({ - domainVersion: DomainVersionV2, - iamSignIns: [IamSignInRole], - }) - assert.strictEqual(result, false) - }) - - it('should return false for V2 domain with only IAM_USER', () => { - const result = isIamDomain({ - domainVersion: DomainVersionV2, - iamSignIns: [IamSignInUser], - }) - assert.strictEqual(result, false) - }) - - it('should return false for V2 domain with missing IamSignIns', () => { - const result = isIamDomain({ - domainVersion: DomainVersionV2, - iamSignIns: undefined, - }) - assert.strictEqual(result, false) - }) - - it('should return false for undefined domain version', () => { - const result = isIamDomain({ - domainVersion: undefined, - iamSignIns: [IamSignInRole, IamSignInUser], - }) - assert.strictEqual(result, false) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.test.ts b/packages/core/src/test/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.test.ts deleted file mode 100644 index 3580a730fb..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/shared/utils/resourceMetadataUtils.test.ts +++ /dev/null @@ -1,292 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as assert from 'assert' -import * as sinon from 'sinon' -import { fs } from '../../../../shared/fs/fs' -import * as extensionUtilities from '../../../../shared/extensionUtilities' -import { - initializeResourceMetadata, - getResourceMetadata, - resourceMetadataFileExists, - resetResourceMetadata, - ResourceMetadata, -} from '../../../../sagemakerunifiedstudio/shared/utils/resourceMetadataUtils' - -describe('resourceMetadataUtils', function () { - let sandbox: sinon.SinonSandbox - - const mockMetadata: ResourceMetadata = { - AppType: 'JupyterServer', - DomainId: 'domain-12345', - SpaceName: 'test-space', - UserProfileName: 'test-user', - ExecutionRoleArn: 'arn:aws:iam::123456789012:role/test-role', - ResourceArn: 'arn:aws:sagemaker:us-west-2:123456789012:app/domain-12345/test-user/jupyterserver/test-app', - ResourceName: 'test-app', - AppImageVersion: '1.0.0', - AdditionalMetadata: { - DataZoneDomainId: 'dz-domain-123', - DataZoneDomainRegion: 'us-west-2', - DataZoneEndpoint: 'https://datazone.us-west-2.amazonaws.com', - DataZoneEnvironmentId: 'env-123', - DataZoneProjectId: 'project-456', - DataZoneScopeName: 'test-scope', - DataZoneStage: 'prod', - DataZoneUserId: 'user-789', - PrivateSubnets: 'subnet-123,subnet-456', - ProjectS3Path: 's3://test-bucket/project/', - SecurityGroup: 'sg-123456789', - }, - ResourceArnCaseSensitive: - 'arn:aws:sagemaker:us-west-2:123456789012:app/domain-12345/test-user/JupyterServer/test-app', - IpAddressType: 'IPv4', - } - - beforeEach(function () { - sandbox = sinon.createSandbox() - resetResourceMetadata() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('initializeResourceMetadata()', function () { - it('should initialize metadata when file exists and is valid JSON', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockMetadata)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.deepStrictEqual(result, mockMetadata) - }) - - it('should not initialize when not in SMUS environment', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(false) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual(result, undefined) - }) - - it('should not throw when file does not exist', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(false) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual(result, undefined) - }) - - it('should handle invalid JSON gracefully', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves('{ invalid json }') - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual(result, undefined) - }) - - it('should handle file read errors gracefully', async function () { - const error = new Error('File read error') - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').rejects(error) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual(result, undefined) - }) - - it('should handle metadata with missing optional fields', async function () { - const minimalMetadata: ResourceMetadata = { - DomainId: 'domain-123', - } - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(minimalMetadata)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.deepStrictEqual(result, minimalMetadata) - }) - - it('should handle metadata with empty AdditionalMetadata', async function () { - const metadataWithEmptyAdditional: ResourceMetadata = { - DomainId: 'domain-123', - AdditionalMetadata: {}, - } - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(metadataWithEmptyAdditional)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.deepStrictEqual(result, metadataWithEmptyAdditional) - }) - - it('should handle empty JSON file', async function () { - const emptyMetadata: ResourceMetadata = {} - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(emptyMetadata)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.deepStrictEqual(result, emptyMetadata) - }) - - it('should handle very large JSON files', async function () { - const largeMetadata = { - ...mockMetadata, - LargeField: 'x'.repeat(10000), - } - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(largeMetadata)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual((result as any).LargeField?.length, 10000) - }) - - it('should handle JSON with unexpected additional fields', async function () { - const metadataWithExtraFields = { - ...mockMetadata, - UnexpectedField: 'unexpected-value', - AdditionalMetadata: { - ...mockMetadata.AdditionalMetadata, - UnexpectedNestedField: 'unexpected-nested-value', - }, - } - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(metadataWithExtraFields)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual((result as any).UnexpectedField, 'unexpected-value') - assert.strictEqual((result as any).AdditionalMetadata?.UnexpectedNestedField, 'unexpected-nested-value') - }) - - it('should handle JSON with undefined values', async function () { - const metadataWithUndefined = { - DomainId: undefined, - AdditionalMetadata: { - DataZoneDomainId: undefined, - }, - } - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(metadataWithUndefined)) - - await initializeResourceMetadata() - const result = getResourceMetadata() - - assert.strictEqual(result?.DomainId, undefined) - assert.strictEqual(result?.AdditionalMetadata?.DataZoneDomainId, undefined) - }) - }) - - describe('getResourceMetadata()', function () { - it('should return undefined when not initialized', function () { - const result = getResourceMetadata() - assert.strictEqual(result, undefined) - }) - - it('should return cached metadata after initialization', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockMetadata)) - - await initializeResourceMetadata() - - const result = getResourceMetadata() - assert.deepStrictEqual(result, mockMetadata) - }) - - it('should return the same instance on multiple calls', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - sandbox.stub(fs, 'existsFile').resolves(true) - sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockMetadata)) - - await initializeResourceMetadata() - - const result1 = getResourceMetadata() - const result2 = getResourceMetadata() - - assert.strictEqual(result1, result2) - assert.deepStrictEqual(result1, mockMetadata) - }) - }) - - describe('resetResourceMetadata()', function () { - it('should reset cached metadata and allow re-initialization', async function () { - sandbox.stub(extensionUtilities, 'isSageMaker').withArgs('SMUS').returns(true) - const existsFileStub = sandbox.stub(fs, 'existsFile').resolves(true) - const readFileTextStub = sandbox.stub(fs, 'readFileText').resolves(JSON.stringify(mockMetadata)) - - await initializeResourceMetadata() - const cached1 = getResourceMetadata() - assert.deepStrictEqual(cached1, mockMetadata) - - sinon.assert.calledOnce(existsFileStub) - sinon.assert.calledOnce(readFileTextStub) - - resetResourceMetadata() - - const cached2 = getResourceMetadata() - assert.strictEqual(cached2, undefined) - - await initializeResourceMetadata() - const cached3 = getResourceMetadata() - assert.deepStrictEqual(cached3, mockMetadata) - - sinon.assert.calledTwice(existsFileStub) - sinon.assert.calledTwice(readFileTextStub) - }) - }) - - describe('resourceMetadataFileExists()', function () { - it('should return true when file exists', async function () { - const existsStub = sandbox.stub(fs, 'existsFile').resolves(true) - - const result = await resourceMetadataFileExists() - - assert.strictEqual(result, true) - sinon.assert.calledOnceWithExactly(existsStub, '/opt/ml/metadata/resource-metadata.json') - }) - - it('should return false when file does not exist', async function () { - sandbox.stub(fs, 'existsFile').resolves(false) - - const result = await resourceMetadataFileExists() - - assert.strictEqual(result, false) - }) - - it('should return false and log error when fs.existsFile throws', async function () { - const error = new Error('Permission denied') - sandbox.stub(fs, 'existsFile').rejects(error) - - const result = await resourceMetadataFileExists() - - assert.strictEqual(result, false) - }) - }) -}) diff --git a/packages/core/src/test/sagemakerunifiedstudio/testUtils.ts b/packages/core/src/test/sagemakerunifiedstudio/testUtils.ts deleted file mode 100644 index 1f56d6f7f9..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/testUtils.ts +++ /dev/null @@ -1,86 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' - -/** - * Creates a mock extension context for SageMaker Unified Studio tests - */ -export function createMockExtensionContext(): any { - return { - subscriptions: [], - workspaceState: { - get: sinon.stub(), - update: sinon.stub(), - }, - globalState: { - get: sinon.stub(), - update: sinon.stub(), - }, - } -} - -/** - * Creates a mock S3 connection for SageMaker Unified Studio tests - */ -export function createMockS3Connection() { - return { - connectionId: 'conn-123', - name: 'project.s3_default_folder', - type: 'S3Connection', - props: { - s3Properties: { - s3Uri: 's3://test-bucket/domain/project/', - }, - }, - } -} - -/** - * Creates a mock credentials provider for SageMaker Unified Studio tests - */ -export function createMockCredentialsProvider() { - return { - getCredentials: async () => ({ - accessKeyId: 'test-key', - secretAccessKey: 'test-secret', - }), - getDomainAccountId: async () => '123456789012', - } -} -/** - * Creates a mock unauthenticated auth provider for SageMaker Unified Studio tests - */ -export function createMockUnauthenticatedAuthProvider(): any { - return { - isConnected: sinon.stub().returns(false), - isConnectionValid: sinon.stub().returns(false), - activeConnection: undefined, - onDidChange: sinon.stub().returns({ dispose: sinon.stub() }), - } -} /** - * - Creates a mock space node for SageMaker Unified Studio tests - */ -export function createMockSpaceNode(): any { - const mockParent = { - getAuthProvider: sinon.stub().returns({ - activeConnection: { domainId: 'test-domain' }, - getDomainAccountId: sinon.stub().resolves('123456789012'), - getDomainId: sinon.stub().returns('test-domain'), - }), - getProjectId: sinon.stub().returns('test-project'), - } - - return { - resource: { - sageMakerClient: {}, - DomainSpaceKey: 'test-space-key', - regionCode: 'us-east-1', - getParent: sinon.stub().returns(mockParent), - }, - getParent: sinon.stub().returns(mockParent), - } -} diff --git a/packages/core/src/test/sagemakerunifiedstudio/uriHandlers.test.ts b/packages/core/src/test/sagemakerunifiedstudio/uriHandlers.test.ts deleted file mode 100644 index ba3aff2b62..0000000000 --- a/packages/core/src/test/sagemakerunifiedstudio/uriHandlers.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SearchParams } from '../../shared/vscode/uriHandler' -import { parseConnectParams } from '../../sagemakerunifiedstudio/uriHandlers' - -describe('SMUS URI Handler', function () { - describe('parseConnectParams', function () { - const validParams = { - connection_identifier: 'arn:aws:sagemaker:us-west-2:123456789012:space/d-abc123/my-space', - domain: 'd-abc123', - user_profile: 'test-user', - session: 'sess-abc123', - ws_url: 'wss://ssm.us-west-2.amazonaws.com/stream', - 'cell-number': '1', - token: 'bearer-token-xyz', - } - - it('successfully parses all required parameters', function () { - const query = new SearchParams(validParams) - const result = parseConnectParams(query) - - assert.strictEqual(result.connection_identifier, validParams.connection_identifier) - assert.strictEqual(result.domain, validParams.domain) - assert.strictEqual(result.user_profile, validParams.user_profile) - assert.strictEqual(result.session, validParams.session) - assert.strictEqual(result.ws_url, validParams.ws_url) - assert.strictEqual(result['cell-number'], validParams['cell-number']) - assert.strictEqual(result.token, validParams.token) - }) - - it('throws error when required parameters are missing', function () { - const requiredParams = [ - 'connection_identifier', - 'domain', - 'user_profile', - 'session', - 'ws_url', - 'cell-number', - 'token', - ] as const - - for (const param of requiredParams) { - const { [param]: _removed, ...paramsWithoutOne } = validParams - const query = new SearchParams(paramsWithoutOne) - - assert.throws( - () => parseConnectParams(query), - new RegExp(`${param}.*must be provided`), - `Should throw error for missing ${param}` - ) - } - }) - - it('handles optional parameters correctly', function () { - // Test with all optional parameters present - const paramsWithAllOptional = { - ...validParams, - app_type: 'CodeEditor', - smus_domain_id: 'smus-domain-789', - smus_domain_account_id: '111222333444', - smus_project_id: 'project-999', - smus_domain_region: 'eu-west-1', - } - const queryWithOptional = new SearchParams(paramsWithAllOptional) - const resultWithOptional = parseConnectParams(queryWithOptional) - - assert.strictEqual(resultWithOptional.app_type, 'CodeEditor') - assert.strictEqual(resultWithOptional.smus_domain_id, 'smus-domain-789') - assert.strictEqual(resultWithOptional.smus_domain_account_id, '111222333444') - assert.strictEqual(resultWithOptional.smus_project_id, 'project-999') - assert.strictEqual(resultWithOptional.smus_domain_region, 'eu-west-1') - - // Test without optional parameters - should return undefined - const queryWithoutOptional = new SearchParams(validParams) - const resultWithoutOptional = parseConnectParams(queryWithoutOptional) - - assert.strictEqual(resultWithoutOptional.app_type, undefined) - assert.strictEqual(resultWithoutOptional.smus_domain_id, undefined) - assert.strictEqual(resultWithoutOptional.smus_domain_account_id, undefined) - assert.strictEqual(resultWithoutOptional.smus_project_id, undefined) - assert.strictEqual(resultWithoutOptional.smus_domain_region, undefined) - }) - }) -}) diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/appNode.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/appNode.test.ts deleted file mode 100644 index d3c9b32555..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/appNode.test.ts +++ /dev/null @@ -1,251 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import assert from 'assert' -import { AppNode } from '../../../../../awsService/appBuilder/explorer/nodes/appNode' -import * as SamProjectModule from '../../../../../awsService/appBuilder/explorer/samProject' -import * as ListSamResourcesModule from '../../../../../lambda/commands/listSamResources' -import * as ResourceNodeModule from '../../../../../awsService/appBuilder/explorer/nodes/resourceNode' -import * as DeployedStackModule from '../../../../../awsService/appBuilder/explorer/nodes/deployedStack' -import sinon from 'sinon' -import path from 'path' - -import { isTreeNode, TreeNode } from '../../../../../shared/treeview/resourceTreeDataProvider' - -describe('AppNode', () => { - let sandbox: sinon.SinonSandbox - let appNode: AppNode - - // Create a mock samAppLocation object - const expectedWorkspaceFolder = { - uri: vscode.Uri.parse('/VSCode Example Workspace'), - name: 'VSCode Example Workspace', - index: 0, - } - const expectProjectRoot = vscode.Uri.parse('/VSCode Example Workspace/Project One Root Folder') - const expectedSamTemplateUri = vscode.Uri.parse('/VSCode Example Workspace/Project One Root Folder/template.yaml') - const expectedSamAppLocation = { - workspaceFolder: expectedWorkspaceFolder, - samTemplateUri: expectedSamTemplateUri, - projectRoot: expectProjectRoot, - } - - beforeEach(function () { - // create sandbox - sandbox = sinon.createSandbox() - // Instantiate class - appNode = new AppNode(expectedSamAppLocation) - }) - - afterEach(function () { - // Restore the stubs after each test - sandbox.restore() - }) - - describe('constructor', () => { - it('should create a new AppNode instance', () => { - assert.strictEqual(appNode instanceof AppNode, true) - }) - - it('should set correct properties', () => { - assert.strictEqual(appNode.id, mockSamAppLocationResponse.samTemplateUri.toString()) - assert.strictEqual(appNode.resource, expectedSamAppLocation) - assert.strictEqual(appNode.label, path.join('VSCode Example Workspace', 'Project One Root Folder')) - }) - }) - - describe('getChildren', () => { - let getAppStub: sinon.SinonStub - let getStackNameStub: sinon.SinonStub - let generateStackNodeStub: sinon.SinonStub - let getDeployedResourcesStub: sinon.SinonStub - let generateResourceNodesStub: sinon.SinonStub - - beforeEach(() => { - // Create a stub for helper functions used in getChildren() to call real method if not override in each test - getAppStub = sandbox.stub(SamProjectModule, 'getApp') - getStackNameStub = sandbox.stub(SamProjectModule, 'getStackName') - generateStackNodeStub = sandbox.stub(DeployedStackModule, 'generateStackNode') - getDeployedResourcesStub = sandbox.stub(ListSamResourcesModule, 'getDeployedResources') - generateResourceNodesStub = sandbox.stub(ResourceNodeModule, 'generateResourceNodes') - }) - - it('should return placeholder item for an empty App', async () => { - // stub getApp() and return mock response - getAppStub.resolves(mockGetAppResponse) - // stub getStackName() and return mock response - getStackNameStub.resolves(mockGetStackNameResponse) - // stub generateStackNode() and return [] - generateStackNodeStub.resolves([]) - // stub getDeployedResources() and return [] - getDeployedResourcesStub.resolves([]) - // stub generateResourceNodes to return empty array simulate empty application - generateResourceNodesStub.returns([]) - - const resources = await appNode.getChildren() - assert.strictEqual(resources.length, 1) - assert(isTreeNode(resources[0])) - - const resourceNode = resources[0] as TreeNode - assert.strictEqual(resourceNode.id, 'placeholder') - assert.strictEqual(resourceNode.resource, '[No resource found in SAM template]') - assert(getAppStub.calledOnce) - assert(getStackNameStub.calledOnce) - assert(generateStackNodeStub.notCalled) - assert(getDeployedResourcesStub.calledOnce) - assert(generateResourceNodesStub.calledOnce) - }) - - it('should return resource without deploy stack name node', async () => { - // stub getApp() and return mock response - getAppStub.resolves(mockGetAppResponse) - // stub getStackName() and return undefined stackName and region simulate undeployed stack - getStackNameStub.resolves({ stackName: undefined, region: undefined }) - // call actual method - generateResourceNodesStub.callThrough() - - const resources = await appNode.getChildren() - assert.strictEqual(resources.length, 1) - assert.strictEqual(resources[0].id, 'MyProjectLambdaFunction') - assert(getAppStub.calledOnce) - assert(getStackNameStub.calledOnce) - assert(generateStackNodeStub.notCalled) - assert(getDeployedResourcesStub.notCalled) - assert(generateResourceNodesStub.calledOnce) - }) - - it('should return resource with all deployed nodes when stack is deployed', async () => { - // stub getApp() and return mock response - getAppStub.resolves(mockGetAppResponse) - // stub getStackName() and return stack name and region - getStackNameStub.resolves(mockGetStackNameResponse) - // stub generateStackNode() and return mock response - generateStackNodeStub.resolves(mockGenerateStackNodeResponse) - // stub getDeployedResources() and return mock response - getDeployedResourcesStub.resolves(mockDeployedResourcesResponse) - // call actual method - generateResourceNodesStub.callThrough() - - const resources = await appNode.getChildren() - assert.strictEqual(resources.length, 2) - assert( - resources.some((node) => node.id === 'MyProjectLambdaFunction'), - 'Missing expected childern node: MyProjectLambdaFunction' - ) - assert( - resources.some((node) => node.id === 'my-project-one-stack-name'), - 'Missing expected childern node: my-project-one-stack-name' - ) - assert(getAppStub.calledOnce) - assert(getStackNameStub.calledOnce) - assert(generateStackNodeStub.calledOnce) - assert(getDeployedResourcesStub.calledOnce) - assert(generateResourceNodesStub.calledOnce) - }) - - it('should return placeholder item when encounter error', async () => { - // stub getApp() and throw error - getAppStub.rejects(new Error('Mock error')) - - const resources = await appNode.getChildren() - assert.strictEqual(resources.length, 1) - assert(isTreeNode(resources[0])) - - const resourceNode = resources[0] as TreeNode - assert.strictEqual(resourceNode.id, 'placeholder') - assert.strictEqual( - resourceNode.resource, - '[Unable to load resource tree for this app. Ensure SAM template is correct.]' - ) - assert(getAppStub.calledOnce) - assert(getStackNameStub.notCalled) - assert(generateStackNodeStub.notCalled) - assert(getDeployedResourcesStub.notCalled) - assert(generateResourceNodesStub.notCalled) - }) - }) - - describe('getTreeItem', () => { - it('should return a TreeItem with the correct properties', () => { - const treeItem = appNode.getTreeItem() - const expextedLabel = path.join('VSCode Example Workspace', 'Project One Root Folder') - - assert.strictEqual(treeItem.label, expextedLabel) - assert.strictEqual(treeItem.contextValue, 'awsAppBuilderAppNode') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.deepStrictEqual(treeItem.resourceUri, expectedSamTemplateUri) - assert.strictEqual(treeItem.tooltip, expectedSamTemplateUri.path) - }) - }) -}) - -const mockGetStackNameResponse = { - stackName: 'my-project-one-stack-name', - region: 'us-east-1', -} - -const mockGenerateStackNodeResponse = [ - { - stackName: 'my-project-one-stack-name', - regionCode: 'us-east-1', - id: 'my-project-one-stack-name', - }, -] - -const mockSamAppLocationResponse = { - samTemplateUri: vscode.Uri.file(path.join('VSCode Example Workspace', 'Project One Root Folder', 'template.yaml')), - workspaceFolder: { - uri: vscode.Uri.file(path.join('VSCode Example Workspace')), - name: 'VSCode Example Workspace', - index: 0, - }, - projectRoot: vscode.Uri.file(path.join('VSCode Example Workspace', 'Project One Root Folder')), -} -const mockResourceTreeEntity = { - Id: 'MyProjectLambdaFunction', - Type: 'AWS::Serverless::Function', - Runtime: 'python3.12', - Handler: 'app.lambda_handler', - Events: [ - { - Id: 'MyProjectLambda', - Type: 'Api', - Path: '/hello', - Method: 'get', - }, - ], -} - -const mockGetAppResponse = { - location: mockSamAppLocationResponse, - resourceTree: [mockResourceTreeEntity], -} - -const mockDeployedResourcesResponse = [ - { - LogicalResourceId: 'MyProjectLambdaFunctionRole', - PhysicalResourceId: 'my-project-one-stack-name-MyProjectLambdaFunctionRo-uvrNoVPn4YWa', - }, - { - LogicalResourceId: 'MyProjectLambdaFunction', - PhysicalResourceId: '-', - }, - { - LogicalResourceId: 'MyProjectLambdaFunctionMyProjectLambdaPermissionProd', - PhysicalResourceId: '-', - }, - { - LogicalResourceId: 'ServerlessRestApi', - PhysicalResourceId: '-', - }, - { - LogicalResourceId: 'ServerlessRestApiDeploymenteba9892cd9', - PhysicalResourceId: '-', - }, - { - LogicalResourceId: 'ServerlessRestApiProdStage', - PhysicalResourceId: '-', - }, -] diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedNode.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedNode.test.ts deleted file mode 100644 index 3dcaef4ce9..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedNode.test.ts +++ /dev/null @@ -1,469 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import * as vscode from 'vscode' -import * as os from 'os' -import { - DeployedResource, - DeployedResourceNode, - generateDeployedNode, -} from '../../../../../awsService/appBuilder/explorer/nodes/deployedNode' -import * as sinon from 'sinon' -import * as LambdaClientModule from '../../../../../../src/shared/clients/lambdaClient' -import * as DefaultS3ClientModule from '../../../../../shared/clients/s3' -import * as ApiGatewayNodeModule from '../../../../../awsService/apigateway/explorer/apiGatewayNodes' -import { beforeEach } from 'mocha' -import { LambdaFunctionNode } from '../../../../../lambda/explorer/lambdaFunctionNode' -import { RestApiNode } from '../../../../../awsService/apigateway/explorer/apiNodes' -import { S3BucketNode } from '../../../../../awsService/s3/explorer/s3BucketNode' -import * as LambdaNodeModule from '../../../../../lambda/explorer/lambdaNodes' -import { getIcon } from '../../../../../shared/icons' -import _ from 'lodash' -import { isTreeNode } from '../../../../../shared/treeview/resourceTreeDataProvider' -import { Any } from '../../../../../shared/utilities/typeConstructors' -import { IamConnection, ProfileMetadata } from '../../../../../auth/connection' -import * as AuthUtils from '../../../../../auth/utils' -import { assertLogsContain } from '../../../../../test/globalSetup.test' -import { GetFunctionResponse } from '@aws-sdk/client-lambda' -import { LambdaCapacityProviderNode } from '../../../../../lambda/explorer/lambdaCapacityProviderNode' -import { DefaultAwsContext } from '../../../../../shared' - -describe('DeployedResourceNode', () => { - const expectedStackName = 'myStack' - const expectedRegionCode = 'us-west-2' - - beforeEach(() => {}) - - afterEach(() => { - // Restore the original function after each test - sinon.restore() - }) - - const getDeployedResource = (explorerNode: any, resourceArn: string, contextValue: string): DeployedResource => { - return { - stackName: expectedStackName, - regionCode: expectedRegionCode, - explorerNode: explorerNode, - arn: resourceArn, - contextValue: contextValue, - } as DeployedResource - } - const testCases = [ - { - explorerNode: sinon.stub(LambdaFunctionNode), - resourceArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function-name', - contextValue: 'awsRegionFunctionNodeDownloadable', - }, - { - explorerNode: sinon.stub(S3BucketNode), - resourceArn: 'arn:aws:s3:::my-bucket-name', - contextValue: 'awsS3BucketNode', - }, - { - explorerNode: sinon.stub(RestApiNode), - resourceArn: 'arn:aws:apigateway:us-east-1::/apis/my-apgw', - contextValue: 'awsApiGatewayNode', - }, - { - explorerNode: sinon.stub(LambdaCapacityProviderNode), - resourceArn: 'arn:aws:lambda:us-east-1:123456789012:capacity-provider:my-capacity-provider-name', - contextValue: 'awsCapacityProviderNode', - }, - ].map(({ explorerNode, resourceArn, contextValue }) => getDeployedResource(explorerNode, resourceArn, contextValue)) - - describe('constructor', () => { - testCases.map((deployedResource: DeployedResource) => { - it(`should create an instance of DeployedResourceNode for ${deployedResource.contextValue}`, () => { - const deployedLambdaNode = new DeployedResourceNode(deployedResource) - - assert.strictEqual(deployedLambdaNode.id, deployedResource.arn) - assert.strictEqual(deployedLambdaNode.contextValue, deployedResource.contextValue) - assert.deepStrictEqual(deployedLambdaNode.resource, deployedResource) - }) - - it('should create an instance with empty id and when resource arn is missing', () => { - const emptyArnDeployedResource = _.cloneDeep(deployedResource) - emptyArnDeployedResource.arn = '' - const deployedLambdaNode = new DeployedResourceNode(emptyArnDeployedResource) - - assert.strictEqual(deployedLambdaNode.id, '') - assert.strictEqual(deployedLambdaNode.contextValue, '') - assert.deepStrictEqual(deployedLambdaNode.resource, emptyArnDeployedResource) - assertLogsContain('Cannot create DeployedResourceNode, the ARN does not exist.', false, 'warn') - }) - }) - }) - - testCases.map((deployedResource: DeployedResource) => { - const deployedLambdaNode = new DeployedResourceNode(deployedResource) - describe('getChildren', () => { - it('should return an empty array', async () => { - const children = await deployedLambdaNode.getChildren() - - assert.deepStrictEqual(children, []) - }) - }) - - describe('getTreeItem', () => { - it('should return a TreeItem with correct properties', () => { - const treeItem = deployedLambdaNode.getTreeItem() - const expectedIconPath = getIcon('vscode-cloud') - - assert.strictEqual(treeItem.label, deployedResource.arn) - assert.strictEqual(treeItem.contextValue, deployedResource.contextValue) - assert.deepStrictEqual(treeItem.iconPath, expectedIconPath) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - assert.strictEqual(treeItem.tooltip, deployedResource.arn) - }) - }) - }) -}) - -describe('generateDeployedNode', () => { - const expectedStackName = 'myStack' - const expectedRegionCode = 'us-west-2' - - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - // Initiate stub sanbox - sandbox = sinon.createSandbox() - // Create a stub for the entire logger module - }) - afterEach(async () => { - sandbox.restore() - }) - - describe('LambdaFunctionNode', () => { - let mockDefaultLambdaClientInstance: sinon.SinonStubbedInstance - let mockLambdaNodeInstance: sinon.SinonStubbedInstance - const iamConnection: IamConnection & { readonly state: ProfileMetadata['connectionState'] } = { - type: 'iam', - id: '0', - label: 'iam', - getCredentials: sinon.stub(), - state: 'valid', - endpointUrl: undefined, - } - - const lambdaDeployedNodeInput = { - deployedResource: { - LogicalResourceId: 'MyLambdaFunction', - PhysicalResourceId: 'my-project-lambda-physical-id', - }, - regionCode: expectedRegionCode, - stackName: expectedStackName, - resourceTreeEntity: { - Id: 'MyLambdaFunction', - Type: 'AWS::Serverless::Function', - }, - } - - beforeEach(() => { - // Stub the constructor of DefaultLambdaClient to return the stub instance - mockDefaultLambdaClientInstance = sandbox.createStubInstance(LambdaClientModule.DefaultLambdaClient) - sandbox.stub(LambdaClientModule, 'DefaultLambdaClient').returns(mockDefaultLambdaClientInstance) - // Stub the constructor of LambdaNode to return stub instance - mockLambdaNodeInstance = sandbox.createStubInstance(LambdaNodeModule.LambdaNode) - sandbox.stub(LambdaNodeModule, 'LambdaNode').returns(mockLambdaNodeInstance) - sandbox.stub(AuthUtils, 'getIAMConnection').resolves(iamConnection) - }) - - it('should return a DeployedResourceNode for valid Lambda function happy path', async () => { - // Simulate successful fetching Lambda function using DefaultLambdaClient - const defaultLambdaClientGetFunctionResponse = { - Configuration: { - FunctionName: 'my-project-lambda-function', - FunctionArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-project-lambda-function', - Runtime: 'python3.12', - }, - } as GetFunctionResponse - - mockDefaultLambdaClientInstance.getFunction.resolves(defaultLambdaClientGetFunctionResponse) - - const expectedFunctionArn = 'arn:aws:lambda:us-east-1:123456789012:function:my-project-lambda-function' - const expectedFunctionName = 'my-project-lambda-function' - const expectedFunctionExplorerNodeTooltip = `${expectedFunctionName}${os.EOL}${expectedFunctionArn}` - - const deployedResourceNodes = (await generateDeployedNode( - lambdaDeployedNodeInput.deployedResource, - lambdaDeployedNodeInput.regionCode, - lambdaDeployedNodeInput.stackName, - lambdaDeployedNodeInput.resourceTreeEntity - )) as DeployedResourceNode[] - - const deployedResourceNodeExplorerNode: LambdaFunctionNode = validateBasicProperties( - deployedResourceNodes, - expectedFunctionArn, - 'awsRegionFunctionNodeDownloadable', - expectedRegionCode, - expectedStackName, - LambdaFunctionNode - ) - - assert.strictEqual(deployedResourceNodeExplorerNode.contextValue, 'awsRegionFunctionNodeDownloadable') - assert.strictEqual(deployedResourceNodeExplorerNode.label, expectedFunctionName) - assert.strictEqual(deployedResourceNodeExplorerNode.iconPath, getIcon('aws-lambda-function')) - assert.strictEqual(deployedResourceNodeExplorerNode.regionCode, expectedRegionCode) - assert.strictEqual(deployedResourceNodeExplorerNode.tooltip, expectedFunctionExplorerNodeTooltip) - assert.strictEqual(deployedResourceNodeExplorerNode.configuration.FunctionArn, expectedFunctionArn) - assert.strictEqual(deployedResourceNodeExplorerNode.configuration.FunctionName, expectedFunctionName) - assert.strictEqual(deployedResourceNodeExplorerNode.configuration.Runtime, 'python3.12') - }) - - it('should log error message given any lambda client error', async () => { - // Simulate failure fetching Lambda function using DefaultLambdaClient - mockDefaultLambdaClientInstance.getFunction.rejects() - - const deployedResourceNodes = await generateDeployedNode( - lambdaDeployedNodeInput.deployedResource, - lambdaDeployedNodeInput.regionCode, - lambdaDeployedNodeInput.stackName, - lambdaDeployedNodeInput.resourceTreeEntity - ) - - assertLogsContain('Error getting Lambda configuration', false, 'error') - assert(deployedResourceNodes.length === 1) - - // Check placeholder propertries - const deployedResourceNode = deployedResourceNodes[0] as DeployedResourceNode - assert.strictEqual(deployedResourceNode.id, 'placeholder') - assert.strictEqual( - deployedResourceNode.resource, - '[Failed to retrieve deployed resource. Ensure correct stack name and region are in the samconfig.toml, and that your account is connected.]' - ) - }) - }) - - describe('S3BucketNode', () => { - let mockDefaultS3ClientInstance: sinon.SinonStubbedInstance - const s3DeployedNodeInput = { - deployedResource: { - LogicalResourceId: 'MyS3SourceBucket', - PhysicalResourceId: 'my-project-source-bucket-physical-id', - }, - regionCode: expectedRegionCode, - stackName: expectedStackName, - resourceTreeEntity: { - Id: 'my-project-source-bucket-physical-id', - Type: 'AWS::S3::Bucket', - }, - } - - it('should return a DeployedResourceNode for valid S3 bucket happy path', async () => { - // Stub the constructor of DefaultLambdaClient to return the stub instance - mockDefaultS3ClientInstance = sandbox.createStubInstance(DefaultS3ClientModule.S3Client) - sandbox.stub(DefaultS3ClientModule, 'S3Client').returns(mockDefaultS3ClientInstance) - const deployedResourceNodes = await generateDeployedNode( - s3DeployedNodeInput.deployedResource, - s3DeployedNodeInput.regionCode, - s3DeployedNodeInput.stackName, - s3DeployedNodeInput.resourceTreeEntity - ) - - const expectedS3BucketArn = 'arn:aws:s3:::my-project-source-bucket-physical-id' - const expectedS3BucketName = 'my-project-source-bucket-physical-id' - - const deployedResourceNodeExplorerNode: S3BucketNode = validateBasicProperties( - deployedResourceNodes as DeployedResourceNode[], - expectedS3BucketArn, - 'awsS3BucketNode', - expectedRegionCode, - expectedStackName, - S3BucketNode - ) - assert.strictEqual(deployedResourceNodeExplorerNode.bucket.Name, expectedS3BucketName) - assert.strictEqual(deployedResourceNodeExplorerNode.bucket.Arn, expectedS3BucketArn) - assert.strictEqual(deployedResourceNodeExplorerNode.bucket.BucketRegion, expectedRegionCode) - assert.strictEqual(deployedResourceNodeExplorerNode.contextValue, 'awsS3BucketNode') - assert.strictEqual(deployedResourceNodeExplorerNode.label, expectedS3BucketName) - assert.strictEqual(deployedResourceNodeExplorerNode.tooltip, expectedS3BucketName) - assert.strictEqual(deployedResourceNodeExplorerNode.iconPath, getIcon('aws-s3-bucket')) - }) - }) - - describe('ApiGatewayNode', () => { - let mockApiGatewayNodeInstance: sinon.SinonStubbedInstance - - const apigatewayDeployedNodeInput = { - deployedResource: { - LogicalResourceId: 'MyRestApi', - PhysicalResourceId: 'my-project-apigw-physical-id', - }, - regionCode: expectedRegionCode, - stackName: expectedStackName, - resourceTreeEntity: { - Id: 'my-project-apigw-physical-id', - Type: 'AWS::Serverless::Api', - }, - } - - const createMockRestApiNode = ( - sandbox: sinon.SinonSandbox, - options: { - id?: string - name?: string - description?: string - } - ) => { - const mockNode = sandbox.createStubInstance(RestApiNode) as sinon.SinonStubbedInstance & { - id?: string - name?: string - description?: string - } - for (const [key, value] of Object.entries(options)) { - value !== undefined && Object.defineProperty(mockNode, key, { value, writable: true }) - } - return mockNode - } - - it('should return a DeployedResourceNode for valid API Gateway happy path', async () => { - // Stub the constructor of DefaultLambdaClient to return the stub instance - mockApiGatewayNodeInstance = sandbox.createStubInstance(ApiGatewayNodeModule.ApiGatewayNode) - sandbox.stub(ApiGatewayNodeModule, 'ApiGatewayNode').returns(mockApiGatewayNodeInstance) - // Simulate successful fetching api gateway parentNode - mockApiGatewayNodeInstance.getChildren.resolves([ - createMockRestApiNode(sandbox, { id: 'my-project-apigw-physical-id', name: 'targetAPI' }), - createMockRestApiNode(sandbox, { id: 'my-project-apigw-other-id-2', name: 'otherAPI1' }), - createMockRestApiNode(sandbox, { id: 'my-project-apigw-other-id-3', name: 'otherAPI2' }), - ]) - - const expectedApiGatewayArn = 'arn:aws:apigateway:us-west-2::/apis/my-project-apigw-physical-id' - const expectedApiGatewayName = apigatewayDeployedNodeInput.deployedResource.PhysicalResourceId - const expectedApiGatewayExplorerNodeLabel = 'targetAPI (my-project-apigw-physical-id)' - - const deployedResourceNodes = await generateDeployedNode( - apigatewayDeployedNodeInput.deployedResource, - apigatewayDeployedNodeInput.regionCode, - apigatewayDeployedNodeInput.stackName, - apigatewayDeployedNodeInput.resourceTreeEntity - ) - - const deployedResourceNodeExplorerNode: RestApiNode = validateBasicProperties( - deployedResourceNodes as DeployedResourceNode[], - expectedApiGatewayArn, - 'awsApiGatewayNode', - expectedRegionCode, - expectedStackName, - RestApiNode - ) - - assert.strictEqual(deployedResourceNodeExplorerNode.id, expectedApiGatewayName) - assert.strictEqual(deployedResourceNodeExplorerNode.regionCode, expectedRegionCode) - assert.strictEqual(deployedResourceNodeExplorerNode.partitionId, 'aws') - assert.strictEqual(deployedResourceNodeExplorerNode.label, expectedApiGatewayExplorerNodeLabel) - assert(!deployedResourceNodeExplorerNode.iconPath) - assert(!deployedResourceNodeExplorerNode.tooltip) - }) - }) - - describe('LambdaCapacityProviderNode', () => { - const capacityProviderDeployedNodeInput = { - deployedResource: { - LogicalResourceId: 'MyCapacityProvider', - PhysicalResourceId: 'my-project-lambda-physical-id', - }, - regionCode: expectedRegionCode, - stackName: expectedStackName, - resourceTreeEntity: { - Id: 'MyCapacityProvider', - Type: 'AWS::Serverless::CapacityProvider', - }, - } - beforeEach(() => { - // Default mock account ID for testing - sandbox.stub(DefaultAwsContext.prototype, 'getCredentialAccountId').returns('123456789012') - }) - it('should return a DeployedResourceNode for valid Lambda Capacity Provider happy path', async () => { - const deployedResourceNodes = await generateDeployedNode( - capacityProviderDeployedNodeInput.deployedResource, - capacityProviderDeployedNodeInput.regionCode, - capacityProviderDeployedNodeInput.stackName, - capacityProviderDeployedNodeInput.resourceTreeEntity - ) - - const expectedCapacityProviderArn = - 'arn:aws:lambda:us-west-2:123456789012:capacity-provider:my-project-lambda-physical-id' - const expectedCapacityProviderName = 'MyCapacityProvider' - - const deployedResourceNodeExplorerNode: LambdaCapacityProviderNode = validateBasicProperties( - deployedResourceNodes as DeployedResourceNode[], - expectedCapacityProviderArn, - 'awsCapacityProviderNode', - expectedRegionCode, - expectedStackName, - LambdaCapacityProviderNode - ) - assert.strictEqual(deployedResourceNodeExplorerNode.contextValue, 'awsCapacityProviderNode') - assert.strictEqual(deployedResourceNodeExplorerNode.name, expectedCapacityProviderName) - assert.strictEqual(deployedResourceNodeExplorerNode.regionCode, expectedRegionCode) - assert.strictEqual(deployedResourceNodeExplorerNode.iconPath, getIcon('vscode-gear')) - }) - }) - - describe('UnsupportedResourceNode', () => { - const unsupportTypeInput = { - deployedResource: { - LogicalResourceId: 'myUnsupportedResource', - PhysicalResourceId: 'my-unsupported-resource-physical-id', - }, - regionCode: expectedRegionCode, - stackName: expectedStackName, - resourceTreeEntity: { - Id: 'my-unsupported-resource-physical-id', - Type: 'AWS::Serverless::UnsupportType', - }, - } - - it('should return a DeployedResourceNode with placeholder', async () => { - const deployedResourceNodes = await generateDeployedNode( - unsupportTypeInput.deployedResource, - unsupportTypeInput.regionCode, - unsupportTypeInput.stackName, - unsupportTypeInput.resourceTreeEntity - ) - - assertLogsContain('Details are missing or are incomplete for:', false, 'info') - - // Check deployedResourceNodes array propertries - assert(deployedResourceNodes.length === 1) - assert(isTreeNode(deployedResourceNodes[0])) - - // Check placeholder propertries - const deployedResourceNode = deployedResourceNodes[0] as DeployedResourceNode - assert.strictEqual(deployedResourceNode.id, 'placeholder') - assert.strictEqual(deployedResourceNode.resource, '[This resource is not yet supported in AppBuilder.]') - }) - }) -}) - -function validateBasicProperties any>( - deployedResourceNodes: DeployedResourceNode[], - expectedArn: string, - expectedContextValue: string, - expectedRegionCode: string, - expectedStackName: string, - expectedExplorerNodeClass: T = Any as any -): InstanceType { - // Check deployedResourceNodes array propertries - assert(deployedResourceNodes.length === 1) - assert(deployedResourceNodes[0] instanceof DeployedResourceNode) - - // // Check deployedResourceNode propertries - const deployedResourceNode = deployedResourceNodes[0] as DeployedResourceNode - assert.strictEqual(deployedResourceNode.id, expectedArn) - assert.strictEqual(deployedResourceNode.contextValue, expectedContextValue) - - // Check deployedResourceNode resource propertries - const deployedResourceNodeResource = deployedResourceNode.resource - assert.strictEqual(deployedResourceNodeResource.regionCode, expectedRegionCode) - assert.strictEqual(deployedResourceNodeResource.stackName, expectedStackName) - assert.strictEqual(deployedResourceNodeResource.contextValue, expectedContextValue) - assert.strictEqual(deployedResourceNodeResource.arn, expectedArn) - - // Check deployedResourceNode resource explorer node propertries - assert(deployedResourceNodeResource.explorerNode instanceof expectedExplorerNodeClass) - return deployedResourceNodeResource.explorerNode as InstanceType -} diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedStack.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedStack.test.ts deleted file mode 100644 index 96c560038f..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/deployedStack.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import { StackNameNode } from '../../../../../awsService/appBuilder/explorer/nodes/deployedStack' - -describe('StackNameNode', () => { - const expectedStackName = 'myStackName' - const expectedRegion = 'us-west-2' - const expectedStackLink = - 'command:aws.explorer.cloudformation.showStack?%7B%22stackName%22%3A%22myStackName%22%2C%22region%22%3A%22us-west-2%22%7D' - - let stackNameNode: StackNameNode - - beforeEach(() => { - // Create an instance of StackNameNode - stackNameNode = new StackNameNode(expectedStackName, expectedRegion) - }) - - it('should create an instance of StackNameNode', () => { - assert.strictEqual(stackNameNode.link, expectedStackLink) - assert.strictEqual(stackNameNode.stackName, expectedStackName) - assert.strictEqual(stackNameNode.regionCode, expectedRegion) - }) - - it('should return empty array when call getChildren()', async () => { - const result = await stackNameNode.getChildren() - assert.deepStrictEqual(result, []) - }) - - it('should return correct getTreeItem properties when call getTreeItem()', () => { - const expectedContextValue = 'awsAppBuilderStackNode' - const expectedLabel = `Stack: ${expectedStackName} (${expectedRegion})` - const treeItem = stackNameNode.getTreeItem() - - assert.deepStrictEqual(treeItem.contextValue, expectedContextValue) - assert.deepStrictEqual(treeItem.label, expectedLabel) - }) -}) diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/propertyNode.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/propertyNode.test.ts deleted file mode 100644 index 8de0f71f50..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/propertyNode.test.ts +++ /dev/null @@ -1,115 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { generatePropertyNodes, PropertyNode } from '../../../../../awsService/appBuilder/explorer/nodes/propertyNode' -import { getIcon } from '../../../../../shared/icons' - -describe('PropertyNode', () => { - describe('constructor', () => { - it('should set properties correctly', async () => { - const expectedKey = 'key' - const expectedValue = 'value' - const node = new PropertyNode(expectedKey, expectedValue) - assert.strictEqual(node.id, expectedKey) - assert.strictEqual(node.resource, expectedValue) - }) - }) - - describe('getChildren', () => { - it('should return an empty array for primitive values', async () => { - const node = new PropertyNode('key', 'value') - const children = await node.getChildren() - assert.deepStrictEqual(children, []) - }) - - it('should return an array of PropertyNodes for objects', async () => { - const node = new PropertyNode('key', { foo: 'bar', baz: 42 }) - const children = await node.getChildren() - assert(children.every((child) => child instanceof PropertyNode)) - assert.deepStrictEqual( - children.map(({ id, resource }) => ({ id, resource })), - [ - { id: 'foo', resource: 'bar' }, - { id: 'baz', resource: 42 }, - ] - ) - }) - - it('should return an array of PropertyNodes for arrays of objects', async () => { - const node = new PropertyNode('key', [ - { foo: 'bar', baz: 42 }, - { foo: 'barz', baz: 52 }, - ]) - const children = await node.getChildren() - assert(children.every((child) => child instanceof PropertyNode)) - assert.deepStrictEqual( - children.map(({ id, resource }) => ({ id, resource })), - [ - { id: '0', resource: { foo: 'bar', baz: 42 } }, - { id: '1', resource: { foo: 'barz', baz: 52 } }, - ] - ) - }) - }) - - describe('getTreeItem', () => { - const validateGetTreeItemBasic = (treeItem: vscode.TreeItem) => { - assert.strictEqual(treeItem.contextValue, 'awsAppBuilderPropertyNode') - assert.strictEqual(treeItem.iconPath, getIcon('vscode-gear')) - } - - it('should return a TreeItem with the correct label for primitive values', () => { - const node = new PropertyNode('key', 'value') - const treeItem = node.getTreeItem() - validateGetTreeItemBasic(treeItem) - assert.strictEqual(treeItem.label, 'key: value') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.None) - }) - - it('should return a TreeItem with the correct label and collapsibleState for objects', () => { - const node = new PropertyNode('key', { foo: 'bar', baz: 42 }) - const treeItem = node.getTreeItem() - validateGetTreeItemBasic(treeItem) - assert.strictEqual(treeItem.label, 'key') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - - it('should return a TreeItem with the correct label and collapsibleState for arrays of objects', () => { - const node = new PropertyNode('key', [ - { foo: 'bar', baz: 42 }, - { foo: 'barz', baz: 52 }, - ]) - const treeItem = node.getTreeItem() - validateGetTreeItemBasic(treeItem) - assert.strictEqual(treeItem.label, 'key') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - }) - - describe('generatePropertyNodes', () => { - it('should filter out Id, Type, and Events properties', () => { - const properties = { - Id: 'id', - Type: 'type', - Events: ['event1', 'event2'], - foo: 'bar', - baz: 42, - } - const nodes = generatePropertyNodes(properties) - const expectedNodes = [ - { key: 'foo', value: 'bar' }, - { key: 'baz', value: 42 }, - ] - assert.strictEqual(nodes.length, expectedNodes.length) - for (const [index, node] of nodes.entries()) { - assert(node instanceof PropertyNode) - assert.strictEqual(node.id, expectedNodes[index].key) - assert.strictEqual(node.resource, expectedNodes[index].value) - } - }) - }) -}) diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/resourceNode.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/resourceNode.test.ts deleted file mode 100644 index 79f4645198..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/resourceNode.test.ts +++ /dev/null @@ -1,405 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { - appRunnerType, - ecrRepositoryType, - s3BucketType, - SERVERLESS_FUNCTION_TYPE, - SERVERLESS_CAPACITY_PROVIDER_TYPE, -} from '../../../../../shared/cloudformation/cloudformation' -import assert from 'assert' -import { ResourceTreeEntity, SamAppLocation } from '../../../../../awsService/appBuilder/explorer/samProject' -import { - generateResourceNodes, - ResourceNode, - generateLambdaNodeFromResource, -} from '../../../../../awsService/appBuilder/explorer/nodes/resourceNode' -import { getIcon } from '../../../../../shared/icons' -import * as DeployedResourceNodeModule from '../../../../../awsService/appBuilder/explorer/nodes/deployedNode' -import * as sinon from 'sinon' -import { afterEach } from 'mocha' -import { DeployedResourceNode } from '../../../../../awsService/appBuilder/explorer/nodes/deployedNode' -import { PropertyNode } from '../../../../../awsService/appBuilder/explorer/nodes/propertyNode' -import { StackResource } from '../../../../../lambda/commands/listSamResources' -import { LambdaFunctionNode } from '../../../../../lambda/explorer/lambdaFunctionNode' -import { ToolkitError } from '../../../../../shared/errors' - -describe('generateLambdaNodeFromResource', () => { - let generateDeployedNodeStub: sinon.SinonStub - const resourceMock = { - deployedResource: { - LogicalResourceId: 'TestFunction', - PhysicalResourceId: 'arn:aws:lambda:us-west-2:123456789012:function:TestFunction', - }, - region: 'us-west-2', - stackName: 'TestStack', - resource: { Id: 'TestFunction', Type: SERVERLESS_FUNCTION_TYPE }, - projectRoot: vscode.Uri.parse('myworkspace/myprojectrootfolder'), - location: vscode.Uri.parse('myworkspace/myprojectrootfolder/template.yaml'), - workspaceFolder: { - uri: vscode.Uri.parse('myworkspace'), - name: 'my-workspace', - index: 0, - }, - functionArn: 'arn:aws:lambda:us-west-2:123456789012:function:TestFunction', - } - - beforeEach(() => { - generateDeployedNodeStub = sinon.stub(DeployedResourceNodeModule, 'generateDeployedNode') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should successfully generate LambdaFunctionNode from resource', async () => { - const mockLambdaNode = {} as LambdaFunctionNode - const mockDeployedNode = { - resource: { - explorerNode: mockLambdaNode, - }, - } as DeployedResourceNode - - generateDeployedNodeStub.resolves([mockDeployedNode]) - const resource = resourceMock - const result = await generateLambdaNodeFromResource(resource) - - assert.strictEqual(result, mockLambdaNode) - assert( - generateDeployedNodeStub.calledOnceWith( - resource.deployedResource, - resource.region, - resource.stackName, - resource.resource, - resource.projectRoot - ) - ) - }) - - it('should throw error when deployedResource is missing', async () => { - const resource = { - region: 'us-west-2', - stackName: 'TestStack', - resource: { Id: 'TestFunction', Type: SERVERLESS_FUNCTION_TYPE }, - } - - await assert.rejects( - async () => await generateLambdaNodeFromResource(resource as any), - ToolkitError, - 'Error getting Lambda info from Appbuilder Node, please check your connection' - ) - }) - - it('should throw error when region is missing', async () => { - const resource = { - deployedResource: { LogicalResourceId: 'TestFunction' }, - stackName: 'TestStack', - resource: { Id: 'TestFunction', Type: SERVERLESS_FUNCTION_TYPE }, - } - - await assert.rejects( - async () => await generateLambdaNodeFromResource(resource as any), - ToolkitError, - 'Error getting Lambda info from Appbuilder Node, please check your connection' - ) - }) - - it('should throw error when generateDeployedNode returns no nodes', async () => { - generateDeployedNodeStub.resolves([]) - - const resource = resourceMock - - await assert.rejects( - async () => await generateLambdaNodeFromResource(resource), - ToolkitError, - 'Error getting Lambda info from Appbuilder Node, please check your connection' - ) - }) - - it('should throw error when generateDeployedNode returns multiple nodes', async () => { - const mockDeployedNode1 = {} as DeployedResourceNode - const mockDeployedNode2 = {} as DeployedResourceNode - generateDeployedNodeStub.resolves([mockDeployedNode1, mockDeployedNode2]) - - const resource = resourceMock - - await assert.rejects( - async () => await generateLambdaNodeFromResource(resource), - ToolkitError, - 'Error getting Lambda info from Appbuilder Node, please check your connection' - ) - }) -}) - -describe('ResourceNode', () => { - const lambdaResourceTreeEntity = { - Id: 'MyFunction', - Type: SERVERLESS_FUNCTION_TYPE, - Runtime: 'python3.12', - Handler: 'app.lambda_handler', - Events: [ - { - Id: 'FileUpload', - Type: 'S3', - Path: undefined, - Method: undefined, - }, - ], - } satisfies ResourceTreeEntity - const workspaceFolder = { - uri: vscode.Uri.parse('myworkspace'), - name: 'my-workspace', - index: 0, - } - const samAppLocation = { - samTemplateUri: vscode.Uri.parse('myworkspace/myprojectrootfolder/template.yaml'), - workspaceFolder, - projectRoot: vscode.Uri.parse('myworkspace/myprojectrootfolder'), - } - const stackName = 'MyStack' - const region = 'us-west-2' - const functionArn = 'arn:aws:lambda:us-west-2:123456789012:function:MyFunction' - const lambdaDeployedResource = { - LogicalResourceId: 'MyFunction', - PhysicalResourceId: functionArn, - } - - describe('constructor', () => { - it('should create a ResourceNode with correct all optional properties', () => { - const resourceNode = new ResourceNode( - samAppLocation, - lambdaResourceTreeEntity, - stackName, - region, - lambdaDeployedResource - ) - - validateNodeBasics(resourceNode, samAppLocation, lambdaResourceTreeEntity) - validateDeployedResourceProps(resourceNode, stackName, region, lambdaDeployedResource) - }) - - it('should create a ResourceNode with correct properties with only required parameters', () => { - const resourceNode = new ResourceNode(samAppLocation, lambdaResourceTreeEntity) - validateNodeBasics(resourceNode, samAppLocation, lambdaResourceTreeEntity) - validateEmptyStackProps(resourceNode) - validateEmptyDeployedResourceProps(resourceNode) - }) - }) - - describe('getChildren', () => { - const nonLambdaResourceTreeEntity = { - Id: 'MyS3Bucket', - Type: s3BucketType, - } - const s3Arn = 'arn:aws:s3:::my-example-bucket' - const nonLambdaDeployedResource = { - LogicalResourceId: 'MyS3BucketId', - PhysicalResourceId: s3Arn, - } - - let generateDeployedNodeStub: sinon.SinonStub - const mockDeployedNodeInstance = sinon.createStubInstance(DeployedResourceNodeModule.DeployedResourceNode) - - beforeEach(() => { - generateDeployedNodeStub = sinon.stub(DeployedResourceNodeModule, 'generateDeployedNode') - }) - - afterEach(() => { - sinon.restore() - }) - - it('should generate deployed nodes and property nodes for deployed lambda function', async () => { - // stub generateDeployedNode() call and return mock deploy - generateDeployedNodeStub.resolves([mockDeployedNodeInstance]) - - const resourceNode = new ResourceNode( - samAppLocation, - lambdaResourceTreeEntity, - stackName, - region, - lambdaDeployedResource - ) - const childrenNodes = await resourceNode.getChildren() - assert( - generateDeployedNodeStub.calledOnceWith( - lambdaDeployedResource, - region, - stackName, - lambdaResourceTreeEntity - ) - ) - assert(childrenNodes.length === 3) - assert(childrenNodes.filter((node) => node instanceof DeployedResourceNode).length === 1) - // Validate 2 property node for Lambda `Runtime` and `Handler` properties - assert(childrenNodes.filter((node) => node instanceof PropertyNode).length === 2) - }) - - it('should generate deployed nodes without property nodes for deployed non lambda function', async () => { - // stub generateDeployedNode() call and return mock deploy - generateDeployedNodeStub.resolves([mockDeployedNodeInstance]) - const resourceNode = new ResourceNode( - samAppLocation, - nonLambdaResourceTreeEntity, - stackName, - region, - nonLambdaDeployedResource - ) - const childrenNodes = await resourceNode.getChildren() - assert( - generateDeployedNodeStub.calledOnceWith( - nonLambdaDeployedResource, - region, - stackName, - nonLambdaResourceTreeEntity - ) - ) - assert(childrenNodes.length === 1) - assert(childrenNodes[0] instanceof DeployedResourceNode) - }) - - it('should generate empty array for non-deployed non lambda resource', async () => { - const resourceNode = new ResourceNode(samAppLocation, nonLambdaResourceTreeEntity) - assert(generateDeployedNodeStub.notCalled) - const childrenNodes = await resourceNode.getChildren() - assert(childrenNodes.length === 0) - }) - }) - - describe('getIconPath', () => { - const testCase = [ - { type: SERVERLESS_FUNCTION_TYPE, expectedIconKey: 'aws-lambda-function' }, - { type: s3BucketType, expectedIconKey: 'aws-s3-bucket' }, - { type: appRunnerType, expectedIconKey: 'aws-apprunner-service' }, - { type: ecrRepositoryType, expectedIconKey: 'aws-ecr-registry' }, - { type: 'Unsupported', expectedIconKey: 'info' }, - { type: SERVERLESS_CAPACITY_PROVIDER_TYPE, expectedIconKey: 'gear' }, - ] - - testCase.map((test) => { - it(`should generate correct icon path for ${test.type}`, () => { - const resourceNode = new ResourceNode(samAppLocation, { Id: 'MyResource', Type: test.type }) - // Access the private method using TypeScript's type assertion - const getIconPath = (resourceNode as any).getIconPath.bind(resourceNode) - const iconPath = getIconPath() - assert.strictEqual(iconPath.id, test.expectedIconKey) - }) - }) - }) - - describe('getTreeItem', () => { - it('should generate correct TreeItem with collapsible state given no deployed resource', () => { - const resourceNode = new ResourceNode(samAppLocation, lambdaResourceTreeEntity) - const treeItem = resourceNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'MyFunction') - assert.strictEqual(treeItem.tooltip, samAppLocation.samTemplateUri.toString()) - assert.strictEqual(treeItem.resourceUri, samAppLocation.samTemplateUri) - assert.strictEqual(treeItem.contextValue, 'awsAppBuilderResourceNode.function') - assert.strictEqual(treeItem.iconPath, getIcon('aws-lambda-function')) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - - it('should generate correct TreeItem without collapsed state given node with deployed resource', () => { - const resourceNode = new ResourceNode( - samAppLocation, - lambdaResourceTreeEntity, - stackName, - region, - lambdaDeployedResource, - functionArn - ) - const treeItem = resourceNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'MyFunction') - assert.strictEqual(treeItem.tooltip, samAppLocation.samTemplateUri.toString()) - assert.strictEqual(treeItem.resourceUri, samAppLocation.samTemplateUri) - assert.strictEqual(treeItem.contextValue, 'awsAppBuilderResourceNode.deployed-function') - assert.strictEqual(treeItem.iconPath, getIcon('aws-lambda-deployed-function')) - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - }) - }) - - describe('generateResourceNodes', () => { - it('should generate correct ResourceNodes without deployed resource', () => { - const resources: ResourceTreeEntity[] = [lambdaResourceTreeEntity, lambdaResourceTreeEntity] - const resourceNodes = generateResourceNodes(samAppLocation, resources) - - assert(resourceNodes.length === 2) - resourceNodes.map((resourceNode) => { - assert(resourceNode instanceof ResourceNode) - validateNodeBasics(resourceNode, samAppLocation, lambdaResourceTreeEntity) - validateEmptyStackProps(resourceNode) - validateEmptyDeployedResourceProps(resourceNode) - }) - }) - }) - - it('should generate correct ResourceNodes with deployed resources without valid type', () => { - const resourceTreeEntityWithoutType = { - Id: 'MyFunction', - Type: '', - } - - const resources: ResourceTreeEntity[] = [resourceTreeEntityWithoutType, resourceTreeEntityWithoutType] - const resourceNodes = generateResourceNodes(samAppLocation, resources, stackName, region) - - assert(resourceNodes.length === 2) - - resourceNodes.map((resourceNode) => { - assert(resourceNode instanceof ResourceNode) - validateNodeBasics(resourceNode, samAppLocation, resourceTreeEntityWithoutType) - assert.strictEqual(resourceNode.resource.stackName, stackName) - assert.strictEqual(resourceNode.resource.region, region) - validateEmptyDeployedResourceProps(resourceNode) - }) - }) - - it('should generate correct ResourceNodes with deployed resources and valid resource type', () => { - const resources: ResourceTreeEntity[] = [lambdaResourceTreeEntity] - const resourceNodes = generateResourceNodes(samAppLocation, resources, stackName, region, [ - lambdaDeployedResource, - ]) - - assert.strictEqual(resourceNodes.length, 1) - resourceNodes.map((resourceNode) => { - assert(resourceNode instanceof ResourceNode) - validateNodeBasics(resourceNode, samAppLocation, lambdaResourceTreeEntity) - validateDeployedResourceProps(resourceNode, stackName, region, lambdaDeployedResource) - }) - }) -}) - -function validateEmptyDeployedResourceProps(resourceNode: ResourceNode) { - assert(!resourceNode.resourceLogicalId) - assert(!resourceNode.resource.deployedResource) -} - -function validateEmptyStackProps(resourceNode: ResourceNode) { - assert(!resourceNode.resource.stackName) - assert(!resourceNode.resource.region) -} - -function validateNodeBasics( - resourceNode: ResourceNode, - samAppLocation: SamAppLocation, - resourceTreeEntity: ResourceTreeEntity -) { - assert.strictEqual(resourceNode.id, resourceTreeEntity.Id) - assert.strictEqual(resourceNode.resource.resource, resourceTreeEntity) - assert.strictEqual(resourceNode.resource.location, samAppLocation.samTemplateUri) - assert.strictEqual(resourceNode.resource.workspaceFolder, samAppLocation.workspaceFolder) -} -function validateDeployedResourceProps( - resourceNode: ResourceNode, - stackName: string, - region: string, - lambdaDeployedResource: StackResource -) { - assert.strictEqual(resourceNode.resource.region, region) - assert.strictEqual(resourceNode.resource.stackName, stackName) - assert.strictEqual(resourceNode.resourceLogicalId, lambdaDeployedResource.LogicalResourceId) - assert.strictEqual(resourceNode.resource.deployedResource, lambdaDeployedResource) -} diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/rootNode.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/rootNode.test.ts deleted file mode 100644 index f5711832ad..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/rootNode.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import assert from 'assert' -import * as sinon from 'sinon' -import { Commands } from '../../../../../shared/vscode/commands2' -import * as Telemetry from '../../../../../shared/telemetry/telemetry' -import * as VsCodeUtils from '../../../../../shared/utilities/vsCodeUtils' -import * as DetectSamProjects from '../../../../../../src/awsService/appBuilder/explorer/detectSamProjects' -import globals from '../../../../../shared/extensionGlobals' - -import { AppBuilderRootNode, getAppNodes } from '../../../../../awsService/appBuilder/explorer/nodes/rootNode' - -import { AppNode } from '../../../../../awsService/appBuilder/explorer/nodes/appNode' - -describe('getAppNodes', async () => { - let sandbox: sinon.SinonSandbox - let detectSamProjectsStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - // Use sandbox.stub instead of sinon.stub - detectSamProjectsStub = sandbox.stub(DetectSamProjects, 'detectSamProjects') - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should return an placeholder item when no SAM projects are found', async () => { - detectSamProjectsStub.resolves([]) - const appNodes = await getAppNodes() - assert.strictEqual(appNodes.length, 1) - assert.strictEqual(appNodes[0].id, 'placeholder') - assert.strictEqual(appNodes[0].resource, '[No SAM templates found in Workspaces]') - }) - - it('should return all SAM projects as AppNode', async () => { - const mockProjects = createMockProject(5) - detectSamProjectsStub.resolves(mockProjects) - const appNodes = await getAppNodes() - assert.strictEqual(appNodes.length, mockProjects.length) - assert(appNodes.every((node) => node instanceof AppNode)) - }) -}) - -describe('AppBuilderRootNode', () => { - let commandRegisterStub: sinon.SinonStub - let openUrlStub: sinon.SinonStub - let telemetryStub: sinon.SinonStub - let rootNode: AppBuilderRootNode - let sandbox: sinon.SinonSandbox - let detectSamProjectsStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - // Use sandbox.stub instead of sinon.stub - detectSamProjectsStub = sandbox.stub(DetectSamProjects, 'detectSamProjects') - // Stub the Commands.register method - commandRegisterStub = sandbox.stub(Commands, 'register') - // Stub the openUrl function - openUrlStub = sandbox.stub(VsCodeUtils, 'openUrl') - // Stub the telemetry.aws_help.emit method - telemetryStub = sandbox.stub(Telemetry, 'telemetry') - rootNode = new AppBuilderRootNode() - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('constructor', () => { - it('should create an AppBuilderRootNode with correct properties', () => { - assert.strictEqual(rootNode.id, 'appBuilder') - assert.strictEqual(rootNode.resource, rootNode) - }) - - it('should register commands correctly', () => { - assert.strictEqual(commandRegisterStub.callCount, 1) - assert.strictEqual(commandRegisterStub.firstCall.args[0], 'aws.appBuilder.viewDocs') - - // Helper functions that used for registering never get called - assert.strictEqual(openUrlStub.callCount, 0) - assert.strictEqual(telemetryStub.callCount, 0) - // Check that the refresh function is set correctly during instantiation - assert.strictEqual(typeof rootNode.refreshAppBuilderExplorer, 'function') - assert.strictEqual(typeof rootNode.refreshAppBuilderForFileExplorer, 'function') - }) - }) - - describe('getChildren', () => { - const mockProjects = createMockProject(5) - - beforeEach(() => { - // Stub detectSamProjectsStub() to return 5 projects - detectSamProjectsStub.resolves(mockProjects) - }) - - it('should generate all child project nodes wihtouth walkthrough when walkthrogh is completed', async () => { - // Set walk throught status to false - const walkThroughState = await globals.globalState.get('aws.toolkit.lambda.walkthroughCompleted') - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', true) - - const childrenAppNodes = await rootNode.getChildren() - - assert.strictEqual(childrenAppNodes.length, mockProjects.length) - // Set walk throught status to original state - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', walkThroughState) - }) - - it('should generate all child project nodes + walkthrough node when walkthrogh is incomplete', async () => { - // Set walk throught status to false - const walkThroughState = await globals.globalState.get('aws.toolkit.lambda.walkthroughCompleted') - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', false) - - const childrenAppNodes = await rootNode.getChildren() - - const walkThroughNode = childrenAppNodes[0] - assert.strictEqual(walkThroughNode.id, 'walkthrough') - assert.strictEqual(childrenAppNodes.filter((node) => node instanceof AppNode).length, mockProjects.length) - - // Set walk throught status to original state - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', walkThroughState) - }) - }) - - describe('getTreeItem', () => { - it('should generate correct TreeItem for AppBuilderRootNode', () => { - const rootNode = AppBuilderRootNode.instance - const treeItem = rootNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'APPLICATION BUILDER') - assert.strictEqual(treeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(treeItem.contextValue, 'awsAppBuilderRootNode') - }) - }) -}) - -function createMockProject(count: number) { - return Array.from({ length: count }, (_, i) => ({ - samTemplateUri: vscode.Uri.parse(`/Mock Workspace/Mock Project ${i + 1}/template.yaml`), - workspaceFolder: { - uri: vscode.Uri.parse('/Mock Workspace'), - name: 'Mock Workspace', - index: 0, - }, - projectRoot: vscode.Uri.parse(`/Mock Workspace/Mock Project ${i + 1}`), - projectName: `Mock Project ${i + 1}`, - projectIndex: i, - })) -} diff --git a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/walkthrough.test.ts b/packages/core/src/test/shared/applicationBuilder/explorer/nodes/walkthrough.test.ts deleted file mode 100644 index f998e6b914..0000000000 --- a/packages/core/src/test/shared/applicationBuilder/explorer/nodes/walkthrough.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import { WalkthroughNode } from '../../../../../awsService/appBuilder/explorer/nodes/walkthroughNode' - -describe('WalkthroughNode', () => { - let walkthroughNode: WalkthroughNode - - // Initialize a new instance before each test to ensure test isolation - beforeEach(() => { - walkthroughNode = new WalkthroughNode() - }) - describe('constructor', () => { - it('should create a WalkthroughNode with correct properties', () => { - assert.strictEqual(walkthroughNode.id, 'walkthrough') - assert.strictEqual(walkthroughNode.resource, walkthroughNode) - }) - }) - describe('getTreeItem', () => { - it('should generate correct TreeItem', () => { - const treeItem = walkthroughNode.getTreeItem() - - assert.strictEqual(treeItem.label, 'Walkthrough of Application Builder') - assert.strictEqual(treeItem.contextValue, 'awsWalkthroughNode') - assert.deepStrictEqual(treeItem.command, { - title: 'Walkthrough of Application Builder', - command: 'aws.toolkit.lambda.openWalkthrough', - }) - }) - }) -}) diff --git a/packages/core/src/test/shared/clients/defaultDocdbClient.test.ts b/packages/core/src/test/shared/clients/defaultDocdbClient.test.ts deleted file mode 100644 index 0a6eaf7b56..0000000000 --- a/packages/core/src/test/shared/clients/defaultDocdbClient.test.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon, { SinonStub } from 'sinon' -import { DefaultDocumentDBClient, DocumentDBClient } from '../../../shared/clients/docdbClient' - -describe('DefaultDocumentDBClient', function () { - const region = 'us-west-2' - - let sdkStub: SinonStub - - beforeEach(() => { - sdkStub = sinon.stub().resolves({ - send: sinon.stub().resolves({ - DBClusters: [], - DBInstances: [], - }), - destroy: sinon.stub(), - }) - }) - - function createClient({ regionCode = region }: { regionCode?: string } = {}): DocumentDBClient { - const client = DefaultDocumentDBClient.create(regionCode) - client.getClient = sdkStub - return client - } - - describe('listClusters', function () { - it('gets a list of clusters', async function () { - const client = createClient() - const result = await client.listClusters() - - assert.ok(result) - assert.equal(0, result.length) - assert(sdkStub.calledOnce) - }) - - it('throws an Error during listing clusters on failure', async function () { - const client = createClient() - client.getClient = sinon.stub().resolves({ - send: sinon.stub().throws(), - }) - - await assert.rejects(async () => await client.listClusters()) - }) - }) - - describe('listInstance', function () { - it('gets a list of instances', async function () { - const client = createClient() - const result = await client.listInstances() - - assert.ok(result) - assert.equal(0, result.length) - assert(sdkStub.calledOnce) - }) - - it('throws an Error during listing instances on failure', async function () { - const client = createClient() - client.getClient = sinon.stub().resolves({ - send: sinon.stub().throws(), - }) - - await assert.rejects(async () => await client.listInstances()) - }) - }) - - describe('startCluster', function () { - it('sends the correct command', async function () { - // arrange - const clusterId = 'test-cluster-1' - const client = createClient() - - // act - await client.startCluster(clusterId) - - // assert - assert(sdkStub.calledOnce) - }) - }) - - describe('stopCluster', function () { - it('sends the correct command', async function () { - // arrange - const clusterId = 'test-cluster-1' - const client = createClient() - - // act - await client.stopCluster(clusterId) - - // assert - assert(sdkStub.calledOnce) - }) - }) -}) diff --git a/packages/core/src/test/shared/clients/defaultIotClient.test.ts b/packages/core/src/test/shared/clients/defaultIotClient.test.ts deleted file mode 100644 index 01cd2740f6..0000000000 --- a/packages/core/src/test/shared/clients/defaultIotClient.test.ts +++ /dev/null @@ -1,637 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ServiceException } from '@smithy/smithy-client' -import { - AttachPolicyCommand, - AttachPolicyRequest, - AttachThingPrincipalCommand, - AttachThingPrincipalRequest, - CreateKeysAndCertificateCommand, - CreateKeysAndCertificateRequest, - CreateKeysAndCertificateResponse, - CreatePolicyCommand, - CreatePolicyRequest, - CreatePolicyResponse, - CreatePolicyVersionCommand, - CreatePolicyVersionRequest, - CreatePolicyVersionResponse, - CreateThingCommand, - CreateThingResponse, - DeleteCertificateCommand, - DeleteCertificateRequest, - DeletePolicyCommand, - DeletePolicyRequest, - DeletePolicyVersionCommand, - DeletePolicyVersionRequest, - DeleteThingCommand, - DeleteThingRequest, - DeleteThingResponse, - DescribeCertificateCommand, - DescribeCertificateRequest, - DescribeCertificateResponse, - DescribeEndpointCommand, - DescribeEndpointRequest, - DescribeEndpointResponse, - DetachPolicyCommand, - DetachPolicyRequest, - DetachThingPrincipalCommand, - DetachThingPrincipalRequest, - GetPolicyVersionCommand, - GetPolicyVersionRequest, - GetPolicyVersionResponse, - IoTClient, - IoTClientResolvedConfig, - ListCertificatesCommand, - ListCertificatesRequest, - ListCertificatesResponse, - ListPoliciesCommand, - ListPoliciesRequest, - ListPoliciesResponse, - ListPolicyVersionsCommand, - ListPolicyVersionsRequest, - ListPolicyVersionsResponse, - ListPrincipalPoliciesCommand, - ListPrincipalPoliciesRequest, - ListPrincipalThingsCommand, - ListPrincipalThingsRequest, - ListPrincipalThingsResponse, - ListTargetsForPolicyCommand, - ListTargetsForPolicyRequest, - ListTargetsForPolicyResponse, - ListThingPrincipalsCommand, - ListThingPrincipalsRequest, - ListThingPrincipalsResponse, - ListThingsCommand, - ListThingsRequest, - ListThingsResponse, - PolicyVersion, - ServiceInputTypes, - ServiceOutputTypes, - SetDefaultPolicyVersionCommand, - SetDefaultPolicyVersionRequest, - UpdateCertificateCommand, - UpdateCertificateRequest, -} from '@aws-sdk/client-iot' -import { DefaultIotClient, ListThingCertificatesResponse } from '../../../shared/clients/iotClient' -import { AwsStub, mockClient } from 'aws-sdk-client-mock' - -class FakeServiceException extends ServiceException { - public region: string = 'us-west-2' - - public constructor(message: string) { - super({ - name: 'FakeServiceException', - $fault: 'client', - $metadata: {}, - message, - }) - } -} - -describe('DefaultIotClient', function () { - const region = 'us-west-2' - const thingName = 'thing' - const policyName = 'policy' - const policyDocument = '{ "key": "value" }' - const nextToken = 'nextToken' - const marker = nextToken - const maxResults = 10 - const pageSize = maxResults - let mockIot: AwsStub - - beforeEach(function () { - mockIot = mockClient(IoTClient) - }) - - const error: ServiceException = new FakeServiceException('Expected failure') as ServiceException - - function createClient({ regionCode = region }: { regionCode?: string } = {}): DefaultIotClient { - return new DefaultIotClient(regionCode, () => new IoTClient()) - } - - /* Functions that create or retrieve resources. */ - - describe('createThing', function () { - const expectedResponse: CreateThingResponse = { thingName: thingName, thingArn: 'arn' } - it('creates a thing', async function () { - mockIot.on(CreateThingCommand).resolves(expectedResponse) - - const response = await createClient().createThing({ thingName }) - - assert.strictEqual(mockIot.commandCalls(CreateThingCommand).length, 1) - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(CreateThingCommand).rejects(error) - - await assert.rejects(createClient().createThing({ thingName }), error) - }) - }) - - describe('createCertificateAndKeys', function () { - const certificateId = 'cert1' - const input: CreateKeysAndCertificateRequest = { setAsActive: undefined } - const expectedResponse: CreateKeysAndCertificateResponse = { - certificateId, - certificateArn: 'arn', - certificatePem: 'pem', - keyPair: { PublicKey: 'publicKey', PrivateKey: 'privateKey' }, - } - - it('creates Certificate and Key Pair', async function () { - mockIot.on(CreateKeysAndCertificateCommand).resolves(expectedResponse) - - const response = await createClient().createCertificateAndKeys(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(CreateKeysAndCertificateCommand).rejects(error) - - await assert.rejects(createClient().createCertificateAndKeys(input), error) - }) - }) - - describe('getEndpoint', function () { - const input: DescribeEndpointRequest = { endpointType: 'iot:Data-ATS' } - const endpointAddress = 'address' - const describeResponse: DescribeEndpointResponse = { endpointAddress } - - it('gets endpoint', async function () { - mockIot.on(DescribeEndpointCommand).resolves(describeResponse) - - const response = await createClient().getEndpoint() - - assert.strictEqual(mockIot.commandCalls(DescribeEndpointCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DescribeEndpointCommand)[0].args[0].input, input) - assert.deepStrictEqual(response, endpointAddress) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DescribeEndpointCommand).rejects(error) - - await assert.rejects(createClient().getEndpoint(), error) - }) - }) - - describe('getPolicyVersion', function () { - const input: GetPolicyVersionRequest = { policyName, policyVersionId: '1' } - const expectedResponse: GetPolicyVersionResponse = { - policyName, - policyDocument, - policyArn: 'arn1', - policyVersionId: '1', - } - - it('gets policy document for version', async function () { - mockIot.on(GetPolicyVersionCommand).resolves(expectedResponse) - - const response = await createClient().getPolicyVersion(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(GetPolicyVersionCommand).rejects(error) - - await assert.rejects(createClient().getPolicyVersion(input), error) - }) - }) - - /* Functions that return void .*/ - - describe('deleteThing', function () { - const input: DeleteThingRequest = { thingName } - - it('deletes a thing', async function () { - mockIot.on(DeleteThingCommand).resolves({} as DeleteThingResponse) - - await createClient().deleteThing({ thingName }) - - assert.strictEqual(mockIot.commandCalls(DeleteThingCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DeleteThingCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DeleteThingCommand).rejects(error) - - await assert.rejects(createClient().deleteThing({ thingName }), error) - }) - }) - - describe('deleteCertificate', function () { - const certificateId = 'cert1' - const input: DeleteCertificateRequest = { certificateId, forceDelete: undefined } - - it('deletes a certificate', async function () { - mockIot.on(DeleteCertificateCommand).resolves({}) - - await createClient().deleteCertificate(input) - - assert.strictEqual(mockIot.commandCalls(DeleteCertificateCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DeleteCertificateCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DeleteCertificateCommand).rejects(error) - - await assert.rejects(createClient().deleteCertificate(input), error) - }) - }) - - describe('updateCertificate', function () { - const certificateId = 'cert1' - const input: UpdateCertificateRequest = { certificateId, newStatus: 'ACTIVE' } - - it('updates a certificate', async function () { - mockIot.on(UpdateCertificateCommand).resolves({}) - - await createClient().updateCertificate(input) - - assert.strictEqual(mockIot.commandCalls(UpdateCertificateCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(UpdateCertificateCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(UpdateCertificateCommand).rejects(error) - - await assert.rejects(createClient().updateCertificate(input), error) - }) - }) - - describe('attachThingPrincipal', function () { - const input: AttachThingPrincipalRequest = { thingName, principal: 'arn1' } - - it('attaches a certificate to a Thing', async function () { - mockIot.on(AttachThingPrincipalCommand).resolves({}) - - await createClient().attachThingPrincipal(input) - - assert.strictEqual(mockIot.commandCalls(AttachThingPrincipalCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(AttachThingPrincipalCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(AttachThingPrincipalCommand).rejects(error) - - await assert.rejects(createClient().attachThingPrincipal(input), error) - }) - }) - - describe('detachThingPrincipal', function () { - const input: DetachThingPrincipalRequest = { thingName, principal: 'arn1' } - - it('detaches a certificate from a Thing', async function () { - mockIot.on(DetachThingPrincipalCommand).resolves({}) - - await createClient().detachThingPrincipal(input) - - assert.strictEqual(mockIot.commandCalls(DetachThingPrincipalCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DetachThingPrincipalCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DetachThingPrincipalCommand).rejects(error) - - await assert.rejects(createClient().detachThingPrincipal(input), error) - }) - }) - - describe('attachPolicy', function () { - const input: AttachPolicyRequest = { policyName, target: 'arn1' } - - it('attaches a policy to a certificate', async function () { - mockIot.on(AttachPolicyCommand).resolves({}) - - await createClient().attachPolicy(input) - - assert.strictEqual(mockIot.commandCalls(AttachPolicyCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(AttachPolicyCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(AttachPolicyCommand).rejects(error) - - await assert.rejects(createClient().attachPolicy(input), error) - }) - }) - - describe('detachPolicy', function () { - const input: DetachPolicyRequest = { policyName, target: 'arn1' } - - it('detaches a policy from a certificate', async function () { - mockIot.on(DetachPolicyCommand).resolves({}) - - await createClient().detachPolicy(input) - - assert.strictEqual(mockIot.commandCalls(DetachPolicyCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DetachPolicyCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DetachPolicyCommand).rejects(error) - - await assert.rejects(createClient().detachPolicy(input), error) - }) - }) - - describe('createPolicy', function () { - const input: CreatePolicyRequest = { policyName, policyDocument } - const expectedResponse: CreatePolicyResponse = { policyName, policyDocument, policyArn: 'arn1' } - - it('creates a policy from a document', async function () { - mockIot.on(CreatePolicyCommand).resolves(expectedResponse) - - await createClient().createPolicy(input) - - assert.strictEqual(mockIot.commandCalls(CreatePolicyCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(CreatePolicyCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(CreatePolicyCommand).rejects(error) - - await assert.rejects(createClient().createPolicy(input), error) - }) - }) - - describe('deletePolicy', function () { - const input: DeletePolicyRequest = { policyName } - - it('deletes a policy', async function () { - mockIot.on(DeletePolicyCommand).resolves({}) - - await createClient().deletePolicy(input) - - assert.strictEqual(mockIot.commandCalls(DeletePolicyCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DeletePolicyCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DeletePolicyCommand).rejects(error) - - await assert.rejects(createClient().deletePolicy(input), error) - }) - }) - - describe('createPolicyVersion', function () { - const input: CreatePolicyVersionRequest = { policyName, policyDocument } - const expectedResponse: CreatePolicyVersionResponse = { policyDocument, policyArn: 'arn1' } - - it('creates a policy version from a document', async function () { - mockIot.on(CreatePolicyVersionCommand).resolves(expectedResponse) - - await createClient().createPolicyVersion(input) - - assert.strictEqual(mockIot.commandCalls(CreatePolicyVersionCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(CreatePolicyVersionCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(CreatePolicyVersionCommand).rejects(error) - - await assert.rejects(createClient().createPolicyVersion(input), error) - }) - }) - - describe('deletePolicyVersion', function () { - const input: DeletePolicyVersionRequest = { policyName, policyVersionId: '1' } - - it('deletes a policy version', async function () { - mockIot.on(DeletePolicyVersionCommand).resolves({}) - - await createClient().deletePolicyVersion(input) - - assert.strictEqual(mockIot.commandCalls(DeletePolicyVersionCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DeletePolicyVersionCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(DeletePolicyVersionCommand).rejects(error) - - await assert.rejects(createClient().deletePolicyVersion(input), error) - }) - }) - - describe('setDefaultPolicyVersion', function () { - const input: SetDefaultPolicyVersionRequest = { policyName, policyVersionId: '1' } - - it('deletes a policy version', async function () { - mockIot.on(SetDefaultPolicyVersionCommand).resolves({}) - - await createClient().setDefaultPolicyVersion(input) - - assert.strictEqual(mockIot.commandCalls(SetDefaultPolicyVersionCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(SetDefaultPolicyVersionCommand)[0].args[0].input, input) - }) - - it('throws an Error on failure', async function () { - mockIot.on(SetDefaultPolicyVersionCommand).rejects(error) - - await assert.rejects(createClient().setDefaultPolicyVersion(input), error) - }) - }) - - // /* Functions that list resources. - - describe('listThings', function () { - const input: ListThingsRequest = { maxResults, nextToken } - const expectedResponse: ListThingsResponse = { things: [{ thingName: 'thing1' }], nextToken } - - it('lists things', async function () { - mockIot.on(ListThingsCommand).resolves(expectedResponse) - - const response = await createClient().listThings(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListThingsCommand).rejects(error) - - await assert.rejects(createClient().listThings(input), error) - }) - }) - - describe('listCertificates', function () { - const input: ListCertificatesRequest = { pageSize, marker, ascendingOrder: undefined } - const expectedResponse: ListCertificatesResponse = { - certificates: [{ certificateId: 'cert1' }], - nextMarker: marker, - } - - it('lists certificates', async function () { - mockIot.on(ListCertificatesCommand).resolves(expectedResponse) - - const response = await createClient().listCertificates(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListCertificatesCommand).rejects(error) - - await assert.rejects(createClient().listCertificates(input), error) - }) - }) - - describe('listThingCertificates', function () { - const certificateId = 'cert1' - const certArn = 'arn:aws:iot:us-west-2:0123456789:cert/cert1' - const input: ListThingPrincipalsRequest = { thingName, maxResults, nextToken } - const principalsResponse: ListThingPrincipalsResponse = { principals: [certArn], nextToken } - - const describeInput: DescribeCertificateRequest = { certificateId } - const describeResponse: DescribeCertificateResponse = { - certificateDescription: { certificateId, certificateArn: certArn }, - } - - const expectedResponse: ListThingCertificatesResponse = { - certificates: [{ certificateId, certificateArn: certArn }], - nextToken: nextToken, - } - - it('lists certificates', async function () { - mockIot.on(ListThingPrincipalsCommand).resolves(principalsResponse) - mockIot.on(DescribeCertificateCommand).resolves(describeResponse) - - const response = await createClient().listThingCertificates(input) - - assert.strictEqual(mockIot.commandCalls(DescribeCertificateCommand).length, 1) - assert.deepStrictEqual(mockIot.commandCalls(DescribeCertificateCommand)[0].args[0].input, describeInput) - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error when certificate listing fails', async function () { - mockIot.on(ListThingPrincipalsCommand).rejects(error) - - await assert.rejects(createClient().listThingCertificates(input), error) - }) - - it('throws an Error when certificate description fails', async function () { - mockIot.on(ListThingPrincipalsCommand).resolves(principalsResponse) - mockIot.on(DescribeCertificateCommand).rejects(error) - - await assert.rejects(createClient().listThingCertificates(input), error) - }) - }) - - describe('listThingsForCert', function () { - const input: ListPrincipalThingsRequest = { principal: 'arn1', maxResults, nextToken } - const listResponse: ListPrincipalThingsResponse = { things: [thingName], nextToken } - const expectedResponse = [thingName] - - it('lists things', async function () { - mockIot.on(ListPrincipalThingsCommand).resolves(listResponse) - - const response = await createClient().listThingsForCert(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListPrincipalThingsCommand).rejects(error) - - await assert.rejects(createClient().listThingsForCert(input), error) - }) - }) - - describe('listPolicies', function () { - const input: ListPoliciesRequest = { pageSize, marker, ascendingOrder: undefined } - const expectedResponse: ListPoliciesResponse = { policies: [{ policyName }], nextMarker: marker } - - it('lists policies', async function () { - mockIot.on(ListPoliciesCommand).resolves(expectedResponse) - - const response = await createClient().listPolicies(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListPoliciesCommand).rejects(error) - - await assert.rejects(createClient().listPolicies(input), error) - }) - }) - - describe('listPrincipalPolicies', function () { - const input: ListPrincipalPoliciesRequest = { - pageSize, - marker, - ascendingOrder: undefined, - principal: 'arn1', - } - const expectedResponse: ListPoliciesResponse = { policies: [{ policyName }], nextMarker: marker } - - it('lists policies for certificate', async function () { - mockIot.on(ListPrincipalPoliciesCommand).resolves(expectedResponse) - - const response = await createClient().listPrincipalPolicies(input) - - assert.deepStrictEqual(response, expectedResponse) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListPrincipalPoliciesCommand).rejects(error) - - await assert.rejects(createClient().listPrincipalPolicies(input), error) - }) - }) - - describe('listPolicyTargets', function () { - const targets = ['arn1', 'arn2'] - const input: ListTargetsForPolicyRequest = { policyName, pageSize, marker } - const listResponse: ListTargetsForPolicyResponse = { targets, nextMarker: marker } - - it('lists certificates', async function () { - mockIot.on(ListTargetsForPolicyCommand).resolves(listResponse) - - const response = await createClient().listPolicyTargets(input) - - assert.deepStrictEqual(response, targets) - }) - - it('throws an Error on failure', async function () { - mockIot.on(ListTargetsForPolicyCommand).rejects(error) - - await assert.rejects(createClient().listPolicyTargets(input), error) - }) - }) - - describe('listPolicyVersions', function () { - const input: ListPolicyVersionsRequest = { policyName } - const expectedVersion1: PolicyVersion = { versionId: '1' } - const expectedVersion2: PolicyVersion = { versionId: '2' } - const listResponse: ListPolicyVersionsResponse = { policyVersions: [expectedVersion1, expectedVersion2] } - - it('lists policy versions', async function () { - mockIot.on(ListPolicyVersionsCommand).resolves(listResponse) - - const iterable = createClient().listPolicyVersions(input) - const responses = [] - for await (const response of iterable) { - responses.push(response) - } - - const [firstVersion, secondVersion, ...otherVersions] = responses - - assert.deepStrictEqual(firstVersion, expectedVersion1) - assert.deepStrictEqual(secondVersion, expectedVersion2) - assert.deepStrictEqual(otherVersions, []) - }) - - it('throws an Error on iterate failure', async function () { - mockIot.on(ListPolicyVersionsCommand).rejects(error) - - const iterable = createClient().listPolicyVersions(input) - await assert.rejects(iterable.next(), error) - }) - }) -}) diff --git a/packages/core/src/test/shared/clients/defaultRedshiftClient.test.ts b/packages/core/src/test/shared/clients/defaultRedshiftClient.test.ts deleted file mode 100644 index 77e2fced64..0000000000 --- a/packages/core/src/test/shared/clients/defaultRedshiftClient.test.ts +++ /dev/null @@ -1,171 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { ClustersMessage, RedshiftClient, DescribeClustersCommand } from '@aws-sdk/client-redshift' -import { - ListDatabasesResponse, - ListSchemasResponse, - RedshiftDataClient, - ListDatabasesCommand, - ListSchemasCommand, -} from '@aws-sdk/client-redshift-data' -import { - ListWorkgroupsResponse, - RedshiftServerlessClient, - ListWorkgroupsCommand, -} from '@aws-sdk/client-redshift-serverless' -import { DefaultRedshiftClient } from '../../../shared/clients/redshiftClient' -import assert = require('assert') -import { ConnectionParams, ConnectionType, RedshiftWarehouseType } from '../../../awsService/redshift/models/models' -import { mockClient, AwsClientStub } from 'aws-sdk-client-mock' - -const nextToken = 'testNextToken' -describe('DefaultRedshiftClient', function () { - let defaultRedshiftClient: DefaultRedshiftClient - let mockRedshift: AwsClientStub - let mockRedshiftData: AwsClientStub - let mockRedshiftServerless: AwsClientStub - const clusterIdentifier = 'ClusterId' - const workgroupName = 'Workgroup' - const dbName = 'DB' - const dbUsername = 'User' - const provisionedDbUserAndPasswordParams = new ConnectionParams( - ConnectionType.TempCreds, - dbName, - clusterIdentifier, - RedshiftWarehouseType.PROVISIONED, - dbUsername - ) - const serverlessFederatedParams = new ConnectionParams( - ConnectionType.TempCreds, - dbName, - workgroupName, - RedshiftWarehouseType.SERVERLESS - ) - beforeEach(function () { - mockRedshift = mockClient(RedshiftClient) - mockRedshiftData = mockClient(RedshiftDataClient) - mockRedshiftServerless = mockClient(RedshiftServerlessClient) - defaultRedshiftClient = new DefaultRedshiftClient( - 'us-east-1', - // @ts-expect-error - () => mockRedshiftData, - () => mockRedshift, - () => mockRedshiftServerless - ) - }) - - afterEach(function () { - mockRedshift.reset() - mockRedshiftData.reset() - mockRedshiftServerless.reset() - }) - - describe('describeProvisionedClusters', function () { - const expectedResponse = { Clusters: [] } as ClustersMessage - - beforeEach(function () { - mockRedshift.on(DescribeClustersCommand).resolves(expectedResponse) - }) - - it('without nextToken should not set Marker', async () => { - const response = await defaultRedshiftClient.describeProvisionedClusters() - const calls = mockRedshift.commandCalls(DescribeClustersCommand) - assert.strictEqual(calls.length, 1) - assert.deepStrictEqual(calls[0].args[0].input, { Marker: undefined, MaxRecords: 20 }) - assert.deepStrictEqual(response.Clusters, []) - }) - - it('with nextToken should set the Marker', async () => { - const response = await defaultRedshiftClient.describeProvisionedClusters(nextToken) - const calls = mockRedshift.commandCalls(DescribeClustersCommand) - assert.strictEqual(calls.length, 1) - assert.deepStrictEqual(calls[0].args[0].input, { Marker: nextToken, MaxRecords: 20 }) - assert.deepStrictEqual(response.Clusters, []) - }) - }) - - describe('listServerlessWorkgroups', function () { - const expectedResponse = { workgroups: [] } as ListWorkgroupsResponse - - beforeEach(function () { - mockRedshiftServerless.on(ListWorkgroupsCommand).resolves(expectedResponse) - }) - - it('without nextToken should not set nextToken in RedshiftServerless request', async () => { - const response = await defaultRedshiftClient.listServerlessWorkgroups() - const calls = mockRedshiftServerless.commandCalls(ListWorkgroupsCommand) - assert.strictEqual(calls.length, 1) - assert.deepStrictEqual(calls[0].args[0].input, { nextToken: undefined, maxResults: 20 }) - assert.deepStrictEqual(response.workgroups, []) - }) - - it('with nextToken should set nextToken in RedshiftServerless request', async () => { - const response = await defaultRedshiftClient.listServerlessWorkgroups(nextToken) - const calls = mockRedshiftServerless.commandCalls(ListWorkgroupsCommand) - assert.strictEqual(calls.length, 1) - assert.deepStrictEqual(calls[0].args[0].input, { nextToken: nextToken, maxResults: 20 }) - assert.deepStrictEqual(response.workgroups, []) - }) - }) - - describe('listDatabases', function () { - const expectedResponse = { Databases: [] } as ListDatabasesResponse - - beforeEach(function () { - mockRedshiftData.on(ListDatabasesCommand).resolves(expectedResponse) - }) - - it('should list databases for provisioned clusters', async () => { - const response = await defaultRedshiftClient.listDatabases(provisionedDbUserAndPasswordParams) - const calls = mockRedshiftData.commandCalls(ListDatabasesCommand) - assert.strictEqual(calls.length, 1) - const input = calls[0].args[0].input - assert.strictEqual(input.ClusterIdentifier, clusterIdentifier) - assert.strictEqual(input.Database, dbName) - assert.strictEqual(input.DbUser, dbUsername) - assert.deepStrictEqual(response.Databases, []) - }) - - it('should list databases for serverless workgroups', async () => { - const response = await defaultRedshiftClient.listDatabases(serverlessFederatedParams) - const calls = mockRedshiftData.commandCalls(ListDatabasesCommand) - assert.strictEqual(calls.length, 1) - const input = calls[0].args[0].input - assert.strictEqual(input.WorkgroupName, workgroupName) - assert.strictEqual(input.Database, dbName) - assert.deepStrictEqual(response.Databases, []) - }) - }) - - describe('listSchemas', function () { - const expectedResponse = { Schemas: [] } as ListSchemasResponse - - beforeEach(function () { - mockRedshiftData.on(ListSchemasCommand).resolves(expectedResponse) - }) - - it('should list schemas for databases in provisioned clusters', async () => { - const response = await defaultRedshiftClient.listSchemas(provisionedDbUserAndPasswordParams, dbName) - const calls = mockRedshiftData.commandCalls(ListSchemasCommand) - assert.strictEqual(calls.length, 1) - const input = calls[0].args[0].input - assert.strictEqual(input.ClusterIdentifier, clusterIdentifier) - assert.strictEqual(input.Database, dbName) - assert.strictEqual(input.DbUser, dbUsername) - assert.deepStrictEqual(response.Schemas, []) - }) - - it('should list schemas for databases in serverless workgroups', async () => { - const response = await defaultRedshiftClient.listSchemas(serverlessFederatedParams, dbName) - const calls = mockRedshiftData.commandCalls(ListSchemasCommand) - assert.strictEqual(calls.length, 1) - const input = calls[0].args[0].input - assert.strictEqual(input.WorkgroupName, workgroupName) - assert.strictEqual(input.Database, dbName) - assert.deepStrictEqual(response.Schemas, []) - }) - }) -}) diff --git a/packages/core/src/test/shared/clients/devenvClient.test.ts b/packages/core/src/test/shared/clients/devenvClient.test.ts deleted file mode 100644 index 6401faa621..0000000000 --- a/packages/core/src/test/shared/clients/devenvClient.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import { SinonStub, SinonStubbedInstance, SinonStubbedMember, createSandbox, createStubInstance } from 'sinon' -import assert from 'assert' -import * as vscode from 'vscode' - -import { UserActivity } from '../../../shared/extensionUtilities' -import { DevEnvClient, DevEnvActivity } from '../../../shared/clients/devenvClient' -import { sleep } from '../../../shared/utilities/timeoutUtils' - -describe('DevEnvActivity', function () { - let triggerUserActivityEvent: (obj: any) => Promise - let userActivityEvent: SinonStub>, vscode.Disposable> - let activitySubscriber: SinonStubbedMember<(timestamp: number) => void> - let devEnvClientStub: SinonStubbedInstance - let devEnvActivity: DevEnvActivity - let sandbox: sinon.SinonSandbox - - before(function () { - sandbox = createSandbox() - }) - - beforeEach(async function () { - userActivityEvent = sandbox.stub(vscode.window, 'onDidChangeActiveColorTheme') - userActivityEvent.callsFake((throttledEventFire: (obj: any) => void) => { - triggerUserActivityEvent = async (obj: any) => { - throttledEventFire(obj) - // There are multiple layers of emitters firing - // so we wait a bit before returning. Otherwise, - // racecondition when waiting for activitySubscriber - // to be called - await sleep(100) - } - return { - dispose: sandbox.stub(), - } - }) - - devEnvClientStub = createStubInstance(DevEnvClient) - - devEnvActivity = (await DevEnvActivity.create( - devEnvClientStub as unknown as DevEnvClient, - new UserActivity(0, [userActivityEvent]) - ))! - }) - - afterEach(function () { - sandbox.restore() - }) - - it('has expected throttle delay that we send updates on user activity', function () { - assert.strictEqual(DevEnvActivity.activityUpdateDelay, 10_000) - }) - - it('does not allow instance to be created if activity API not working', async function () { - devEnvClientStub.getActivity.throws() - const instance = await DevEnvActivity.create( - devEnvClientStub as unknown as DevEnvClient, - new UserActivity(0, [userActivityEvent]) - ) - assert.strictEqual(instance, undefined) - }) - - describe('activity subscribers are properly notified', function () { - beforeEach(function () { - activitySubscriber = sandbox.stub() - devEnvActivity.onActivityUpdate(activitySubscriber) - }) - - it('when user activity is explicitly updated', async () => { - assert.strictEqual(activitySubscriber.callCount, 0) - await devEnvActivity.sendActivityUpdate() - assert.strictEqual(activitySubscriber.callCount, 1) - }) - - it('when vscode user activity event is emitted', async () => { - assert.strictEqual(activitySubscriber.callCount, 0) - devEnvActivity.setUpdateActivityOnIdeActivity(true) - await triggerUserActivityEvent({}) - assert.strictEqual(activitySubscriber.callCount, 1) - }) - - it('when we discover a different activity timestamp on the server', async () => { - assert.strictEqual(activitySubscriber.callCount, 0) - - await devEnvActivity.sendActivityUpdate(111) // We send an activity update - assert.strictEqual(activitySubscriber.callCount, 1) - - devEnvClientStub.getActivity.resolves(222) // If we retrieve the latest activity timestamp it will be different - await devEnvActivity.getLatestActivity() - assert.strictEqual(activitySubscriber.callCount, 2) - }) - }) -}) diff --git a/packages/core/src/test/shared/clients/sagemakerClient.test.ts b/packages/core/src/test/shared/clients/sagemakerClient.test.ts deleted file mode 100644 index f99b8ed82e..0000000000 --- a/packages/core/src/test/shared/clients/sagemakerClient.test.ts +++ /dev/null @@ -1,435 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as assert from 'assert' -import { SagemakerClient } from '../../../shared/clients/sagemaker' -import { AppDetails, SpaceDetails, DescribeDomainCommandOutput, AppType } from '@aws-sdk/client-sagemaker' -import { DescribeDomainResponse } from '@amzn/sagemaker-client' -import { intoCollection } from '../../../shared/utilities/collectionUtils' -import { ToolkitError } from '../../../shared/errors' -import { getTestWindow } from '../vscode/window' -import { InstanceTypeInsufficientMemoryMessage } from '../../../awsService/sagemaker/constants' - -describe('SagemakerClient.fetchSpaceAppsAndDomains', function () { - const region = 'test-region' - let client: SagemakerClient - let listAppsStub: sinon.SinonStub - - const appDetails: AppDetails[] = [ - { AppName: 'app1', DomainId: 'domain1', SpaceName: 'space1', AppType: 'CodeEditor' }, - { AppName: 'app2', DomainId: 'domain2', SpaceName: 'space2', AppType: 'CodeEditor' }, - { AppName: 'app3', DomainId: 'domain2', SpaceName: 'space3', AppType: 'JupyterLab' }, - ] - - const spaceDetails: SpaceDetails[] = [ - { SpaceName: 'space1', DomainId: 'domain1' }, - { SpaceName: 'space2', DomainId: 'domain2' }, - { SpaceName: 'space3', DomainId: 'domain2' }, - { SpaceName: 'space4', DomainId: 'domain3' }, - ] - - const domain1: DescribeDomainResponse = { DomainId: 'domain1', DomainName: 'domainName1' } - const domain2: DescribeDomainResponse = { DomainId: 'domain2', DomainName: 'domainName2' } - const domain3: DescribeDomainResponse = { - DomainId: 'domain3', - DomainName: 'domainName3', - DomainSettings: { UnifiedStudioSettings: { DomainId: 'unifiedStudioDomain1' } }, - } - - beforeEach(function () { - client = new SagemakerClient(region) - - listAppsStub = sinon.stub(client, 'listApps').returns(intoCollection([appDetails])) - sinon.stub(client, 'listSpaces').returns(intoCollection([spaceDetails])) - sinon.stub(client, 'describeDomain').callsFake(async ({ DomainId }) => { - switch (DomainId) { - case 'domain1': - return domain1 as DescribeDomainCommandOutput - case 'domain2': - return domain2 as DescribeDomainCommandOutput - case 'domain3': - return domain3 as DescribeDomainCommandOutput - default: - return {} as DescribeDomainCommandOutput - } - }) - }) - - afterEach(function () { - sinon.restore() - }) - - it('returns a map of space details with corresponding app details', async function () { - const [spaceApps, domains] = await client.fetchSpaceAppsAndDomains() - - assert.strictEqual(spaceApps.size, 3) - assert.strictEqual(domains.size, 3) - - const spaceAppKey1 = 'domain1__space1' - const spaceAppKey2 = 'domain2__space2' - const spaceAppKey3 = 'domain2__space3' - - assert.ok(spaceApps.has(spaceAppKey1), 'Expected spaceApps to have key for domain1__space1') - assert.ok(spaceApps.has(spaceAppKey2), 'Expected spaceApps to have key for domain2__space2') - assert.ok(spaceApps.has(spaceAppKey3), 'Expected spaceApps to have key for domain2__space3') - - assert.deepStrictEqual(spaceApps.get(spaceAppKey1)?.App?.AppName, 'app1') - assert.deepStrictEqual(spaceApps.get(spaceAppKey2)?.App?.AppName, 'app2') - assert.deepStrictEqual(spaceApps.get(spaceAppKey3)?.App?.AppName, 'app3') - - const domainKey1 = 'domain1' - const domainKey2 = 'domain2' - - assert.ok(domains.has(domainKey1), 'Expected domains to have key for domain1') - assert.ok(domains.has(domainKey2), 'Expected domains to have key for domain2') - - assert.deepStrictEqual(domains.get(domainKey1)?.DomainName, 'domainName1') - assert.deepStrictEqual(domains.get(domainKey2)?.DomainName, 'domainName2') - }) - - it('returns map even if some spaces have no matching apps', async function () { - listAppsStub.returns(intoCollection([{ AppName: 'app1', DomainId: 'domain1', SpaceName: 'space1' }])) - - const [spaceApps] = await client.fetchSpaceAppsAndDomains() - - const spaceAppKey2 = 'domain2__space2' - const spaceAppKey3 = 'domain2__space3' - - assert.strictEqual(spaceApps.size, 3) - assert.strictEqual(spaceApps.get(spaceAppKey2)?.App, undefined) - assert.strictEqual(spaceApps.get(spaceAppKey3)?.App, undefined) - }) - - it('filters out unified studio domains when filterSmusDomains is true', async function () { - const [spaceApps] = await client.fetchSpaceAppsAndDomains(undefined, true) - - assert.strictEqual(spaceApps.size, 3) - assert.ok(!spaceApps.has('domain3__space4')) - }) - - it('includes unified studio domains when filterSmusDomains is false', async function () { - const [spaceApps] = await client.fetchSpaceAppsAndDomains(undefined, false) - - assert.strictEqual(spaceApps.size, 4) - assert.ok(spaceApps.has('domain3__space4')) - }) - - it('handles AccessDeniedException and shows error message', async function () { - sinon.stub(client, 'listSpaceApps').rejects({ name: 'AccessDeniedException' }) - - await assert.rejects(client.fetchSpaceAppsAndDomains()) - - const messages = getTestWindow().shownMessages - assert.ok(messages.some((m) => m.message.includes('AccessDeniedException'))) - }) -}) - -describe('SagemakerClient.listSpaceApps', function () { - const region = 'test-region' - let client: SagemakerClient - - const appDetails: AppDetails[] = [ - { AppName: 'app1', DomainId: 'domain1', SpaceName: 'space1', AppType: AppType.CodeEditor }, - { AppName: 'app2', DomainId: 'domain2', SpaceName: 'space2', AppType: AppType.JupyterLab }, - { AppName: 'app3', DomainId: 'domain2', SpaceName: 'space3', AppType: 'Studio' as any }, - ] - - const spaceDetails: SpaceDetails[] = [ - { SpaceName: 'space1', DomainId: 'domain1' }, - { SpaceName: 'space2', DomainId: 'domain2' }, - { SpaceName: 'space3', DomainId: 'domain2' }, - ] - - beforeEach(function () { - client = new SagemakerClient(region) - sinon.stub(client, 'listApps').returns(intoCollection([appDetails])) - sinon.stub(client, 'listSpaces').returns(intoCollection([spaceDetails])) - }) - - afterEach(function () { - sinon.restore() - }) - - it('returns space apps with correct mapping', async function () { - const spaceApps = await client.listSpaceApps() - - assert.strictEqual(spaceApps.size, 3) - assert.strictEqual(spaceApps.get('domain1__space1')?.App?.AppName, 'app1') - assert.strictEqual(spaceApps.get('domain2__space2')?.App?.AppName, 'app2') - assert.strictEqual(spaceApps.get('domain2__space3')?.App, undefined) // Studio app filtered out - }) - - it('filters by domain when domainId provided', async function () { - const newClient = new SagemakerClient(region) - const listAppsStub = sinon.stub(newClient, 'listApps').returns(intoCollection([])) - const listSpacesStub = sinon.stub(newClient, 'listSpaces').returns(intoCollection([])) - - await newClient.listSpaceApps('domain1') - - sinon.assert.calledWith(listAppsStub, { DomainIdEquals: 'domain1' }) - sinon.assert.calledWith(listSpacesStub, { DomainIdEquals: 'domain1' }) - }) -}) - -describe('SagemakerClient.listAppForSpace', function () { - const region = 'test-region' - let client: SagemakerClient - let listAppsStub: sinon.SinonStub - - beforeEach(function () { - client = new SagemakerClient(region) - listAppsStub = sinon.stub(client, 'listApps') - }) - - afterEach(function () { - sinon.restore() - }) - - it('returns first app for given domain and space', async function () { - const appDetails: AppDetails[] = [ - { AppName: 'app1', DomainId: 'domain1', SpaceName: 'space1', AppType: AppType.CodeEditor }, - ] - listAppsStub.returns(intoCollection([appDetails])) - - const result = await client.listAppForSpace('domain1', 'space1') - - assert.strictEqual(result?.AppName, 'app1') - sinon.assert.calledWith(listAppsStub, { DomainIdEquals: 'domain1', SpaceNameEquals: 'space1' }) - }) - - it('returns undefined when no apps found', async function () { - listAppsStub.returns(intoCollection([[]])) - - const result = await client.listAppForSpace('domain1', 'space1') - - assert.strictEqual(result, undefined) - }) -}) - -describe('SagemakerClient.waitForAppInService', function () { - const region = 'test-region' - let client: SagemakerClient - let describeAppStub: sinon.SinonStub - - beforeEach(function () { - client = new SagemakerClient(region) - describeAppStub = sinon.stub(client, 'describeApp') - }) - - afterEach(function () { - sinon.restore() - }) - - it('resolves when app reaches InService status', async function () { - describeAppStub.resolves({ Status: 'InService' }) - - await client.waitForAppInService('domain1', 'space1', 'CodeEditor') - - sinon.assert.calledOnce(describeAppStub) - }) - - it('throws error when app status is Failed', async function () { - describeAppStub.resolves({ Status: 'Failed' }) - - await assert.rejects( - client.waitForAppInService('domain1', 'space1', 'CodeEditor'), - /App failed to start. Status: Failed/ - ) - }) - - it('throws error when app status is DeleteFailed', async function () { - describeAppStub.resolves({ Status: 'DeleteFailed' }) - - await assert.rejects( - client.waitForAppInService('domain1', 'space1', 'CodeEditor'), - /App failed to start. Status: DeleteFailed/ - ) - }) - - it('times out after max retries', async function () { - describeAppStub.resolves({ Status: 'Pending' }) - - const sagemakerModule = await import('../../../shared/clients/sagemaker.js') - const originalValue = sagemakerModule.waitForAppConfig.hardTimeoutRetries - sagemakerModule.waitForAppConfig.hardTimeoutRetries = 3 - - try { - await assert.rejects( - client.waitForAppInService('domain1', 'space1', 'CodeEditor'), - /Timed out waiting for app/ - ) - } finally { - sagemakerModule.waitForAppConfig.hardTimeoutRetries = originalValue - } - }) -}) - -describe('SagemakerClient.startSpace', function () { - const region = 'test-region' - let client: SagemakerClient - let describeSpaceStub: sinon.SinonStub - let updateSpaceStub: sinon.SinonStub - let waitForSpaceStub: sinon.SinonStub - let createAppStub: sinon.SinonStub - - beforeEach(function () { - client = new SagemakerClient(region) - describeSpaceStub = sinon.stub(client, 'describeSpace') - updateSpaceStub = sinon.stub(client, 'updateSpace') - waitForSpaceStub = sinon.stub(client as any, 'waitForSpaceInService') - createAppStub = sinon.stub(client, 'createApp') - }) - - afterEach(function () { - sinon.restore() - }) - - it('enables remote access and starts the app', async function () { - describeSpaceStub.resolves({ - SpaceSettings: { - RemoteAccess: 'DISABLED', - AppType: 'CodeEditor', - CodeEditorAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', - SageMakerImageArn: 'arn:aws:sagemaker:us-west-2:img', - SageMakerImageVersionAlias: '1.0.0', - }, - }, - }, - }) - - updateSpaceStub.resolves({}) - waitForSpaceStub.resolves() - createAppStub.resolves({}) - - await client.startSpace('my-space', 'my-domain') - - sinon.assert.calledOnce(updateSpaceStub) - sinon.assert.calledOnce(waitForSpaceStub) - sinon.assert.calledOnce(createAppStub) - }) - - it('skips enabling remote access if already enabled', async function () { - describeSpaceStub.resolves({ - SpaceSettings: { - RemoteAccess: 'ENABLED', - AppType: 'CodeEditor', - CodeEditorAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', - SageMakerImageArn: 'arn:aws:sagemaker:us-west-2:img', - SageMakerImageVersionAlias: '1.0.0', - }, - }, - }, - }) - - createAppStub.resolves({}) - - await client.startSpace('my-space', 'my-domain') - - sinon.assert.notCalled(updateSpaceStub) - sinon.assert.notCalled(waitForSpaceStub) - sinon.assert.calledOnce(createAppStub) - }) - - it('throws error on unsupported app type', async function () { - describeSpaceStub.resolves({ - SpaceSettings: { - RemoteAccess: 'ENABLED', - AppType: 'Studio', - }, - }) - - await assert.rejects(client.startSpace('my-space', 'my-domain'), /Unsupported AppType "Studio"/) - }) - - it('uses fallback resource spec when none provided', async function () { - describeSpaceStub.resolves({ - SpaceSettings: { - RemoteAccess: 'ENABLED', - AppType: 'JupyterLab', - JupyterLabAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.large', - }, - }, - }, - }) - - createAppStub.resolves({}) - - await client.startSpace('my-space', 'my-domain') - - sinon.assert.calledOnceWithExactly( - createAppStub, - sinon.match.hasNested('ResourceSpec', { - InstanceType: 'ml.t3.large', - SageMakerImageArn: 'arn:aws:sagemaker:us-west-2:542918446943:image/sagemaker-distribution-cpu', - SageMakerImageVersionAlias: '3.2.0', - }) - ) - }) - - it('handles AccessDeniedException gracefully', async function () { - describeSpaceStub.rejects({ name: 'AccessDeniedException', message: 'no access' }) - - await assert.rejects(client.startSpace('my-space', 'my-domain'), /You do not have permission to start spaces/) - }) - - it('prompts user for insufficient memory instance type', async function () { - describeSpaceStub.resolves({ - SpaceName: 'my-space', - SpaceSettings: { - RemoteAccess: 'ENABLED', - AppType: 'CodeEditor', - CodeEditorAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.medium', // Insufficient memory type - }, - }, - }, - }) - - createAppStub.resolves({}) - - const promise = client.startSpace('my-space', 'my-domain') - - // Wait for the error message to appear and select "Restart Space and Connect" - const expectedMessage = InstanceTypeInsufficientMemoryMessage('my-space', 'ml.t3.medium', 'ml.t3.large') - await getTestWindow().waitForMessage(new RegExp(expectedMessage.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) - getTestWindow().getFirstMessage().selectItem('Restart Space and Connect') - - await promise - sinon.assert.calledOnce(updateSpaceStub) - sinon.assert.calledOnce(createAppStub) - }) - - it('throws error when user declines insufficient memory upgrade', async function () { - describeSpaceStub.resolves({ - SpaceName: 'my-space', - SpaceSettings: { - RemoteAccess: 'ENABLED', - AppType: 'CodeEditor', - CodeEditorAppSettings: { - DefaultResourceSpec: { - InstanceType: 'ml.t3.medium', - }, - }, - }, - }) - - const promise = client.startSpace('my-space', 'my-domain') - - // Wait for the error message to appear and select "Cancel" - const expectedMessage = InstanceTypeInsufficientMemoryMessage('my-space', 'ml.t3.medium', 'ml.t3.large') - await getTestWindow().waitForMessage(new RegExp(expectedMessage.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))) - getTestWindow().getFirstMessage().selectItem('Cancel') - - await assert.rejects(promise, (err: ToolkitError) => err.message === 'InstanceType has insufficient memory.') - }) -}) diff --git a/packages/core/src/test/shared/codelens/codeLensUtils.test.ts b/packages/core/src/test/shared/codelens/codeLensUtils.test.ts deleted file mode 100644 index 55bde675fe..0000000000 --- a/packages/core/src/test/shared/codelens/codeLensUtils.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import * as path from 'path' -import * as codeLensUtils from '../../../shared/codelens/codeLensUtils' -import * as Picker from '../../../shared/ui/picker' -import { - API_TARGET_TYPE, - CODE_TARGET_TYPE, - TEMPLATE_TARGET_TYPE, -} from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import * as AddSamDebugConfiguration from '../../../shared/sam/debugger/commands/addSamDebugConfiguration' - -describe('codeLensUtils', async function () { - let sandbox: sinon.SinonSandbox - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - describe('invokeCodeLensCommandPalette', async function () { - const range1: vscode.Range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(0, 1)) - const range2: vscode.Range = new vscode.Range(new vscode.Position(1, 0), new vscode.Position(1, 1)) - const doc: Pick = { - getText: (range: vscode.Range) => { - if (range.start.line === 0) { - return 'one' - } else if (range.start.line === 1) { - return 'two' - } - - return 'other' - }, - } - - it('filters out launch configs leading to the invoker UI', async function () { - sandbox.stub(Picker, 'promptUser').resolves(undefined) - const spy = sandbox.spy(Picker, 'createQuickPick') - await codeLensUtils.invokeCodeLensCommandPalette(doc, [ - { - isResolved: true, - range: range1, - command: { - command: 'foo', - title: 'hasUI', - arguments: ['foo', 'bar', true], - }, - }, - { - isResolved: true, - range: range2, - command: { - command: 'foo', - title: 'noUI', - arguments: ['foo', 'bar', false], - }, - }, - ]) - assert.ok(spy.calledOnce, 'Spy called more than once') - assert.strictEqual(spy.args[0][0].items?.length, 1, 'createQuickPick called with multiple items') - assert.deepStrictEqual( - spy.args[0][0].items![0], - { - label: 'two', - detail: 'Function on line 2', - lens: { - command: { - title: 'noUI', - arguments: ['foo', 'bar', false], - command: 'foo', - }, - range: range2, - isResolved: true, - }, - }, - 'createQuickPick called with incorrect item' - ) - }) - - it('provides a single quick pick item with no codelens if no codelenses are provided', async function () { - sandbox.stub(Picker, 'promptUser').resolves(undefined) - const spy = sandbox.spy(Picker, 'createQuickPick') - await codeLensUtils.invokeCodeLensCommandPalette(doc, []) - assert.ok(spy.calledOnce, 'Spy called more than once') - assert.strictEqual(spy.args[0][0].items?.length, 1, 'createQuickPick called with multiple items') - assert.deepStrictEqual( - spy.args[0][0].items![0], - { - label: 'No handlers found in current file', - detail: 'Ensure your language extension is working', - description: 'Click here to go back', - }, - 'createQuickPick called with incorrect item' - ) - }) - - it('returns undefined if no value or an invalid value is chosen', async function () { - sandbox - .stub(Picker, 'promptUser') - .onFirstCall() - .resolves(undefined) - .onSecondCall() - .resolves([ - { - label: 'noLens', - detail: 'noLens', - }, - ]) - .onThirdCall() - .resolves([ - { - label: 'noCommand', - detail: 'noCommand', - }, - ]) - const createSpy = sandbox.spy(Picker, 'createQuickPick') - const finalSpy = sandbox.spy(codeLensUtils, 'pickAddSamDebugConfiguration') - await codeLensUtils.invokeCodeLensCommandPalette( - doc, - [ - /* stub handles all pick logic */ - ], - finalSpy - ) - await codeLensUtils.invokeCodeLensCommandPalette( - doc, - [ - /* stub handles all pick logic */ - ], - finalSpy - ) - await codeLensUtils.invokeCodeLensCommandPalette( - doc, - [ - /* stub handles all pick logic */ - ], - finalSpy - ) - - assert.ok(createSpy.calledThrice, 'Not all test payloads run') - assert.ok(finalSpy.notCalled, 'pickAddSamDebugConfiguration called; function did not return undefined') - }) - - it('can pass valid codelens contents to pickAddSamDebugConfiguration', async function () { - const arg0: AddSamDebugConfiguration.AddSamDebugConfigurationInput = { - resourceName: 'foo', - rootUri: vscode.Uri.parse('file:///asdf'), - } - const arg1: AddSamDebugConfiguration.AddSamDebugConfigurationInput[] = [] - const arg2 = false - const target = { - label: 'two', - detail: 'Function on line 2', - lens: { - command: { - title: 'noUI', - arguments: [arg0, arg1, arg2], - command: 'foo', - }, - range: range2, - isResolved: true, - }, - } - sandbox - .stub(Picker, 'promptUser') - .onFirstCall() - .resolves([target as any]) - const finalStub = sandbox - .stub(codeLensUtils, 'pickAddSamDebugConfiguration') - .onFirstCall() - .resolves(undefined) - - await codeLensUtils.invokeCodeLensCommandPalette( - doc, - [ - /* stub handles all pick logic */ - ], - finalStub - ) - - assert.ok(finalStub.calledOnce, 'pickAddSamDebugConfiguration not called once and only once') - assert.ok(finalStub.calledWith(arg0, arg1, arg2), 'pickAddSamDebugConfiguration called with incorrect args') - }) - }) - - describe('pickAddSamDebugConfiguration', function () { - it('should use CODE_TARGET_TYPE with no templateConfigs', async function () { - const addSamStub = sandbox.stub(AddSamDebugConfiguration, 'addSamDebugConfiguration') - const codeConfig: AddSamDebugConfiguration.AddSamDebugConfigurationInput = { - resourceName: 'codeResource', - rootUri: vscode.Uri.file('path'), - } - const templateConfigs: AddSamDebugConfiguration.AddSamDebugConfigurationInput[] = [] - - await codeLensUtils.pickAddSamDebugConfiguration(codeConfig, templateConfigs, false) - - assert.strictEqual(addSamStub.calledOnceWith(codeConfig, CODE_TARGET_TYPE, false), true) - }) - - it('should use CODE_TARGET_TYPE when no template option is chosen', async function () { - sandbox.stub(Picker, 'promptUser').resolves(undefined) - sandbox.stub(Picker, 'verifySinglePickerOutput').returns({ label: 'No Template' }) - const addSamStub = sandbox.stub(AddSamDebugConfiguration, 'addSamDebugConfiguration').resolves() - const codeConfig: AddSamDebugConfiguration.AddSamDebugConfigurationInput = { - resourceName: 'codeResource', - rootUri: vscode.Uri.file('path'), - } - const templateConfigs: AddSamDebugConfiguration.AddSamDebugConfigurationInput[] = [ - { resourceName: 'templateNoApi', rootUri: vscode.Uri.file('path') }, - ] - - await codeLensUtils.pickAddSamDebugConfiguration(codeConfig, templateConfigs, false) - - assert.strictEqual(addSamStub.calledOnceWith(codeConfig, CODE_TARGET_TYPE, false), true) - }) - - it('should use API_TARGET_TYPE when API template option is chosen', async function () { - sandbox.stub(Picker, 'promptUser').resolves(undefined) - sandbox - .stub(Picker, 'verifySinglePickerOutput') - .returns({ label: `${path.sep}path:templateWithApi (API Event: eventName)` }) - const addSamStub = sandbox.stub(AddSamDebugConfiguration, 'addSamDebugConfiguration').resolves() - const codeConfig: AddSamDebugConfiguration.AddSamDebugConfigurationInput = { - resourceName: 'codeResource', - rootUri: vscode.Uri.file('path'), - } - const templateConfigs: AddSamDebugConfiguration.AddSamDebugConfigurationInput[] = [ - { - resourceName: 'templateWithApi', - rootUri: vscode.Uri.file('path'), - apiEvent: { name: 'eventName', event: { Type: 'Api' } }, - }, - ] - - await codeLensUtils.pickAddSamDebugConfiguration(codeConfig, templateConfigs, false) - - assert.strictEqual(addSamStub.args[0][1], API_TARGET_TYPE) - }) - - it('should use TEMPLATE_TARGET_TYPE when non API template option is chosen', async function () { - sandbox.stub(Picker, 'promptUser').resolves(undefined) - sandbox.stub(Picker, 'verifySinglePickerOutput').returns({ label: `${path.sep}path:templateNoApi` }) - const addSamStub = sandbox.stub(AddSamDebugConfiguration, 'addSamDebugConfiguration').resolves() - const codeConfig: AddSamDebugConfiguration.AddSamDebugConfigurationInput = { - resourceName: 'codeResource', - rootUri: vscode.Uri.file('path'), - } - const templateConfigs: AddSamDebugConfiguration.AddSamDebugConfigurationInput[] = [ - { resourceName: 'templateNoApi', rootUri: vscode.Uri.file('path') }, - ] - - await codeLensUtils.pickAddSamDebugConfiguration(codeConfig, templateConfigs, false) - - assert.strictEqual(addSamStub.args[0][1], TEMPLATE_TARGET_TYPE) - }) - }) -}) diff --git a/packages/core/src/test/shared/codelens/csharpCodeLensProvider.test.ts b/packages/core/src/test/shared/codelens/csharpCodeLensProvider.test.ts deleted file mode 100644 index 86db6f5479..0000000000 --- a/packages/core/src/test/shared/codelens/csharpCodeLensProvider.test.ts +++ /dev/null @@ -1,311 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import * as path from 'path' -import * as vscode from 'vscode' -import * as sampleDotNetSamProgram from './sampleDotNetSamProgram' - -import { - DotNetLambdaHandlerComponents, - generateDotNetLambdaHandler, - getLambdaHandlerComponents, - isPublicClassSymbol, - isValidLambdaHandler, -} from '../../../shared/codelens/csharpCodeLensProvider' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { fs } from '../../../shared' - -const fakeRange = new vscode.Range(0, 0, 0, 0) - -describe('getLambdaHandlerComponents', async function () { - let tempFolder: string - - beforeEach(async function () { - // Make a temp folder for all these tests - tempFolder = await makeTemporaryToolkitFolder() - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('Detects a public function symbol', async function () { - const programFile = path.join(tempFolder, 'program.cs') - await fs.writeFile(programFile, sampleDotNetSamProgram.getFunctionText()) - - const textDoc = await vscode.workspace.openTextDocument(programFile) - const documentSymbols = sampleDotNetSamProgram.getDocumentSymbols() - const assembly = 'myAssembly' - - const componentsArray = getLambdaHandlerComponents(textDoc, documentSymbols, assembly) - - assert.ok(componentsArray) - assert.strictEqual(componentsArray.length, 1, 'Expected only one set of Lambda Handler components') - const components = componentsArray[0] - assert.strictEqual(components.assembly, 'myAssembly', 'Unexpected Lambda Handler assembly') - assert.strictEqual(components.namespace, 'HelloWorld', 'Unexpected Lambda Handler Namespace') - assert.strictEqual(components.class, 'Function', 'Unexpected Lambda Handler Class') - assert.strictEqual(components.method, 'FunctionHandler', 'Unexpected Lambda Handler Function') - }) -}) - -describe('isPublicClassSymbol', async function () { - const sampleClassSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'HelloWorld.Function', - '', - vscode.SymbolKind.Class, - fakeRange, - fakeRange - ) - - it('returns true for a public class', async function () { - const doc = { - getText: (range?: vscode.Range): string => { - return 'public class Function {}' - }, - } - - const isPublic = isPublicClassSymbol(doc, sampleClassSymbol) - assert.strictEqual(isPublic, true, 'Expected symbol to be a public class') - }) - - it('returns false when symbol is not of type Class', async function () { - const symbol = new vscode.DocumentSymbol( - sampleClassSymbol.name, - sampleClassSymbol.detail, - vscode.SymbolKind.Method, - sampleClassSymbol.range, - sampleClassSymbol.selectionRange - ) - - const doc = { - getText: (range?: vscode.Range): string => { - return 'public class Function {}' - }, - } - - const isPublic = isPublicClassSymbol(doc, symbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public class') - }) - - it('returns false when class is not public', async function () { - const doc = { - getText: (range?: vscode.Range): string => 'private class ', - } - - const isPublic = isPublicClassSymbol(doc, sampleClassSymbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public class') - }) -}) - -describe('isPublicMethodSymbol', async function () { - const validPublicMethodTests = [ - { - scenario: 'signature all on one line', - functionHandler: generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'ILambdaContext'), - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'signature across many lines', - functionHandler: generateFunctionHandler( - 'FunctionHandler', - 'APIGatewayProxyResponse', - 'ILambdaContext', - true - ), - functionSignatureParams: { access: 'public', beforeFunctionName: true, afterSignature: true }, - }, - { - scenario: 'method name on another line', - functionHandler: generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'ILambdaContext'), - functionSignatureParams: { access: 'public', beforeFunctionName: true }, - }, - { - scenario: 'args on many lines', - functionHandler: generateFunctionHandler( - 'FunctionHandler', - 'APIGatewayProxyResponse', - 'ILambdaContext', - true - ), - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'first arg is generic', - functionHandler: generateFunctionHandler( - 'FunctionHandler', - 'APIGatewayProxyResponse', - 'ILambdaContext' - ), - functionSignatureParams: { access: 'public' }, - }, - ] - - for (const test of validPublicMethodTests) { - const sampleMethodSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)', - '', - vscode.SymbolKind.Method, - fakeRange, - fakeRange - ) - it(`returns true for a public method symbol when ${test.scenario}`, async () => { - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature(test.functionHandler, test.functionSignatureParams) - ), - } - - const isPublic = isValidLambdaHandler(doc, sampleMethodSymbol) - assert.strictEqual(isPublic, true, 'Expected symbol to be a public method') - }) - } - - it('returns false for a symbol that is not a method', async function () { - const symbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)', - '', - vscode.SymbolKind.Class, - fakeRange, - fakeRange - ) - - const doc = { - getText: (range?: vscode.Range): string => { - throw new Error('getText is unused') - }, - } - - const isPublic = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public method') - }) - - it('returns false when the method is not public', async function () { - const handler = generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'ILambdaContext') - const symbol = new vscode.DocumentSymbol(handler, '', vscode.SymbolKind.Method, fakeRange, fakeRange) - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature( - generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'ILambdaContext'), - { access: 'private' } - ) - ), - } - - const isPublic = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public method') - }) - - it('returns false when a private method name contains the word public in it', async function () { - const symbol = new vscode.DocumentSymbol('notpublicmethod', '', vscode.SymbolKind.Method, fakeRange, fakeRange) - - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature( - generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'ILambdaContext'), - { access: 'private' } - ) - ), - } - - const isPublic = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public method') - }) - - it('returns false when the second parameter is not an ILambdaContext', async function () { - const symbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, string context)', - '', - vscode.SymbolKind.Method, - fakeRange, - fakeRange - ) - - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature( - generateFunctionHandler('FunctionHandler', 'APIGatewayProxyResponse', 'string'), - { access: 'public' } - ) - ), - } - - const isPublic = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isPublic, false, 'Expected symbol not to be a public method') - }) - - /** - * Simulates the contents of a TextDocument that corresponds to a Method Symbol's range - */ - function generateFunctionDeclaration(functionSignature: string): string { - return `${functionSignature} - { - string location = GetCallingIP().Result; - Dictionary body = new Dictionary - { - { "message", "hello world" }, - { "location", location }, - }; - - return new APIGatewayProxyResponse - { - Body = JsonConvert.SerializeObject(body), - StatusCode = 200, - Headers = new Dictionary { { "Content-Type", "application/json" } } - }; - } -` - } - - /** - * Simulates the Function signature portion of the contents of a TextDocument that corresponds - * to a Method Symbol's range. Used with generateFunctionDeclaration. - */ - function generateFunctionSignature( - functionHandler: string, - params: { - access: string - beforeFunctionName?: boolean - afterSignature?: boolean - } - ): string { - const beforeFunctionText = params.beforeFunctionName ? os.EOL : '' - const afterSignatureText = params.afterSignature ? os.EOL : '' - - return `${params.access} APIGatewayProxyResponse ${beforeFunctionText}${functionHandler}${afterSignatureText}` - } - - function generateFunctionHandler( - functionName: string, - param1: string, - param2: string, - beforeArgument: boolean = false - ): string { - const beforeArgumentText = beforeArgument ? os.EOL : '' - - return `${functionName}(${beforeArgumentText}${param1} apigProxyEvent, ${beforeArgumentText}${param2} context)` - } -}) - -describe('generateDotNetLambdaHandler', async function () { - it('produces a handler name', async function () { - const components: DotNetLambdaHandlerComponents = { - assembly: 'myAssembly', - namespace: 'myNamespace', - class: 'myClass', - method: 'foo', - handlerRange: undefined!, - } - - const handlerName = generateDotNetLambdaHandler(components) - assert.strictEqual(handlerName, 'myAssembly::myNamespace.myClass::foo', 'Handler name mismatch') - }) -}) diff --git a/packages/core/src/test/shared/codelens/goCodeLensProvider.test.ts b/packages/core/src/test/shared/codelens/goCodeLensProvider.test.ts deleted file mode 100644 index 4c828249a4..0000000000 --- a/packages/core/src/test/shared/codelens/goCodeLensProvider.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' - -import { isValidFuncSignature } from '../../../shared/codelens/goCodeLensProvider' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { fs } from '../../../shared' - -describe('getLambdaHandlerCandidates', async function () { - let tempFolder: string - let programFile: string - let dummyMod: string - - before(async function () { - // Make a temp folder for all these tests - tempFolder = await makeTemporaryToolkitFolder('golenstest') - programFile = path.join(tempFolder, 'program.go') - dummyMod = path.join(tempFolder, 'go.mod') - - await fs.writeFile(programFile, getFunctionText()) - await fs.writeFile(dummyMod, 'require github.com/aws/aws-lambda-go v1.13.3\nmodule hello-world\ngo 1.14') - }) - - after(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('Detects only good function symbols from a mock program', async function () { - const textDoc: vscode.TextDocument = await vscode.workspace.openTextDocument(programFile) - const candidates: vscode.DocumentSymbol[] = getDocumentSymbols().filter((symbol) => - isValidFuncSignature(textDoc, symbol) - ) - - assert.ok(candidates) - assert.strictEqual(candidates.length, 2, 'Expected two Lambda Handler components') - assert.strictEqual(candidates[0].name, 'handler', 'Unexpected handler name') - assert.strictEqual(candidates[1].name, 'multiLine', 'Unexpected handler name') - }) -}) - -/** - * Generates DocumentSymbols that would be generated by the Go language server. - */ -export function getDocumentSymbols(): vscode.DocumentSymbol[] { - const symbols: vscode.DocumentSymbol[] = [] - - const badFuncSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'notAHandler', - '()', - vscode.SymbolKind.Function, - new vscode.Range(38, 4, 41, 4), - new vscode.Range(38, 9, 38, 20) - ) - symbols.push(badFuncSymbol) - - const goodFuncSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'handler', - '(request events.APIGatewayProxyRequest)', - vscode.SymbolKind.Function, - new vscode.Range(23, 0, 51, 0), - new vscode.Range(23, 5, 23, 12) - ) - symbols.push(goodFuncSymbol) - - const mainSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'main', - '()', - vscode.SymbolKind.Function, - new vscode.Range(52, 0, 55, 0), - new vscode.Range(52, 5, 52, 9) - ) - symbols.push(mainSymbol) - - const manyArgsSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'tooManyArgs', - '(a, b, c int)', - vscode.SymbolKind.Function, - new vscode.Range(56, 0, 59, 0), - new vscode.Range(56, 5, 56, 16) - ) - symbols.push(manyArgsSymbol) - - const multiLineSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'multiLine', - '(a int, b string)', - vscode.SymbolKind.Function, - new vscode.Range(60, 0, 64, 0), - new vscode.Range(60, 5, 60, 14) - ) - symbols.push(multiLineSymbol) - - const manyReturnSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'tooManyReturns', - '(a int, b int)', - vscode.SymbolKind.Function, - new vscode.Range(65, 0, 68, 0), - new vscode.Range(65, 5, 65, 19) - ) - symbols.push(manyReturnSymbol) - - return symbols -} - -/** - * Emits file contents from the stock SAM CLI app for Go with some additional functions to test. - */ -export function getFunctionText(): string { - return String.raw` -package main - -import ( - "errors" - "fmt" - "io/ioutil" - "net/http" - - "github.com/aws/aws-lambda-go/events" - "github.com/aws/aws-lambda-go/lambda" -) - -var ( - // DefaultHTTPGetAddress Default Address - DefaultHTTPGetAddress = "https://checkip.amazonaws.com" - - // ErrNoIP No IP found in response - ErrNoIP = errors.New("No IP in HTTP response") - - // ErrNon200Response non 200 status code in response - ErrNon200Response = errors.New("Non 200 Response found") -) - -func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { - resp, err := http.Get(DefaultHTTPGetAddress) - if err != nil { - return events.APIGatewayProxyResponse{}, err - } - - if resp.StatusCode != 200 { - return events.APIGatewayProxyResponse{}, ErrNon200Response - } - - ip, err := ioutil.ReadAll(resp.Body) - if err != nil { - return events.APIGatewayProxyResponse{}, err - } - - func notAHandler() int { - return 0 - } - - if len(ip) == 0 { - return events.APIGatewayProxyResponse{}, ErrNoIP - } - - return events.APIGatewayProxyResponse{ - Body: fmt.Sprintf("Hello, %v", string(ip)), - StatusCode: 200, - }, nil -} - -func main() { - lambda.Start(handler) -} - -func tooManyArgs(a, b, c int) { - -} - -func multiLine(c Context, // this parameter is a context - s string) /* this parameter is a string */ error { - return 1 -} - -func tooManyReturns(a int, b int) (x, y, z string) { - -} -` -} diff --git a/packages/core/src/test/shared/codelens/javaCodeLensProvider.test.ts b/packages/core/src/test/shared/codelens/javaCodeLensProvider.test.ts deleted file mode 100644 index f7d42d516b..0000000000 --- a/packages/core/src/test/shared/codelens/javaCodeLensProvider.test.ts +++ /dev/null @@ -1,453 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import * as path from 'path' -import * as vscode from 'vscode' -import { - generateJavaLambdaHandler, - getLambdaHandlerComponents, - isValidClassSymbol, - isValidLambdaHandler, - JavaLambdaHandlerComponents, -} from '../../../shared/codelens/javaCodeLensProvider' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import * as SampleJavaSamProgram from './sampleJavaSamProgram' -import { fs } from '../../../shared' - -const fakeRange = new vscode.Range(0, 0, 0, 0) - -describe('javaCodeLensProvider', () => { - describe('getLambdaHandlerComponents', () => { - let tempFolder: string - - beforeEach(async () => { - // Make a temp folder for all these tests - tempFolder = await makeTemporaryToolkitFolder() - }) - - afterEach(async () => { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('Detects a public function symbol', async function () { - const programFile = path.join(tempFolder, 'App.java') - await fs.writeFile(programFile, SampleJavaSamProgram.getFunctionText()) - - const textDoc = await vscode.workspace.openTextDocument(programFile) - const documentSymbols = SampleJavaSamProgram.getDocumentSymbols() - - const componentsArray = getLambdaHandlerComponents(textDoc, documentSymbols) - - assert.ok(componentsArray) - assert.strictEqual(componentsArray.length, 1, 'Expected only one set of Lambda Handler components') - const components = componentsArray[0] - assert.strictEqual(components.package, 'helloworld', 'Unexpected Lambda Handler Package') - assert.strictEqual(components.class, 'App', 'Unexpected Lambda Handler Class') - assert.strictEqual(components.method, 'handleRequest', 'Unexpected Lambda Handler Function') - }) - }) - - describe('isValidClassSymbol', () => { - const sampleClassSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'HelloWorld.Function', - '', - vscode.SymbolKind.Class, - fakeRange, - fakeRange - ) - - it('returns true for a public class', async function () { - const doc = { - getText: (range?: vscode.Range): string => { - return 'public class Function {}' - }, - } - - assert.strictEqual(isValidClassSymbol(doc, sampleClassSymbol), true, 'Expected symbol to be a public class') - }) - - it('returns false when symbol is not of type Class', async function () { - const symbol = new vscode.DocumentSymbol( - sampleClassSymbol.name, - sampleClassSymbol.detail, - vscode.SymbolKind.Method, - sampleClassSymbol.range, - sampleClassSymbol.selectionRange - ) - - const doc = { - getText: (range?: vscode.Range): string => { - return 'public method Function {}' - }, - } - - assert.strictEqual(isValidClassSymbol(doc, symbol), false, 'Expected symbol not to be a public class') - }) - - it('returns false when class is not public', async function () { - const doc = { - getText: (range?: vscode.Range): string => 'private class Function {}', - } - - assert.strictEqual( - isValidClassSymbol(doc, sampleClassSymbol), - false, - 'Expected symbol not to be a public class' - ) - }) - - it('returns false when class is abstract', async function () { - const doc = { - getText: (range?: vscode.Range): string => 'public abstract class Function {}', - } - - assert.strictEqual( - isValidClassSymbol(doc, sampleClassSymbol), - false, - 'Expected symbol not to be a public class' - ) - }) - }) - - describe('isValidMethodSignature/isValidLambdaHandler', () => { - const validPublicMethodTests = [ - { - scenario: 'signature all on one line', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ], - }, - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'signature across many lines', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public', beforeFunctionName: true, afterSignature: true }, - }, - { - scenario: 'method name on another line', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public', beforeFunctionName: true }, - }, - { - scenario: 'args on many lines', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'first arg is generic', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'Foo' }, - { name: 'ctx', type: 'Context' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'only one arg is present', - functionHandlerParams: { - name: 'FunctionHandler', - args: [{ name: 'param1', type: 'String' }], - beforeArg: true, - }, - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'an input and output stream are present with no context', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'OutputStream' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public' }, - }, - { - scenario: 'three params are present', - functionHandlerParams: { - name: 'FunctionHandler', - args: [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'OutputStream' }, - { name: 'ctx', type: 'Context' }, - ], - beforeArg: true, - }, - functionSignatureParams: { access: 'public' }, - }, - ] - - for (const test of validPublicMethodTests) { - const sampleMethodSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)', - '', - vscode.SymbolKind.Method, - fakeRange, - fakeRange - ) - it(`returns true for a public method symbol when ${test.scenario}`, async function () { - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature( - generateFunctionHandler( - test.functionHandlerParams.name, - test.functionHandlerParams.args, - test.functionHandlerParams.beforeArg - ), - test.functionSignatureParams - ) - ), - } - - const isValid = isValidLambdaHandler(doc, sampleMethodSymbol) - assert.strictEqual(isValid, true, 'Expected symbol to be a valid method') - }) - } - - it('returns false for a symbol that is not a method', async function () { - const symbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)', - '', - vscode.SymbolKind.Class, - fakeRange, - fakeRange - ) - - const doc = { - getText: (range?: vscode.Range): string => { - throw new Error('getText is unused') - }, - } - - const isValid = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isValid, false, 'Expected symbol not to be a public method') - }) - - it('returns false when the method is not public', async function () { - const handler = generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ]) - const symbol = new vscode.DocumentSymbol(handler, '', vscode.SymbolKind.Method, fakeRange, fakeRange) - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration(generateFunctionSignature(handler, { access: 'private' })), - } - - const isValid = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isValid, false, 'Expected symbol not to be a public method') - }) - - it('returns false when a private method name contains the word public in it', async function () { - const symbol = new vscode.DocumentSymbol( - 'notpublicmethod', - '', - vscode.SymbolKind.Method, - fakeRange, - fakeRange - ) - - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration( - generateFunctionSignature( - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'Context' }, - ]), - { access: 'private' } - ) - ), - } - - const isValid = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isValid, false, 'Expected symbol not to be a public method') - }) - - it('returns false when the second parameter is not a Context AND the params are not input and output streams', async function () { - failedHandlerRunner( - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'String' }, - { name: 'ctx', type: 'int' }, - ]) - ) - }) - - it('returns false when one param is a stream and the other is not', async function () { - const testHandlers = [ - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'int' }, - { name: 'param2', type: 'OutputStream' }, - ]), - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'int' }, - ]), - ] - for (const test of testHandlers) { - failedHandlerRunner(test) - } - }) - - it('returns false when any combination of three parameters is not two streams and a context', async function () { - const testHandlers = [ - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'OutputStream' }, - { name: 'ctx', type: 'int' }, - ]), - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'int' }, - { name: 'param2', type: 'OutputStream' }, - { name: 'ctx', type: 'Context' }, - ]), - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'int' }, - { name: 'ctx', type: 'Context' }, - ]), - ] - - for (const test of testHandlers) { - failedHandlerRunner(test) - } - }) - - it('returns false with more than three parameters', async function () { - failedHandlerRunner( - generateFunctionHandler('FunctionHandler', [ - { name: 'param1', type: 'InputStream' }, - { name: 'param2', type: 'OutputStream' }, - { name: 'ctx', type: 'Context' }, - { name: 'silly', type: 'Whatever' }, - ]) - ) - }) - - it('returns false with zero parameters', async function () { - failedHandlerRunner(generateFunctionHandler('FunctionHandler', [])) - }) - }) - - describe('generateJavaLambdaHandler', () => { - it('produces a handler name', async function () { - const components: JavaLambdaHandlerComponents = { - package: 'package', - class: 'myClass', - method: 'foo', - handlerRange: undefined!, - } - - const handlerName = generateJavaLambdaHandler(components) - assert.strictEqual(handlerName, 'package.myClass::foo', 'Handler name mismatch') - }) - }) - - function failedHandlerRunner(handler: string) { - const symbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - handler, - '', - vscode.SymbolKind.Method, - fakeRange, - fakeRange - ) - - const doc = { - getText: (range?: vscode.Range): string => - generateFunctionDeclaration(generateFunctionSignature(handler, { access: 'public' })), - } - - const isValid = isValidLambdaHandler(doc, symbol) - assert.strictEqual(isValid, false) - } - - function generateFunctionDeclaration(functionSignature: string): string { - return `${functionSignature} { - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("X-Custom-Header", "application/json"); - - APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(headers); - try { - final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); - String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); - - return response - .withStatusCode(200) - .withBody(output); - } catch (IOException e) { - return response - .withBody("{}") - .withStatusCode(500); - } - } -` - } - - /** - * Simulates the Function signature portion of the contents of a TextDocument that corresponds - * to a Method Symbol's range. Used with generateFunctionDeclaration. - */ - function generateFunctionSignature( - functionHandler: string, - params: { - access: string - beforeFunctionName?: boolean - afterSignature?: boolean - } - ): string { - const beforeFunctionText = params.beforeFunctionName ? os.EOL : '' - const afterSignatureText = params.afterSignature ? os.EOL : '' - - return `${params.access} APIGatewayProxyResponseEvent ${beforeFunctionText}${functionHandler}${afterSignatureText}` - } - - function generateFunctionHandler( - functionName: string, - params: { - type: string - name: string - }[], - beforeArgument: boolean = false - ): string { - const beforeArgumentText = beforeArgument ? os.EOL : '' - const paramsString = params.map((curr) => `${beforeArgumentText}${curr.type} ${curr.name}`).join(', ') - - return `${functionName}(${paramsString})` - } -}) diff --git a/packages/core/src/test/shared/codelens/localLambdaRunner.test.ts b/packages/core/src/test/shared/codelens/localLambdaRunner.test.ts deleted file mode 100644 index e9e396d7a7..0000000000 --- a/packages/core/src/test/shared/codelens/localLambdaRunner.test.ts +++ /dev/null @@ -1,159 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as fsUtils from '../../../shared/filesystemUtilities' -import { SamCliBuildInvocation, SamCliBuildInvocationArguments } from '../../../shared/sam/cli/samCliBuild' -import * as localLambdaRunner from '../../../shared/sam/localLambdaRunner' -import { ChildProcessResult } from '../../../shared/utilities/processUtils' -import { FakeExtensionContext } from '../../fakeExtensionContext' -import { SamLaunchRequestArgs } from '../../../shared/sam/debugger/awsSamDebugger' -import { assertLogsContain } from '../../globalSetup.test' -import { ToolkitError } from '../../../shared/errors' -import { fs } from '../../../shared' - -describe('localLambdaRunner', async function () { - let tempDir: string - before(async function () { - await FakeExtensionContext.create() - }) - - beforeEach(async function () { - tempDir = await fsUtils.makeTemporaryToolkitFolder() - }) - - afterEach(async function () { - await fs.delete(tempDir, { recursive: true }) - }) - - describe('attachDebugger', async function () { - let actualRetries: number = 0 - - beforeEach(async function () { - actualRetries = 0 - }) - - async function onWillRetry(): Promise { - actualRetries++ - } - - async function startDebuggingReturnsTrue( - folder: vscode.WorkspaceFolder | undefined, - nameOrConfiguration: string | vscode.DebugConfiguration - ): Promise { - return Promise.resolve(true) - } - - async function startDebuggingReturnsFalse( - folder: vscode.WorkspaceFolder | undefined, - nameOrConfiguration: string | vscode.DebugConfiguration - ): Promise { - return Promise.resolve(false) - } - - it('Successful attach has no retries', async function () { - await localLambdaRunner.attachDebugger({ - debugConfig: {} as any as SamLaunchRequestArgs, - onStartDebugging: startDebuggingReturnsTrue, - onWillRetry, - }) - - assert.strictEqual(actualRetries, 0, 'Did not expect any retries when attaching debugger succeeds') - }) - - it('Successful attach logs that the debugger attached', async function () { - await localLambdaRunner.attachDebugger({ - debugConfig: {} as any as SamLaunchRequestArgs, - onStartDebugging: startDebuggingReturnsTrue, - onWillRetry, - }) - - // match string 'AWS.output.sam.local.attach.success' - assertLogsContain('Debugger attached', false, 'info') - }) - - it('Failure to attach throws an error', async function () { - const results = localLambdaRunner.attachDebugger({ - debugConfig: {} as any as SamLaunchRequestArgs, - onStartDebugging: startDebuggingReturnsFalse, - onWillRetry, - }) - - await assert.rejects(results, /failed to attach debugger/i) - }) - - it('Uses a code for exceeding the retry limit', async function () { - const results = await localLambdaRunner - .attachDebugger({ - debugConfig: {} as any as SamLaunchRequestArgs, - onStartDebugging: startDebuggingReturnsFalse, - onWillRetry, - }) - .catch((e) => e) - - assert.ok(results instanceof ToolkitError) - assert.strictEqual(results.code, 'DebuggerRetryLimit') - }) - - it('Does not fail if attach succeeds during retries', async function () { - await localLambdaRunner.attachDebugger({ - debugConfig: {} as any as SamLaunchRequestArgs, - onStartDebugging: async ( - folder: vscode.WorkspaceFolder | undefined, - nameOrConfiguration: string | vscode.DebugConfiguration - ): Promise => { - const retVal = actualRetries === 0 ? true : undefined - - return retVal! - }, - onWillRetry, - }) - }) - }) - - describe('executeSamBuild', function () { - const failedChildProcess: ChildProcessResult = { - exitCode: 1, - error: new Error('you are already dead'), - stdout: 'friendly failure message', - stderr: 'big ugly failure message', - } - - const successfulChildProcess: ChildProcessResult = { - exitCode: 0, - error: undefined, - stdout: 'everything sunny all the time always', - stderr: 'nothing to report', - } - - function generateSamBuildParams(isSuccessfulBuild: boolean): SamCliBuildInvocationArguments { - return { - buildDir: tempDir, - baseDir: tempDir, - templatePath: tempDir, - manifestPath: undefined, // not needed for testing - invoker: { - stop: () => {}, - invoke: async (): Promise => - isSuccessfulBuild ? successfulChildProcess : failedChildProcess, - }, - environmentVariables: undefined, - useContainer: false, - skipPullImage: true, - } - } - - it('fails when the child process returns a nonzero exit code', async function () { - const samArgs = generateSamBuildParams(false) - await assert.rejects(new SamCliBuildInvocation(samArgs).execute()) - }) - - it('succeeds when the child process returns with an exit code of 0', async function () { - const samArgs = generateSamBuildParams(true) - assert.strictEqual(await new SamCliBuildInvocation(samArgs).execute(), 0) - }) - }) -}) diff --git a/packages/core/src/test/shared/codelens/pythonCodeLensProvider.test.ts b/packages/core/src/test/shared/codelens/pythonCodeLensProvider.test.ts deleted file mode 100644 index f96d75e123..0000000000 --- a/packages/core/src/test/shared/codelens/pythonCodeLensProvider.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { getLocalRootVariants } from '../../../shared/utilities/pathUtils' - -describe('pythonCodeLensProvider', async function () { - describe('getLocalRootVariants', async function () { - if (process.platform === 'win32') { - const testScenarios = [ - { - situation: 'lower case drive letter', - inputText: 'c:\\src\\code.js', - asLower: 'c:\\src\\code.js', - asUpper: 'C:\\src\\code.js', - }, - { - situation: 'upper case drive letter', - inputText: 'C:\\src\\code.js', - asLower: 'c:\\src\\code.js', - asUpper: 'C:\\src\\code.js', - }, - ] - - for (const test of testScenarios) { - it(`Returns cased-drive variants for windows platforms: ${test.situation}`, async () => { - const variants = getLocalRootVariants(test.inputText) - assert.ok(variants) - assert.strictEqual(variants.length, 2, 'Expected two variants') - assert.strictEqual(variants[0], test.asLower, 'Unexpected variant text') - assert.strictEqual(variants[1], test.asUpper, 'Unexpected variant text') - }) - } - - it('Returns the same string for network location - windows', async function () { - const variants = getLocalRootVariants('//share/src/code.js') - assert.ok(variants) - assert.strictEqual(variants.length, 1, 'Only expected one variant') - assert.strictEqual(variants[0], '//share/src/code.js', 'Unexpected variant text') - }) - - it('Returns the same string for weird input - windows', async function () { - const variants = getLocalRootVariants('src/code.js') - assert.ok(variants) - assert.strictEqual(variants.length, 1, 'Only expected one variant') - assert.strictEqual(variants[0], 'src/code.js', 'Unexpected variant text') - }) - } else { - const testScenarios = [ - { - situation: 'Looks like a windows path - lower case drive', - inputText: 'c:\\src\\code.js', - }, - { - situation: 'Looks like a windows path - upper case drive', - inputText: 'C:\\src\\code.js', - }, - { - situation: 'non-windows path', - inputText: '/src/code.js', - }, - ] - - for (const test of testScenarios) { - it(`Returns the same string for non-windows platforms: ${test.situation}`, async () => { - const variants = getLocalRootVariants(test.inputText) - assert.ok(variants) - assert.strictEqual(variants.length, 1, 'Only expected one variant') - assert.strictEqual(variants[0], test.inputText, 'Unexpected variant text') - }) - } - } - }) -}) diff --git a/packages/core/src/test/shared/codelens/sampleDotNetSamProgram.ts b/packages/core/src/test/shared/codelens/sampleDotNetSamProgram.ts deleted file mode 100644 index c800400eb8..0000000000 --- a/packages/core/src/test/shared/codelens/sampleDotNetSamProgram.ts +++ /dev/null @@ -1,103 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' - -/** - * @deprecated These are not easy to update if the related document changes... - */ - -export function getDocumentSymbols(): vscode.DocumentSymbol[] { - const namespaceSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'HelloWorld', - '', - vscode.SymbolKind.Namespace, - new vscode.Range(12, 0, 48, 1), - new vscode.Range(12, 10, 12, 20) - ) - const classSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'HelloWorld.Function', - '', - vscode.SymbolKind.Class, - new vscode.Range(15, 4, 47, 5), - new vscode.Range(15, 17, 15, 25) - ) - const privateMethodSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'GetCallingIP()', - '', - vscode.SymbolKind.Method, - new vscode.Range(20, 8, 28, 9), - new vscode.Range(20, 42, 20, 54) - ) - const publicMethodSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context)', - '', - vscode.SymbolKind.Method, - new vscode.Range(30, 8, 46, 9), - new vscode.Range(30, 51, 30, 66) - ) - - namespaceSymbol.children.push(classSymbol) - classSymbol.children.push(privateMethodSymbol) - classSymbol.children.push(publicMethodSymbol) - - return [namespaceSymbol] -} - -/** - * @deprecated - */ -export function getFunctionText(): string { - return String.raw` -using System.Collections.Generic; -using System.Net.Http; -using System.Text.Json; -using System.Threading.Tasks; - -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Core; - -// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] - -namespace HelloWorld -{ - - public class Function - { - - private static readonly HttpClient client = new HttpClient(); - - private static async Task GetCallingIP() - { - client.DefaultRequestHeaders.Accept.Clear(); - client.DefaultRequestHeaders.Add("User-Agent", "AWS Lambda .Net Client"); - - var msg = await client.GetStringAsync("http://checkip.amazonaws.com/").ConfigureAwait(continueOnCapturedContext:false); - - return msg.Replace("\n",""); - } - - public async Task FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - - var location = await GetCallingIP(); - var body = new Dictionary - { - { "message", "hello world" }, - { "location", location } - }; - - return new APIGatewayProxyResponse - { - Body = JsonSerializer.Serialize(body), - StatusCode = 200, - Headers = new Dictionary { { "Content-Type", "application/json" } } - }; - } - } -} -` -} diff --git a/packages/core/src/test/shared/codelens/sampleJavaSamProgram.ts b/packages/core/src/test/shared/codelens/sampleJavaSamProgram.ts deleted file mode 100644 index e9562a49b2..0000000000 --- a/packages/core/src/test/shared/codelens/sampleJavaSamProgram.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' - -/** - * This file emits the App.java file contents from the stock SAM CLI app for dotnet, as well as - * the corresponding DocumentSymbols generated by VS Code. - */ - -export function getDocumentSymbols(): vscode.DocumentSymbol[] { - const packageSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'helloworld', - '', - vscode.SymbolKind.Package, - new vscode.Range(0, 0, 0, 19), - new vscode.Range(0, 0, 0, 19) - ) - const classSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'App', - '', - vscode.SymbolKind.Class, - new vscode.Range(15, 0, 47, 1), - new vscode.Range(18, 13, 18, 16) - ) - const handleRequestSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'handleRequest(APIGatewayProxyRequestEvent, Context)', - ' : APIGatewayProxyResponseEvent', - vscode.SymbolKind.Method, - new vscode.Range(20, 4, 39, 5), - new vscode.Range(20, 40, 20, 53) - ) - const privateMethodSymbol: vscode.DocumentSymbol = new vscode.DocumentSymbol( - 'getPageContents(String)', - ' : String', - vscode.SymbolKind.Method, - new vscode.Range(41, 4, 46, 5), - new vscode.Range(41, 19, 41, 34) - ) - - classSymbol.children.push(handleRequestSymbol) - classSymbol.children.push(privateMethodSymbol) - - return [packageSymbol, classSymbol] -} - -export function getFunctionText(): string { - return String.raw`package helloworld; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent; -import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent; - -/** - * Handler for requests to Lambda function. - */ -public class App implements RequestHandler { - - public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) { - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - headers.put("X-Custom-Header", "application/json"); - - APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent() - .withHeaders(headers); - try { - final String pageContents = this.getPageContents("https://checkip.amazonaws.com"); - String output = String.format("{ \"message\": \"hello world\", \"location\": \"%s\" }", pageContents); - - return response - .withStatusCode(200) - .withBody(output); - } catch (IOException e) { - return response - .withBody("{}") - .withStatusCode(500); - } - } - - private String getPageContents(String address) throws IOException{ - URL url = new URL(address); - try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) { - return br.lines().collect(Collectors.joining(System.lineSeparator())); - } - } -} - -` -} diff --git a/packages/core/src/test/shared/debug/launchConfiguration.test.ts b/packages/core/src/test/shared/debug/launchConfiguration.test.ts deleted file mode 100644 index 120e4f12c6..0000000000 --- a/packages/core/src/test/shared/debug/launchConfiguration.test.ts +++ /dev/null @@ -1,312 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import * as vscode from 'vscode' -import { - DebugConfigurationSource, - LaunchConfiguration, - getConfigsMappedToTemplates, - getReferencedHandlerPaths, -} from '../../../shared/debug/launchConfiguration' -import { AwsSamDebuggerConfiguration } from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import { AwsSamDebugConfigurationValidator } from '../../../shared/sam/debugger/awsSamDebugConfigurationValidator' -import * as pathutils from '../../../shared/utilities/pathUtils' -import * as testutil from '../../testUtil' -import globals from '../../../shared/extensionGlobals' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { stub } from '../../utilities/stubber' -import sinon from 'sinon' - -const samDebugConfiguration: AwsSamDebuggerConfiguration = { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'template', - templatePath: '/template.yaml', - logicalId: 'resource', - }, -} - -/** - * Asserts that the given launch.json representations are equivalent. - * - * @param assertSam If true, assert that all configs are valid type=aws-sam configs. - * - * @see samDebugConfigProvider.test.ts:assertEqualLaunchConfigs() which - * compares a single launch-config item (as opposed to the entire launch.json) - */ -function assertEqualLaunchJsons(actual: any, expected: any, workspace: vscode.Uri, assertSam: boolean) { - // Deep-copy, don't modify the original structures. - actual = JSON.parse(JSON.stringify(actual)) - expected = JSON.parse(JSON.stringify(expected)) - // Skip special checks if the inputs are completely wrong, go straight to - // the deep-compare. This gives more meaningful output. - const sameLength = actual.length === expected.length - - if (assertSam && sameLength) { - for (let i = 0; i < actual.length; i++) { - const configActual = actual[i] - const configExpected = expected[i] - const isCodeTarget = 'code' === configActual?.invokeTarget?.target - const templateActual = configActual?.invokeTarget?.templatePath - const templateExpected = configExpected?.invokeTarget?.templatePath - if (templateExpected !== undefined) { - if (!isCodeTarget && templateActual === undefined) { - assert.fail(`not a valid type=aws-sam config: ${JSON.stringify(configActual)}`) - } - const fullpath = pathutils.normalize(path.resolve(workspace.fsPath, templateExpected)) - testutil.assertEqualPaths(templateActual, fullpath) - } - } - } - - // Remove noisy properties before doing a deep-compare. - for (let i = 0; i < actual.length; i++) { - for (const o of [actual[i], expected[i]]) { - if (o.invokeTarget?.templatePath) { - delete o.invokeTarget.templatePath - } - } - } - assert.deepStrictEqual(actual, expected) -} -function createMockSamDebugConfig(addons?: Partial): AwsSamDebuggerConfiguration { - return { - ...samDebugConfiguration, - ...addons, - } -} - -const debugConfigurations: vscode.DebugConfiguration[] = [ - createMockSamDebugConfig(), - createMockSamDebugConfig({ type: 'not-aws-sam' }), - createMockSamDebugConfig({ request: 'invalid-request' }), -] - -const templateUri = vscode.Uri.file('/template.yaml') - -describe('LaunchConfiguration', function () { - let mockConfigSource: DebugConfigurationSource - let mockSamValidator: AwsSamDebugConfigurationValidator - const setDebugConfigurationsStub = sinon.stub() - - /** Test workspace. */ - const workspace = vscode.workspace.workspaceFolders![0] - const testLaunchJson = vscode.Uri.file(path.join(workspace.uri.fsPath, '.vscode/launch.json')) - /** Object representation of the launch.json test file. */ - let testLaunchJsonData: any - const templateUriJsPlainApp = vscode.Uri.file(path.join(workspace.uri.fsPath, 'js-plain-sam-app/template.yaml')) - const templateUriPython37 = vscode.Uri.file( - path.join(workspace.uri.fsPath, 'python3.7-plain-sam-app/template.yaml') - ) - const templateUriCsharp = vscode.Uri.file(path.join(workspace.uri.fsPath, 'csharp6-zip/template.yaml')) - - beforeEach(async function () { - testLaunchJsonData = JSON.parse(await testutil.fromFile(testLaunchJson.fsPath)) - const registry = await globals.templateRegistry - registry.addWatchPatterns([CloudFormation.templateFileGlobPattern]) - await registry.rebuild() - - // TODO: remove mocks in favor of testing src/testFixtures/ data. - mockConfigSource = { - getDebugConfigurations: () => debugConfigurations, - setDebugConfigurations: setDebugConfigurationsStub, - } - mockSamValidator = { - validate: async () => ({ isValid: true }), - } - }) - - afterEach(async function () { - setDebugConfigurationsStub.reset() - ;(await globals.templateRegistry).reset() - }) - - it('getConfigsMappedToTemplates(type=api)', async function () { - const actual = await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriJsPlainApp), 'api') - assert.deepStrictEqual(actual.size, 0) - - const actual2 = Array.from( - await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriPython37), 'api') - ) - assert.deepStrictEqual(actual2.length, 1) - assert.deepStrictEqual(actual2[0].name, 'test-launchconfig-6-python3.7') - assert.deepStrictEqual(actual2[0].invokeTarget.target, 'api') - assert.deepStrictEqual((actual2[0].invokeTarget as any).logicalId, 'HelloWorldFunction') - - const actual3 = Array.from(await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriCsharp), 'api')) - assert.deepStrictEqual(actual3.length, 1) - assert.deepStrictEqual(actual3[0].name, 'test-launchconfig-8-api') - assert.deepStrictEqual(actual3[0].invokeTarget.target, 'api') - assert.deepStrictEqual((actual3[0].invokeTarget as any).logicalId, 'HelloWorldFunction') - }) - - it('getConfigsMappedToTemplates(type=undefined) returns target=template + target=api resources', async function () { - const actual = Array.from( - await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriJsPlainApp), undefined) - ) - assert.deepStrictEqual(actual.length, 1) - assert.deepStrictEqual((actual[0].invokeTarget as any).logicalId, 'SourceCodeBesidePackageJson') - - const actual2 = Array.from( - await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriPython37), undefined) - ) - assert.deepStrictEqual(actual2.length, 2) - assert.deepStrictEqual(actual2[0].name, 'test-launchconfig-5-python3.7') - assert.deepStrictEqual(actual2[1].name, 'test-launchconfig-6-python3.7') - assert.deepStrictEqual(actual2[0].invokeTarget.target, 'template') - assert.deepStrictEqual(actual2[1].invokeTarget.target, 'api') - - const actual3 = Array.from( - await getConfigsMappedToTemplates(new LaunchConfiguration(templateUriCsharp), undefined) - ) - assert.deepStrictEqual(actual3.length, 1) - assert.deepStrictEqual(actual3[0].name, 'test-launchconfig-8-api') - assert.deepStrictEqual((actual3[0].invokeTarget as any).logicalId, 'HelloWorldFunction') - }) - - it('gets debug configurations', function () { - const launchConfig = new LaunchConfiguration(templateUriJsPlainApp) - const expected = testLaunchJsonData['configurations'] - assertEqualLaunchJsons(launchConfig.getDebugConfigurations(), expected, workspace.uri, false) - }) - - it('gets aws-sam debug configurations', async function () { - const launchConfig = new LaunchConfiguration(templateUriJsPlainApp) - const expected = (testLaunchJsonData['configurations'] as any[]).filter( - (o) => o.type === 'aws-sam' && o.request === 'direct-invoke' - ) - const actual = await launchConfig.getSamDebugConfigurations() - assertEqualLaunchJsons(actual, expected, workspace.uri, true) - }) - - it('adds single debug configuration', async function () { - const launchConfig = new LaunchConfiguration(templateUri, mockConfigSource, mockSamValidator) - await launchConfig.addDebugConfiguration(samDebugConfiguration) - - const expected = [samDebugConfiguration, ...debugConfigurations] - assert(setDebugConfigurationsStub.calledOnceWith(expected)) - }) - - it('adds multiple debug configurations', async function () { - const launchConfig = new LaunchConfiguration(templateUri, mockConfigSource, mockSamValidator) - await launchConfig.addDebugConfigurations([samDebugConfiguration, samDebugConfiguration]) - - const expected = [samDebugConfiguration, samDebugConfiguration, ...debugConfigurations] - assert(setDebugConfigurationsStub.calledOnceWith(expected)) - }) -}) - -describe('getReferencedHandlerPaths', function () { - it('includes all resources as absolute paths to root dir + handlers', async function () { - const mockLaunchConfig = createMockLaunchConfig() - - const resultSet = await getReferencedHandlerPaths(mockLaunchConfig) - const workspaceFolder = mockLaunchConfig.workspaceFolder!.uri.fsPath - - // template type handlers, these are all false as we throw all of these out - assert.strictEqual(resultSet.has('resource'), false) - assert.strictEqual(resultSet.has('relativePathGoodTemplate'), false) - assert.strictEqual(resultSet.has('relativePathBadTemplate'), false) - assert.strictEqual(resultSet.has('absolutePathGoodTemplate'), false) - assert.strictEqual(resultSet.has('absolutePathBadTemplate'), false) - // code type handlers, these are all true as we keep all code-type handlers - assert.strictEqual( - resultSet.has(pathutils.normalize(path.resolve(workspaceFolder, 'inProject', 'relativeRoot'))), - true - ) - assert.strictEqual( - resultSet.has(pathutils.normalize(path.resolve(workspaceFolder, 'inProject', 'absoluteRoot'))), - true - ) - assert.strictEqual( - resultSet.has(pathutils.normalize(path.resolve(workspaceFolder, 'notInProject', 'differentRelativeRoot'))), - true - ) - assert.strictEqual( - resultSet.has(pathutils.normalize(path.resolve(workspaceFolder, 'notInProject', 'differentAbsoluteRoot'))), - true - ) - }) -}) - -function createMockLaunchConfig(): LaunchConfiguration { - const workspaceFolder = '/absolutely/this/is/the/right/path' - - // init mockLaunchConfig - const mockLaunchConfig = stub(LaunchConfiguration, { - workspaceFolder: { - uri: vscode.Uri.file(workspaceFolder), - name: 'mockAroundTheClock', - index: 1, - }, - scopedResource: vscode.Uri.file(path.join(workspaceFolder, 'template.yaml')), - }) - mockLaunchConfig.getSamDebugConfigurations.resolves([ - // default: template target with not-in-workspace, absolute-pathed templatePath - createMockSamDebugConfig(), - createMockSamDebugConfig({ - invokeTarget: { - target: 'template', - templatePath: 'template.yaml', - logicalId: 'relativePathGoodTemplate', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'template', - templatePath: 'template2.yaml', - logicalId: 'relativePathBadTemplate', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'template', - templatePath: pathutils.normalize(path.resolve(workspaceFolder, 'template.yaml')), - logicalId: 'absolutePathGoodTemplate', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'template', - templatePath: pathutils.normalize(path.resolve(workspaceFolder, 'template2.yaml')), - logicalId: 'absolutePathBadTemplate', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'code', - lambdaHandler: 'relativeRoot', - projectRoot: 'inProject', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'code', - lambdaHandler: 'differentRelativeRoot', - projectRoot: 'notInProject', - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'code', - lambdaHandler: 'absoluteRoot', - projectRoot: pathutils.normalize(path.resolve(workspaceFolder, 'inProject')), - }, - }), - createMockSamDebugConfig({ - invokeTarget: { - target: 'code', - lambdaHandler: 'differentAbsoluteRoot', - projectRoot: pathutils.normalize(path.resolve(workspaceFolder, 'notInProject')), - }, - }), - ]) - - return mockLaunchConfig -} diff --git a/packages/core/src/test/shared/errors.test.ts b/packages/core/src/test/shared/errors.test.ts index 093feceb01..42fbbad814 100644 --- a/packages/core/src/test/shared/errors.test.ts +++ b/packages/core/src/test/shared/errors.test.ts @@ -26,7 +26,6 @@ import { UnauthorizedException } from '@aws-sdk/client-sso' import { AWSError } from 'aws-sdk' import { AccessDeniedException } from '@aws-sdk/client-sso-oidc' import { OidcClient } from '../../auth/sso/clients' -import { SamCliError } from '../../shared/sam/cli/samCliInvokerUtils' import { DiskCacheError } from '../../shared/utilities/cacheUtils' class TestAwsError extends Error implements AWSError { @@ -97,7 +96,7 @@ export function fakeErrorChain(err1?: Error, err2?: Error, err3?: Error, err4?: } catch (e2) { try { // new Error('error 3') - const e = err3 ? err3 : new SamCliError('sam error', { cause: e2 as Error }) + const e = err3 ? err3 : new ToolkitError('sam error', { cause: e2 as Error }) trySetCause(e, e2) throw e } catch (e3) { diff --git a/packages/core/src/test/shared/fs/templateRegistry.test.ts b/packages/core/src/test/shared/fs/templateRegistry.test.ts index c7e92518c7..84145f1cb0 100644 --- a/packages/core/src/test/shared/fs/templateRegistry.test.ts +++ b/packages/core/src/test/shared/fs/templateRegistry.test.ts @@ -6,14 +6,10 @@ import assert from 'assert' import * as path from 'path' import * as vscode from 'vscode' -import { - CloudFormationTemplateRegistry, - getResourcesForHandler, - getResourcesForHandlerFromTemplateDatum, -} from '../../../shared/fs/templateRegistry' +import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' import { badYaml, makeSampleSamTemplateYaml, strToYamlFile } from '../cloudformation/cloudformationTestUtils' -import { assertEqualPaths, toFile } from '../../testUtil' +import { assertEqualPaths } from '../../testUtil' import * as CloudFormation from '../../../shared/cloudformation/cloudformation' import { WatchedItem } from '../../../shared/fs/watchedFiles' import { fs } from '../../../shared' @@ -110,415 +106,6 @@ describe('CloudFormation Template Registry', async function () { }) }) }) - - // Consts for the non-class functions - const rootPath = path.join('i', 'am', 'your', 'father') - const nestedPath = path.join('s', 'brothers', 'nephews', 'cousins', 'former', 'roommate') - const otherPath = path.join('obi-wan', 'killed', 'your', 'father') - const matchingResource: { - Type: 'AWS::Serverless::Function' - Properties: CloudFormation.ZipResourceProperties - } = { - Type: 'AWS::Serverless::Function', - Properties: { - Handler: 'index.handler', - CodeUri: path.join(nestedPath), - Runtime: 'nodejs16.x', - }, - } - const nonParentTemplate = { - path: path.join(otherPath, 'template.yaml'), - item: {}, - } - const workingTemplate = { - path: path.join(rootPath, 'template.yaml'), - item: { - Resources: { - resource1: matchingResource, - }, - }, - } - const noResourceTemplate = { - path: path.join(rootPath, 'template.yaml'), - item: { - Resources: {}, - }, - } - const compiledResource: { - Type: 'AWS::Serverless::Function' - Properties: CloudFormation.ZipResourceProperties - } = { - Type: 'AWS::Serverless::Function', - Properties: { - Handler: 'Asdf::Asdf.Function::FunctionHandler', - CodeUri: path.join(nestedPath), - Runtime: 'dotnet6', - }, - } - const dotNetTemplate = { - path: path.join(rootPath, 'template.yaml'), - item: { - Resources: { - resource1: compiledResource, - }, - }, - } - const multiResourceTemplate = { - path: path.join(rootPath, 'template.yaml'), - item: { - Resources: { - resource1: matchingResource, - resource2: { - ...matchingResource, - Properties: { - ...matchingResource.Properties, - Timeout: 5000, - }, - }, - }, - }, - } - const badRuntimeTemplate = { - path: path.join(rootPath, 'template.yaml'), - item: { - Resources: { - badResource: { - ...matchingResource, - Properties: { - ...matchingResource.Properties, - Runtime: 'COBOL-60', - }, - }, - goodResource: matchingResource, - }, - }, - } - - describe('getResourcesForHandler', function () { - it('handles empty input', function () { - // Empty `unfilteredTemplates` input: - assert.deepStrictEqual( - getResourcesForHandler(path.join(rootPath, nestedPath, 'index.js'), 'handler', []), - [] - ) - }) - - it('returns an array containing resources that contain references to the handler in question', function () { - const val = getResourcesForHandler(path.join(rootPath, nestedPath, 'index.js'), 'handler', [ - nonParentTemplate, - workingTemplate, - noResourceTemplate, - dotNetTemplate, - multiResourceTemplate, - badRuntimeTemplate, - ]) - - assert.deepStrictEqual(val, [ - { - name: 'resource1', - resourceData: matchingResource, - templateDatum: workingTemplate, - }, - { - name: 'resource1', - resourceData: matchingResource, - templateDatum: multiResourceTemplate, - }, - { - name: 'resource2', - resourceData: { - ...matchingResource, - Properties: { - ...matchingResource.Properties, - Timeout: 5000, - }, - }, - templateDatum: multiResourceTemplate, - }, - { - name: 'goodResource', - resourceData: matchingResource, - templateDatum: badRuntimeTemplate, - }, - ]) - }) - }) - - describe('getResourcesForHandlerFromTemplateDatum', function () { - it('returns an empty array if the given template is not a parent of the handler file in question', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, 'index.js'), - 'handler', - nonParentTemplate - ) - - assert.deepStrictEqual(val, []) - }) - - it('returns an empty array if the template has no resources', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, nestedPath, 'index.js'), - 'handler', - noResourceTemplate - ) - - assert.deepStrictEqual(val, []) - }) - - it('returns a template resource if it has a matching handler', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, nestedPath, 'index.js'), - 'handler', - workingTemplate - ) - - assert.deepStrictEqual(val, [ - { - name: 'resource1', - resourceData: matchingResource, - }, - ]) - }) - - it('ignores path handling if using a compiled language', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, nestedPath, 'index.cs'), - 'Asdf::Asdf.Function::FunctionHandler', - dotNetTemplate - ) - - assert.deepStrictEqual(val, [ - { - name: 'resource1', - resourceData: compiledResource, - }, - ]) - }) - - it('returns all template resources if it has multiple matching handlers', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, nestedPath, 'index.js'), - 'handler', - multiResourceTemplate - ) - - assert.deepStrictEqual(val, [ - { - name: 'resource1', - resourceData: matchingResource, - }, - { - name: 'resource2', - resourceData: { - ...matchingResource, - Properties: { - ...matchingResource.Properties, - Timeout: 5000, - }, - }, - }, - ]) - }) - - it('does not break if the resource has a non-parseable runtime', function () { - const val = getResourcesForHandlerFromTemplateDatum( - path.join(rootPath, nestedPath, 'index.js'), - 'handler', - badRuntimeTemplate - ) - - assert.deepStrictEqual(val, [ - { - name: 'goodResource', - resourceData: matchingResource, - }, - ]) - }) - }) - - describe('getResourcesForHandlerFromTemplateDatum (image tests)', async function () { - let tempFolder: string - let helloPath: string - let nestedPath: string - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - helloPath = path.join(tempFolder, 'hello-world') - nestedPath = path.join(helloPath, 'nested') - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - const resource: { - Type: 'AWS::Serverless::Function' - Properties: CloudFormation.ImageResourceProperties - } = { - Type: 'AWS::Serverless::Function', - Properties: { - PackageType: 'Image', - Events: { - HelloWorld: { - Type: 'Api', - Properties: { - Path: '/hello', - Method: 'get', - }, - }, - }, - }, - } - - it('checks for an exact handler match to a relative path in a Dockerfile for image functions', async function () { - await toFile('CMD: ["index.handler"]', path.join(helloPath, 'Dockerfile')) - await toFile('CMD: ["index.handler"]', path.join(nestedPath, 'Dockerfile')) - - const helloWorldResource = { - ...resource, - Metadata: { - DockerTag: 'nodejs16.x-v1', - DockerContext: './hello-world', - Dockerfile: 'Dockerfile', - }, - } - - const helloWorldNestedResource = { - ...resource, - Metadata: { - DockerTag: 'nodejs16.x-v1', - DockerContext: './hello-world/nested', - Dockerfile: 'Dockerfile', - }, - } - - const template = { - path: path.join(tempFolder, 'template.yaml'), - item: { - Resources: { - helloWorldResource, - helloWorldNestedResource, - }, - }, - } - const val = getResourcesForHandlerFromTemplateDatum( - path.join(helloPath, 'index.js'), - 'index.handler', - template - ) - - assert.deepStrictEqual( - val, - [ - { - name: 'helloWorldResource', - resourceData: helloWorldResource, - }, - ], - 'non-nested case failed' - ) - - const val2 = getResourcesForHandlerFromTemplateDatum( - path.join(nestedPath, 'index.js'), - 'index.handler', - template - ) - - assert.deepStrictEqual( - val2, - [ - { - name: 'helloWorldNestedResource', - resourceData: helloWorldNestedResource, - }, - ], - 'nested case failed' - ) - }) - - it('checks for an exact handler match for C# files in a Dockerfile for image functions', async function () { - await toFile( - 'CMD: ["HelloWorld::HelloWorld.Function::FunctionHandler"]', - path.join(helloPath, 'Dockerfile') - ) - await toFile( - 'CMD: ["HelloWorld::HelloWorld.Function::FunctionHandler"]', - path.join(nestedPath, 'Dockerfile') - ) - - const helloWorldResource = { - ...resource, - Metadata: { - DockerTag: 'dotnetcore3.1-v1', - DockerContext: './hello-world', - Dockerfile: 'Dockerfile', - DockerBuildArgs: { - SAM_BUILD_MODE: 'run', - }, - }, - } - - const helloWorldNestedResource = { - ...resource, - Metadata: { - DockerTag: 'dotnetcore3.1-v1', - DockerContext: './hello-world/nested', - Dockerfile: 'Dockerfile', - DockerBuildArgs: { - SAM_BUILD_MODE: 'run', - }, - }, - } - - const template = { - path: path.join(tempFolder, 'template.yaml'), - item: { - Resources: { - helloWorldResource, - helloWorldNestedResource, - }, - }, - } - const val = getResourcesForHandlerFromTemplateDatum( - path.join(helloPath, 'HelloWorld.cs'), - 'HelloWorld::HelloWorld.Function::FunctionHandler', - template - ) - - assert.deepStrictEqual( - val, - [ - { - name: 'helloWorldResource', - resourceData: helloWorldResource, - }, - ], - 'non-nested case failed' - ) - - const val2 = getResourcesForHandlerFromTemplateDatum( - path.join(nestedPath, 'HelloWorld.cs'), - 'HelloWorld::HelloWorld.Function::FunctionHandler', - template - ) - - // checks all Dockerfiles in parent paths - assert.deepStrictEqual( - val2, - [ - { - name: 'helloWorldResource', - resourceData: helloWorldResource, - }, - { - name: 'helloWorldNestedResource', - resourceData: helloWorldNestedResource, - }, - ], - 'nested case failed' - ) - }) - }) }) function assertValidTestTemplate(data: WatchedItem | undefined, filename: string): void { diff --git a/packages/core/src/test/shared/globalState.test.ts b/packages/core/src/test/shared/globalState.test.ts index d632b74505..563034c63c 100644 --- a/packages/core/src/test/shared/globalState.test.ts +++ b/packages/core/src/test/shared/globalState.test.ts @@ -6,7 +6,6 @@ import assert from 'assert' import { GlobalState } from '../../shared/globalState' import { FakeMemento } from '../fakeExtensionContext' -import * as redshift from '../../awsService/redshift/models/models' describe('GlobalState', function () { let globalState: GlobalState @@ -111,65 +110,6 @@ describe('GlobalState', function () { assert.deepStrictEqual(globalState.values(), []) }) - describe('redshift state', function () { - const testArn1 = 'arn:foo/bar/baz/1' - const testArn2 = 'arn:foo/bar/baz/2' - - const fakeCxn1: redshift.ConnectionParams = { - connectionType: redshift.ConnectionType.SecretsManager, - database: 'fake-db', - warehouseIdentifier: 'warhouse-id-1', - warehouseType: redshift.RedshiftWarehouseType.SERVERLESS, - region: { - id: 'us-east-2', - name: 'region name', - }, - password: 'password-1', - secret: 'secret 1', - } - const fakeCxn2: redshift.ConnectionParams = { - ...fakeCxn1, - password: 'pw 2', - secret: 'secret 2', - warehouseIdentifier: 'wh-id-2', - database: 'fake db 2', - } - - it('get/set connection state and special DELETE_CONNECTION value', async () => { - await globalState.saveRedshiftConnection(testArn1, 'DELETE_CONNECTION') - await globalState.saveRedshiftConnection(testArn2, undefined) - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn1), 'DELETE_CONNECTION') - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn2), undefined) - await globalState.saveRedshiftConnection(testArn1, fakeCxn1) - await globalState.saveRedshiftConnection(testArn2, fakeCxn2) - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn1), fakeCxn1) - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn2), fakeCxn2) - }) - - it('validation', async () => { - await globalState.saveRedshiftConnection(testArn1, 'foo' as any) - await globalState.saveRedshiftConnection(testArn2, 99 as any) - - // Assert that bad state was set. - assert.deepStrictEqual(globalState.get('aws.redshift.connections'), { - [testArn1]: 'foo', - [testArn2]: 99, - }) - - // Bad state is logged and returns undefined. - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn1), undefined) - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn2), undefined) - - await globalState.saveRedshiftConnection(testArn2, fakeCxn2) - assert.deepStrictEqual(globalState.getRedshiftConnection(testArn2), fakeCxn2) - // Stored state is now "partially bad". - assert.deepStrictEqual(globalState.get('aws.redshift.connections'), { - [testArn1]: 'foo', - [testArn2]: fakeCxn2, - }) - }) - }) - describe('SSO sessions', function () { const session1 = 'session-1' const session2 = 'session-2' diff --git a/packages/core/src/test/shared/sam/build.test.ts b/packages/core/src/test/shared/sam/build.test.ts deleted file mode 100644 index bed4fee7e2..0000000000 --- a/packages/core/src/test/shared/sam/build.test.ts +++ /dev/null @@ -1,654 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { globals, ToolkitError } from '../../../shared' -import * as SamUtilsModule from '../../../shared/sam/utils' -import * as ProcessTerminalUtils from '../../../shared/sam/processTerminal' -import * as ResolveEnvModule from '../../../shared/env/resolveEnv' -import * as ProcessUtilsModule from '../../../shared/utilities/processUtils' -import { AppNode } from '../../../awsService/appBuilder/explorer/nodes/appNode' -import { - BuildParams, - BuildWizard, - createParamsSourcePrompter, - getBuildFlags, - ParamsSource, - resolveBuildFlags, - runBuild, - SamBuildResult, -} from '../../../shared/sam/build' -import { TreeNode } from '../../../shared/treeview/resourceTreeDataProvider' -import { createWizardTester } from '../wizards/wizardTestUtils' -import assert from 'assert' -import { createBaseTemplate } from '../cloudformation/cloudformationTestUtils' -import { getProjectRootUri } from '../../../shared/sam/utils' -import sinon from 'sinon' -import { createMultiPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import * as config from '../../../shared/sam/config' -import * as utils from '../../../shared/sam/utils' -import { PrompterTester } from '../wizards/prompterTester' -import { getWorkspaceFolder, TestFolder } from '../../testUtil' -import { samconfigCompleteData, validTemplateData } from './samTestUtils' -import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' -import { getTestWindow } from '../vscode/window' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { SamAppLocation } from '../../../awsService/appBuilder/explorer/samProject' -import { SemVer } from 'semver' - -describe('SAM BuildWizard', async function () { - const createTester = async (params?: Partial, arg?: TreeNode | undefined) => - createWizardTester(new BuildWizard({ ...params }, await globals.templateRegistry, arg)) - - it('shows steps in correct order when triggered from command palette', async function () { - const tester = await createTester() - tester.template.assertShowFirst() - tester.paramsSource.assertShowSecond() - }) - - it('shows steps in correct order when triggered from appBuilder', async function () { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspaceFolder) - - const templateUri = vscode.Uri.joinPath(workspaceFolder.uri, 'template.yaml') - const projectRootUri = getProjectRootUri(templateUri) - const samAppLocation = { - samTemplateUri: templateUri, - workspaceFolder: workspaceFolder, - projectRoot: projectRootUri, - } - const appNode = new AppNode(samAppLocation) - const tester = await createTester({}, appNode) - tester.template.assertDoesNotShow() - tester.paramsSource.assertShowFirst() - }) - - it('shows steps in correct order when triggered from command palette', async function () { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspaceFolder) - - const templateUri = vscode.Uri.joinPath(workspaceFolder.uri, 'template.yaml') - const template = { uri: templateUri, data: createBaseTemplate() } - const tester = await createTester({ template }) - tester.template.assertDoesNotShow() - tester.paramsSource.assertShowFirst() - }) - - it('set the correct project root', async function () { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspaceFolder) - - const templateUri = vscode.Uri.joinPath(workspaceFolder.uri, 'template.yaml') - const template = { uri: templateUri, data: createBaseTemplate() } - const tester = await createTester({ template }) - tester.projectRoot.path.assertValue(workspaceFolder.uri.path) - }) -}) - -describe('SAM build helper functions', () => { - describe('getBuildFlags', () => { - let sandbox: sinon.SinonSandbox - let projectRoot: vscode.Uri - const defaultFlags: string[] = ['--cached', '--parallel', '--save-params', '--use-container'] - let quickPickItems: DataQuickPickItem[] - - beforeEach(() => { - sandbox = sinon.createSandbox() - projectRoot = vscode.Uri.parse('file:///path/to/project') - quickPickItems = [ - { - label: 'Beta features', - data: '--beta-features', - description: 'Enable beta features', - }, - { - label: 'Build in source', - data: '--build-in-source', - description: 'Opts in to build project in the source folder', - }, - { - label: 'Cached', - data: '--cached', - description: 'Reuse build artifacts that have not changed from previous builds', - }, - { - label: 'Debug', - data: '--debug', - description: 'Turn on debug logging to print debug messages and display timestamps', - }, - { - label: 'Parallel', - data: '--parallel', - description: 'Enable parallel builds for AWS SAM template functions and layers', - }, - { - label: 'Skip prepare infra', - data: '--skip-prepare-infra', - description: 'Skip preparation stage when there are no infrastructure changes', - }, - { - label: 'Skip pull image', - data: '--skip-pull-image', - description: 'Skip pulling down the latest Docker image for Lambda runtime', - }, - { - label: 'Use container', - data: '--use-container', - description: 'Build functions with an AWS Lambda-like container', - }, - { - label: 'Save parameters', - data: '--save-params', - description: 'Save to samconfig.toml as default parameters', - }, - ] - }) - - afterEach(() => { - sandbox.restore() // Restore all stubs after each test - }) - - it('should return flags from buildFlagsPrompter when paramsSource is Specify', async () => { - PrompterTester.init() - .handleQuickPick('Select build flags', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - assert.strictEqual(picker.title, 'Select build flags') - assert.deepStrictEqual(picker.items, quickPickItems) - const betaFeatures = picker.items[0] - const buildInSource = picker.items[1] - const cached = picker.items[2] - assert.strictEqual(betaFeatures.data, '--beta-features') - assert.strictEqual(buildInSource.data, '--build-in-source') - assert.strictEqual(cached.data, '--cached') - const acceptedItems = [betaFeatures, buildInSource, cached] - picker.acceptItems(...acceptedItems) - }) - .build() - - const flags = await createMultiPick(quickPickItems, { - title: 'Select build flags', - ignoreFocusOut: true, - }).prompt() - - assert.deepStrictEqual(flags, JSON.stringify(['--beta-features', '--build-in-source', '--cached'])) - }) - - it('should return config file flag when paramsSource is SamConfig', async () => { - const mockConfigFileUri = vscode.Uri.parse('file:///path/to/samconfig.toml') - const getConfigFileUriStub = sandbox.stub().resolves(mockConfigFileUri) - sandbox.stub(config, 'getConfigFileUri').callsFake(getConfigFileUriStub) - - const flags = await getBuildFlags(ParamsSource.SamConfig, projectRoot, defaultFlags) - assert.deepStrictEqual(flags, ['--config-file', mockConfigFileUri.fsPath]) - }) - - it('should return default flags if getConfigFileUri throws an error', async () => { - const getConfigFileUriStub = sinon.stub().rejects(new Error('Config file not found')) - sandbox.stub(config, 'getConfigFileUri').callsFake(getConfigFileUriStub) - - const flags = await getBuildFlags(ParamsSource.SamConfig, projectRoot, defaultFlags) - assert.deepStrictEqual(flags, defaultFlags) - }) - }) - - describe('createParamsSourcePrompter', () => { - it('should return a prompter with the correct items with no valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify build flags', - data: ParamsSource.Specify, - }, - { - label: 'Use default values', - data: ParamsSource.DefaultValues, - description: 'cached = true, parallel = true, use_container = true', - }, - ] - const prompter = createParamsSourcePrompter(false) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for build') - assert.strictEqual(quickPick.placeholder, 'Select configuration options for sam build') - assert.strictEqual(quickPick.items.length, 2) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) - - it('should return a prompter with the correct items with valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify build flags', - data: ParamsSource.Specify, - }, - { - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - }, - ] - const prompter = createParamsSourcePrompter(true) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for build') - assert.strictEqual(quickPick.placeholder, 'Select configuration options for sam build') - assert.strictEqual(quickPick.items.length, 2) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) - }) - - describe('resolveBuildFlags', () => { - let sandbox: sinon.SinonSandbox - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('uses --no-use-container when --use-container is absent', async () => { - const normalVersion = new SemVer('1.133.0') - const buildFlags = ['--cached', '--debug', '--parallel'] - const expectedBuildFlags = ['--cached', '--debug', '--parallel', '--no-use-container'] - return testResolveBuildFlags(sandbox, normalVersion, buildFlags, expectedBuildFlags) - }) - - it('preserves buildFlags when SAM CLI version < 1.133', async () => { - const lowerVersion = new SemVer('1.110.0') - const buildFlags = ['--cached', '--parallel', '--save-params'] - const expectedBuildFlags = ['--cached', '--parallel', '--save-params'] - return testResolveBuildFlags(sandbox, lowerVersion, buildFlags, expectedBuildFlags) - }) - - it('respects existing --use-container flag', async () => { - const normalVersion = new SemVer('1.110.0') - const buildFlags = ['--cached', '--parallel', '--save-params', '--use-container'] - const expectedBuildFlags = ['--cached', '--parallel', '--save-params', '--use-container'] - return testResolveBuildFlags(sandbox, normalVersion, buildFlags, expectedBuildFlags) - }) - }) -}) - -describe('SAM runBuild', () => { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - let workspaceFolder: vscode.WorkspaceFolder - let templateFile: vscode.Uri - - let mockGetSpawnEnv: sinon.SinonStub - let mockGetSamCliPath: sinon.SinonStub - let mockChildProcessClass: sinon.SinonStub - let mockSamBuildChildProcess: sinon.SinonStub - - let registry: CloudFormationTemplateRegistry - - // Dependency clients - beforeEach(async function () { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - workspaceFolder = getWorkspaceFolder(testFolder.path) - sandbox = sinon.createSandbox() - registry = await globals.templateRegistry - - // Create template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - await registry.addItem(templateFile) - - mockGetSpawnEnv = sandbox.stub(ResolveEnvModule, 'getSpawnEnv').callsFake( - sandbox.stub().resolves({ - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }) - ) - }) - - afterEach(() => { - sandbox.restore() - registry.reset() - }) - - describe(':) path', () => { - let spyRunInterminal: sinon.SinonSpy - - beforeEach(() => { - mockGetSamCliPath = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ path: 'sam-cli-path' })) - - // Mock child process with required properties that get called in ProcessTerminal - mockSamBuildChildProcess = Object.create(ProcessUtilsModule.ChildProcess.prototype, { - stopped: { get: sandbox.stub().returns(false) }, - stop: { value: sandbox.stub().resolves({}) }, - run: { - value: sandbox.stub().resolves({ - exitCode: 0, - stdout: 'Mock successful build command execution ', - stderr: '', - }), - }, - }) - spyRunInterminal = sandbox.spy(ProcessTerminalUtils, 'runInTerminal') - mockChildProcessClass = sandbox.stub(ProcessUtilsModule, 'ChildProcess').returns(mockSamBuildChildProcess) - }) - - afterEach(() => { - spyRunInterminal.resetHistory() - sandbox.restore() - }) - - const verifyCorrectDependencyCall = () => { - // Prefer count comparison for debugging flakiness - assert.strictEqual(1, mockGetSamCliPath.callCount, 'GetSamCliPath') - assert.strictEqual(1, mockChildProcessClass.callCount, 'ChildProcessClass') - assert.strictEqual(1, mockGetSpawnEnv.callCount, 'GetSpawnEnv') - assert.strictEqual(1, spyRunInterminal.callCount, 'RunInterminal') - assert.deepEqual(spyRunInterminal.getCall(0).args, [mockSamBuildChildProcess, 'build']) - } - - it('[entry: command palette] with specify flags should instantiate correct process in terminal', async () => { - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify parameter source for build', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 2) - const items = quickPick.items - assert.deepStrictEqual(items[0], { data: ParamsSource.Specify, label: 'Specify build flags' }) - assert.deepStrictEqual(items[1], { - label: 'Use default values', - data: ParamsSource.DefaultValues, - description: 'cached = true, parallel = true, use_container = true', - }) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Select build flags', async (quickPick) => { - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 9) - const item1 = quickPick.items[2] as DataQuickPickItem - const item2 = quickPick.items[3] as DataQuickPickItem - const item3 = quickPick.items[7] as DataQuickPickItem - const item4 = quickPick.items[8] as DataQuickPickItem - - assert.deepStrictEqual(item1, { - label: 'Cached', - data: '--cached', - description: 'Reuse build artifacts that have not changed from previous builds', - }) - assert.deepStrictEqual(item2, { - label: 'Debug', - data: '--debug', - description: 'Turn on debug logging to print debug messages and display timestamps', - }) - assert.deepStrictEqual(item3, { - label: 'Use container', - data: '--use-container', - description: 'Build functions with an AWS Lambda-like container', - }) - assert.deepStrictEqual(item4, { - label: 'Save parameters', - data: '--save-params', - description: 'Save to samconfig.toml as default parameters', - }) - quickPick.acceptItems(item1, item2, item3, item4) - }) - .build() - - // Invoke sync command from command palette - // Instead of await runBuild(), prefer this to avoid flakiness due to race condition - await delayedRunBuild() - - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'build', - '--cached', - '--debug', - '--use-container', - '--save-params', - '--template', - `${templateFile.fsPath}`, - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - prompterTester.assertCallAll() - verifyCorrectDependencyCall() - }) - - it('[entry: appbuilder node] with default flags should instantiate correct process in terminal and show progress notification', async () => { - const prompterTester = getPrompterTester() - const expectedSamAppLocation = { - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - } - - // Instead of await runBuild(), prefer this to avoid flakiness due to race condition - await delayedRunBuild(expectedSamAppLocation) - - getTestWindow() - .getFirstMessage() - .assertProgress(`Building SAM template at ${expectedSamAppLocation.samTemplateUri.path}`) - - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'build', - '--cached', - '--parallel', - '--save-params', - '--use-container', - '--template', - `${templateFile.fsPath}`, - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - verifyCorrectDependencyCall() - prompterTester.assertCallAll() - }) - - it('[entry: appbuilder node] should throw an error when running two build processes in parallel for the same template', async () => { - const prompterTester = getPrompterTester() - const expectedSamAppLocation = { - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - } - await assert.rejects( - async () => { - await runInParallel(expectedSamAppLocation) - }, - (e: any) => { - assert.strictEqual(e instanceof ToolkitError, true) - assert.strictEqual(e.message, 'This template is already being built') - assert.strictEqual(e.code, 'BuildInProgress') - return true - } - ) - prompterTester.assertCallAll(undefined, 2) - }) - - it('[entry: command palette] use samconfig should instantiate correct process in terminal', async () => { - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigCompleteData)) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify parameter source for build', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - const items = quickPick.items - - assert.deepStrictEqual(items[1], { - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - }) - quickPick.acceptItem(quickPick.items[1]) - }) - .build() - - // Reset the spy before running the test to ensure clean state - spyRunInterminal.resetHistory() - - // Instead of await runBuild(), prefer this to avoid flakiness due to race condition - await delayedRunBuild() - - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - ['build', '--config-file', `${samconfigFile.fsPath}`, '--template', `${templateFile.fsPath}`], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - - verifyCorrectDependencyCall() - prompterTester.assertCallAll() - }) - }) - - describe(':( path', () => { - let appNode: AppNode - beforeEach(async () => { - mockGetSamCliPath = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ path: 'sam-cli-path' })) - - appNode = new AppNode({ - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - }) - await testFolder.write('samconfig.toml', samconfigCompleteData) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should abort when customer cancel build wizard', async () => { - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - picker.dispose() - }) - - try { - await runBuild(appNode) - assert.fail('should have thrown CancellationError') - } catch (error: any) { - assert(error instanceof CancellationError) - assert.strictEqual(error.agent, 'user') - } - }) - - it('should throw ToolkitError when build command fail', async () => { - const prompterTester = PrompterTester.init() - .handleQuickPick('Specify parameter source for build', async (quickPick) => { - await quickPick.untilReady() - assert.deepStrictEqual(quickPick.items[1].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[1]) - }) - .build() - - // Mock child process with required properties that get called in ProcessTerminal - mockSamBuildChildProcess = Object.create(ProcessUtilsModule.ChildProcess.prototype, { - stopped: { get: sandbox.stub().returns(false) }, - stop: { value: sandbox.stub().resolves({}) }, - run: { - value: sandbox.stub().resolves({ - exitCode: -1, - stdout: 'Mock build command execution failure', - stderr: 'Docker is unreachable.', - }), - }, - }) - mockChildProcessClass = sandbox.stub(ProcessUtilsModule, 'ChildProcess').returns(mockSamBuildChildProcess) - - try { - await runBuild(appNode) - assert.fail('should have thrown ToolkitError') - } catch (error: any) { - assert(error instanceof ToolkitError) - assert.strictEqual(error.message, 'Failed to build SAM template') - assert(error.details?.['terminal'] as unknown as vscode.Terminal) - assert.strictEqual((error.details?.['terminal'] as unknown as vscode.Terminal).name, 'SAM build') - } - prompterTester.assertCallAll() - }) - }) -}) - -async function runInParallel(samLocation: SamAppLocation): Promise<[SamBuildResult, SamBuildResult]> { - return Promise.all([runBuild(new AppNode(samLocation)), delayedRunBuild(samLocation)]) -} - -// We add a small delay to avoid the unlikely but possible race condition. -async function delayedRunBuild(samLocation?: SamAppLocation): Promise { - return new Promise(async (resolve, reject) => { - // Add a small delay before returning the build promise - setTimeout(() => { - // Do nothing, just let the delay pass - }, 20) - - const buildPromise = samLocation ? runBuild(new AppNode(samLocation)) : runBuild() - buildPromise.then(resolve).catch(reject) - }) -} - -function getPrompterTester() { - return PrompterTester.init() - .handleQuickPick('Specify parameter source for build', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - const items = quickPick.items - assert.strictEqual(quickPick.items.length, 2) - assert.deepStrictEqual(items[0], { data: ParamsSource.Specify, label: 'Specify build flags' }) - assert.deepStrictEqual(items[1].label, 'Use default values') - quickPick.acceptItem(quickPick.items[1]) - }) - .build() -} -function testResolveBuildFlags( - sandbox: sinon.SinonSandbox, - parsedVersion: SemVer, - buildFlags: string[], - expectedBuildFlags: string[] -) { - const pathAndVersionStub = sandbox.stub().resolves({ path: 'file:///path/to/cli', parsedVersion }) - sandbox.stub(utils, 'getSamCliPathAndVersion').callsFake(pathAndVersionStub) - return resolveBuildFlags(buildFlags, parsedVersion).then((resolvedBuildFlags) => { - assert.deepEqual(resolvedBuildFlags, expectedBuildFlags) - }) -} diff --git a/packages/core/src/test/shared/sam/cli/samCliBuild.test.ts b/packages/core/src/test/shared/sam/cli/samCliBuild.test.ts deleted file mode 100644 index 2082626c55..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliBuild.test.ts +++ /dev/null @@ -1,292 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import { makeUnexpectedExitCodeError } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { FileFunctions, SamCliBuildInvocation } from '../../../../shared/sam/cli/samCliBuild' -import { SamCliProcessInvoker } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { ChildProcessResult } from '../../../../shared/utilities/processUtils' -import { getTestLogger } from '../../../globalSetup.test' -import { assertArgNotPresent, assertArgsContainArgument } from './samCliTestUtils' -import { - assertLogContainsBadExitInformation, - BadExitCodeSamCliProcessInvoker, - TestSamCliProcessInvoker, -} from './testSamCliProcessInvoker' -import { fs } from '../../../../shared' - -describe('SamCliBuildInvocation', async function () { - class FakeChildProcessResult implements ChildProcessResult { - public exitCode: number = 0 - public error = undefined - public stdout: string = '' - public stderr: string = '' - } - - // Returns FakeChildProcessResult for each invoke - class ExtendedTestSamCliProcessInvoker extends TestSamCliProcessInvoker { - public constructor(onInvoke: (...args: any[]) => void) { - super((spawnOptions: SpawnOptions, ...args: any[]) => { - onInvoke(...args) - - return new FakeChildProcessResult() - }) - } - } - - let tempFolder: string - let placeholderTemplateFile: string - const badExitCodeProcessInvoker = new BadExitCodeSamCliProcessInvoker({}) - const nonRelevantArg = 'arg is not of interest to this test' - const fakeFileFunctions: FileFunctions = { - fileExists: async (filePath: string): Promise => true, - } - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - placeholderTemplateFile = path.join(tempFolder, 'template.yaml') - await fs.writeFile(placeholderTemplateFile, '') - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('invokes `sam build` with args', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assert.ok(args.length >= 2, 'Expected args to be present') - assert.strictEqual(args[0], 'build') - // --debug is present because tests run with "debug" log-level. #1403 - assert.strictEqual(args[1], '--debug') - assert.strictEqual(args[4], '--template') - assert.strictEqual(args[6], '--base-dir') - - // `extraArgs` are appended to the end. - assert.strictEqual(args[8], '--parameter-overrides') - assert.strictEqual(args[9], 'math=math') - assert.strictEqual(args[10], '--build-dir') - assert.strictEqual(args[11], 'my/build/dir/') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - baseDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - extraArgs: ['--build-dir', 'my/build/dir/'], - parameterOverrides: ['math=math'], - }).execute() - }) - - it('Passes Build Dir to sam cli', async function () { - const expectedBuildDir = '/build' - - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgsContainArgument(args, '--build-dir', expectedBuildDir) - }) - - await new SamCliBuildInvocation({ - buildDir: expectedBuildDir, - baseDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('Passes Base Dir to sam cli', async function () { - const expectedBaseDir = '/src' - - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgsContainArgument(args, '--base-dir', expectedBaseDir) - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - baseDir: expectedBaseDir, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('Does not pass Base Dir to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--base-dir') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('Passes Template to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgsContainArgument(args, '--template', placeholderTemplateFile) - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - baseDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('passes --use-container to sam cli if useContainer is true', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assert.notStrictEqual(args.indexOf('--use-container'), -1, 'Expected --use-container arg') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - useContainer: true, - }).execute() - }) - - it('does not pass --use-container to sam cli if useContainer is false', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--use-container') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - useContainer: false, - }).execute() - }) - - it('does not pass --use-container to sam cli if useContainer is undefined', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--use-container') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('passes --manifest to sam cli if manifestPath is set', async function () { - const expectedArg = 'mypath' - - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgsContainArgument(args, '--manifest', expectedArg) - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - manifestPath: expectedArg, - }).execute() - }) - - it('does not pass --manifest to sam cli if manifestPath is not set', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--manifest') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('Passes docker network to sam cli', async function () { - const expectedDockerNetwork = 'hello-world' - - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgsContainArgument(args, '--docker-network', expectedDockerNetwork) - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - dockerNetwork: expectedDockerNetwork, - }).execute() - }) - - it('Does not pass docker network to sam cli if undefined', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--docker-network') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('passes --skip-pull-image to sam cli if skipPullImage is true', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assert.notStrictEqual(args.indexOf('--skip-pull-image'), -1, 'Expected --skip-pull-image arg') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - skipPullImage: true, - }).execute() - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImageis false', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--skip-pull-image') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - skipPullImage: false, - }).execute() - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImage is undefined', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker((args: any[]) => { - assertArgNotPresent(args, '--skip-pull-image') - }) - - await new SamCliBuildInvocation({ - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: processInvoker, - }).execute() - }) - - it('throws on unexpected exit code', async function () { - const builder = new SamCliBuildInvocation( - { - buildDir: nonRelevantArg, - templatePath: placeholderTemplateFile, - invoker: badExitCodeProcessInvoker, - }, - { - file: fakeFileFunctions, - } - ) - - const expectedError = makeUnexpectedExitCodeError(badExitCodeProcessInvoker.error.message) - await assert.rejects(builder.execute(), expectedError, 'Expected error was not thrown') - - await assertLogContainsBadExitInformation( - getTestLogger(), - badExitCodeProcessInvoker.makeChildProcessResult(), - 0 - ) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliConfiguration.test.ts b/packages/core/src/test/shared/sam/cli/samCliConfiguration.test.ts deleted file mode 100644 index 476df03671..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliConfiguration.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import { SamCliSettings } from '../../../../shared/sam/cli/samCliSettings' -import { TestSettings } from '../../../utilities/testSettingsConfiguration' -import { fs } from '../../../../shared' - -describe('samCliConfiguration', function () { - let tempFolder: string - let settingsConfiguration: TestSettings - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - settingsConfiguration = new TestSettings() - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('uses config value when referencing file that exists', async function () { - const fakeCliLocation = path.join(tempFolder, 'fakeSamCli') - - await createSampleFile(fakeCliLocation) - const config = new SamCliSettings({} as any, settingsConfiguration) - await config.update('location', fakeCliLocation) - - assert.strictEqual(await config.getOrDetectSamCli().then((r) => r.path), fakeCliLocation) - }) - - it('calls location provider when config not set', async function () { - let timesCalled: number = 0 - - const config = new SamCliSettings( - { - getLocation: async () => { - timesCalled++ - - return undefined - }, - }, - settingsConfiguration - ) - - await config.getOrDetectSamCli() - - assert.strictEqual(timesCalled, 1) - }) - - it('location provider detects a file', async function () { - const fakeCliLocation = path.join(tempFolder, 'fakeSamCli') - - const config = new SamCliSettings( - { - getLocation: async () => ({ path: fakeCliLocation, version: '' }), - }, - settingsConfiguration - ) - - assert.strictEqual(config.get('location', ''), '') - assert.strictEqual(await config.getOrDetectSamCli().then((r) => r.path), fakeCliLocation) - }) - - it('location provider does not detect a file', async function () { - const config = new SamCliSettings( - { - getLocation: async () => undefined, - }, - settingsConfiguration - ) - - assert.strictEqual(await config.getOrDetectSamCli().then((r) => r.path), undefined) - }) - - async function createSampleFile(filename: string): Promise { - await fs.writeFile(filename, 'hi') - } -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliDeploy.test.ts b/packages/core/src/test/shared/sam/cli/samCliDeploy.test.ts deleted file mode 100644 index 383418ee9f..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliDeploy.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { makeUnexpectedExitCodeError } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { runSamCliDeploy, SamCliDeployParameters } from '../../../../shared/sam/cli/samCliDeploy' -import { getTestLogger } from '../../../globalSetup.test' -import { - assertArgIsPresent, - assertArgNotPresent, - assertArgsContainArgument, - MockSamCliProcessInvoker, -} from './samCliTestUtils' -import { assertLogContainsBadExitInformation, BadExitCodeSamCliProcessInvoker } from './testSamCliProcessInvoker' - -describe('runSamCliDeploy', async function () { - const fakeRegion = 'region' - const fakeStackName = 'stackName' - const fakeS3BucketName = 'coolbucket' - const fakeTemplateFile = 'template' - let invokeCount: number - - beforeEach(function () { - invokeCount = 0 - }) - - it('does not include --parameter-overrides if there are no overrides', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgNotPresent(args, '--parameter-overrides') - }) - - await runSamCliDeploy(makeSampleSamCliDeployParameters(new Map()), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('includes overrides as a string of key=value pairs', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, '--parameter-overrides') - const overridesIndex = args.findIndex((arg) => arg === '--parameter-overrides') - assert.strictEqual(overridesIndex > -1, true) - assert.strictEqual(args.length >= overridesIndex + 3, true) - assert.strictEqual(args[overridesIndex + 1], 'key1=value1') - assert.strictEqual(args[overridesIndex + 2], 'key2=value2') - }) - - await runSamCliDeploy( - makeSampleSamCliDeployParameters( - new Map([ - ['key1', 'value1'], - ['key2', 'value2'], - ]) - ), - invoker - ) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('includes a template, stack name, bucket, and region', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgsContainArgument(args, '--template-file', fakeTemplateFile) - assertArgsContainArgument(args, '--stack-name', fakeStackName) - assertArgsContainArgument(args, '--region', fakeRegion) - assertArgsContainArgument(args, '--s3-bucket', fakeS3BucketName) - }) - - await runSamCliDeploy(makeSampleSamCliDeployParameters(new Map()), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('Passes all cloudformation capabilities', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, '--capabilities') - assertArgIsPresent(args, 'CAPABILITY_IAM') - assertArgIsPresent(args, 'CAPABILITY_NAMED_IAM') - assertArgIsPresent(args, 'CAPABILITY_AUTO_EXPAND') - }) - - await runSamCliDeploy(makeSampleSamCliDeployParameters(new Map()), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('throws on unexpected exit code', async function () { - const badExitCodeProcessInvoker = new BadExitCodeSamCliProcessInvoker({}) - - await assert.rejects( - runSamCliDeploy(makeSampleSamCliDeployParameters(new Map()), badExitCodeProcessInvoker), - makeUnexpectedExitCodeError(badExitCodeProcessInvoker.error.message), - 'Expected error was not thrown' - ) - - await assertLogContainsBadExitInformation( - getTestLogger(), - badExitCodeProcessInvoker.makeChildProcessResult(), - 0 - ) - }) - - function makeSampleSamCliDeployParameters( - parameterOverrides: Map, - ecrRepo: string | undefined = undefined - ): SamCliDeployParameters { - return { - environmentVariables: {}, - parameterOverrides: parameterOverrides, - region: fakeRegion, - stackName: fakeStackName, - s3Bucket: fakeS3BucketName, - templateFile: fakeTemplateFile, - } - } -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliFeatureRegistry.test.ts b/packages/core/src/test/shared/sam/cli/samCliFeatureRegistry.test.ts deleted file mode 100644 index 7efe8aff70..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliFeatureRegistry.test.ts +++ /dev/null @@ -1,179 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { detectFeaturesInTemplate } from '../../../../shared/sam/cli/samCliFeatureRegistry' -import * as samUtils from '../../../../shared/sam/utils' - -describe('samCliFeatureRegistry', () => { - let sandbox: sinon.SinonSandbox - let getSamCliPathAndVersionStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - getSamCliPathAndVersionStub = sandbox.stub(samUtils, 'getSamCliPathAndVersion') - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('detectFeaturesInTemplate', () => { - const testCases = [ - { - name: 'should detect Capacity Provider resource type when version is unsupported', - samVersion: '1.148.0', - template: { - Resources: { - MyCapacityProvider: { - Type: 'AWS::Serverless::CapacityProvider', - Properties: {}, - }, - }, - }, - expectedCount: 1, - expectedIds: ['CAPACITY_PROVIDER'], - }, - { - name: 'should return empty array when SAM CLI version meets all requirements', - samVersion: '1.149.0', - template: { - Resources: { - MyCapacityProvider: { - Type: 'AWS::Serverless::CapacityProvider', - Properties: {}, - }, - }, - }, - expectedCount: 0, - expectedIds: [], - }, - { - name: 'should return empty array when SAM CLI version exceeds all requirements', - samVersion: '2.0.0', - template: { - Resources: { - MyCapacityProvider: { - Type: 'AWS::Serverless::CapacityProvider', - Properties: {}, - }, - }, - }, - expectedCount: 0, - expectedIds: [], - }, - { - name: 'should detect CapacityProviderConfig property on Function', - samVersion: '1.148.0', - template: { - Resources: { - MyFunction: { - Type: 'AWS::Serverless::Function', - Properties: { - CapacityProviderConfig: 'MyCapacityProvider', - }, - }, - }, - }, - expectedCount: 1, - expectedIds: ['CAPACITY_PROVIDER_CONFIG'], - }, - { - name: 'should detect CapacityProviderConfig in Globals', - samVersion: '1.148.0', - template: { - Globals: { - Function: { - CapacityProviderConfig: 'MyCapacityProvider', - }, - }, - Resources: {}, - }, - expectedCount: 1, - expectedIds: ['CAPACITY_PROVIDER_CONFIG'], - }, - { - name: 'should detect multiple features', - samVersion: '1.148.0', - template: { - Resources: { - MyCapacityProvider: { - Type: 'AWS::Serverless::CapacityProvider', - Properties: {}, - }, - MyFunction: { - Type: 'AWS::Serverless::Function', - Properties: { - CapacityProviderConfig: 'MyCapacityProvider', - }, - }, - }, - }, - expectedCount: 2, - expectedIds: ['CAPACITY_PROVIDER', 'CAPACITY_PROVIDER_CONFIG'], - }, - { - name: 'should not detect duplicate features', - samVersion: '1.148.0', - template: { - Resources: { - MyFunction1: { - Type: 'AWS::Serverless::Function', - Properties: { - CapacityProviderConfig: 'MyCapacityProvider', - }, - }, - MyFunction2: { - Type: 'AWS::Serverless::Function', - Properties: { - CapacityProviderConfig: 'MyCapacityProvider', - }, - }, - }, - }, - expectedCount: 1, - expectedIds: ['CAPACITY_PROVIDER_CONFIG'], - }, - { - name: 'should return empty array for template without special features', - samVersion: '1.148.0', - template: { - Resources: { - MyFunction: { - Type: 'AWS::Serverless::Function', - Properties: { - Runtime: 'nodejs18.x', - }, - }, - }, - }, - expectedCount: 0, - expectedIds: [], - }, - { - name: 'should handle empty template', - samVersion: '1.148.0', - template: {}, - expectedCount: 0, - expectedIds: [], - }, - ] - - for (const { name, samVersion, template, expectedCount, expectedIds } of testCases) { - it(name, async () => { - getSamCliPathAndVersionStub.resolves({ path: '/path/to/sam', parsedVersion: samVersion }) - - const { unsupported, version } = await detectFeaturesInTemplate(template) - - assert.strictEqual(version, samVersion) - assert.strictEqual(unsupported.length, expectedCount) - for (const id of expectedIds) { - assert(unsupported.some((f) => f.id === id)) - } - }) - } - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliInfo.test.ts b/packages/core/src/test/shared/sam/cli/samCliInfo.test.ts deleted file mode 100644 index dc27ad4ed8..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliInfo.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SamCliInfoInvocation, SamCliInfoResponse } from '../../../../shared/sam/cli/samCliInfo' - -describe('SamCliInfoInvocation', async function () { - class TestSamCliInfoCommand extends SamCliInfoInvocation { - public override convertOutput(text: string): SamCliInfoResponse | undefined { - return super.convertOutput(text) - } - } - - it('converts sam info response to SamCliInfoResponse', async function () { - const response = new TestSamCliInfoCommand('').convertOutput('{"version": "1.2.3"}') - - assert.ok(response) - assert.strictEqual(response!.version, '1.2.3') - }) - - it('converts sam info response containing unexpected fields to SamCliInfoResponse', async function () { - const response = new TestSamCliInfoCommand('').convertOutput('{"version": "1.2.3", "bananas": "123"}') - - assert.ok(response) - assert.strictEqual(response!.version, '1.2.3') - }) - - it('converts sam info response without version to SamCliInfoResponse', async function () { - const response = new TestSamCliInfoCommand('').convertOutput('{}') - - assert.ok(response) - assert.strictEqual(response!.version, undefined) - }) - - it('converts non-response to undefined', async function () { - for (const output of ['qwerty', '{"version": "1.2.3"} you have no new email messages']) { - const response = new TestSamCliInfoCommand('').convertOutput(output) - - assert.strictEqual(response, undefined, `Expected text to not parse: ${output}`) - } - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliInit.test.ts b/packages/core/src/test/shared/sam/cli/samCliInit.test.ts deleted file mode 100644 index 854fdc9161..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliInit.test.ts +++ /dev/null @@ -1,238 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports -import { - eventBridgeStarterAppTemplate, - getSamCliTemplateParameter, - helloWorldTemplate, -} from '../../../../lambda/models/samTemplates' -import { SamCliContext } from '../../../../shared/sam/cli/samCliContext' -import { runSamCliInit, SamCliInitArgs } from '../../../../shared/sam/cli/samCliInit' -import { SamCliProcessInvoker, makeUnexpectedExitCodeError } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { ChildProcessResult } from '../../../../shared/utilities/processUtils' -import { getTestLogger } from '../../../globalSetup.test' -import { assertArgIsPresent, assertArgNotPresent, assertArgsContainArgument } from './samCliTestUtils' -import { - assertLogContainsBadExitInformation, - BadExitCodeSamCliProcessInvoker, - TestSamCliProcessInvoker, -} from './testSamCliProcessInvoker' - -import { SchemaTemplateExtraContext } from '../../../../eventSchemas/templates/schemasAppTemplateUtils' -import { FakeSamCliValidator } from '../../../fakeExtensionContext' - -describe('runSamCliInit', async function () { - class FakeChildProcessResult implements ChildProcessResult { - public exitCode: number = 0 - public error = undefined - public stdout: string = '' - public stderr: string = '' - } - - // Returns FakeChildProcessResult for each invoke - class ExtendedTestSamCliProcessInvoker extends TestSamCliProcessInvoker { - public constructor(onInvoke: (spawnOptions: SpawnOptions, ...args: any[]) => void) { - super((spawnOptions: SpawnOptions, ...args: any[]) => { - onInvoke(spawnOptions, ...args) - - return new FakeChildProcessResult() - }) - } - } - - const defaultFakeValidator = new FakeSamCliValidator() - - const sampleDependencyManager = 'npm' - - let sampleSamInitArgs: SamCliInitArgs - - before(function () { - sampleSamInitArgs = { - name: 'qwerty', - location: '/some/path/to/code.js', - runtime: 'nodejs14.x', - template: helloWorldTemplate, - dependencyManager: sampleDependencyManager, - } - }) - - describe('runSamCliInit with HelloWorld template', async function () { - it('Passes init command to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assert.ok(args.length > 0, 'Expected args to be present') - assert.strictEqual(args[0], 'init', 'Expected first arg to be the init command') - } - ) - - const context: SamCliContext = { - validator: defaultFakeValidator, - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('Passes name to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgsContainArgument(args, '--name', sampleSamInitArgs.name) - } - ) - - const context: SamCliContext = { - validator: defaultFakeValidator, - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('Passes location to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assert.strictEqual(spawnOptions.cwd, sampleSamInitArgs.location, 'Unexpected cwd') - } - ) - - const context: SamCliContext = { - validator: defaultFakeValidator, - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('Passes runtime to sam cli', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgsContainArgument(args, '--runtime', sampleSamInitArgs.runtime!) - } - ) - - const context: SamCliContext = { - validator: defaultFakeValidator, - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('throws on unexpected exit code', async function () { - const badExitCodeProcessInvoker = new BadExitCodeSamCliProcessInvoker({}) - const context: SamCliContext = { - validator: defaultFakeValidator, - invoker: badExitCodeProcessInvoker, - } - - const expectedError = makeUnexpectedExitCodeError(badExitCodeProcessInvoker.error.message) - await assert.rejects( - runSamCliInit(sampleSamInitArgs, context), - expectedError, - 'Expected error was not thrown' - ) - - await assertLogContainsBadExitInformation( - getTestLogger(), - badExitCodeProcessInvoker.makeChildProcessResult(), - 0 - ) - }) - - it('Passes --no-interactive', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgIsPresent(args, '--no-interactive') - } - ) - - const context: SamCliContext = { - validator: new FakeSamCliValidator(), - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('Passes --app-template', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgsContainArgument(args, '--app-template', getSamCliTemplateParameter(helloWorldTemplate)) - assertArgNotPresent(args, '--extra-content') - } - ) - - const context: SamCliContext = { - validator: new FakeSamCliValidator(), - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - - it('Passes --dependency-manager', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgsContainArgument(args, '--dependency-manager', sampleDependencyManager) - } - ) - - const context: SamCliContext = { - validator: new FakeSamCliValidator(), - invoker: processInvoker, - } - - await runSamCliInit(sampleSamInitArgs, context) - }) - }) - - describe('runSamCliInit With EventBridgeStartAppTemplate', async function () { - let extraContent: SchemaTemplateExtraContext - let samInitArgsWithExtraContent: SamCliInitArgs - - before(function () { - // eslint-disable-next-line @typescript-eslint/naming-convention - extraContent = { - AWS_Schema_registry: 'testRegistry', - AWS_Schema_name: 'testSchema', - AWS_Schema_root: 'test', - AWS_Schema_source: 'AWS', - AWS_Schema_detail_type: 'ec2', - user_agent: 'testAgent', - } - - samInitArgsWithExtraContent = { - name: 'qwerty', - location: '/some/path/to/code.js', - runtime: 'python3.9', - template: eventBridgeStarterAppTemplate, - extraContent: extraContent, - dependencyManager: sampleDependencyManager, - } - }) - - it('Passes --extra-context for eventBridgeStarterAppTemplate', async function () { - const processInvoker: SamCliProcessInvoker = new ExtendedTestSamCliProcessInvoker( - (spawnOptions: SpawnOptions, args: any[]) => { - assertArgsContainArgument( - args, - '--app-template', - getSamCliTemplateParameter(eventBridgeStarterAppTemplate) - ) - assertArgsContainArgument(args, '--extra-context', JSON.stringify(extraContent)) - } - ) - - const context: SamCliContext = { - validator: new FakeSamCliValidator(), - invoker: processInvoker, - } - - await runSamCliInit(samInitArgsWithExtraContent, context) - }) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliInvokerUtils.test.ts b/packages/core/src/test/shared/sam/cli/samCliInvokerUtils.test.ts deleted file mode 100644 index 882166eab4..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliInvokerUtils.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - addTelemetryEnvVar, - collectSamErrors, - logAndThrowIfUnexpectedExitCode, - makeUnexpectedExitCodeError, -} from '../../../../shared/sam/cli/samCliInvokerUtils' -import { getTestLogger } from '../../../globalSetup.test' -import { assertLogContainsBadExitInformation } from './testSamCliProcessInvoker' - -describe('logAndThrowIfUnexpectedExitCode', async function () { - it('does not throw on expected exit code', async function () { - logAndThrowIfUnexpectedExitCode( - { - exitCode: 123, - error: undefined, - stderr: '', - stdout: '', - }, - 123 - ) - }) - - it('throws on unexpected exit code', async function () { - const exitError = new Error('bad result') - const finalError = makeUnexpectedExitCodeError(exitError.message) - const childProcessResult = { - exitCode: 123, - error: exitError, - stderr: 'stderr text', - stdout: 'stdout text', - } - - assert.throws( - () => logAndThrowIfUnexpectedExitCode(childProcessResult, 456), - finalError, - 'Correct error was not thrown' - ) - await assertLogContainsBadExitInformation(getTestLogger(), childProcessResult, 456) - }) -}) - -/** Prepends ESC control character to `text`. */ -function prependEscapeCode(text: string): string { - return String.fromCodePoint(27) + text -} - -describe('collectSamErrors()', async () => { - it('collects messages', async () => { - const input = ` - This line is ignored - foo bar Error: xxx Docker is not reachable!! - another line - foo Error: user is bored Error: - Error: Running AWS SAM projects locally requires Docker. - 'urllib3.exceptions.ProtocolError: ('Connection aborted.', FileNotFoundError(2, 'No such file or directory'))' - ok - ${prependEscapeCode('[33m known escape sequence')} - ok again - ${prependEscapeCode('[100m unknown escape sequence')} - ` - const result = collectSamErrors(input) - assert.deepStrictEqual(result, [ - 'Docker is not reachable', - 'user is bored Error:', - 'Running AWS SAM projects locally requires Docker.', - 'known escape sequence', - ]) - }) -}) - -describe('addTelemetryEnvVar', async function () { - it('adds a new variable, preserving the existing contents', async function () { - const result = await addTelemetryEnvVar({ - cwd: '/foo', - env: { AWS_REGION: 'us-east-1' }, - }) - - assert.deepStrictEqual(result, { - cwd: '/foo', - env: { - AWS_TOOLING_USER_AGENT: result.env?.['AWS_TOOLING_USER_AGENT'], - SAM_CLI_TELEMETRY: '0', - AWS_REGION: 'us-east-1', - }, - }) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliListResources.test.ts b/packages/core/src/test/shared/sam/cli/samCliListResources.test.ts deleted file mode 100644 index 51efce4af3..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliListResources.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { runSamCliListResource, SamCliListResourcesParameters } from '../../../../shared/sam/cli/samCliListResources' -import { assertArgIsPresent, assertArgsContainArgument, MockSamCliProcessInvoker } from './samCliTestUtils' -import { getTestLogger } from '../../../globalSetup.test' -import * as featureRegistry from '../../../../shared/sam/cli/samCliFeatureRegistry' - -describe('runSamCliListResource', function () { - let invokeCount: number - let sandbox: sinon.SinonSandbox - let validateSamCliVersionForTemplateFileStub: sinon.SinonStub - let showWarningStub: sinon.SinonStub - const fakeTemplateFile = 'template.yaml' - const fakeStackName = 'testStack' - const fakeRegion = 'us-west-2' - const fakeProjectRoot = { fsPath: '/project/root' } as any - - beforeEach(function () { - invokeCount = 0 - sandbox = sinon.createSandbox() - validateSamCliVersionForTemplateFileStub = sandbox - .stub(featureRegistry, 'validateSamCliVersionForTemplateFile') - .resolves() - showWarningStub = sandbox.stub(featureRegistry, 'showWarningWithSamCliUpdateInstruction').resolves() - }) - - afterEach(function () { - sandbox.restore() - }) - - function makeSampleParameters(region?: string): SamCliListResourcesParameters { - return { - templateFile: fakeTemplateFile, - stackName: fakeStackName, - region: region, - projectRoot: fakeProjectRoot, - } - } - - it('includes template file, stack name, and JSON output format', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgsContainArgument(args, '--template-file', fakeTemplateFile) - assertArgsContainArgument(args, '--stack-name', fakeStackName) - assertArgsContainArgument(args, '--output', 'json') - }) - - await runSamCliListResource(makeSampleParameters(), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('includes the region argument if provided', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, '--region') - assertArgsContainArgument(args, '--region', fakeRegion) - }) - - await runSamCliListResource(makeSampleParameters(fakeRegion), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('does not include region argument if not provided', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assert.strictEqual(args.includes('--region'), false, 'Region argument should not be present') - }) - - await runSamCliListResource(makeSampleParameters(), invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('logs message on CFN error', async function () { - const message = 'Resource does not exist on Cloudformation' - const invoker = new MockSamCliProcessInvoker(() => { - throw new Error(message) - }) - const logger = getTestLogger() - - await runSamCliListResource(makeSampleParameters(), invoker) - - const logs = logger.getLoggedEntries() - assert.ok(logs.find((entry) => entry === message)) - }) - - it('validates template before invoking SAM CLI', async function () { - const invoker = new MockSamCliProcessInvoker(() => {}) - - await runSamCliListResource(makeSampleParameters(), invoker) - - assert.ok( - validateSamCliVersionForTemplateFileStub.calledOnce, - 'validateSamCliVersionForTemplateFile should be called once' - ) - }) - - it('returns empty array when validation fails', async function () { - const validationError = new Error('SAM CLI version too old') - validateSamCliVersionForTemplateFileStub.rejects(validationError) - const invoker = new MockSamCliProcessInvoker(() => { - invokeCount++ - }) - - const result = await runSamCliListResource(makeSampleParameters(), invoker) - - assert.strictEqual(invokeCount, 0, 'SAM CLI should not be invoked when validation fails') - assert.deepStrictEqual(result, [], 'Should return empty array on validation failure') - assert.ok(showWarningStub.calledOnce, 'Should show warning message') - }) - - it('shows user-friendly error message when validation fails', async function () { - const validationError = new Error('Your SAM CLI version does not support feature X') - validateSamCliVersionForTemplateFileStub.rejects(validationError) - const invoker = new MockSamCliProcessInvoker(() => {}) - - await runSamCliListResource(makeSampleParameters(), invoker) - - assert.ok(showWarningStub.calledOnce) - const errorMessage = showWarningStub.firstCall.args[0] - assert.ok(errorMessage.includes('Failed to run SAM CLI list resources')) - assert.ok(errorMessage.includes(validationError.message)) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliLocalInvoke.test.ts b/packages/core/src/test/shared/sam/cli/samCliLocalInvoke.test.ts deleted file mode 100644 index b1db2f4e16..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliLocalInvoke.test.ts +++ /dev/null @@ -1,223 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import { - SamCliLocalInvokeInvocation, - SamCliLocalInvokeInvocationArguments, - SamLocalInvokeCommand, - SamLocalInvokeCommandArgs, -} from '../../../../shared/sam/cli/samCliLocalInvoke' -import * as SamUtilsModule from '../../../../shared/sam/utils' -import { ChildProcess } from '../../../../shared/utilities/processUtils' -import { assertArgIsPresent, assertArgNotPresent, assertArgsContainArgument } from './samCliTestUtils' -import { fs } from '../../../../shared' -import { SamCliSettings } from '../../../../shared/sam/cli/samCliSettings' -import { isWin } from '../../../../shared/vscode/env' -import sinon from 'sinon' -import { SemVer } from 'semver' - -describe('SamCliLocalInvokeInvocation', async function () { - class TestSamLocalInvokeCommand implements SamLocalInvokeCommand { - public constructor(private readonly onInvoke: ({ ...params }: SamLocalInvokeCommandArgs) => void) {} - - public async invoke({ ...params }: SamLocalInvokeCommandArgs): Promise { - this.onInvoke(params) - return {} as ChildProcess // Fake, not used by tests. - } - } - - let tempFolder: string - let placeholderTemplateFile: string - let placeholderEventFile: string - const nonRelevantArg = 'arg is not of interest to this test' - let mockGetSamCliVersion: sinon.SinonStub - const validRuntime = 'python3.10' - const invalidRuntime = 'python2.7' - const validCliVersion = new SemVer('1.135.0') - const invalidCliVersion = new SemVer('1.134.0') - let sandbox: sinon.SinonSandbox - - before(async function () { - // File system search on windows can take a while. - if (isWin()) { - this.retries(3) - } - // This will place the result in the cache allowing all tests to run under same conditions. - await SamCliSettings.instance.getOrDetectSamCli() - }) - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - placeholderTemplateFile = path.join(tempFolder, 'template.yaml') - placeholderEventFile = path.join(tempFolder, 'event.json') - await fs.writeFile(placeholderTemplateFile, '') - await fs.writeFile(placeholderEventFile, '') - sandbox = sinon.createSandbox() - }) - - afterEach(async function () { - sandbox.restore() - await fs.delete(tempFolder, { recursive: true }) - }) - - function createTaskInvoker(onInvoke: (invokeArgs: SamLocalInvokeCommandArgs) => void): SamLocalInvokeCommand { - return new TestSamLocalInvokeCommand(onInvoke) - } - - async function executeInvocation( - options: Partial - ): Promise { - let invokeArgs: SamLocalInvokeCommandArgs = {} as SamLocalInvokeCommandArgs - const invocation = new SamCliLocalInvokeInvocation({ - templateResourceName: nonRelevantArg, - templatePath: placeholderTemplateFile, - eventPath: placeholderEventFile, - environmentVariablePath: nonRelevantArg, - invoker: createTaskInvoker((args: SamLocalInvokeCommandArgs) => { - invokeArgs = args - }), - ...options, - }) - await invocation.execute() - return invokeArgs - } - - it('invokes `sam local` with args', async function () { - mockGetSamCliVersion = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ parsedVersion: validCliVersion })) - - const taskInvoker: SamLocalInvokeCommand = new TestSamLocalInvokeCommand( - (invokeArgs: SamLocalInvokeCommandArgs) => { - assert(mockGetSamCliVersion.calledOnce) - assert.ok(invokeArgs.args.length >= 2, 'Expected args to be present') - assert.strictEqual(invokeArgs.args[0], 'local') - assert.strictEqual(invokeArgs.args[1], 'invoke') - // --debug is present because tests run with "debug" log-level. #1403 - assert.strictEqual(invokeArgs.args[2], '--debug') - assert.strictEqual(invokeArgs.args[4], '--template') - assert.strictEqual(invokeArgs.args[6], '--event') - assert.strictEqual(invokeArgs.args[8], '--env-vars') - // `extraArgs` are appended to the end. - assert.strictEqual(invokeArgs.args[10], '--build-dir') - assert.strictEqual(invokeArgs.args[11], 'my/build/dir/') - - // 'runtime' is the last argument. - assert.strictEqual(invokeArgs.args[invokeArgs.args.length - 2], '--runtime') - assert.strictEqual(invokeArgs.args[invokeArgs.args.length - 1], 'python3.10') - } - ) - - await new SamCliLocalInvokeInvocation({ - templateResourceName: nonRelevantArg, - templatePath: placeholderTemplateFile, - eventPath: placeholderEventFile, - environmentVariablePath: nonRelevantArg, - invoker: taskInvoker, - extraArgs: ['--build-dir', 'my/build/dir/'], - runtime: validRuntime, - }).execute() - }) - - it('Passes template resource name to sam cli', async function () { - const expectedResourceName = 'HelloWorldResource' - const invokeArgs = await executeInvocation({ templateResourceName: expectedResourceName }) - assertArgIsPresent(invokeArgs.args, expectedResourceName) - }) - - it('Passes template path to sam cli', async function () { - const invokeArgs = await executeInvocation({}) - assertArgsContainArgument(invokeArgs.args, '--template', placeholderTemplateFile) - }) - - it('Passes event path to sam cli', async function () { - const invokeArgs = await executeInvocation({}) - assertArgsContainArgument(invokeArgs.args, '--event', placeholderEventFile) - }) - - it('Passes env-vars path to sam cli', async function () { - const expectedEnvVarsPath = 'envvars.json' - const invokeArgs = await executeInvocation({ environmentVariablePath: expectedEnvVarsPath }) - assertArgsContainArgument(invokeArgs.args, '--env-vars', expectedEnvVarsPath) - }) - - it('Passes debug port to sam cli', async function () { - const expectedDebugPort = '1234' - const invokeArgs = await executeInvocation({ debugPort: expectedDebugPort }) - assertArgsContainArgument(invokeArgs.args, '-d', expectedDebugPort) - }) - - it('undefined debug port does not pass to sam cli', async function () { - const invokeArgs = await executeInvocation({ debugPort: undefined }) - assertArgNotPresent(invokeArgs.args, '-d') - }) - - it('Passes docker network to sam cli', async function () { - const expectedDockerNetwork = 'hello-world' - const invokeArgs = await executeInvocation({ dockerNetwork: expectedDockerNetwork }) - assertArgsContainArgument(invokeArgs.args, '--docker-network', expectedDockerNetwork) - }) - - it('Does not pass docker network to sam cli when undefined', async function () { - const invokeArgs = await executeInvocation({ dockerNetwork: undefined }) - assertArgNotPresent(invokeArgs.args, '--docker-network') - }) - - it('passes --skip-pull-image to sam cli if skipPullImage is true', async function () { - const invokeArgs = await executeInvocation({ skipPullImage: true }) - assertArgIsPresent(invokeArgs.args, '--skip-pull-image') - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImage is false', async function () { - const invokeArgs = await executeInvocation({ skipPullImage: false }) - assertArgNotPresent(invokeArgs.args, '--skip-pull-image') - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImage is undefined', async function () { - const invokeArgs = await executeInvocation({ skipPullImage: undefined }) - assertArgNotPresent(invokeArgs.args, '--skip-pull-image') - }) - - it('Passes debuggerPath to sam cli', async function () { - const expectedDebuggerPath = path.join('foo', 'bar') - const invokeArgs = await executeInvocation({ debuggerPath: expectedDebuggerPath }) - assertArgsContainArgument(invokeArgs.args, '--debugger-path', expectedDebuggerPath) - }) - - it('Does not pass debuggerPath to sam cli when undefined', async function () { - const invokeArgs = await executeInvocation({}) - assertArgNotPresent(invokeArgs.args, '--debugger-path') - }) - - it('Passes runtime to sam cli', async function () { - mockGetSamCliVersion = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ parsedVersion: new SemVer('1.135.0') })) - - const invokeArgs = await executeInvocation({ runtime: validRuntime }) - assertArgsContainArgument(invokeArgs.args, '--runtime', validRuntime) - }) - - it('Does not pass runtime to sam cli when version < 1.135.0', async function () { - mockGetSamCliVersion = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ parsedVersion: invalidCliVersion })) - - const invokeArgs = await executeInvocation({ runtime: validRuntime }) - assertArgNotPresent(invokeArgs.args, '--runtime') - }) - - it('Does not pass runtime to sam cli when runtime is deprecated', async function () { - mockGetSamCliVersion = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ parsedVersion: validCliVersion })) - - const invokeArgs = await executeInvocation({ runtime: invalidRuntime }) - assertArgNotPresent(invokeArgs.args, '--runtime') - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliPackage.test.ts b/packages/core/src/test/shared/sam/cli/samCliPackage.test.ts deleted file mode 100644 index 4bae0f7c4d..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliPackage.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { makeUnexpectedExitCodeError } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { runSamCliPackage, SamCliPackageParameters } from '../../../../shared/sam/cli/samCliPackage' -import { getTestLogger } from '../../../globalSetup.test' -import { assertArgNotPresent, assertArgsContainArgument, MockSamCliProcessInvoker } from './samCliTestUtils' -import { assertLogContainsBadExitInformation, BadExitCodeSamCliProcessInvoker } from './testSamCliProcessInvoker' - -describe('SamCliPackageInvocation', async function () { - let invokeCount: number - const packageParameters: SamCliPackageParameters = { - sourceTemplateFile: 'template', - destinationTemplateFile: 'output', - environmentVariables: {}, - region: 'region', - s3Bucket: 'bucket', - } - - beforeEach(function () { - invokeCount = 0 - }) - - it('includes a template, s3 bucket, output template file, and region', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgsContainArgument(args, '--template-file', 'template') - assertArgsContainArgument(args, '--s3-bucket', 'bucket') - assertArgsContainArgument(args, '--output-template-file', 'output') - assertArgsContainArgument(args, '--region', 'region') - assertArgNotPresent(args, '--image-repository') - }) - - await runSamCliPackage(packageParameters, invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('includes a template, s3 bucket, output template file, region, and repo', async function () { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgsContainArgument(args, '--template-file', 'template') - assertArgsContainArgument(args, '--s3-bucket', 'bucket') - assertArgsContainArgument(args, '--output-template-file', 'output') - assertArgsContainArgument(args, '--region', 'region') - assertArgsContainArgument(args, '--image-repository', 'ecrRepo') - }) - - await runSamCliPackage({ ...packageParameters, ecrRepo: 'ecrRepo' }, invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('throws on unexpected exit code', async function () { - const badExitCodeProcessInvoker = new BadExitCodeSamCliProcessInvoker({}) - const expectedError = makeUnexpectedExitCodeError(badExitCodeProcessInvoker.error.message) - await assert.rejects( - runSamCliPackage(packageParameters, badExitCodeProcessInvoker), - expectedError, - 'Expected error was not thrown' - ) - - await assertLogContainsBadExitInformation( - getTestLogger(), - badExitCodeProcessInvoker.makeChildProcessResult(), - 0 - ) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliRemoteTestEvent.test.ts b/packages/core/src/test/shared/sam/cli/samCliRemoteTestEvent.test.ts deleted file mode 100644 index 928fd8fc07..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliRemoteTestEvent.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - runSamCliRemoteTestEvents, - SamCliRemoteTestEventsParameters, - TestEventsOperation, -} from '../../../../shared/sam/cli/samCliRemoteTestEvent' -import assert from 'assert' -import { makeUnexpectedExitCodeError } from '../../../../shared/sam/cli/samCliInvokerUtils' -import { getTestLogger } from '../../../globalSetup.test' -import { - assertArgIsPresent, - assertArgNotPresent, - assertArgsContainArgument, - MockSamCliProcessInvoker, -} from './samCliTestUtils' -import { assertLogContainsBadExitInformation, BadExitCodeSamCliProcessInvoker } from './testSamCliProcessInvoker' - -describe('runSamCliRemoteTestEvents', () => { - const fakeFunctionArn = 'arn::aws:123456' - const fakeName = 'name' - const fakeEventSample = '{testKey: testValue}' - let invokeCount: number - - beforeEach(() => { - invokeCount = 0 - }) - - it('should call invoker with correct arguments for List operation', async () => { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, fakeFunctionArn) - assertArgNotPresent(args, '--file') - assertArgNotPresent(args, '--name') - assertArgIsPresent(args, 'remote') - assertArgIsPresent(args, 'test-event') - assertArgIsPresent(args, TestEventsOperation.List) - }) - - const params: SamCliRemoteTestEventsParameters = { - functionArn: fakeFunctionArn, - operation: TestEventsOperation.List, - } - - await runSamCliRemoteTestEvents(params, invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('should call invoker with correct arguments for Get operation with name', async () => { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, fakeFunctionArn) - assertArgsContainArgument(args, '--name', fakeName) - assertArgIsPresent(args, 'remote') - assertArgIsPresent(args, 'test-event') - assertArgIsPresent(args, TestEventsOperation.Get) - assertArgNotPresent(args, '--file') - }) - - const params: SamCliRemoteTestEventsParameters = { - functionArn: fakeFunctionArn, - operation: TestEventsOperation.Get, - name: fakeName, - } - - await runSamCliRemoteTestEvents(params, invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('should call invoker with correct arguments for Put operation with event sample', async () => { - const invoker = new MockSamCliProcessInvoker((args) => { - invokeCount++ - assertArgIsPresent(args, fakeFunctionArn) - assertArgsContainArgument(args, '--name', fakeName) - assertArgNotPresent(args, 'list') - assertArgIsPresent(args, 'remote') - assertArgIsPresent(args, 'test-event') - assertArgIsPresent(args, '--file') - assertArgIsPresent(args, TestEventsOperation.Put) - }) - - const params: SamCliRemoteTestEventsParameters = { - functionArn: fakeFunctionArn, - operation: TestEventsOperation.Put, - eventSample: fakeEventSample, - name: fakeName, - } - - await runSamCliRemoteTestEvents(params, invoker) - - assert.strictEqual(invokeCount, 1, 'Unexpected invoke count') - }) - - it('should show error message and return empty array on error', async () => { - const badExitCodeProcessInvoker = new BadExitCodeSamCliProcessInvoker({}) - - const params: SamCliRemoteTestEventsParameters = { - functionArn: fakeFunctionArn, - operation: TestEventsOperation.List, - } - - await assert.rejects( - runSamCliRemoteTestEvents(params, badExitCodeProcessInvoker), - makeUnexpectedExitCodeError(badExitCodeProcessInvoker.error.message), - 'Expected error was not thrown' - ) - - await assertLogContainsBadExitInformation( - getTestLogger(), - badExitCodeProcessInvoker.makeChildProcessResult(), - 0 - ) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliStartApi.test.ts b/packages/core/src/test/shared/sam/cli/samCliStartApi.test.ts deleted file mode 100644 index 8964d7740f..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliStartApi.test.ts +++ /dev/null @@ -1,164 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { join } from 'path' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import { buildSamCliStartApiArguments } from '../../../../shared/sam/cli/samCliStartApi' -import { assertArgIsPresent, assertArgNotPresent, assertArgsContainArgument } from './samCliTestUtils' -import { fs } from '../../../../shared' - -describe('SamCliStartApi', async function () { - let tempFolder: string - let placeholderTemplateFile: string - let placeholderEventFile: string - const nonRelevantArg = 'arg is not of interest to this test' - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - placeholderTemplateFile = join(tempFolder, 'template.yaml') - placeholderEventFile = join(tempFolder, 'event.json') - await fs.writeFile(placeholderTemplateFile, '') - await fs.writeFile(placeholderEventFile, '') - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - it('invokes `sam local start-api` with correct args', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - extraArgs: ['--build-dir', 'my/build/dir/'], - }) - - assert.ok(invokeArgs.length >= 2, 'Expected args to be present') - assert.strictEqual(invokeArgs[0], 'local') - assert.strictEqual(invokeArgs[1], 'start-api') - // --debug is present because tests run with "debug" log-level. #1403 - assert.strictEqual(invokeArgs[2], '--debug') - assert.strictEqual(invokeArgs[3], '--template') - assert.strictEqual(invokeArgs[5], '--env-vars') - - // `extraArgs` are appended to the end. - assert.strictEqual(invokeArgs[7], '--build-dir') - assert.strictEqual(invokeArgs[8], 'my/build/dir/') - }) - - it('Passes template path to sam cli', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - }) - - assertArgsContainArgument(invokeArgs, '--template', placeholderTemplateFile) - }) - - it('Passes env-vars path to sam cli', async function () { - const expectedEnvVarsPath = 'envvars.json' - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: expectedEnvVarsPath, - }) - - assertArgsContainArgument(invokeArgs, '--env-vars', expectedEnvVarsPath) - }) - - it('Passes debug port to sam cli', async function () { - const expectedDebugPort = '1234' - - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - debugPort: expectedDebugPort, - }) - - assertArgsContainArgument(invokeArgs, '--debug-port', expectedDebugPort) - }) - - it('undefined debug port does not pass to sam cli', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - debugPort: undefined, - }) - - assertArgNotPresent(invokeArgs, '--debug-port') - }) - - it('Passes docker network to sam cli', async function () { - const expectedDockerNetwork = 'hello-world' - - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - dockerNetwork: expectedDockerNetwork, - }) - - assertArgsContainArgument(invokeArgs, '--docker-network', expectedDockerNetwork) - }) - - it('Does not pass docker network to sam cli when undefined', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - dockerNetwork: undefined, - }) - - assertArgNotPresent(invokeArgs, '--docker-network') - }) - - it('passes --skip-pull-image to sam cli if skipPullImage is true', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - skipPullImage: true, - }) - - assertArgIsPresent(invokeArgs, '--skip-pull-image') - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImage is false', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - skipPullImage: false, - }) - - assertArgNotPresent(invokeArgs, '--skip-pull-image') - }) - - it('does not pass --skip-pull-image to sam cli if skipPullImage is undefined', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - skipPullImage: undefined, - }) - - assertArgNotPresent(invokeArgs, '--skip-pull-image') - }) - - it('Passes debuggerPath to sam cli', async function () { - const expectedDebuggerPath = join('foo', 'bar') - - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - debuggerPath: expectedDebuggerPath, - }) - - assertArgsContainArgument(invokeArgs, '--debugger-path', expectedDebuggerPath) - }) - - it('Does not pass debuggerPath to sam cli when undefined', async function () { - const invokeArgs = await buildSamCliStartApiArguments({ - templatePath: placeholderTemplateFile, - environmentVariablePath: nonRelevantArg, - }) - - assertArgNotPresent(invokeArgs, '--debugger-path') - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliTestUtils.ts b/packages/core/src/test/shared/sam/cli/samCliTestUtils.ts deleted file mode 100644 index e3c541f10f..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliTestUtils.ts +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - makeRequiredSamCliProcessInvokeOptions, - SamCliProcessInvokeOptions, - SamCliProcessInvoker, -} from '../../../../shared/sam/cli/samCliInvokerUtils' -import { ChildProcessResult } from '../../../../shared/utilities/processUtils' - -export class MockSamCliProcessInvoker implements SamCliProcessInvoker { - public constructor(private readonly validateArgs: (args: string[]) => void) {} - - public stop(): void {} - - public async invoke(options?: SamCliProcessInvokeOptions): Promise { - const invokeSettings = makeRequiredSamCliProcessInvokeOptions(options) - - this.validateArgs(invokeSettings.arguments) - - return { - exitCode: 0, - } as any as ChildProcessResult - } -} - -export function assertArgsContainArgument(args: any[], argOfInterest: string, expectedArgValue: string) { - const argPos = args.indexOf(argOfInterest) - assert.notStrictEqual(argPos, -1, `Expected arg '${argOfInterest}' was not found`) - assert.ok(args.length >= argPos + 2, `Args does not contain a value for '${argOfInterest}'`) - assert.strictEqual(args[argPos + 1], expectedArgValue, `Arg '${argOfInterest}' did not have expected value`) -} - -export function assertArgIsPresent(args: any[], argOfInterest: string) { - assert.notStrictEqual(args.indexOf(argOfInterest), -1, `Expected '${argOfInterest}' arg`) -} - -export function assertArgNotPresent(args: any[], argOfInterest: string) { - assert.strictEqual(args.indexOf(argOfInterest), -1, `Did not expect '${argOfInterest}' arg`) -} diff --git a/packages/core/src/test/shared/sam/cli/samCliValidationNotification.test.ts b/packages/core/src/test/shared/sam/cli/samCliValidationNotification.test.ts deleted file mode 100644 index 3156e2afb1..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliValidationNotification.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' - -import { - InvalidSamCliError, - InvalidSamCliVersionError, - SamCliNotFoundError, - SamCliVersionValidation, - SamCliVersionValidatorResult, -} from '../../../../shared/sam/cli/samCliValidator' -import { - SamCliValidationNotification, - SamCliValidationNotificationAction, - getInvalidSamMsg, -} from '../../../../shared/sam/cli/samCliValidationNotification' - -describe('getInvalidSamMsg', async function () { - const fakeSamCliValidationNotification: SamCliValidationNotification = { - show: () => { - throw new Error('show is unused') - }, - } - const actionLabelUpdateSamCli = 'Install latest SAM CLI' - const actionLabelUpdateToolkit = 'Install latest AWS Toolkit' - - it('handles SamCliNotFoundError', async function () { - getInvalidSamMsg( - new SamCliNotFoundError(), - (message: string, actions: SamCliValidationNotificationAction[]): SamCliValidationNotification => { - assert.ok(message.includes('Cannot find SAM CLI'), `unexpected validation message: ${message}`) - assert.strictEqual(actions.length, 1, 'unexpected action count') - assert.strictEqual( - actions[0].label(), - actionLabelUpdateSamCli, - `unexpected action label: ${actions[0].label()}` - ) - - return fakeSamCliValidationNotification - } - ) - }) - - const versionValidationTestScenarios = [ - { - situation: 'SAM CLI Version is too low', - versionValidation: SamCliVersionValidation.VersionTooLow, - messageFragment: 'Update SAM CLI.', - actionLabel: actionLabelUpdateSamCli, - }, - { - situation: 'SAM CLI Version is too high', - versionValidation: SamCliVersionValidation.VersionTooHigh, - messageFragment: 'Update AWS Toolkit.', - actionLabel: actionLabelUpdateToolkit, - }, - { - situation: 'SAM CLI failed to run', - versionValidation: SamCliVersionValidation.VersionNotParseable, - messageFragment: process.platform === 'win32' ? 'known issues' : 'SAM CLI failed to run', - actionLabel: actionLabelUpdateSamCli, - }, - ] - - for (const test of versionValidationTestScenarios) { - it(`handles InvalidSamCliVersionError - ${test.situation}`, async () => { - const validatorResult: SamCliVersionValidatorResult = { - version: '1.2.3', - validation: test.versionValidation, - } - const error = new InvalidSamCliVersionError(validatorResult) - - getInvalidSamMsg( - error, - (message: string, actions: SamCliValidationNotificationAction[]): SamCliValidationNotification => { - const hasMsg = message.includes(test.messageFragment) - const hasVersion = - test.versionValidation === SamCliVersionValidation.VersionNotParseable || - message.includes(validatorResult.version!) - assert.ok(hasMsg && hasVersion, `unexpected validation message: ${message}`) - assert.strictEqual(actions.length, 1, 'unexpected action count') - assert.strictEqual( - actions[0].label(), - test.actionLabel, - `unexpected action label: ${actions[0].label()}` - ) - - return fakeSamCliValidationNotification - } - ) - }) - } - - it('handles Unexpected input', async function () { - getInvalidSamMsg( - new InvalidSamCliError('different error'), - (message: string, actions: SamCliValidationNotificationAction[]): SamCliValidationNotification => { - assert.ok( - message.includes('Unexpected error while') && message.includes('different error'), - `unexpected validation message: ${message}` - ) - assert.strictEqual(actions.length, 0, 'unexpected actions found') - - return fakeSamCliValidationNotification - } - ) - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/samCliValidator.test.ts b/packages/core/src/test/shared/sam/cli/samCliValidator.test.ts deleted file mode 100644 index 15c1a57a13..0000000000 --- a/packages/core/src/test/shared/sam/cli/samCliValidator.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import globals from '../../../../shared/extensionGlobals' -import { SamCliInfoResponse } from '../../../../shared/sam/cli/samCliInfo' -import { - DefaultSamCliValidator, - maxSamCliVersionExclusive, - minSamCliVersion, - SamCliValidatorContext, - SamCliVersionValidation, -} from '../../../../shared/sam/cli/samCliValidator' - -describe('DefaultSamCliValidator', async function () { - class TestSamCliValidatorContext implements SamCliValidatorContext { - public samCliVersionId: string = new globals.clock.Date().valueOf().toString() - public getInfoCallCount: number = 0 - public mockSamLocation: string = '' - - public constructor(public samCliVersion: string) {} - - public async samCliLocation(): Promise { - return this.mockSamLocation - } - - public async getSamCliInfo(): Promise { - this.getInfoCallCount++ - - return { - version: this.samCliVersion, - } - } - } - - const scenarios = [ - { - situation: 'valid version string', - version: minSamCliVersion, - expected: SamCliVersionValidation.Valid, - }, - { - situation: '"nightly" version', - version: '1.86.1-dev202306120901', - expected: SamCliVersionValidation.Valid, - }, - { - situation: 'version too low', - version: '0.1.0', - expected: SamCliVersionValidation.VersionTooLow, - }, - { - situation: 'version too high', - version: maxSamCliVersionExclusive, - expected: SamCliVersionValidation.VersionTooHigh, - }, - { - situation: 'version unparsable (empty string)', - version: '', - expected: SamCliVersionValidation.VersionNotParseable, - }, - { - situation: 'version unparsable (random text)', - version: 'what.in.tarnation', - expected: SamCliVersionValidation.VersionNotParseable, - }, - ] as const - - describe('detectValidSamCli', async function () { - for (const test of scenarios) { - it(`found SAM CLI, ${test.situation}`, async () => { - const validatorContext = new TestSamCliValidatorContext(test.version) - validatorContext.mockSamLocation = 'somesamclipath' - const samCliValidator = new DefaultSamCliValidator(validatorContext) - const actual = await samCliValidator.detectValidSamCli() - - assert.strictEqual(actual.samCliFound, true, 'Expected to find sam cli') - assert.strictEqual(actual.versionValidation?.version, test.version) - assert.strictEqual(actual.versionValidation?.validation, test.expected) - }) - } - - it('SAM CLI not found', async function () { - const validatorContext = new TestSamCliValidatorContext('') - validatorContext.mockSamLocation = '' - const samCliValidator = new DefaultSamCliValidator(validatorContext) - const actual = await samCliValidator.detectValidSamCli() - - assert.strictEqual(actual.samCliFound, false, 'Expected sam cli NOT found') - }) - }) - - describe('getVersionValidatorResult', async function () { - for (const test of scenarios) { - it(test.situation, async () => { - const validatorContext = new TestSamCliValidatorContext(test.version) - const samCliValidator = new DefaultSamCliValidator(validatorContext) - const actual = await samCliValidator.getVersionValidatorResult() - - assert.strictEqual(actual.version, test.version) - assert.strictEqual(actual.validation, test.expected) - }) - } - }) - - describe('validateSamCliVersion', async function () { - for (const test of scenarios) { - it(test.situation, async () => { - const validation = DefaultSamCliValidator.validateSamCliVersion(test.version) - assert.strictEqual(validation, test.expected) - }) - } - }) -}) diff --git a/packages/core/src/test/shared/sam/cli/testSamCliProcessInvoker.ts b/packages/core/src/test/shared/sam/cli/testSamCliProcessInvoker.ts deleted file mode 100644 index 8528b0c618..0000000000 --- a/packages/core/src/test/shared/sam/cli/testSamCliProcessInvoker.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SpawnOptions } from 'child_process' // eslint-disable-line no-restricted-imports - -import { isError } from 'lodash' -import { - makeRequiredSamCliProcessInvokeOptions, - SamCliProcessInvokeOptions, - SamCliProcessInvoker, -} from '../../../../shared/sam/cli/samCliInvokerUtils' -import { ChildProcessResult } from '../../../../shared/utilities/processUtils' -import { TestLogger } from '../../../testLogger' - -export class TestSamCliProcessInvoker implements SamCliProcessInvoker { - public constructor(private readonly onInvoke: (spawnOptions: SpawnOptions, ...args: any[]) => ChildProcessResult) {} - - public stop(): void {} - public async invoke(options?: SamCliProcessInvokeOptions): Promise { - const invokeOptions = makeRequiredSamCliProcessInvokeOptions(options) - - return this.onInvoke(invokeOptions.spawnOptions, invokeOptions.arguments) - } -} - -export class BadExitCodeSamCliProcessInvoker extends TestSamCliProcessInvoker { - public exitCode: number - public error: Error - public stdout: string - public stderr: string - - public constructor({ - exitCode = -1, - error = new Error('Bad Result'), - stdout = 'stdout message', - stderr = 'stderr message', - }: { - exitCode?: number - error?: Error - stdout?: string - stderr?: string - }) { - super((spawnOptions: SpawnOptions, ...args: any[]) => { - return this.makeChildProcessResult() - }) - - this.exitCode = exitCode - this.error = error - this.stdout = stdout - this.stderr = stderr - } - - public makeChildProcessResult(): ChildProcessResult { - const result: ChildProcessResult = { - exitCode: this.exitCode, - error: this.error, - stdout: this.stdout, - stderr: this.stderr, - } - - return result - } -} - -export class FakeChildProcessResult implements ChildProcessResult { - public exitCode: number - public error: Error | undefined - public stdout: string - public stderr: string - - public constructor({ exitCode = 0, stdout = '', stderr = '', ...params }: Partial) { - this.exitCode = exitCode - this.error = params.error - this.stdout = stdout - this.stderr = stderr - } -} - -export function assertErrorContainsBadExitMessage(actualError: Error, sourceErrorMessage: string) { - assert.strictEqual( - actualError.message, - `Error with child process: ${sourceErrorMessage}`, - 'Unexpected error message' - ) -} - -export async function assertLogContainsBadExitInformation( - logger: TestLogger, - errantChildProcessResult: ChildProcessResult, - expectedExitCode: number -): Promise { - const expectedTexts = [ - { - text: `SAM CLI failed (exitcode: ${errantChildProcessResult.exitCode}, expected ${expectedExitCode}`, - verifyMessage: 'Log message missing for exit code', - }, - ] - - const logText = logger - .getLoggedEntries() - .filter((x) => !isError(x)) - .join('\n') - for (const expectedText of expectedTexts) { - assert.ok(logText.includes(expectedText.text), expectedText.verifyMessage) - } -} diff --git a/packages/core/src/test/shared/sam/config.test.ts b/packages/core/src/test/shared/sam/config.test.ts deleted file mode 100644 index 8125d436da..0000000000 --- a/packages/core/src/test/shared/sam/config.test.ts +++ /dev/null @@ -1,460 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' - -import { TestFolder } from '../../testUtil' -import { samconfigCompleteData, samconfigCompleteDataWithoutGlobal, samconfigInvalidData } from './samTestUtils' -import { - createNewConfigFile, - getConfigFileUri, - SamConfig, - validateSamBuildConfig, - validateSamDeployConfig, - validateSamSyncConfig, - writeSamconfigGlobal, -} from '../../../shared/sam/config' -import assert from 'assert' -import { ToolkitError } from '../../../shared' - -import { assertLogsContain } from '../../globalSetup.test' -import { JsonMap } from '@iarna/toml' -import path from 'path' - -describe('SamConfig', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - }) - describe('fromConfigFileUri', () => { - it('should load the valid config file', async () => { - const validSamconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigCompleteData)) - const config = await SamConfig.fromConfigFileUri(validSamconfigFile) - assert(config instanceof SamConfig) - }) - - it('should throw error when loading invalid config file', async () => { - const invalidSamconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigInvalidData)) - try { - await SamConfig.fromConfigFileUri(invalidSamconfigFile) - assert.fail('Should have thrown ToolkitError for parsing eror') - } catch (err) { - assert(err instanceof ToolkitError) - assert.strictEqual(err.code, 'samConfigParseError') - } - }) - }) - - describe('fromProjectRoot', () => { - it('should load the valid config file', async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - const config = await SamConfig.fromProjectRoot(projectRoot) - assert(config instanceof SamConfig) - }) - it('should throw error when loading invalid config file', async () => { - await testFolder.write('samconfig.toml', samconfigInvalidData) - - try { - await SamConfig.fromProjectRoot(projectRoot) - assert.fail('Should have thrown ToolkitError for parsing eror') - } catch (err) { - assert(err instanceof ToolkitError) - assert.strictEqual(err.code, 'samConfigParseError') - } - }) - - it('should throw error when (unlikely) no project root found', async () => { - try { - await SamConfig.fromProjectRoot(undefined as unknown as vscode.Uri) - assert.fail('Should have thrown ToolkitError for parsing eror') - } catch (err) { - assert(err instanceof ToolkitError) - assert.strictEqual(err.message, 'No project folder found') - assert.strictEqual(err.code, 'samNoProjectRootFound') - } - }) - }) - - describe('getCommandParam', () => { - let samconfig: SamConfig - - beforeEach(async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - samconfig = await SamConfig.fromProjectRoot(projectRoot) - }) - - it('should return parameter from primary section if it exists', () => { - const region = samconfig.getCommandParam('global', 'region') - const deployChangeSet = samconfig.getCommandParam('deploy', 'confirm_changeset') - - assert.strictEqual(region, 'us-west-2') - assert.strictEqual(deployChangeSet, false) - }) - - it('should return parameter from global section if primary section does not have it', () => { - const region = samconfig.getCommandParam('deploy', 'region') - const stackName = samconfig.getCommandParam('sync', 'stack_name') - - assert.strictEqual(region, 'us-west-2') - assert.strictEqual(stackName, 'project-1') - }) - - it('should return undefined if parameter is not found in either section', () => { - // These flags are not expected to be in their respective commands - const cached = samconfig.getCommandParam('deploy', 'cached') - const resolveS3 = samconfig.getCommandParam('build', 'resolve_s3') - - assert(!cached) - assert(!resolveS3) - }) - }) - - describe('listEnvironments', () => { - let samconfig: SamConfig - - beforeEach(async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - samconfig = await SamConfig.fromProjectRoot(projectRoot) - }) - it('should return a list of environments', () => { - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - build: { - parameters: { - cached: true, - parallel: true, - use_container: true, - }, - }, - global: { - parameters: { - stack_name: 'project-1', - region: 'us-west-2', - }, - }, - deploy: { - parameters: { - confirm_changeset: false, - resolve_s3: true, - }, - }, - sync: { - parameters: { - s3_bucket: 'aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr', - dependency_layer: false, - watch: false, - }, - }, - }) - }) - }) -}) - -describe('getConfigFileUri', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - }) - - it('should return uri of a valid if a config file exists in a project root', async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const uri = await getConfigFileUri(projectRoot) - assert(uri instanceof vscode.Uri) - assert.strictEqual(uri.fsPath, path.join(testFolder.path, 'samconfig.toml')) - }) - - it('should throw error if a config file doesn not exists in a project root', async () => { - try { - // No samconfig.toml file created for this test - await getConfigFileUri(projectRoot) - assert.fail('Should have thrown ToolkitError eror') - } catch (err) { - assert(err instanceof ToolkitError) - assert.strictEqual(err.code, 'samNoConfigFound') - assert.strictEqual(err.message, `No samconfig.toml file found in ${projectRoot.fsPath}`) - } - }) -}) - -describe('writeSamconfigGlobal', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - }) - - it('should create a new samconfig.toml if not already exist', async () => { - try { - await SamConfig.fromProjectRoot(projectRoot) - assert.fail('Should not have any samconfig.toml in test folder is expected at this point') - } catch (err) { - // start writing - await writeSamconfigGlobal(projectRoot, 'stack-1', 'us-east-1') - assertLogsContain('No samconfig.toml found, creating...', true, 'warn') - } - - const samconfig = await SamConfig.fromProjectRoot(projectRoot) - assert(samconfig instanceof SamConfig) - - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - }) - }) - - it('should edit existing samconfig.toml in global command only', async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - await writeSamconfigGlobal(projectRoot, 'stack-1', 'us-east-1') - - const samconfig = await SamConfig.fromProjectRoot(projectRoot) - assert(samconfig instanceof SamConfig) - - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - build: { - parameters: { - cached: true, - parallel: true, - use_container: true, - }, - }, - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - deploy: { - parameters: { - confirm_changeset: false, - resolve_s3: true, - }, - }, - sync: { - parameters: { - s3_bucket: 'aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr', - dependency_layer: false, - watch: false, - }, - }, - }) - }) - - it('should add global command to existing samconfig.toml', async () => { - await testFolder.write('samconfig.toml', samconfigCompleteDataWithoutGlobal) - await writeSamconfigGlobal(projectRoot, 'stack-1', 'us-east-1') - - const samconfig = await SamConfig.fromProjectRoot(projectRoot) - assert(samconfig instanceof SamConfig) - - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - build: { - parameters: { - cached: true, - parallel: true, - use_container: true, - }, - }, - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - deploy: { - parameters: { - confirm_changeset: false, - resolve_s3: true, - }, - }, - sync: { - parameters: { - s3_bucket: 'aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr', - dependency_layer: false, - watch: false, - }, - }, - }) - }) -}) - -describe('createNewConfigFile', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - }) - - it('should create a new samconfig.toml if not already exist', async () => { - const data = { - default: { - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - }, - } as JsonMap - - await createNewConfigFile(projectRoot, data) - - const samconfig = await SamConfig.fromProjectRoot(projectRoot) - assert(samconfig instanceof SamConfig) - - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - }) - }) - - it('should create a new samconfig.toml and overwrite existing file', async () => { - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const data = { - default: { - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - }, - } as JsonMap - - await createNewConfigFile(projectRoot, data) - - const samconfig = await SamConfig.fromProjectRoot(projectRoot) - assert(samconfig instanceof SamConfig) - - const paramteres = samconfig.listEnvironments() - assert.strictEqual(paramteres.length, 1) - - const defaultEnvironment = paramteres[0] - assert.strictEqual(defaultEnvironment.name, 'default') - assert.deepStrictEqual(defaultEnvironment.commands, { - global: { - parameters: { - stack_name: 'stack-1', - region: 'us-east-1', - }, - }, - }) - }) -}) - -describe('with valid samconfig', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - - await testFolder.write('samconfig.toml', samconfigCompleteData) - await SamConfig.fromProjectRoot(projectRoot) - }) - - it('validateSamDeployConfig should return true', async () => { - assert(await validateSamDeployConfig(projectRoot)) - }) - - it('validateSamSyncConfig should return true', async () => { - assert(await validateSamSyncConfig(projectRoot)) - }) - - it('validateSamBuildConfig should return true', async () => { - assert(await validateSamBuildConfig(projectRoot)) - }) -}) - -describe('with valid samconfig missing global flag', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - - await testFolder.write('samconfig.toml', samconfigCompleteDataWithoutGlobal) - }) - - it('validateSamDeployConfig should return false', async () => { - assert.strictEqual(await validateSamDeployConfig(projectRoot), false) - }) - - it('validateSamSyncConfig should return false', async () => { - assert.strictEqual(await validateSamSyncConfig(projectRoot), false) - }) - - it('validateSamBuildConfig should return true', async () => { - assert(await validateSamBuildConfig(projectRoot)) - }) -}) - -describe('with valid samconfig with only global flag', () => { - let testFolder: TestFolder - let projectRoot: vscode.Uri - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - - await writeSamconfigGlobal(projectRoot, 'stack1', 'us-east-1') - await SamConfig.fromProjectRoot(projectRoot) - }) - - it('validateSamDeployConfig should return false', async () => { - assert.strictEqual(await validateSamDeployConfig(projectRoot), false) - }) - - it('validateSamSyncConfig should return false', async () => { - assert.strictEqual(await validateSamSyncConfig(projectRoot), false) - }) - - it('validateSamBuildConfig should return false', async () => { - assert.strictEqual(await validateSamBuildConfig(projectRoot), false) - }) -}) diff --git a/packages/core/src/test/shared/sam/debugger/awsSamDebugConfigurationValidator.test.ts b/packages/core/src/test/shared/sam/debugger/awsSamDebugConfigurationValidator.test.ts deleted file mode 100644 index fd112fcf6c..0000000000 --- a/packages/core/src/test/shared/sam/debugger/awsSamDebugConfigurationValidator.test.ts +++ /dev/null @@ -1,204 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' - -import * as CloudFormation from '../../../../shared/cloudformation/cloudformation' -import { CloudFormationTemplateRegistry } from '../../../../shared/fs/templateRegistry' -import { - AwsSamDebuggerConfiguration, - TemplateTargetProperties, -} from '../../../../shared/sam/debugger/awsSamDebugConfiguration' -import { DefaultAwsSamDebugConfigurationValidator } from '../../../../shared/sam/debugger/awsSamDebugConfigurationValidator' -import { createBaseTemplate } from '../../cloudformation/cloudformationTestUtils' -import { WatchedItem } from '../../../../shared/fs/watchedFiles' -import { stub } from '../../../utilities/stubber' - -function createTemplateConfig(): AwsSamDebuggerConfiguration { - return { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'template', - templatePath: '/', - logicalId: 'TestResource', - }, - } -} - -function createImageTemplateConfig(): AwsSamDebuggerConfiguration { - return { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'template', - templatePath: '/image', - logicalId: 'TestResource', - }, - lambda: { - runtime: 'nodejs18.x', - }, - } -} - -function createCodeConfig(): AwsSamDebuggerConfiguration { - return { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'code', - lambdaHandler: 'foo', - projectRoot: 'bar', - }, - } -} - -function createApiConfig(): AwsSamDebuggerConfiguration { - return { - type: 'aws-sam', - name: 'name', - request: 'direct-invoke', - invokeTarget: { - target: 'api', - templatePath: '/', - logicalId: 'TestResource', - }, - api: { - path: '/', - httpMethod: 'get', - }, - } -} - -function createTemplateData(): WatchedItem { - return { - path: '/', - item: createBaseTemplate(), - } -} - -function createImageTemplateData(): WatchedItem { - return { - path: '/image', - item: createBaseTemplate(), - } -} - -describe('DefaultAwsSamDebugConfigurationValidator', function () { - const templateConfig = createTemplateConfig() - const imageTemplateConfig = createImageTemplateConfig() - const codeConfig = createCodeConfig() - const apiConfig = createApiConfig() - const templateData = createTemplateData() - const imageTemplateData = createImageTemplateData() - - const mockRegistry = stub(CloudFormationTemplateRegistry, { name: '', items: [] }) - const mockFolder = { uri: vscode.Uri.file('/test') } - - let validator: DefaultAwsSamDebugConfigurationValidator - - beforeEach(function () { - mockRegistry.getItem.withArgs('/').returns(templateData) - mockRegistry.getItem.withArgs('/image').returns(imageTemplateData) - - validator = new DefaultAwsSamDebugConfigurationValidator(mockFolder) - }) - - it('returns invalid when resolving debug configurations with an invalid request type', async () => { - templateConfig.request = 'not-direct-invoke' - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it('returns invalid when resolving debug configurations with an invalid target type', async () => { - templateConfig.invokeTarget.target = 'not-valid' as any - - const result = await validator.validate(templateConfig as any, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it("returns invalid when resolving template debug configurations with a template that isn't in the registry", async () => { - const mockEmptyRegistry = stub(CloudFormationTemplateRegistry, { name: '', items: [] }) - mockEmptyRegistry.getItem.withArgs('/').returns(undefined) - - validator = new DefaultAwsSamDebugConfigurationValidator(mockFolder) - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it("returns invalid when resolving template debug configurations with a template that doesn't have the set resource", async () => { - const target = templateConfig.invokeTarget as TemplateTargetProperties - target.logicalId = 'wrong' - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it("returns invalid when resolving template debug configurations with a template that isn't serverless", async () => { - const target = templateConfig.invokeTarget as TemplateTargetProperties - target.logicalId = 'OtherResource' - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it('returns undefined when resolving template debug configurations with a resource that has an invalid runtime in template', async () => { - const properties = templateData.item.Resources?.TestResource?.Properties as CloudFormation.ZipResourceProperties - properties.Runtime = 'invalid' - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it("API config returns invalid when resolving with a template that isn't serverless", async () => { - const target = templateConfig.invokeTarget as TemplateTargetProperties - target.logicalId = 'OtherResource' - mockRegistry.addItem.resolves() - - const result = await validator.validate(apiConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it('API config is invalid when it does not have an API field', async () => { - const config = createApiConfig() - config.api = undefined - mockRegistry.addItem.resolves() - - const result = await validator.validate(config, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it("API config is invalid when its path does not start with a '/'", async () => { - const config = createApiConfig() - - config.api!.path = 'noleadingslash' - mockRegistry.addItem.resolves() - - const result = await validator.validate(config, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it('returns invalid when resolving code debug configurations with invalid runtimes', async () => { - codeConfig.lambda = { runtime: 'asd' } - - const result = await validator.validate(codeConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) - - it('returns invalid when Image app does not declare runtime', async () => { - const lambda = imageTemplateConfig.lambda - - delete lambda?.runtime - - const result = await validator.validate(templateConfig, mockRegistry) - assert.strictEqual(result.isValid, false) - }) -}) diff --git a/packages/core/src/test/shared/sam/debugger/samDebugConfigProvider.test.ts b/packages/core/src/test/shared/sam/debugger/samDebugConfigProvider.test.ts deleted file mode 100644 index d00273f642..0000000000 --- a/packages/core/src/test/shared/sam/debugger/samDebugConfigProvider.test.ts +++ /dev/null @@ -1,3261 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as os from 'os' -import * as path from 'path' -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import * as lambdaModel from '../../../../lambda/models/samLambdaRuntime' -import { CloudFormationTemplateRegistry } from '../../../../shared/fs/templateRegistry' -import { makeTemporaryToolkitFolder } from '../../../../shared/filesystemUtilities' -import { - TemplateTargetProperties, - AwsSamDebuggerConfiguration, - API_TARGET_TYPE, - AWS_SAM_DEBUG_TYPE, - CODE_TARGET_TYPE, - DIRECT_INVOKE_TYPE, - TEMPLATE_TARGET_TYPE, - createTemplateAwsSamDebugConfig, - ensureRelativePaths, - createCodeAwsSamDebugConfig, - CodeTargetProperties, - createApiAwsSamDebugConfig, - APIGatewayProperties, - PathMapping, -} from '../../../../shared/sam/debugger/awsSamDebugConfiguration' -import { SamDebugConfigProvider, SamLaunchRequestArgs } from '../../../../shared/sam/debugger/awsSamDebugger' -import * as debugConfiguration from '../../../../lambda/local/debugConfiguration' -import * as pathutil from '../../../../shared/utilities/pathUtils' -import { FakeExtensionContext } from '../../../fakeExtensionContext' -import * as testutil from '../../../testUtil' -import { assertFileText } from '../../../testUtil' -import { - makeSampleSamTemplateYaml, - makeSampleYamlResource, - makeSampleYamlParameters, -} from '../../cloudformation/cloudformationTestUtils' -import { CredentialsStore } from '../../../../auth/credentials/store' -import { CredentialsProviderManager } from '../../../../auth/providers/credentialsProviderManager' -import { Credentials } from '@aws-sdk/types' -import { ExtContext } from '../../../../shared/extensions' -import { getLogger } from '../../../../shared/logger/logger' -import { CredentialsProvider } from '../../../../auth/providers/credentials' -import globals from '../../../../shared/extensionGlobals' -import { isCI } from '../../../../shared/vscode/env' -import { fs } from '../../../../shared' -import { Runtime } from '@aws-sdk/client-lambda' - -/** - * Asserts the contents of a "launch config" (the result of `makeConfig()` or - * `resolveDebugConfiguration()` invoked on a user-provided "debug config"). - */ -function assertEqualLaunchConfigs(actual: SamLaunchRequestArgs, expected: SamLaunchRequestArgs) { - // Deep copy, do not modify the original variables. - actual = { ...actual } - expected = { ...expected } - - assert.strictEqual(actual.workspaceFolder.name, expected.workspaceFolder.name) - - // Compare filepaths (before removing them for deep-compare). - testutil.assertEqualPaths(actual.workspaceFolder.uri.fsPath, expected.workspaceFolder.uri.fsPath) - - // Port number is unstable; check that it looks reasonable. - assert.ok(!actual.apiPort || actual.apiPort > 5000) - assert.ok(!actual.debugPort || actual.debugPort > 5000) - assert.strictEqual(actual.port, expected.port) - - // Build dir is randomly-generated; check that it looks reasonable. - assert.ok(actual.baseBuildDir && actual.baseBuildDir.length > 9) - if (expected.type === 'python') { - // manifestPath is randomly-generated; check that it looks reasonable. - assert.ok(actual.manifestPath && actual.manifestPath.length > 9) - } - - // should never be defined if we're not debugging - if (expected.noDebug) { - delete expected.debugArgs - delete expected.containerEnvFile - delete expected.containerEnvVars - } - - // Normalize path fields before comparing. - for (const o of [actual, expected]) { - o.codeRoot = pathutil.normalize(o.codeRoot) - o.containerEnvFile = o.containerEnvFile ? pathutil.normalize(o.containerEnvFile) : o.containerEnvFile - o.envFile = o.envFile ? pathutil.normalize(o.envFile) : undefined - o.eventPayloadFile = o.eventPayloadFile ? pathutil.normalize(o.eventPayloadFile) : undefined - o.debuggerPath = o.debuggerPath ? pathutil.normalize(o.debuggerPath) : o.debuggerPath - o.localRoot = o.localRoot ? pathutil.normalize(o.localRoot) : o.localRoot - } - - // Remove noisy properties before doing a deep-compare. - for (const o of [actual, expected]) { - delete o.manifestPath - delete (o as any).documentUri - delete (o as any).templatePath - delete (o as any).workspaceFolder - delete (o as any).codeRoot - delete (o as any).localRoot // Node-only - delete (o as any).debuggerPath // Dotnet-only - } - assert.deepStrictEqual(actual, expected) -} - -/** - * Tests `noDebug=true` for the given `input` and fixes up the given `expected` - * result, for a `target=template` config. - */ -async function assertEqualNoDebugTemplateTarget( - input: any, - expected: SamLaunchRequestArgs, - folder: vscode.WorkspaceFolder, - debugConfigProvider: SamDebugConfigProvider, - noFiles?: boolean -) { - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: noFiles ? undefined : `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: noFiles ? undefined : `${actualNoDebug.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) -} - -describe('SamDebugConfigurationProvider', async function () { - let debugConfigProvider: SamDebugConfigProvider - let tempFolder: string - let tempFolderSimilarName: string | undefined - let tempFile: vscode.Uri - let fakeWorkspaceFolder: vscode.WorkspaceFolder - let fakeContext: ExtContext - let sandbox: sinon.SinonSandbox - const resourceName = 'myResource' - const fakeCredentials: Credentials = { - accessKeyId: 'fake-access-id', - secretAccessKey: 'fake-secret', - sessionToken: 'fake-session', - } - - beforeEach(async function () { - fakeContext = await FakeExtensionContext.getFakeExtContext() - await fakeContext.awsContext.setCredentials({ - accountId: '9888888', - credentials: fakeCredentials, - credentialsId: 'profile:fake', - defaultRegion: 'us-west-2', - }) - debugConfigProvider = new SamDebugConfigProvider(fakeContext) - sandbox = sinon.createSandbox() - - fakeWorkspaceFolder = await testutil.createTestWorkspaceFolder() - tempFolder = fakeWorkspaceFolder.uri.fsPath - tempFile = vscode.Uri.file(path.join(tempFolder, 'test.yaml')) - tempFolderSimilarName = undefined - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - if (tempFolderSimilarName) { - await fs.delete(tempFolderSimilarName, { recursive: true }) - } - ;(await globals.templateRegistry).reset() - sandbox.restore() - }) - - describe('provideDebugConfig', async function () { - it('failure modes', async function () { - // No workspace folder: - assert.deepStrictEqual(await debugConfigProvider.provideDebugConfigurations(undefined), []) - // Workspace with no templates: - assert.deepStrictEqual(await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder), []) - - // Malformed template.yaml: - await testutil.toFile('bogus', tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - assert.deepStrictEqual(await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder), []) - }) - - it('Ignores non function type resources', async function () { - const bigYamlStr = `${makeSampleSamTemplateYaml(true)}\nTestResource2:\n . Type: AWS::Serverless::Api` - - await testutil.toFile(bigYamlStr, tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.strictEqual(provided!.length, 1) - }) - - it('returns one item if a template with one resource is in the workspace', async function () { - await testutil.toFile(makeSampleSamTemplateYaml(true), tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.notStrictEqual(provided, undefined) - assert.strictEqual(provided!.length, 1) - assert.strictEqual( - provided![0].name, - `${path.basename(fakeWorkspaceFolder.uri.fsPath)}:TestResource (nodejs12.x)` - ) - }) - - it('returns multiple items if a template with multiple resources is in the workspace', async function () { - const resources = ['resource1', 'resource2'] - const bigYamlStr = `${makeSampleSamTemplateYaml(true, { - resourceName: resources[0], - })}\n${makeSampleYamlResource({ resourceName: resources[1] })}` - await testutil.toFile(bigYamlStr, tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.notStrictEqual(provided, undefined) - if (provided) { - assert.strictEqual(provided.length, 2) - assert.ok(resources.includes((provided[0].invokeTarget as TemplateTargetProperties).logicalId)) - assert.ok(resources.includes((provided[1].invokeTarget as TemplateTargetProperties).logicalId)) - } - }) - - it('only detects the specifically targeted workspace folder (and its subfolders)', async function () { - const resources = ['resource1', 'resource2'] - const badResourceName = 'notIt' - - const nestedDir = path.join(tempFolder, 'nested') - const nestedYaml = vscode.Uri.file(path.join(nestedDir, 'test.yaml')) - tempFolderSimilarName = tempFolder + 'SimilarName' - const similarNameYaml = vscode.Uri.file(path.join(tempFolderSimilarName, 'test.yaml')) - - await fs.mkdir(nestedDir) - await fs.mkdir(tempFolderSimilarName) - - await testutil.toFile(makeSampleSamTemplateYaml(true, { resourceName: resources[0] }), tempFile.fsPath) - await testutil.toFile(makeSampleSamTemplateYaml(true, { resourceName: resources[1] }), nestedYaml.fsPath) - await testutil.toFile( - makeSampleSamTemplateYaml(true, { resourceName: badResourceName }), - similarNameYaml.fsPath - ) - - await (await globals.templateRegistry).addItem(tempFile) - await (await globals.templateRegistry).addItem(nestedYaml) - await (await globals.templateRegistry).addItem(similarNameYaml) - - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.notStrictEqual(provided, undefined) - if (provided) { - assert.strictEqual(provided.length, 2) - assert.ok(resources.includes((provided[0].invokeTarget as TemplateTargetProperties).logicalId)) - assert.ok(resources.includes((provided[1].invokeTarget as TemplateTargetProperties).logicalId)) - assert.ok(!resources.includes(badResourceName)) - } - }) - - it('Returns api function type resources as additional api configurations', async function () { - const bigYamlStr = `${makeSampleSamTemplateYaml(true)} - Events: - HelloWorld2: - Type: Api - Properties: - Path: /hello - Method: get` - - await testutil.toFile(bigYamlStr, tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.strictEqual(provided!.length, 2) - assert.strictEqual(provided![1].invokeTarget.target, API_TARGET_TYPE) - assert.strictEqual(provided![1].api?.path, '/hello') - assert.strictEqual(provided![1].api?.httpMethod, 'get') - }) - - it('Ignores HttpApi events', async function () { - const bigYamlStr = `${makeSampleSamTemplateYaml(true)} - Events: - HelloWorld2: - Type: HttpApi` - - await testutil.toFile(bigYamlStr, tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - const provided = await debugConfigProvider.provideDebugConfigurations(fakeWorkspaceFolder) - assert.strictEqual(provided!.length, 1) - }) - }) - - describe('makeConfig', async function () { - describe('buildDir', function () { - it('uses `buildDir` as `baseBuildDir` when provided', async function () { - const buildDir = (await testutil.createTestWorkspaceFolder('my-build-dir')).uri.fsPath - const folder = testutil.getWorkspaceFolder(testutil.getProjectDir()) - const launchConfig = await getConfig( - debugConfigProvider, - await globals.templateRegistry, - 'testFixtures/workspaceFolder/js-plain-sam-app/' - ) - const config = launchConfig.config as AwsSamDebuggerConfiguration & { - invokeTarget: { target: 'template' } - } - config.sam = Object.assign(launchConfig.config.sam ?? {}, { buildDir }) - config.invokeTarget.templatePath = config.invokeTarget.templatePath.replace( - '${workspaceFolder}', - launchConfig.folder.uri.fsPath - ) - - const actual = await debugConfigProvider.makeConfig(folder, config) - testutil.assertEqualPaths(actual?.baseBuildDir ?? '', buildDir) - }) - }) - - it('failure modes', async function () { - const config = await getConfig( - debugConfigProvider, - await globals.templateRegistry, - 'testFixtures/workspaceFolder/csharp6-zip/' - ) - - // No workspace folder: - // Stub vscode.workspace.workspaceFolders to be undefined to ensure rejection - sandbox.stub(vscode.workspace, 'workspaceFolders').value(undefined) - await assert.rejects(() => debugConfigProvider.makeConfig(undefined, config.config)) - // Restore for subsequent tests - sandbox.restore() - sandbox = sinon.createSandbox() - - // No launch.json (vscode will pass an empty config.request): - await assert.rejects(() => debugConfigProvider.makeConfig(undefined, { ...config.config, request: '' })) - - // Unknown runtime: - config.config.lambda = { - runtime: 'happy-runtime-42', - } - await assert.rejects(() => debugConfigProvider.makeConfig(config.folder, config.config)) - - // bad credentials - const mockCredentialsStore: CredentialsStore = new CredentialsStore() - - const credentialsProvider: CredentialsProvider = { - getCredentials: sandbox.stub().resolves({} as any as AWS.Credentials), - getProviderType: sandbox.stub().resolves('profile'), - getTelemetryType: sandbox.stub().resolves('staticProfile'), - getCredentialsId: sandbox.stub().returns({ - credentialSource: 'sharedCredentials', - credentialTypeId: 'someId', - }), - getDefaultRegion: sandbox.stub().returns('someRegion'), - getHashCode: sandbox.stub().returns('1234'), - canAutoConnect: sandbox.stub().returns(true), - isAvailable: sandbox.stub().returns(Promise.resolve(true)), - } - const getCredentialsProviderStub = sandbox.stub( - CredentialsProviderManager.getInstance(), - 'getCredentialsProvider' - ) - getCredentialsProviderStub.resolves(credentialsProvider) - sandbox.stub(mockCredentialsStore, 'upsertCredentials').throws() - const debugConfigProviderMockCredentials = new SamDebugConfigProvider({ - ...fakeContext, - credentialsStore: mockCredentialsStore, - }) - - await assert.rejects(() => - debugConfigProviderMockCredentials.makeConfig(config.folder, { - ...config.config, - aws: { - credentials: 'profile:error', - }, - }) - ) - }) - - it('generates a valid resource name based on the projectDir #1685', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/go1-plain-sam-app') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-go-code-logicalid', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'hello-world', - projectRoot: '.', // Issue #1685 - }, - lambda: { - runtime: 'go1.x', - }, - } - const config = (await debugConfigProvider.makeConfig(folder, input))! - - await assertFileText( - config.templatePath, - `Resources: - go1plainsamapp: - Type: AWS::Serverless::Function - Properties: - Handler: hello-world - CodeUri: >- - ${config.codeRoot} - Runtime: go1.x -` - ) - }) - - it('rejects when resolving debug configurations with an invalid request type', async function () { - await assert.rejects(() => - debugConfigProvider.makeConfig(undefined, { - type: AWS_SAM_DEBUG_TYPE, - name: 'whats in a name', - request: 'not-direct-invoke', - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'sick handles', - projectRoot: 'root as in beer', - }, - }) - ) - }) - - it('rejects when resolving debug configurations with an invalid target type', async function () { - const tgt = 'not-code' as 'code' - await assert.rejects(() => - debugConfigProvider.makeConfig(undefined, { - type: AWS_SAM_DEBUG_TYPE, - name: 'whats in a name', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: tgt, - lambdaHandler: 'sick handles', - projectRoot: 'root as in beer', - }, - }) - ) - }) - - it("rejects when resolving template debug configurations with a template that isn't in the registry", async () => { - await assert.rejects(() => debugConfigProvider.makeConfig(undefined, createFakeConfig({}))) - }) - - it("rejects when resolving template debug configurations with a template that doesn't have the set resource", async () => { - await createAndRegisterYaml({}, tempFile, await globals.templateRegistry) - await assert.rejects(() => - debugConfigProvider.makeConfig(undefined, createFakeConfig({ templatePath: tempFile.fsPath })) - ) - }) - - it('rejects when resolving template debug configurations with a resource that has an invalid runtime in template', async function () { - await createAndRegisterYaml( - { resourceName, runtime: 'moreLikeRanOutOfTime' }, - tempFile, - await globals.templateRegistry - ) - await assert.rejects( - () => - debugConfigProvider.makeConfig( - undefined, - createFakeConfig({ - templatePath: tempFile.fsPath, - logicalId: resourceName, - }) - ), - /runtime/i - ) - }) - - it('rejects when resolving template debug configurations with a resource that has an invalid runtime in template', async function () { - await testutil.toFile( - makeSampleSamTemplateYaml(true, { resourceName, runtime: 'moreLikeRanOutOfTime' }), - tempFile.fsPath - ) - await assert.rejects(() => - debugConfigProvider.makeConfig(undefined, { - type: AWS_SAM_DEBUG_TYPE, - name: 'whats in a name', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: tempFile.fsPath, - logicalId: resourceName, - }, - }) - ) - }) - - it('rejects when resolving code debug configurations with invalid runtimes', async function () { - await assert.rejects(() => - debugConfigProvider.makeConfig(undefined, { - ...createBaseCodeConfig({}), - lambda: { - runtime: 'COBOL', - }, - }) - ) - }) - - it('supports workspace-relative template path ("./foo.yaml")', async function () { - await testutil.toFile(makeSampleSamTemplateYaml(true, { runtime: 'nodejs18.x' }), tempFile.fsPath) - // Register with *full* path. - await (await globals.templateRegistry).addItem(tempFile) - // Simulates launch.json: - // "invokeTarget": { - // "target": "./test.yaml", - // }, - const relPath = './' + path.relative(fakeWorkspaceFolder.uri.path, tempFile.path) - - // Assert that the relative path correctly maps to the full path in the registry. - const name = 'Test rel path' - const resolved = await debugConfigProvider.makeConfig(fakeWorkspaceFolder, { - type: AWS_SAM_DEBUG_TYPE, - name: name, - request: 'direct-invoke', - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: relPath, - logicalId: 'TestResource', - // lambdaHandler: 'sick handles', - // projectRoot: 'root as in beer' - }, - }) - assert.strictEqual(resolved!.name, name) - }) - - it('target=code: javascript', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/js-manifest-in-root/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'whats in a name', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'my.test.handler', - projectRoot: 'src', - }, - lambda: { - runtime: 'nodejs18.x', - // For target=code these envvars are written to the input-template.yaml. - environmentVariables: { - 'test-envvar-1': 'test value 1', - 'test-envvar-2': 'test value 2', - }, - memoryMb: 1.2, - timeoutSec: 9000, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - 'test-payload-key-2': 'test payload value 2', - }, - }, - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs18.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: pathutil.normalize(path.join(appDir, 'src')), // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'my.test.handler', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - localRoot: pathutil.normalize(path.join(appDir, 'src')), // Normalized to absolute path. - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - region: 'us-west-2', - remoteRoot: '/var/task', - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.envFile!, - '{"src":{"test-envvar-1":"test value 1","test-envvar-2":"test value 2"}}' - ) - await assertFileText( - expected.eventPayloadFile!, - '{"test-payload-key-1":"test payload value 1","test-payload-key-2":"test payload value 2"}' - ) - await assertFileText( - expected.templatePath, - `Resources: - src: - Type: AWS::Serverless::Function - Properties: - Handler: my.test.handler - CodeUri: >- - ${expected.codeRoot} - Runtime: nodejs18.x - Environment: - Variables: - test-envvar-1: test value 1 - test-envvar-2: test value 2 - MemorySize: 1.2 - Timeout: 9000 -` - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'ignoredLocal', - remoteRoot: 'ignoredRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: `${actualWithPathMapping.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=code: typescript', async function () { - /** - * When executing the test on macOS in CI the tests fail with the following error: - * 'Error: TypeScript compiler "tsc" not found in node_modules/ or the system - * - * See: https://github.com/aws/aws-toolkit-vscode/issues/5587 - */ - if (isCI() && os.platform() === 'darwin') { - this.skip() - } - - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/ts-plain-sam-app/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-ts-code', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'app.handler', - projectRoot: 'src', - }, - lambda: { - runtime: 'nodejs18.x', - // For target=code these envvars are written to the input-template.yaml. - environmentVariables: { - 'test-envvar-1': 'test value 1', - 'test-envvar-2': 'test value 2', - }, - memoryMb: 1.2, - timeoutSec: 9000, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - 'test-payload-key-2': 'test payload value 2', - }, - }, - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs18.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: pathutil.normalize(path.join(appDir, 'src')), // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'app.handler', - invokeTarget: { - ...input.invokeTarget, - }, - lambda: { - ...input.lambda, - }, - localRoot: pathutil.normalize(path.join(appDir, 'src')), // Normalized to absolute path. - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - region: 'us-west-2', - remoteRoot: '/var/task', - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.envFile!, - '{"src":{"test-envvar-1":"test value 1","test-envvar-2":"test value 2"}}' - ) - await assertFileText( - expected.eventPayloadFile!, - '{"test-payload-key-1":"test payload value 1","test-payload-key-2":"test payload value 2"}' - ) - await assertFileText( - expected.templatePath, - `Resources: - src: - Type: AWS::Serverless::Function - Properties: - Handler: app.handler - CodeUri: >- - ${expected.codeRoot} - Runtime: nodejs18.x - Environment: - Variables: - test-envvar-1: test value 1 - test-envvar-2: test value 2 - MemorySize: 1.2 - Timeout: 9000 -` - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'ignoredLocal', - remoteRoot: 'ignoredRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: `${actualWithPathMapping.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: javascript', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/js-manifest-in-root') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-js-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'SourceCodeTwoFoldersDeep', - }, - lambda: { - // For target=template these are written to env-vars.json, - // NOT the input-template.yaml. - environmentVariables: { - 'test-js-template-envvar-1': 'test target=template envvar value 1', - 'test-js-template-envvar-2': 'test target=template envvar value 2', - }, - memoryMb: 1.01, - timeoutSec: 99, - payload: { - json: { - 'test-js-template-key-1': 'test js target=template value 1', - 'test-js-template-key-2': 'test js target=template value 2', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! - - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs20.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: appDir, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'src/subfolder/app.handlerTwoFoldersDeep', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - localRoot: appDir, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - region: 'us-west-2', - remoteRoot: '/var/task', - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.envFile!, - '{"SourceCodeTwoFoldersDeep":{"test-js-template-envvar-1":"test target=template envvar value 1","test-js-template-envvar-2":"test target=template envvar value 2"}}' - ) - await assertFileText( - expected.eventPayloadFile!, - '{"test-js-template-key-1":"test js target=template value 1","test-js-template-key-2":"test js target=template value 2"}' - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'ignoredLocal', - remoteRoot: 'ignoredRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: `${actualWithPathMapping.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: Image javascript', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/js-image-sam-app') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-js-image-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'HelloWorldFunction', - }, - lambda: { - runtime: 'nodejs18.x', - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! - - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs18.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - containerEnvFile: `${actual.baseBuildDir}/container-env-vars.json`, - containerEnvVars: { - NODE_OPTIONS: `--inspect=0.0.0.0:${actual.debugPort} --max-http-header-size 81920`, - }, - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: appDir, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'HelloWorldFunction', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: 3, - }, - localRoot: appDir, - name: input.name, - architecture: undefined, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - region: 'us-west-2', - remoteRoot: '/var/task', - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - parameterOverrides: undefined, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.containerEnvFile!, - `{"NODE_OPTIONS":"--inspect=0.0.0.0:${actual.debugPort} --max-http-header-size 81920"}` - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'ignoredLocal', - remoteRoot: 'ignoredRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - baseBuildDir: actualWithPathMapping.baseBuildDir, - containerEnvFile: `${actualWithPathMapping.baseBuildDir}/container-env-vars.json`, - envFile: undefined, - eventPayloadFile: undefined, - architecture: undefined, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // Test noDebug=true. - await assertEqualNoDebugTemplateTarget(input, expected, folder, debugConfigProvider, true) - }) - - it('target=api: javascript', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/js-manifest-in-root') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input: AwsSamDebuggerConfiguration = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-js-api', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: API_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'SourceCodeTwoFoldersDeep', - }, - api: { - path: '/hello', - httpMethod: 'post', - headers: { - 'user-agent': 'mozilla 42', - }, - querystring: 'foo&bar=baz', - }, - lambda: { - // For target=template these are written to env-vars.json, - // NOT the input-template.yaml. - environmentVariables: { - 'test-js-template-envvar-1': 'test target=template envvar value 1', - 'test-js-template-envvar-2': 'test target=template envvar value 2', - }, - memoryMb: 1.01, - timeoutSec: 99, - payload: { - json: { - 'test-js-template-key-1': 'test js target=template value 1', - 'test-js-template-key-2': 'test js target=template value 2', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! - - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs20.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: appDir, - apiPort: actual.apiPort, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'src/subfolder/app.handlerTwoFoldersDeep', - invokeTarget: { ...input.invokeTarget }, - api: { - ...(input.api as APIGatewayProperties), - }, - lambda: { - ...input.lambda, - }, - localRoot: appDir, - name: input.name, - architecture: undefined, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - region: 'us-west-2', - remoteRoot: '/var/task', - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - parameterOverrides: undefined, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.envFile!, - '{"SourceCodeTwoFoldersDeep":{"test-js-template-envvar-1":"test target=template envvar value 1","test-js-template-envvar-2":"test target=template envvar value 2"}}' - ) - await assertFileText( - expected.eventPayloadFile!, - '{"test-js-template-key-1":"test js target=template value 1","test-js-template-key-2":"test js target=template value 2"}' - ) - - // Test noDebug=true. - await assertEqualNoDebugTemplateTarget(input, expected, folder, debugConfigProvider) - }) - - it('target=code: java17 maven', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/java17-gradle/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const handler = 'helloworld.App::handleRequest' - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'Test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: handler, - projectRoot: 'HelloWorldFunction', - }, - lambda: { - runtime: 'java17', - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'java17', - runtimeFamily: lambdaModel.RuntimeFamily.Java, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: expectedCodeRoot, // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: handler, - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: undefined, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - } - - const expectedDebug = { - ...expected, - // - // Java-specific fields - // - hostName: '127.0.0.1', - port: actual.debugPort, - } - - assertEqualLaunchConfigs(actual, expectedDebug) - - await assertFileText( - expected.templatePath, - `Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - Handler: ${handler} - CodeUri: >- - ${input.invokeTarget.projectRoot} - Runtime: java17 -` - ) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=code: java 17 gradle', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/java17-gradle/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const handler = 'helloworld.App::handleRequest' - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'Test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: handler, - projectRoot: 'HelloWorldFunction', - }, - lambda: { - runtime: 'java17', - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'java17', - runtimeFamily: lambdaModel.RuntimeFamily.Java, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: expectedCodeRoot, // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: handler, - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: undefined, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - } - - const expectedDebug = { - ...expected, - // - // Java-specific fields - // - hostName: '127.0.0.1', - port: actual.debugPort, - } - - assertEqualLaunchConfigs(actual, expectedDebug) - - await assertFileText( - expectedDebug.templatePath, - `Resources: - HelloWorldFunction: - Type: AWS::Serverless::Function - Properties: - Handler: ${handler} - CodeUri: >- - ${input.invokeTarget.projectRoot} - Runtime: java17 -` - ) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: java maven', async function () { - const handler = 'helloworld.App::handleRequest' - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/java17-maven/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-java-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'HelloWorldFunction', - }, - lambda: { - environmentVariables: { - 'test-envvar-1': 'test value 1', - }, - memoryMb: 512, - timeoutSec: 20, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'java17', - runtimeFamily: lambdaModel.RuntimeFamily.Java, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: expectedCodeRoot, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: handler, - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - parameterOverrides: undefined, - architecture: 'x86_64', - region: 'us-west-2', - } - - const expectedDebug = { - ...expected, - // - // Java-specific fields - // - hostName: '127.0.0.1', - port: actual.debugPort, - } - - assertEqualLaunchConfigs(actual, expectedDebug) - await assertFileText(expectedDebug.envFile!, '{"HelloWorldFunction":{"test-envvar-1":"test value 1"}}') - await assertFileText(expectedDebug.eventPayloadFile!, '{"test-payload-key-1":"test payload value 1"}') - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: Image java', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/java17-image') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-java-image-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'HelloWorldFunction', - }, - lambda: { - runtime: 'java11', - environmentVariables: { - 'test-envvar-1': 'test value 1', - }, - memoryMb: 512, - timeoutSec: 20, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'java11', - runtimeFamily: lambdaModel.RuntimeFamily.Java, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - containerEnvFile: `${actual.baseBuildDir}/container-env-vars.json`, - containerEnvVars: { - _JAVA_OPTIONS: `-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,quiet=y,address=*:${actual.debugPort} -XX:MaxHeapSize=2834432k -XX:MaxMetaspaceSize=163840k -XX:ReservedCodeCacheSize=81920k -XX:+UseSerialGC -XX:-TieredCompilation -Djava.net.preferIPv4Stack=true`, - }, - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: expectedCodeRoot, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'HelloWorldFunction', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - parameterOverrides: undefined, - architecture: 'x86_64', - region: 'us-west-2', - } - - const expectedDebug = { - ...expected, - // - // Java-specific fields - // - hostName: '127.0.0.1', - port: actual.debugPort, - } - - assertEqualLaunchConfigs(actual, expectedDebug) - await assertFileText(expectedDebug.envFile!, '{"HelloWorldFunction":{"test-envvar-1":"test value 1"}}') - await assertFileText(expectedDebug.eventPayloadFile!, '{"test-payload-key-1":"test payload value 1"}') - await assertFileText( - expectedDebug.containerEnvFile!, - `{"_JAVA_OPTIONS":"${expectedDebug.containerEnvVars!._JAVA_OPTIONS}"}` - ) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=code: dotnet/csharp', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/csharp6-zip/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'Test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'HelloWorld::HelloWorld.Function::FunctionHandler', - projectRoot: 'src/HelloWorld', - }, - lambda: { - runtime: 'dotnet6', - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const codeRoot = input.invokeTarget.projectRoot - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'dotnet6', // lambdaModel.dotNetRuntimes[0], - runtimeFamily: lambdaModel.RuntimeFamily.DotNet, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: expectedCodeRoot, // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'HelloWorld::HelloWorld.Function::FunctionHandler', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: undefined, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - - // - // Csharp-related fields - // - debuggerPath: codeRoot + '/.vsdbg', // Normalized to absolute path. - processName: 'dotnet', - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'sh', - }, - windows: { - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'powershell', - }, - }, - } - - assertEqualLaunchConfigs(actual, expected) - - await assertFileText( - expected.templatePath, - `Resources: - HelloWorld: - Type: AWS::Serverless::Function - Properties: - Handler: HelloWorld::HelloWorld.Function::FunctionHandler - CodeUri: >- - ${input.invokeTarget.projectRoot} - Runtime: dotnet6 -` - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - sourceFileMap: { - somethingRemote: 'somethingLocal', - anotherRemote: 'anotherLocal', - }, - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: dotnet/csharp', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/csharp6-zip') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-csharp-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'HelloWorldFunction', - }, - sam: { - containerBuild: true, // #3864 - }, - lambda: { - environmentVariables: { - 'test-envvar-1': 'test value 1', - }, - memoryMb: 42, - timeoutSec: 10, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const codeRoot = `${appDir}/src/HelloWorld` - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'dotnet6', // lambdaModel.dotNetRuntimes[0], - runtimeFamily: lambdaModel.RuntimeFamily.DotNet, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: expectedCodeRoot, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'HelloWorld::HelloWorld.Function::FunctionHandler', - invokeTarget: { ...input.invokeTarget }, - sam: { ...input.sam }, - lambda: { - ...input.lambda, - }, - name: input.name, - architecture: 'x86_64', - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - mountWith: 'write', - region: 'us-west-2', - - // - // Csharp-related fields - // - debuggerPath: codeRoot + '/.vsdbg', // Normalized to absolute path. - processName: 'dotnet', - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'sh', - }, - windows: { - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'powershell', - }, - }, - parameterOverrides: undefined, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText(expected.envFile!, '{"HelloWorldFunction":{"test-envvar-1":"test value 1"}}') - await assertFileText(expected.eventPayloadFile!, '{"test-payload-key-1":"test payload value 1"}') - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - sourceFileMap: { - somethingRemote: 'somethingLocal', - anotherRemote: 'anotherLocal', - }, - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: `${actualWithPathMapping.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: Image dotnet/csharp', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/csharp6-image') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-csharp-image-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'template.yaml', - logicalId: 'HelloWorldFunction', - }, - lambda: { - runtime: 'dotnet6', - environmentVariables: { - 'test-envvar-1': 'test value 1', - }, - memoryMb: 42, - timeoutSec: 10, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - }, - }, - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'template.yaml')) - const actual = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const codeRoot = `${appDir}/src/HelloWorld` - const expectedCodeRoot = (actual.baseBuildDir ?? 'fail') + '/input' - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'dotnet6', // lambdaModel.dotNetRuntimes[0], - runtimeFamily: lambdaModel.RuntimeFamily.DotNet, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - containerEnvFile: `${actual.baseBuildDir}/container-env-vars.json`, - containerEnvVars: { - _AWS_LAMBDA_DOTNET_DEBUGGING: '1', - }, - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: expectedCodeRoot, - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'HelloWorldFunction', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - - // - // Csharp-related fields - // - debuggerPath: codeRoot + '/.vsdbg', // Normalized to absolute path. - processName: 'dotnet', - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - // tslint:disable-next-line: no-invalid-template-strings - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'sh', - }, - sourceFileMap: { - '/build': codeRoot, - }, - windows: { - pipeTransport: { - debuggerPath: '/tmp/lambci_debug_files/vsdbg', - // tslint:disable-next-line: no-invalid-template-strings - pipeArgs: [ - '-c', - `docker exec -i $(docker ps -q -f publish=${actual.debugPort}) \${debuggerCommand}`, - ], - pipeCwd: codeRoot, - pipeProgram: 'powershell', - }, - }, - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText(expected.envFile!, '{"HelloWorldFunction":{"test-envvar-1":"test value 1"}}') - await assertFileText(expected.eventPayloadFile!, '{"test-payload-key-1":"test payload value 1"}') - await assertFileText(expected.containerEnvFile!, '{"_AWS_LAMBDA_DOTNET_DEBUGGING":"1"}') - - // Windows: sourceFileMap driveletter must be uppercase. - if (os.platform() === 'win32') { - const sourceFileMap = actual.sourceFileMap!['/build'] - assert.ok(/^[A-Z]:/.test(sourceFileMap.substring(0, 2)), 'sourceFileMap driveletter must be uppercase') - } - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - sourceFileMap: { - somethingRemote: 'somethingLocal', - anotherRemote: 'anotherLocal', - }, - baseBuildDir: actualWithPathMapping.baseBuildDir, - containerEnvFile: `${actualWithPathMapping.baseBuildDir}/container-env-vars.json`, - envFile: `${actualWithPathMapping.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedCodeRootNoDebug = (actualNoDebug.baseBuildDir ?? 'fail') + '/input' - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - codeRoot: expectedCodeRootNoDebug, - noDebug: true, - request: 'launch', - debuggerPath: undefined, - debugPort: undefined, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - delete expectedNoDebug.processName - delete expectedNoDebug.pipeTransport - delete expectedNoDebug.sourceFileMap - delete expectedNoDebug.windows - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=code: python 3.14', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/python3.14-plain-sam-app') - ) - const relPayloadPath = `events/event.json` - const absPayloadPath = `${appDir}/${relPayloadPath}` - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'Test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'app.lambda_handler', - projectRoot: 'hello_world', - }, - lambda: { - runtime: 'python3.14', - payload: { - path: relPayloadPath, - }, - }, - } - - // Invoke with noDebug=false (the default). - const actual = (await debugConfigProvider.makeConfig(folder, input))! - // Expected result with noDebug=false. - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'python3.14' as Runtime, - runtimeFamily: lambdaModel.RuntimeFamily.Python, - type: AWS_SAM_DEBUG_TYPE, - handlerName: 'app.lambda_handler', - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: pathutil.normalize(path.join(appDir, 'hello_world')), - debugArgs: [ - `/tmp/lambci_debug_files/py_debug_wrapper.py --listen 0.0.0.0:${actual.debugPort} --wait-for-client --log-to-stderr --debug`, - ], - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: undefined, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - port: actual.debugPort, - redirectOutput: false, - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - - // - // Python-related fields - // - host: 'localhost', - pathMappings: [ - { - localRoot: pathutil.normalize(path.join(appDir, 'hello_world')), - remoteRoot: '/var/task', - }, - ], - } - - // Windows: pathMappings has uppercase and lowercase variants. - // See getLocalRootVariants(). ref: 4bd1418863edd45e27 - if (os.platform() === 'win32') { - const localRoot: string = expected.pathMappings[0].localRoot - expected.pathMappings.unshift({ - localRoot: localRoot.substring(0, 1).toLowerCase() + localRoot.substring(1), - remoteRoot: '/var/task', - }) - } - - assertEqualLaunchConfigs(actual, expected) - assert.strictEqual(await fs.readFileText(actual.eventPayloadFile!), await fs.readFileText(absPayloadPath)) - await assertFileText( - expected.templatePath, - `Resources: - helloworld: - Type: AWS::Serverless::Function - Properties: - Handler: ${expected.handlerName} - CodeUri: >- - ${expected.codeRoot} - Runtime: python3.14 -` - ) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - pathMappings: inputWithPathMapping.lambda.pathMappings, - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: undefined, - eventPayloadFile: `${actualWithPathMapping.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! as SamLaunchRequestArgs - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - handlerName: 'app.lambda_handler', - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: undefined, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('target=template: python 3.14 (deep project tree)', async function () { - // To test a deeper tree, use "testFixtures/workspaceFolder/" as the root. - const appDir = pathutil.normalize(path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/')) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-py314-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'python3.14-plain-sam-app/template.yaml', - logicalId: 'HelloWorldFunction', - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'python3.14-plain-sam-app/template.yaml')) - - // Invoke with noDebug=false (the default). - const actual = (await debugConfigProvider.makeConfig(folder, input))! - // Expected result with noDebug=false. - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'python3.14' as Runtime, - runtimeFamily: lambdaModel.RuntimeFamily.Python, - type: AWS_SAM_DEBUG_TYPE, - handlerName: 'app.lambda_handler', - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: pathutil.normalize(path.join(appDir, 'python3.14-plain-sam-app/hello_world')), - debugArgs: [ - `/tmp/lambci_debug_files/py_debug_wrapper.py --listen 0.0.0.0:${actual.debugPort} --wait-for-client --log-to-stderr --debug`, - ], - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - invokeTarget: { ...input.invokeTarget }, - lambda: { - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: 3, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - port: actual.debugPort, - redirectOutput: false, - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - - // - // Python-related fields - // - host: 'localhost', - pathMappings: [ - { - localRoot: pathutil.normalize(path.join(appDir, 'python3.14-plain-sam-app/hello_world')), - remoteRoot: '/var/task', - }, - ], - } - - // Windows: pathMappings has uppercase and lowercase variants. - // See getLocalRootVariants(). ref: 4bd1418863edd45e27 - if (os.platform() === 'win32') { - const localRoot: string = expected.pathMappings[0].localRoot - expected.pathMappings.unshift({ - localRoot: localRoot.substring(0, 1).toLowerCase() + localRoot.substring(1), - remoteRoot: '/var/task', - }) - } - - assertEqualLaunchConfigs(actual, expected) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - pathMappings: inputWithPathMapping.lambda.pathMappings, - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // Test noDebug=true. - expected.handlerName = 'app.lambda_handler' - await assertEqualNoDebugTemplateTarget(input, expected, folder, debugConfigProvider, true) - }) - - it('target=api: python 3.14 (deep project tree)', async function () { - // Use "testFixtures/workspaceFolder/" as the project root to test - // a deeper tree. - const appDir = pathutil.normalize(path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/')) - const folder = testutil.getWorkspaceFolder(appDir) - const input: AwsSamDebuggerConfiguration = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-py314-api', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: API_TARGET_TYPE, - templatePath: 'python3.14-plain-sam-app/template.yaml', - logicalId: 'HelloWorldFunction', - }, - api: { - path: '/hello', - httpMethod: 'put', - headers: { - 'accept-language': 'es-CA', - }, - querystring: 'name1=value1&foo&bar', - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'python3.14-plain-sam-app/template.yaml')) - - // Invoke with noDebug=false (the default). - const actual = (await debugConfigProvider.makeConfig(folder, input))! - // Expected result with noDebug=false. - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'python3.14' as Runtime, - runtimeFamily: lambdaModel.RuntimeFamily.Python, - type: AWS_SAM_DEBUG_TYPE, - handlerName: 'app.lambda_handler', - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: pathutil.normalize(path.join(appDir, 'python3.14-plain-sam-app/hello_world')), - debugArgs: [ - `/tmp/lambci_debug_files/py_debug_wrapper.py --listen 0.0.0.0:${actual.debugPort} --wait-for-client --log-to-stderr --debug`, - ], - apiPort: actual.apiPort, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - invokeTarget: { ...input.invokeTarget }, - api: { - ...(input.api as APIGatewayProperties), - }, - lambda: { - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: 3, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - port: actual.debugPort, - redirectOutput: false, - architecture: undefined, - region: 'us-west-2', - - // - // Python-related fields - // - host: 'localhost', - pathMappings: [ - { - localRoot: pathutil.normalize(path.join(appDir, 'python3.14-plain-sam-app/hello_world')), - remoteRoot: '/var/task', - }, - ], - parameterOverrides: undefined, - } - - // Windows: pathMappings has uppercase and lowercase variants. - // See getLocalRootVariants(). ref: 4bd1418863edd45e27 - if (os.platform() === 'win32') { - const localRoot: string = expected.pathMappings[0].localRoot - expected.pathMappings.unshift({ - localRoot: localRoot.substring(0, 1).toLowerCase() + localRoot.substring(1), - remoteRoot: '/var/task', - }) - } - - assertEqualLaunchConfigs(actual, expected) - - // Test noDebug=true. - expected.handlerName = 'app.lambda_handler' - await assertEqualNoDebugTemplateTarget(input, expected, folder, debugConfigProvider, true) - }) - - it('target=template: Image python 3.14', async function () { - // Use "testFixtures/workspaceFolder/" as the project root to test - // a deeper tree. - const appDir = pathutil.normalize(path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/')) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-py314-image-template', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: 'python3.14-image-sam-app/template.yaml', - logicalId: 'HelloWorldFunction', - }, - lambda: { - runtime: 'python3.14', - }, - } - const templatePath = vscode.Uri.file(path.join(appDir, 'python3.14-image-sam-app/template.yaml')) - - // Invoke with noDebug=false (the default). - const actual = (await debugConfigProvider.makeConfig(folder, input))! - // Expected result with noDebug=false. - const expected: SamLaunchRequestArgs = { - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'python3.14' as Runtime, - runtimeFamily: lambdaModel.RuntimeFamily.Python, - type: AWS_SAM_DEBUG_TYPE, - handlerName: 'HelloWorldFunction', - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: undefined, - eventPayloadFile: undefined, - codeRoot: pathutil.normalize(path.join(appDir, 'python3.14-image-sam-app/hello_world')), - debugArgs: [ - `/var/lang/bin/python3.14 /tmp/lambci_debug_files/py_debug_wrapper.py --listen 0.0.0.0:${actual.debugPort} --wait-for-client --log-to-stderr /var/runtime/bootstrap.py --debug`, - ], - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - invokeTarget: { ...input.invokeTarget }, - lambda: { - environmentVariables: {}, - memoryMb: undefined, - timeoutSec: 3, - runtime: 'python3.14', - }, - name: input.name, - templatePath: pathutil.normalize(path.join(path.dirname(templatePath.fsPath), 'template.yaml')), - port: actual.debugPort, - redirectOutput: false, - architecture: undefined, - region: 'us-west-2', - - // - // Python-related fields - // - host: 'localhost', - pathMappings: [ - { - localRoot: pathutil.normalize(path.join(appDir, 'python3.14-image-sam-app/hello_world')), - remoteRoot: '/var/task', - }, - ], - parameterOverrides: undefined, - } - - // Windows: pathMappings has uppercase and lowercase variants. - // See getLocalRootVariants(). ref: 4bd1418863edd45e27 - if (os.platform() === 'win32') { - const localRoot: string = expected.pathMappings[0].localRoot - expected.pathMappings.unshift({ - localRoot: localRoot.substring(0, 1).toLowerCase() + localRoot.substring(1), - remoteRoot: '/var/task', - }) - } - - assertEqualLaunchConfigs(actual, expected) - - // - // Test pathMapping - // - const inputWithPathMapping = { - ...input, - lambda: { - ...input.lambda, - pathMappings: [ - { - localRoot: 'somethingLocal', - remoteRoot: 'somethingRemote', - }, - { - localRoot: 'anotherLocal', - remoteRoot: 'anotherRemote', - }, - ] as PathMapping[], - }, - } - const actualWithPathMapping = (await debugConfigProvider.makeConfig(folder, inputWithPathMapping))! - const expectedWithPathMapping: SamLaunchRequestArgs = { - ...expected, - lambda: { - ...expected.lambda, - pathMappings: inputWithPathMapping.lambda.pathMappings, - }, - pathMappings: inputWithPathMapping.lambda.pathMappings, - baseBuildDir: actualWithPathMapping.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - assertEqualLaunchConfigs(actualWithPathMapping, expectedWithPathMapping) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'launch', - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: undefined, - eventPayloadFile: undefined, - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - - it('verify python debug option not set for non-debug log level', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/python3.14-plain-sam-app') - ) - const relPayloadPath = `events/event.json` - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'Test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'app.lambda_handler', - projectRoot: 'hello_world', - }, - lambda: { - runtime: 'python3.14', // Arbitrary choice of runtime for this test - payload: { - path: relPayloadPath, - }, - }, - } - // Debug option should not appear now - getLogger().setLogLevel('verbose') - const actual = (await debugConfigProvider.makeConfig(folder, input))! - // Just check that a debug flag is not being passed to the wrapper - assert.strictEqual( - actual.debugArgs![0], - `/tmp/lambci_debug_files/py_debug_wrapper.py --listen 0.0.0.0:${actual.debugPort} --wait-for-client --log-to-stderr`, - 'Debug option was set for log level "verbose"' - ) - getLogger().setLogLevel('debug') - }) - - it('debugconfig with "aws" section', async function () { - // Simluates credentials in "aws.credentials" launch-config field. - const configCredentials: Credentials = { - accessKeyId: 'access-from-config', - secretAccessKey: 'secret-from-config', - sessionToken: 'session-from-config', - } - const mockCredentialsStore: CredentialsStore = new CredentialsStore() - - const credentialsProvider: CredentialsProvider = { - getCredentials: sandbox.stub().resolves({} as any as AWS.Credentials), - getProviderType: sandbox.stub().resolves('profile'), - getTelemetryType: sandbox.stub().resolves('staticProfile'), - getCredentialsId: sandbox.stub().returns({ - credentialSource: 'profile', - credentialTypeId: 'someId', - }), - getDefaultRegion: sandbox.stub().returns('someRegion'), - getHashCode: sandbox.stub().returns('1234'), - canAutoConnect: sandbox.stub().returns(true), - isAvailable: sandbox.stub().returns(Promise.resolve(true)), - } - const getCredentialsProviderStub = sandbox.stub( - CredentialsProviderManager.getInstance(), - 'getCredentialsProvider' - ) - getCredentialsProviderStub.resolves(credentialsProvider) - const upsertStub = sandbox.stub(mockCredentialsStore, 'upsertCredentials') - upsertStub.resolves({ - credentials: configCredentials, - credentialsHashCode: 'unimportant', - }) - const debugConfigProviderMockCredentials = new SamDebugConfigProvider({ - ...fakeContext, - credentialsStore: mockCredentialsStore, - }) - - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/js-manifest-in-root/') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const awsSection = { - aws: { - credentials: 'profile:success', - region: 'us-weast-9', - }, - } - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-extraneous-env', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: tempFile.fsPath, - logicalId: resourceName, - }, - lambda: { - // These are written to env-vars.json, but ignored by SAM. - environmentVariables: { - var1: '2', - var2: '1', - }, - }, - ...awsSection, - } - await testutil.toFile( - makeSampleSamTemplateYaml(true, { - resourceName: resourceName, - runtime: 'nodejs18.x', - handler: 'my.test.handler', - codeUri: 'codeuri', - }), - tempFile.fsPath - ) - const actual = (await debugConfigProviderMockCredentials.makeConfig(folder, input))! - const tempDir = path.dirname(actual.codeRoot) - - const expected: SamLaunchRequestArgs = { - // The `aws.credentials` field in debug config, overrides default toolkit credentials. - awsCredentials: configCredentials, - ...awsSection, - type: AWS_SAM_DEBUG_TYPE, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: undefined, - codeRoot: pathutil.normalize(path.join(tempDir, 'codeuri')), // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'my.test.handler', - invokeTarget: { - target: 'template', - templatePath: pathutil.normalize(path.join(tempDir ?? '?', 'test.yaml')), - logicalId: 'myResource', - }, - lambda: { - environmentVariables: { - var1: '2', - var2: '1', - }, - memoryMb: undefined, - timeoutSec: 12345, // From template.yaml. - }, - localRoot: pathutil.normalize(path.join(tempDir, 'codeuri')), // Normalized to absolute path. - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: 'x86_64', - // The `aws.region` field in debug config, overrides default toolkit region. - region: 'us-weast-9', - - // - // Node-related fields - // - address: 'localhost', - port: actual.debugPort, - preLaunchTask: undefined, - protocol: 'inspector', - remoteRoot: '/var/task', - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'nodejs18.x', - runtimeFamily: lambdaModel.RuntimeFamily.NodeJS, - skipFiles: ['/var/runtime/node_modules/**/*.js', '/**/*.js'], - continueOnAttach: true, - stopOnEntry: false, - } - - assertEqualLaunchConfigs(actual, expected) - }) - - it('target=code: go', async function () { - const appDir = pathutil.normalize( - path.join(testutil.getProjectDir(), 'testFixtures/workspaceFolder/go1-plain-sam-app') - ) - const folder = testutil.getWorkspaceFolder(appDir) - const input = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test-go-code', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'hello-world', - projectRoot: 'hello-world', - }, - lambda: { - runtime: 'go1.x', - // For target=code these envvars are written to the input-template.yaml. - environmentVariables: { - 'test-envvar-1': 'test value 1', - 'test-envvar-2': 'test value 2', - }, - memoryMb: 1.2, - timeoutSec: 9000, - payload: { - json: { - 'test-payload-key-1': 'test payload value 1', - 'test-payload-key-2': 'test payload value 2', - }, - }, - }, - } - const actual = (await debugConfigProvider.makeConfig(folder, input))! - const expected: SamLaunchRequestArgs = { - type: AWS_SAM_DEBUG_TYPE, - awsCredentials: fakeCredentials, - request: 'attach', // Input "direct-invoke", output "attach". - runtime: 'go1.x', - runtimeFamily: lambdaModel.RuntimeFamily.Go, - workspaceFolder: { - index: 0, - name: 'test-workspace-folder', - uri: vscode.Uri.file(appDir), - }, - baseBuildDir: actual.baseBuildDir, // Random, sanity-checked by assertEqualLaunchConfigs(). - envFile: `${actual.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actual.baseBuildDir}/event.json`, - codeRoot: pathutil.normalize(path.join(appDir, 'hello-world')), // Normalized to absolute path. - apiPort: undefined, - debugPort: actual.debugPort, - documentUri: vscode.Uri.file(''), // TODO: remove or test. - handlerName: 'hello-world', - invokeTarget: { ...input.invokeTarget }, - lambda: { - ...input.lambda, - }, - name: input.name, - templatePath: pathutil.normalize(path.join(actual.baseBuildDir!, 'app___vsctk___template.yaml')), - parameterOverrides: undefined, - architecture: undefined, - region: 'us-west-2', - - // - // Go-related fields - // - host: 'localhost', - mode: 'remote', - processName: 'godelve', - port: actual.debugPort, - preLaunchTask: undefined, - skipFiles: [], - debugArgs: ['-delveAPI=2'], - debugAdapter: 'legacy', - } - - assertEqualLaunchConfigs(actual, expected) - await assertFileText( - expected.envFile!, - '{"helloworld":{"test-envvar-1":"test value 1","test-envvar-2":"test value 2"}}' - ) - await assertFileText( - expected.eventPayloadFile!, - '{"test-payload-key-1":"test payload value 1","test-payload-key-2":"test payload value 2"}' - ) - await assertFileText( - expected.templatePath, - `Resources: - helloworld: - Type: AWS::Serverless::Function - Properties: - Handler: hello-world - CodeUri: >- - ${expected.codeRoot} - Runtime: go1.x - Environment: - Variables: - test-envvar-1: test value 1 - test-envvar-2: test value 2 - MemorySize: 1.2 - Timeout: 9000 -` - ) - - // - // Test noDebug=true. - // - ;(input as any).noDebug = true - const actualNoDebug = (await debugConfigProvider.makeConfig(folder, input))! - const expectedNoDebug: SamLaunchRequestArgs = { - ...expected, - noDebug: true, - request: 'attach', - mode: undefined, - debugPort: undefined, - port: -1, - baseBuildDir: actualNoDebug.baseBuildDir, - envFile: `${actualNoDebug.baseBuildDir}/env-vars.json`, - eventPayloadFile: `${actualNoDebug.baseBuildDir}/event.json`, - } - // The assert function should just check for this but all well - if (actualNoDebug.debugArgs === undefined) { - delete actualNoDebug.debugArgs - } - assertEqualLaunchConfigs(actualNoDebug, expectedNoDebug) - }) - }) -}) - -describe('ensureRelativePaths', function () { - it('ensures paths are relative', function () { - const workspace: vscode.WorkspaceFolder = { - uri: vscode.Uri.file('/test1/'), - name: 'test workspace', - index: 0, - } - const templateConfig = createTemplateAwsSamDebugConfig( - undefined, - undefined, - false, - 'test name 1', - '/test1/template.yaml' - ) - assert.strictEqual( - (templateConfig.invokeTarget as TemplateTargetProperties).templatePath, - '/test1/template.yaml' - ) - ensureRelativePaths(workspace, templateConfig) - assert.strictEqual((templateConfig.invokeTarget as TemplateTargetProperties).templatePath, 'template.yaml') - - const codeConfig = createCodeAwsSamDebugConfig( - undefined, - 'testName1', - '/test1/project', - lambdaModel.getDefaultRuntime(lambdaModel.RuntimeFamily.NodeJS)! - ) - assert.strictEqual((codeConfig.invokeTarget as CodeTargetProperties).projectRoot, '/test1/project') - ensureRelativePaths(workspace, codeConfig) - assert.strictEqual((codeConfig.invokeTarget as CodeTargetProperties).projectRoot, 'project') - }) -}) - -function createBaseCodeConfig(params: { - name?: string - lambdaHandler?: string - projectRoot?: string -}): AwsSamDebuggerConfiguration { - return { - type: AWS_SAM_DEBUG_TYPE, - name: params.name ?? 'whats in a name', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: params.lambdaHandler ?? 'sick handles', - projectRoot: params.projectRoot ?? 'root as in beer', - }, - } -} - -/** - * Gets a basic launch.json config for testing purposes, by generating the - * config from a sample project located at `rootFolder`. - */ -async function getConfig( - debugConfigProvider: SamDebugConfigProvider, - registry: CloudFormationTemplateRegistry, - rootFolder: string -): Promise<{ config: AwsSamDebuggerConfiguration; folder: vscode.WorkspaceFolder }> { - const appDir = pathutil.normalize(path.join(testutil.getProjectDir(), rootFolder)) - const folder = testutil.getWorkspaceFolder(appDir) - const templateFile = pathutil.normalize(path.join(appDir, 'template.yaml')) - await registry.addItem(vscode.Uri.file(templateFile)) - - // Generate config(s) from a sample project. - const configs = await debugConfigProvider.provideDebugConfigurations(folder) - if (!configs || configs.length === 0) { - throw Error(`failed to generate config from: ${rootFolder}`) - } - return { - config: configs[0], - folder: folder, - } -} - -function createFakeConfig(params: { - name?: string - target?: string - templatePath?: string - logicalId?: string -}): AwsSamDebuggerConfiguration { - return { - type: AWS_SAM_DEBUG_TYPE, - name: params.name ?? 'whats in a name', - request: DIRECT_INVOKE_TYPE, - invokeTarget: - !params.target || params.target === TEMPLATE_TARGET_TYPE - ? { - target: TEMPLATE_TARGET_TYPE, - templatePath: params.templatePath ?? 'somewhere else', - logicalId: params.logicalId ?? 'you lack resources', - } - : { - target: CODE_TARGET_TYPE, - lambdaHandler: 'test-handler', - projectRoot: 'test-project-root', - }, - } -} - -async function createAndRegisterYaml( - subValues: { - resourceName?: string - resourceType?: string - runtime?: string - handler?: string - }, - file: vscode.Uri, - registry: CloudFormationTemplateRegistry -) { - await testutil.toFile(makeSampleSamTemplateYaml(true, subValues), file.fsPath) - await registry.addItem(file) -} - -describe('createTemplateAwsSamDebugConfig', function () { - const name = 'my body is a template' - const templatePath = path.join('two', 'roads', 'diverged', 'in', 'a', 'yellow', 'wood') - - it('creates a template-type SAM debugger configuration with minimal configurations', function () { - const config = createTemplateAwsSamDebugConfig(undefined, undefined, false, name, templatePath) - assert.deepStrictEqual(config, { - name: `yellow:${name}`, - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - logicalId: name, - templatePath: templatePath, - }, - lambda: { - payload: {}, - environmentVariables: {}, - }, - }) - }) - - it('creates a template-type SAM debugger configuration with additional params', function () { - const params = { - eventJson: { - payload: 'uneventufl', - }, - environmentVariables: { - varial: 'invert to fakie', - }, - dockerNetwork: 'rockerFretwork', - } - const config = createTemplateAwsSamDebugConfig(undefined, undefined, false, name, templatePath, params) - assert.deepStrictEqual(config.lambda?.payload?.json, params.eventJson) - assert.deepStrictEqual(config.lambda?.environmentVariables, params.environmentVariables) - assert.strictEqual(config.sam?.dockerNetwork, params.dockerNetwork) - assert.strictEqual(config.sam?.containerBuild, undefined) - }) - - it('creates a template-type SAM debugger configuration with a runtime', function () { - const config = createTemplateAwsSamDebugConfig(undefined, 'runtime', true, name, templatePath, undefined) - assert.deepStrictEqual(config.lambda?.runtime, 'runtime') - }) -}) - -describe('createApiAwsSamDebugConfig', function () { - const name = 'my body is a template' - const templatePath = path.join('two', 'roads', 'diverged', 'in', 'a', 'yellow', 'wood') - const runtime = 'timeToRun' - - it('creates a API-type SAM debugger configuration with minimal configurations', function () { - const config = createApiAwsSamDebugConfig(undefined, undefined, name, templatePath) - assert.deepStrictEqual(config, { - name: `API yellow:${name}`, - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: API_TARGET_TYPE, - logicalId: name, - templatePath: templatePath, - }, - api: { - path: '/', - httpMethod: 'get', - payload: { - json: {}, - }, - }, - }) - }) - - it('creates a API-type SAM debugger configuration with additional params', function () { - const config = createApiAwsSamDebugConfig(undefined, runtime, name, templatePath, { - payload: { json: { key: 'value' } }, - httpMethod: 'OPTIONS', - path: '/api', - }) - assert.deepStrictEqual(config, { - name: `API yellow:${name} (${runtime})`, - type: AWS_SAM_DEBUG_TYPE, - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: API_TARGET_TYPE, - logicalId: name, - templatePath: templatePath, - }, - api: { - path: '/api', - httpMethod: 'OPTIONS', - payload: { - json: { key: 'value' }, - }, - }, - lambda: { - runtime, - }, - }) - }) -}) - -describe('debugConfiguration', function () { - let tempFolder: string - let tempFile: vscode.Uri - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - tempFile = vscode.Uri.file(path.join(tempFolder, 'test.yaml')) - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - const r = await globals.templateRegistry - r.reset() - }) - - it('getCodeRoot(), getHandlerName() with invokeTarget=code', async function () { - const folder = testutil.getWorkspaceFolder(tempFolder) - const relativePath = 'src' - const fullPath = pathutil.normalize(path.join(tempFolder, relativePath)) - - const config = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: CODE_TARGET_TYPE, - lambdaHandler: 'my.test.handler', - projectRoot: '', - }, - lambda: { - runtime: [...lambdaModel.nodeJsRuntimes.values()][0], - }, - } - - assert.strictEqual(await debugConfiguration.getHandlerName(folder, config), 'my.test.handler') - - // Config with relative path: - config.invokeTarget.projectRoot = relativePath - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, config), fullPath) - - // Config with absolute path: - config.invokeTarget.projectRoot = fullPath - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, config), fullPath) - }) - - it('getCodeRoot(), getHandlerName() with invokeTarget=template', async function () { - const folder = testutil.getWorkspaceFolder(tempFolder) - const relativePath = 'src' - const fullPath = pathutil.normalize(path.join(tempFolder, relativePath)) - - const config = { - type: AWS_SAM_DEBUG_TYPE, - name: 'test debugconfig', - request: DIRECT_INVOKE_TYPE, - invokeTarget: { - target: TEMPLATE_TARGET_TYPE, - templatePath: tempFile.fsPath, - logicalId: 'TestResource', - }, - lambda: { - runtime: [...lambdaModel.nodeJsRuntimes.values()][0], - }, - sam: { - template: { - parameters: { - override: 'override', - }, - }, - }, - } - - // Template with relative path: - await testutil.toFile( - makeSampleSamTemplateYaml(true, { codeUri: relativePath, handler: 'handler' }), - tempFile.fsPath - ) - await (await globals.templateRegistry).addItem(tempFile) - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, config), fullPath) - assert.strictEqual(await debugConfiguration.getHandlerName(folder, config), 'handler') - - // Template with absolute path: - await testutil.toFile(makeSampleSamTemplateYaml(true, { codeUri: fullPath }), tempFile.fsPath) - await (await globals.templateRegistry).addItem(tempFile) - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, config), fullPath) - - // Template with refs that don't override: - const tempFileRefs = vscode.Uri.file(path.join(tempFolder, 'testRefs.yaml')) - const fileRefsConfig = { - ...config, - invokeTarget: { - ...config.invokeTarget, - templatePath: tempFileRefs.fsPath, - }, - } - const paramStr = makeSampleYamlParameters({ - notOverride: { - Type: 'String', - Default: 'notDoingAnything', - }, - }) - await testutil.toFile( - makeSampleSamTemplateYaml(true, { codeUri: fullPath, handler: 'handler' }, paramStr), - tempFileRefs.fsPath - ) - await (await globals.templateRegistry).addItem(tempFileRefs) - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, fileRefsConfig), fullPath) - assert.strictEqual(await debugConfiguration.getHandlerName(folder, fileRefsConfig), 'handler') - - // Template with refs that overrides handler via default parameter value in YAML template - const tempFileDefaultRefs = vscode.Uri.file(path.join(tempFolder, 'testDefaultRefs.yaml')) - const fileDefaultRefsConfig = { - ...config, - invokeTarget: { - ...config.invokeTarget, - templatePath: tempFileDefaultRefs.fsPath, - }, - } - const paramStrDefaultOverride = makeSampleYamlParameters({ - defaultOverride: { - Type: 'String', - Default: 'thisWillOverride', - }, - }) - await testutil.toFile( - makeSampleSamTemplateYaml( - true, - { codeUri: fullPath, handler: '!Ref defaultOverride' }, - paramStrDefaultOverride - ), - tempFileDefaultRefs.fsPath - ) - await (await globals.templateRegistry).addItem(tempFileDefaultRefs) - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, fileDefaultRefsConfig), fullPath) - assert.strictEqual(await debugConfiguration.getHandlerName(folder, fileDefaultRefsConfig), 'thisWillOverride') - - // Template with refs that overrides handler via override value in launch config - const tempFileOverrideRef = vscode.Uri.file(path.join(tempFolder, 'testOverrideRefs.yaml')) - const fileOverrideRefConfig = { - ...config, - invokeTarget: { - ...config.invokeTarget, - templatePath: tempFileOverrideRef.fsPath, - }, - } - const paramStrNoDefaultOverride = makeSampleYamlParameters({ - override: { - Type: 'String', - }, - }) - await testutil.toFile( - makeSampleSamTemplateYaml(true, { codeUri: fullPath, handler: '!Ref override' }, paramStrNoDefaultOverride), - tempFileOverrideRef.fsPath - ) - await (await globals.templateRegistry).addItem(tempFileOverrideRef) - assert.strictEqual(await debugConfiguration.getCodeRoot(folder, fileOverrideRefConfig), fullPath) - assert.strictEqual(await debugConfiguration.getHandlerName(folder, fileOverrideRefConfig), 'override') - }) -}) diff --git a/packages/core/src/test/shared/sam/deploy.test.ts b/packages/core/src/test/shared/sam/deploy.test.ts deleted file mode 100644 index 3928f50ceb..0000000000 --- a/packages/core/src/test/shared/sam/deploy.test.ts +++ /dev/null @@ -1,1313 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { S3Bucket } from '../../../shared/clients/s3' -import { AppNode } from '../../../awsService/appBuilder/explorer/nodes/appNode' -import { assertTelemetry, getWorkspaceFolder, TestFolder } from '../../testUtil' -import { DeployParams, DeployWizard, getDeployWizard, runDeploy } from '../../../shared/sam/deploy' -import { globals, ToolkitError } from '../../../shared' -import sinon from 'sinon' -import { samconfigCompleteData, samconfigInvalidData, validTemplateData } from './samTestUtils' -import * as SamUtilsModule from '../../../shared/sam/utils' -import assert from 'assert' -import { getTestWindow } from '../vscode/window' -import { CloudFormationClient } from '../../../shared/clients/cloudFormation' -import { intoCollection } from '../../../shared/utilities/collectionUtils' -import { clickBackButton, createPromptHandler, PrompterTester } from '../wizards/prompterTester' -import { RegionNode } from '../../../awsexplorer/regionNode' -import { createTestRegionProvider } from '../regions/testUtil' -import { S3Client } from '../../../shared/clients/s3' -import * as CloudFormationClientModule from '../../../shared/clients/cloudFormation' -import * as S3ClientModule from '../../../shared/clients/s3' -import * as ProcessUtilsModule from '../../../shared/utilities/processUtils' -import * as ProcessTerminalModule from '../../../shared/sam/processTerminal' -import * as ResolveEnvModule from '../../../shared/env/resolveEnv' -import * as SamConfiModule from '../../../shared/sam/config' -import { UserAgent as __UserAgent } from '@smithy/types' - -import { SamAppLocation } from '../../../awsService/appBuilder/explorer/samProject' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import { TemplateItem } from '../../../shared/ui/sam/templatePrompter' -import { ParamsSource } from '../../../shared/ui/sam/paramsSourcePrompter' -import { BucketSource } from '../../../shared/ui/sam/bucketPrompter' -import { TestInputBox, TestQuickPick } from '../vscode/quickInput' - -describe('SAM DeployWizard', async function () { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - let workspaceFolder: vscode.WorkspaceFolder - let templateFile: vscode.Uri - - let mockDefaultCFNClient: sinon.SinonStubbedInstance - let mockDefaultS3Client: sinon.SinonStubbedInstance - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - workspaceFolder = getWorkspaceFolder(testFolder.path) - sandbox = sinon.createSandbox() - - // Simulate return of deployed stacks - mockDefaultCFNClient = sandbox.createStubInstance(CloudFormationClientModule.CloudFormationClient) - sandbox.stub(CloudFormationClientModule, 'CloudFormationClient').returns(mockDefaultCFNClient) - mockDefaultCFNClient.listAllStacks.returns(intoCollection(stackSummaries)) - - // Simulate return of list bucket - mockDefaultS3Client = sandbox.createStubInstance(S3ClientModule.S3Client) - sandbox.stub(S3ClientModule, 'S3Client').returns(mockDefaultS3Client) - mockDefaultS3Client.listBucketsIterable.returns(intoCollection(s3BucketListSummary)) - - // generate template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile) - }) - - afterEach(() => { - sandbox.restore() - }) - - describe('entry: template file', () => { - it('happy path with invalid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 1. ('Specify required parameters and save as defaults') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 1. 'stack1' - * - bucketSource : [Select] 1. ('Create a SAM CLI managed S3 bucket') - * - bucketName : [Skip] automatically set for bucketSource option 1 - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigInvalidData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack1') - assert.strictEqual(quickPick.items[1].label, 'stack2') - assert.strictEqual(quickPick.items[2].label, 'stack3') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', (quickPick) => { - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Create a SAM CLI managed S3 bucket') - assert.strictEqual(quickPick.items[1].label, 'Specify an S3 bucket') - quickPick.acceptItem(quickPick.items[0]) - }) - .build() - - const parameters = await (await getDeployWizard(templateFile)).run() - - assert(parameters) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, 0) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack1') - assert.strictEqual(parameters.bucketSource, 0) - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] null; will use 'us-west-2' from samconfig - * - stackName : [Skip] null; will use 'project-1' from samconfig - * - bucketSource : [Skip] null; will use value from from samconfig - * - bucketName : [Skip] automatically set for bucketSource option 1 - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - - const parameters = await (await getDeployWizard(templateFile)).run() - - assert(parameters) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, 2) - assert(!parameters.region) - assert(!parameters.stackName) - assert(!parameters.bucketSource) - prompterTester.assertCallAll() - }) - }) - - describe('entry: region node', () => { - const expectedRegionId = 'us-west-2' - let regionNode: RegionNode - - beforeEach(async () => { - // Create RegionNode as entry point - regionNode = new RegionNode( - { id: expectedRegionId, name: 'US West (N. California)' }, - createTestRegionProvider() - ) - }) - - it('happy path without/invalid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Skip?] undefined - * - DestinationBucketName : [Skip?] undefined - * - * - template : [Select] template/yaml set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Select] 1. 'stack1' - * - bucketSource : [Select] 2. ('Specify an S3 bucket') - * - bucketName : [Select] 1. 'stack-1-bucket' - */ - - // provide testWindow so that we can call other api - const testWindow = getTestWindow() - - PrompterTester.init({ testWindow }) - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack1') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', (quickPick) => { - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Create a SAM CLI managed S3 bucket') - assert.strictEqual(quickPick.items[1].label, 'Specify an S3 bucket') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack-1-bucket') - assert.strictEqual(quickPick.items[1].label, 'stack-2-bucket') - assert.strictEqual(quickPick.items[2].label, 'stack-3-bucket') - quickPick.acceptItem(quickPick.items[0]) - }) - .build() - - const parameters = await (await getDeployWizard(regionNode)).run() - - assert(parameters) - // assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - // assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - assert.strictEqual(parameters.paramsSource, 1) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack1') - assert.strictEqual(parameters.bucketSource, 1) - assert.strictEqual(parameters.bucketName, 'stack-1-bucket') - }) - - it('happy path with samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Skip?] undefined - * - DestinationBucketName : [Skip?] undefined - * - * - template : [Select] template.yaml - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Skip] null; will use 'project-1' from samconfig - * - bucketSource : [Skip] null; will use value from from samconfig - * - bucketName : [Skip] null; will use value from samconfig file skip from bucketSource option - */ - - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - - const parameters = await (await getDeployWizard(regionNode)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - assert.strictEqual(parameters.paramsSource, 2) - assert.strictEqual(parameters.region, 'us-west-2') - assert(!parameters.stackName) - assert(!parameters.bucketSource) - prompterTester.assertCallAll() - }) - }) - - describe('entry: appBuilder', () => { - let appNode: AppNode - - beforeEach(async () => { - // Create a mock samAppLocation object - const expectedSamAppLocation = { - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - } - appNode = new AppNode(expectedSamAppLocation) - }) - - it('happy path without/invalid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 2. 'stack2' - * - bucketSource : [Select] 1. ('Create a SAM CLI managed S3 bucket') - * - bucketName : [Skip] automatically set for bucketSource option 1 - */ - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack1') - assert.strictEqual(quickPick.items[1].label, 'stack2') - assert.strictEqual(quickPick.items[2].label, 'stack3') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', (quickPick) => { - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Create a SAM CLI managed S3 bucket') - assert.strictEqual(quickPick.items[1].label, 'Specify an S3 bucket') - quickPick.acceptItem(quickPick.items[0]) - }) - .build() - - const parameters = await (await getDeployWizard(appNode)).run() - - assert(parameters) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, 1) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack2') - assert.strictEqual(parameters.bucketSource, 0) - assert(!parameters.bucketName) - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] null; will use value from samconfig file - * - stackName : [Skip] null; will use value from samconfig file - * - bucketSource : [Skip] null; will use value from samconfig file - * - bucketName : [Skip] automatically set for bucketSource option 1 - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - - const parameters = await (await getDeployWizard(appNode)).run() - - assert(parameters) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, 2) - assert(!parameters.region) - assert(!parameters.stackName) - assert(!parameters.bucketSource) - prompterTester.assertCallAll() - }) - }) - - describe('entry: command palette', () => { - it('happy path without/invalid samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Skip?] undefined - * - DestinationBucketName : [Skip?] undefined - * - * - template : [Select] template/yaml set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Select] 3. 'stack3' - * - bucketSource : [Select] 2. ('Specify an S3 bucket') - * - bucketName : [Select] 3. 'stack-3-bucket' - */ - - // Create a second project folder to simulate multiple project in 1 workspace - const testFolder2 = await TestFolder.create() - const templateFile2 = vscode.Uri.file(await testFolder2.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile2) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack1') - assert.strictEqual(quickPick.items[1].label, 'stack2') - assert.strictEqual(quickPick.items[2].label, 'stack3') - quickPick.acceptItem(quickPick.items[2]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', (quickPick) => { - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, 'Create a SAM CLI managed S3 bucket') - assert.strictEqual(quickPick.items[1].label, 'Specify an S3 bucket') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack-1-bucket') - assert.strictEqual(quickPick.items[1].label, 'stack-2-bucket') - assert.strictEqual(quickPick.items[2].label, 'stack-3-bucket') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - - const parameters = await (await getDeployWizard()).run() - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - assert.strictEqual(parameters.paramsSource, 1) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack3') - assert.strictEqual(parameters.bucketSource, 1) - assert.strictEqual(parameters.bucketName, 'stack-3-bucket') - prompterTester.assertCallAll() - }) - - it('happy path without/invalid samconfig.toml with backward click', async () => { - /** - * Selection: - * - SourceBucketName : [Skip?] undefined - * - DestinationBucketName : [Skip?] undefined - * - * - template : [Select] template/yaml set - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Select] 3. 'stack3' - * - bucketSource : [Select] 2. ('Specify an S3 bucket') - * - bucketName : [Select] 3. 'stack-3-bucket' - */ - - // Create a second project folder to simulate multiple project in 1 workspace - const testFolder2 = await TestFolder.create() - const templateFile2 = vscode.Uri.file(await testFolder2.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile2) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox( - 'Specify SAM Template parameter value for SourceBucketName', - (() => { - return createPromptHandler({ - default: async (inputBox: TestInputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }, - numbered: [ - { - order: [2], - handler: clickBackButton, - }, - { - order: [1], - handler: async (inputBox: TestInputBox) => { - inputBox.acceptValue('my-source-bucket-name-not-final') - }, - }, - ], - }) - })() - ) - .handleInputBox( - 'Specify SAM Template parameter value for DestinationBucketName', - (() => { - return createPromptHandler({ - default: async (inputBox: TestInputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }, - numbered: [ - { - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Specify parameter source for deploy', - (() => { - return createPromptHandler({ - default: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - quickPick.acceptItem(quickPick.items[1]) - }, - numbered: [ - { - order: [1], - handler: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) - }, - }, - { - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Select a region', - (() => { - return createPromptHandler({ - default: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }, - numbered: [ - { - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Select a CloudFormation Stack', - (() => { - return createPromptHandler({ - default: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[2].label, 'stack3') - quickPick.acceptItem(quickPick.items[2]) - }, - numbered: [ - { - order: [1], - handler: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, 'stack1') - quickPick.acceptItem(quickPick.items[0]) - }, - }, - { - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Specify S3 bucket for deployment artifacts', - (() => { - return createPromptHandler({ - default: async (quickPick: TestQuickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[1].label, 'Specify an S3 bucket') - quickPick.acceptItem(quickPick.items[1]) - }, - numbered: [ - { - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Select an S3 Bucket', - (() => { - return createPromptHandler({ - default: async (picker: TestQuickPick) => { - await picker.untilReady() - assert.strictEqual(picker.items[2].label, 'stack-3-bucket') - picker.acceptItem(picker.items[2]) - }, - numbered: [ - { - order: [1], - handler: clickBackButton, - }, - ], - }) - })() - ) - .build() - - const parameters = await (await getDeployWizard()).run() - assert(parameters) - - const expectedCallOrder = [ - 'Select a SAM/CloudFormation Template', - 'Specify SAM Template parameter value for SourceBucketName', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify parameter source for deploy', - 'Select a region', - 'Select a CloudFormation Stack', - 'Specify S3 bucket for deployment artifacts', - 'Select an S3 Bucket', - 'Specify S3 bucket for deployment artifacts', - 'Select a CloudFormation Stack', - 'Select a region', - 'Specify parameter source for deploy', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify SAM Template parameter value for SourceBucketName', - 'Select a SAM/CloudFormation Template', - 'Specify SAM Template parameter value for SourceBucketName', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify parameter source for deploy', - 'Select a region', - 'Select a CloudFormation Stack', - 'Specify S3 bucket for deployment artifacts', - 'Select an S3 Bucket', - ] - - assert.deepStrictEqual( - { - ...parameters, - template: { uri: { fsPath: parameters.template.uri.fsPath } }, - projectRoot: { fsPath: parameters.projectRoot.fsPath }, - }, - { - template: { - uri: { - fsPath: templateFile.fsPath, - }, - }, - projectRoot: { - fsPath: projectRoot.fsPath, - }, - templateParameters: { - SourceBucketName: 'my-source-bucket-name', - DestinationBucketName: 'my-destination-bucket-name', - }, - paramsSource: 1, - region: 'us-west-2', - stackName: 'stack3', - bucketSource: 1, - bucketName: 'stack-3-bucket', - } - ) - - for (const [index, title] of expectedCallOrder.entries()) { - prompterTester.assertCallOrder(title, index + 1) - } - }) - - it('happy path with samconfig.toml', async () => { - /** - * Selection: - * - SourceBucketName : [Skip?] undefined - * - DestinationBucketName : [Skip?] undefined - * - * - template : [Select] template.yaml - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] null; will use value from samconfig file - * - stackName : [Skip] null; will use value from samconfig file - * - bucketSource : [Skip] null; will use value from samconfig file - * - bucketName : [Skip] automatically set for bucketSource option 1 - */ - - // Create a second project folder to simulate multiple project in 1 workspace - const testFolder2 = await TestFolder.create() - const templateFile2 = vscode.Uri.file(await testFolder2.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile2) - - await testFolder.write('samconfig.toml', samconfigCompleteData) - // Simulate return of deployed stacks - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 2) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for deploy', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - const parameters = await (await getDeployWizard()).run() - assert(parameters) - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.templateParameters.SourceBucketName, 'my-source-bucket-name') - assert.strictEqual(parameters.templateParameters.DestinationBucketName, 'my-destination-bucket-name') - assert.strictEqual(parameters.paramsSource, 2) - assert(!parameters.region) - assert(!parameters.stackName) - assert(!parameters.bucketSource) - prompterTester.assertCallAll() - }) - }) -}) - -describe('SAM Deploy', () => { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - let workspaceFolder: vscode.WorkspaceFolder - let templateFile: vscode.Uri - - let mockDeployParams: DeployParams - let mockGetSpawnEnv: sinon.SinonStub - let mockGetSamCliPath: sinon.SinonStub - let mockRunInTerminal: sinon.SinonStub - let spyWriteSamconfigGlobal: sinon.SinonSpy - - let appNode: AppNode - - // Dependency clients - let mockChildProcess: sinon.SinonStub - beforeEach(async function () { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - workspaceFolder = getWorkspaceFolder(testFolder.path) - sandbox = sinon.createSandbox() - - // Create template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile) - - spyWriteSamconfigGlobal = sandbox.spy(SamConfiModule, 'writeSamconfigGlobal') - - mockGetSpawnEnv = sandbox.stub().resolves({ - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }) - sandbox.stub(ResolveEnvModule, 'getSpawnEnv').callsFake(mockGetSpawnEnv) - - appNode = new AppNode({ - samTemplateUri: templateFile, - workspaceFolder: workspaceFolder, - } as SamAppLocation) - }) - - afterEach(() => { - sandbox.restore() - }) - - describe(':) path', () => { - beforeEach(() => { - mockGetSamCliPath = sandbox.stub().resolves({ path: 'sam-cli-path' }) - sandbox.stub(SamUtilsModule, 'getSamCliPathAndVersion').callsFake(mockGetSamCliPath) - - mockChildProcess = sandbox.stub().resolves({}) - sandbox.stub(ProcessUtilsModule, 'ChildProcess').callsFake(mockChildProcess) - - mockRunInTerminal = sandbox.stub().resolves(Promise.resolve()) - sandbox.stub(ProcessTerminalModule, 'runInTerminal').callsFake(mockRunInTerminal) - }) - - it('[ParamsSource.SamConfig] should instantiate the correct ChildProcess', async () => { - // Mock result from DeployWizard; the Wizard is already tested separately - mockDeployParams = { - paramsSource: ParamsSource.SamConfig, - templateParameters: { - SourceBucketName: 'my-source-bucket-name', - DestinationBucketName: 'my-destination-bucket-name', - }, - region: undefined, - stackName: undefined, - bucketName: undefined, - template: { uri: templateFile, data: {} } as TemplateItem, - bucketSource: BucketSource.UserProvided, - projectRoot: projectRoot, - } as unknown as DeployParams - - // Create samconfig.toml in temporary test folder - const samconfigFile = await testFolder.write('samconfig.toml', samconfigCompleteData) - - // Stub the DeployWizard output - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - - await runDeploy(appNode) - - // Check that ChildProcess for build and deploy are instantiated correctly - assert.strictEqual(mockChildProcess.callCount, 2) - const buildChildProcess = mockChildProcess.getCall(0) - const deployChildProcess = mockChildProcess.getCall(1) - - assert.deepEqual(buildChildProcess.args, [ - 'sam-cli-path', - ['build', '--cached'], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert.deepEqual(deployChildProcess.args, [ - 'sam-cli-path', - [ - 'deploy', - '--no-confirm-changeset', - '--region', - // Expect region information from samconfig.toml - 'us-west-2', - '--config-file', - `${samconfigFile}`, - '--parameter-overrides', - `ParameterKey=SourceBucketName,ParameterValue=${mockDeployParams.templateParameters.SourceBucketName} ` + - `ParameterKey=DestinationBucketName,ParameterValue=${mockDeployParams.templateParameters.DestinationBucketName}`, - ], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(spyWriteSamconfigGlobal.notCalled) - // Check that runInTerminal is called with the correct arguments for build - assert.strictEqual(mockRunInTerminal.callCount, 2) - const buildProcess = mockChildProcess.getCall(0).returnValue - assert.deepEqual(mockRunInTerminal.getCall(0).args, [buildProcess, 'build']) - - const deployProcess = mockChildProcess.getCall(1).returnValue // Get the instance from the first call - assert.deepEqual(mockRunInTerminal.getCall(1).args, [deployProcess, 'deploy']) - - assertTelemetry('sam_deploy', { result: 'Succeeded', source: 'appBuilderDeploy' }) - }) - - it('[ParamsSource.SamConfig] when trigger from region node should instantiate the correct ChildProcess without region flag', async () => { - // Mock result from DeployWizard; the Wizard is already tested separately - mockDeployParams = { - paramsSource: ParamsSource.SamConfig, - templateParameters: { - SourceBucketName: 'my-source-bucket-name', - DestinationBucketName: 'my-destination-bucket-name', - }, - // Simulate entry from region node when region ('us-east-1') differ from 'us-west-2' in samconfig.toml - region: 'us-east-1', - stackName: undefined, - bucketName: undefined, - template: { uri: templateFile, data: {} } as TemplateItem, - bucketSource: BucketSource.UserProvided, - projectRoot: projectRoot, - } as unknown as DeployParams - - // Create samconfig.toml in temporary test folder - const samconfigFile = await testFolder.write('samconfig.toml', samconfigCompleteData) - - // Stub the DeployWizard output - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - - await runDeploy(appNode) - - // Check that ChildProcess for build and deploy are instantiated correctly - assert.strictEqual(mockChildProcess.callCount, 2) - const buildChildProcess = mockChildProcess.getCall(0) - const deployChildProcess = mockChildProcess.getCall(1) - - assert.deepEqual(buildChildProcess.args, [ - 'sam-cli-path', - ['build', '--cached'], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert.deepEqual(deployChildProcess.args, [ - 'sam-cli-path', - [ - 'deploy', - '--no-confirm-changeset', - '--region', - `${mockDeployParams.region}`, - '--config-file', - `${samconfigFile}`, - '--parameter-overrides', - `ParameterKey=SourceBucketName,ParameterValue=${mockDeployParams.templateParameters.SourceBucketName} ` + - `ParameterKey=DestinationBucketName,ParameterValue=${mockDeployParams.templateParameters.DestinationBucketName}`, - ], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(spyWriteSamconfigGlobal.notCalled) - // Check that runInTerminal is called with the correct arguments for build - assert.strictEqual(mockRunInTerminal.callCount, 2) - const buildProcess = mockChildProcess.getCall(0).returnValue - assert.deepEqual(mockRunInTerminal.getCall(0).args, [buildProcess, 'build']) - - const deployProcess = mockChildProcess.getCall(1).returnValue // Get the instance from the first call - assert.deepEqual(mockRunInTerminal.getCall(1).args, [deployProcess, 'deploy']) - - assertTelemetry('sam_deploy', { result: 'Succeeded', source: 'appBuilderDeploy' }) - }) - - it('[ParamsSource.SpecifyAndSave] should instantiate the correct ChildProcess for sam build and deploy', async () => { - // Mock result from DeployWizard; the Wizard is already tested separately - mockDeployParams = { - paramsSource: ParamsSource.SpecifyAndSave, - templateParameters: { - SourceBucketName: 'my-source-bucket-name', - DestinationBucketName: 'my-destination-bucket-name', - }, - region: 'us-east-1', - stackName: 'stack1', - bucketName: undefined, - template: { uri: templateFile, data: {} } as TemplateItem, - bucketSource: BucketSource.SamCliManaged, - projectRoot: projectRoot, - } as unknown as DeployParams - - // Stub the DeployWizard output - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - - await runDeploy(appNode) - - // Check that ChildProcess for build and deploy are instantiated correctly - assert.strictEqual(mockChildProcess.callCount, 2) - const buildChildProcess = mockChildProcess.getCall(0) - const deployChildProcess = mockChildProcess.getCall(1) - - assert.deepEqual(buildChildProcess.args, [ - 'sam-cli-path', - ['build', '--cached'], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - - assert.deepEqual(deployChildProcess.args, [ - 'sam-cli-path', - [ - 'deploy', - '--no-confirm-changeset', - '--region', - `${mockDeployParams.region}`, - '--stack-name', - `${mockDeployParams.stackName}`, - '--resolve-s3', - '--capabilities', - 'CAPABILITY_IAM', - 'CAPABILITY_NAMED_IAM', - '--save-params', - '--parameter-overrides', - `ParameterKey=SourceBucketName,ParameterValue=${mockDeployParams.templateParameters.SourceBucketName} ` + - `ParameterKey=DestinationBucketName,ParameterValue=${mockDeployParams.templateParameters.DestinationBucketName}`, - ], - { - spawnOptions: { - cwd: mockDeployParams.projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - - assert( - spyWriteSamconfigGlobal.calledOnceWith(projectRoot, mockDeployParams.stackName, mockDeployParams.region) - ) - - // Check that runInTerminal is called with the correct arguments for build - assert.strictEqual(mockRunInTerminal.callCount, 2) - const buildProcess = mockChildProcess.getCall(0).returnValue - assert.deepEqual(mockRunInTerminal.getCall(0).args, [buildProcess, 'build']) - - const deployProcess = mockChildProcess.getCall(1).returnValue // Get the instance from the first call - assert.deepEqual(mockRunInTerminal.getCall(1).args, [deployProcess, 'deploy']) - - assertTelemetry('sam_deploy', { result: 'Succeeded', source: 'appBuilderDeploy' }) - }) - }) - - describe(':( path', () => { - let mockDeployParams: DeployParams - beforeEach(async () => { - mockDeployParams = { - paramsSource: ParamsSource.SpecifyAndSave, - templateParameters: { - SourceBucketName: 'my-source-bucket-name', - DestinationBucketName: 'my-destination-bucket-name', - }, - region: 'us-east-1', - stackName: 'stack1', - bucketName: undefined, - template: { uri: templateFile, data: {} } as TemplateItem, - bucketSource: BucketSource.SamCliManaged, - projectRoot: projectRoot, - } as unknown as DeployParams - }) - - it('should gracefully shut down when cancel by user', async () => { - try { - // Breaking point - sandbox.stub(DeployWizard.prototype, 'run').resolves(undefined as unknown as DeployParams) - - await runDeploy(appNode) - assert.fail('Should have thrown an CancellationError') - } catch (err) { - assert(spyWriteSamconfigGlobal.notCalled) - assert(err instanceof CancellationError) - } - }) - - it('should throw error given issue with sam cli prerequisite version', async () => { - try { - // Expect to be called - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - - // Break point - mockGetSamCliPath = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .rejects( - new ToolkitError('SAM CLI version 1.53.0 or higher is required', { code: 'VersionTooLow' }) - ) - - // Not expect to be called - mockChildProcess = sandbox.stub().resolves({}) - sandbox.stub(ProcessUtilsModule, 'ChildProcess').callsFake(mockChildProcess) - mockRunInTerminal = sandbox.stub().resolves(Promise.resolve()) - sandbox.stub(ProcessTerminalModule, 'runInTerminal').callsFake(mockRunInTerminal) - - await runDeploy(appNode) - assert.fail('Should have thrown an Error') - } catch (err) { - assert(err instanceof ToolkitError) - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcess.notCalled) - assert(mockRunInTerminal.notCalled) - assert(spyWriteSamconfigGlobal.notCalled) - assert.strictEqual(err.message, 'Failed to deploy SAM template') - } - }) - - it('should throw error given issue with building template', async () => { - try { - // Happy Stub - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - mockGetSamCliPath = sandbox.stub().resolves({ path: 'sam-cli-path' }) - sandbox.stub(SamUtilsModule, 'getSamCliPathAndVersion').callsFake(mockGetSamCliPath) - mockChildProcess = sandbox.stub().resolves({}) - sandbox.stub(ProcessUtilsModule, 'ChildProcess').callsFake(mockChildProcess) - - // Breaking point - mockRunInTerminal = sandbox - .stub(ProcessTerminalModule, 'runInTerminal') - .rejects(new ToolkitError('SAM CLI was cancelled before exiting', { cancelled: true })) - - await runDeploy(appNode) - assert.fail('Should have thrown an Error') - } catch (err) { - assert(err instanceof ToolkitError) - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcess.calledOnce) - assert(mockRunInTerminal.calledOnce) - assert(spyWriteSamconfigGlobal.notCalled) - assert.strictEqual(err.message, 'Failed to deploy SAM template') - } - }) - - it('should throw error and write to samconfig.toml given no update when deploying template', async () => { - try { - // Happy Stub - sandbox.stub(DeployWizard.prototype, 'run').resolves(mockDeployParams) - mockGetSamCliPath = sandbox.stub().resolves({ path: 'sam-cli-path' }) - sandbox.stub(SamUtilsModule, 'getSamCliPathAndVersion').callsFake(mockGetSamCliPath) - mockChildProcess = sandbox.stub().resolves({}) - sandbox.stub(ProcessUtilsModule, 'ChildProcess').callsFake(mockChildProcess) - - // Breaking point - mockRunInTerminal = sandbox.stub(ProcessTerminalModule, 'runInTerminal').callsFake((input, cmd) => { - if (cmd === 'deploy') { - throw new ToolkitError('The stack is up to date', { - code: 'NoUpdateExitCode', - }) - } - return Promise.resolve() - }) - - await runDeploy(appNode) - assert.fail('Should have thrown an Error') - } catch (err) { - assert(err instanceof ToolkitError) - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcess.calledTwice) - assert(mockRunInTerminal.calledTwice) - assert(spyWriteSamconfigGlobal.calledOnce) - assert.strictEqual(err.message, 'Failed to deploy SAM template') - } - }) - }) -}) - -const s3BucketListSummary: Array & { readonly region: string }> = [ - { Name: 'stack-1-bucket', region: 'us-west-2', Arn: 'arn-1' }, - { Name: 'stack-2-bucket', region: 'us-west-2', Arn: 'arn-2' }, - { Name: 'stack-3-bucket', region: 'us-west-2', Arn: 'arn-3' }, -] - -const stackSummaries: CloudFormationClientModule.StackSummary[][] = [ - [ - { - StackName: 'stack1', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - { - StackName: 'stack2', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - { - StackName: 'stack3', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - ], -] diff --git a/packages/core/src/test/shared/sam/samTestUtils.ts b/packages/core/src/test/shared/sam/samTestUtils.ts deleted file mode 100644 index 709fc1dfc1..0000000000 --- a/packages/core/src/test/shared/sam/samTestUtils.ts +++ /dev/null @@ -1,138 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' -import { fs } from '../../../shared' - -export async function generateSamconfigFileCustomData(dir: vscode.Uri, body: string) { - const uri = vscode.Uri.joinPath(dir, 'samconfig.toml') - const data = ` - version = 0.1 - - ${body} -` - await fs.writeFile(uri, data) - return uri -} - -export const samconfigCompleteData = ` -version = 0.1 - -[default.build.parameters] -cached = true -parallel = true -use_container = true - -[default.global.parameters] -stack_name = "project-1" -region = "us-west-2" - -[default.deploy.parameters] -confirm_changeset = false -resolve_s3 = true - -[default.sync.parameters] -s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr" -dependency_layer = false -watch = false -` -export const samconfigCompleteDataWithoutGlobal = ` -version = 0.1 - -[default.build.parameters] -cached = true -parallel = true -use_container = true - -[default.deploy.parameters] -confirm_changeset = false -resolve_s3 = true - -[default.sync.parameters] -s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr" -dependency_layer = false -watch = false -` -export const samconfigInvalidData = ` -version = 0.1 - -[default.global.parameters] -stack_name = project-1 -region = us-west-2 -` -export function generateSamconfigData(data: { - sync?: Array<{ key: string; value: string }> - build?: Array<{ key: string; value: string }> - deploy?: Array<{ key: string; value: string }> - global?: Array<{ key: string; value: string }> -}): string { - const result: string[] = ['version = 0.1'] - - for (const [operation, parameters] of Object.entries(data)) { - if (parameters && parameters.length > 0) { - result.push('', `[default.${operation}.parameters]`) - for (const { key, value } of parameters) { - value && result.push(`${key} = "${value}"`) - } - } - } - - return result.join('\n') -} - -export const validTemplateData = ` -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 - -Parameters: - SourceBucketName: - Type: String - Default: project-1-source-bucket - DestinationBucketName: - Type: String - Default: project-1-destination-bucket - -Resources: - SourceBucket: - Type: AWS::S3::Bucket - Properties: - BucketName: !Ref SourceBucketName - ResizerFunction: - Type: AWS::Serverless::Function - Properties: - CodeUri: src/ - Handler: app.lambda_handler - Runtime: python3.12 - MemorySize: 2048 - Environment: - Variables: - DESTINATION_BUCKETNAME: !Ref DestinationBucketName - Events: - FileUpload: - Type: S3 - Properties: - Bucket: !Ref SourceBucket - Events: s3:ObjectCreated:* -` - -export const capacityProviderTemplateData = ` -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 - -Resources: - MyCapacityProvider: - Type: AWS::Serverless::CapacityProvider - Properties: - InstanceRequirements: - Architectures: - - x86_64 - MyFunction: - Type: AWS::Serverless::Function - Properties: - Handler: app.handler - Runtime: python3.13 - CodeUri: ./src - CapacityProviderConfig: - Arn: !GetAtt MyCapacityProvider.Arn -` diff --git a/packages/core/src/test/shared/sam/sync.test.ts b/packages/core/src/test/shared/sam/sync.test.ts deleted file mode 100644 index da7a8086f3..0000000000 --- a/packages/core/src/test/shared/sam/sync.test.ts +++ /dev/null @@ -1,2204 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as SamUtilsModule from '../../../shared/sam/utils' -import * as ProcessTerminalUtils from '../../../shared/sam/processTerminal' -import * as S3ClientModule from '../../../shared/clients/s3' -import * as SamConfigModule from '../../../shared/sam/config' -import * as ResolveEnvModule from '../../../shared/env/resolveEnv' -import * as ProcessUtilsModule from '../../../shared/utilities/processUtils' -import { CancellationError } from '../../../shared/utilities/timeoutUtils' -import * as CloudFormationClientModule from '../../../shared/clients/cloudFormation' - -import { - createEnvironmentPrompter, - ensureBucket, - ensureRepo, - getSyncParamsFromConfig, - getSyncWizard, - prepareSyncParams, - runSync, - saveAndBindArgs, - syncFlagItems, - SyncParams, - SyncWizard, -} from '../../../shared/sam/sync' - -import { - createBaseImageTemplate, - createBaseTemplate, - makeSampleSamTemplateYaml, -} from '../cloudformation/cloudformationTestUtils' -import { createWizardTester } from '../wizards/wizardTestUtils' -import { AWSTreeNodeBase } from '../../../shared/treeview/nodes/awsTreeNodeBase' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { ToolkitError } from '../../../shared/errors' -import globals from '../../../shared/extensionGlobals' -import fs from '../../../shared/fs/fs' -import { createMultiPick, DataQuickPickItem } from '../../../shared/ui/pickerPrompter' -import sinon from 'sinon' -import { getTestWindow } from '../vscode/window' -import { S3Client } from '../../../shared/clients/s3' -import { RequiredProps } from '../../../shared/utilities/tsUtils' -import { Bucket } from '@aws-sdk/client-s3' -import { CloudFormationClient } from '../../../shared/clients/cloudFormation' -import { intoCollection } from '../../../shared/utilities/collectionUtils' -import { SamConfig, Environment, parseConfig } from '../../../shared/sam/config' -import { RegionProvider } from '../../../shared/regions/regionProvider' -import { Region } from '../../../shared/regions/endpoints' -import { RegionNode } from '../../../awsexplorer/regionNode' -import { getProjectRootUri } from '../../../shared/sam/utils' -import { AppNode } from '../../../awsService/appBuilder/explorer/nodes/appNode' -import * as Cfn from '../../../shared/cloudformation/cloudformation' -import { getWorkspaceFolder, TestFolder } from '../../testUtil' -import { TemplateItem } from '../../../shared/ui/sam/templatePrompter' -import { ParamsSource } from '../../../shared/ui/sam/paramsSourcePrompter' -import { CloudFormationTemplateRegistry } from '../../../shared/fs/templateRegistry' - -import { samconfigCompleteData, samconfigInvalidData, validTemplateData } from '../../shared/sam/samTestUtils' -import { assertTelemetry, assertTelemetryCurried } from '../../testUtil' -import { clickBackButton, createPromptHandler, PrompterTester } from '../wizards/prompterTester' -import { createTestRegionProvider } from '../regions/testUtil' -import { ToolkitPromptSettings } from '../../../shared/settings' -import { DefaultEcrClient } from '../../../shared/clients/ecrClient' -import assert from 'assert' -import { BucketSource } from '../../../shared/ui/sam/bucketPrompter' -import { TestInputBox, TestQuickPick } from '../vscode/quickInput' - -describe('SAM SyncWizard', async function () { - const createTester = async (params?: Partial) => - createWizardTester(new SyncWizard({ deployType: 'code', ...params }, await globals.templateRegistry)) - - it('skips prompts if user chooses samconfig file as params source', async function () { - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri || vscode.Uri.file('/') - const rootFolderUri = vscode.Uri.joinPath(workspaceUri, 'my') - const templateUri = vscode.Uri.joinPath(rootFolderUri, 'template.yaml') - const tester = await createTester({ - template: { uri: templateUri, data: createBaseTemplate() }, - paramsSource: ParamsSource.SamConfig, - projectRoot: rootFolderUri, - }) - tester.template.assertDoesNotShow() - tester.region.assertDoesNotShow() - tester.stackName.assertDoesNotShow() - tester.bucketName.assertDoesNotShow() - }) - - it('prompts for ECR repo if template has image-based resource', async function () { - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri || vscode.Uri.file('/') - const rootFolderUri = vscode.Uri.joinPath(workspaceUri, 'my') - const templateUri = vscode.Uri.joinPath(rootFolderUri, 'template.yaml') - const template = { uri: templateUri, data: createBaseImageTemplate() } - const tester = await createTester({ - template, - paramsSource: ParamsSource.Specify, - }) - tester.ecrRepoUri.assertShow() - }) - - it('skips prompt for ECR repo if template has no image-based resources', async function () { - const template = { uri: vscode.Uri.file('/'), data: createBaseTemplate() } - const tester = await createTester({ template }) - tester.ecrRepoUri.assertDoesNotShow() - }) - - it('skips prompt for ECR repo if param source is to use samconfig', async function () { - const template = { uri: vscode.Uri.file('/'), data: createBaseTemplate() } - const tester = await createTester({ template, paramsSource: ParamsSource.SamConfig }) - tester.ecrRepoUri.assertDoesNotShow() - }) - - it("uses the template's workspace subfolder as the project root is not set", async function () { - const workspaceUri = vscode.workspace.workspaceFolders?.[0]?.uri - assert.ok(workspaceUri) - const rootFolderUri = vscode.Uri.joinPath(workspaceUri, 'my') - assert.ok(rootFolderUri) - - const templateUri = vscode.Uri.joinPath(rootFolderUri, 'template.yaml') - const template = { uri: templateUri, data: createBaseTemplate() } - const tester = await createTester({ template, projectRoot: rootFolderUri }) - tester.projectRoot.path.assertValue(rootFolderUri.path) - }) -}) - -describe('SAM SyncWizard', async () => { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - let workspaceFolder: vscode.WorkspaceFolder - let templateFile: vscode.Uri - - let mockDefaultCFNClient: sinon.SinonStubbedInstance - let mockDefaultS3Client: sinon.SinonStubbedInstance - let registry: CloudFormationTemplateRegistry - - beforeEach(async () => { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - workspaceFolder = getWorkspaceFolder(testFolder.path) - sandbox = sinon.createSandbox() - - // Simulate return of deployed stacks - mockDefaultCFNClient = sandbox.createStubInstance(CloudFormationClientModule.CloudFormationClient) - sandbox.stub(CloudFormationClientModule, 'CloudFormationClient').returns(mockDefaultCFNClient) - mockDefaultCFNClient.listAllStacks.returns(intoCollection(stackSummaries)) - - // Simulate return of list bucket - mockDefaultS3Client = sandbox.createStubInstance(S3ClientModule.S3Client) - sandbox.stub(S3ClientModule, 'S3Client').returns(mockDefaultS3Client) - mockDefaultS3Client.listBucketsIterable.returns(intoCollection(s3BucketListSummary)) - - // generate template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - registry = await globals.templateRegistry - await registry.addItem(templateFile) - }) - - afterEach(() => { - sandbox.restore() - registry.reset() - }) - - describe('entry: template file', () => { - it('happy path with invalid samconfig.toml', async () => { - /** - * Selection: - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 1. ('Specify required parameters and save as defaults') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 1. 'stack1' - * - bucketSource : [Select] 1. BucketSource.SamCliManaged - * - bucketName : [Skip] undefined - * - syncFlags : [Select] ["--dependency-layer","--use-container","--save-params"] - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigInvalidData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'stack1') - assert.strictEqual(quickPick.items[1].label, 'stack2') - assert.strictEqual(quickPick.items[2].label, 'stack3') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.deepEqual(picker.items[0], { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }) - assert.deepEqual(picker.items[1], { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }) - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - const saveParam = picker.items.filter((item) => item.label === 'Save parameters')[0] - picker.acceptItems(dependencyLayer, useContainer, saveParam) - }) - .build() - - const parameters = await (await getSyncWizard('infra', templateFile, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.Specify) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack1') - assert.strictEqual(parameters.bucketSource, BucketSource.SamCliManaged) - assert(!parameters.bucketName) - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--dependency-layer","--use-container","--save-params"]') - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] null; will use 'us-west-2' from samconfig - * - stackName : [Skip] null; will use 'project-1' from samconfig - * - bucketSource : [Skip] null; - * - bucketName : [Skip] automatically set for bucketSource option 1 - * - syncFlags : [Skip] null; will use flags from samconfig - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (quickPick) => { - // Need time to check samconfig.toml file and generate options - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 3) - assert.strictEqual(quickPick.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(quickPick.items[1].label, 'Specify required parameters') - assert.strictEqual(quickPick.items[2].label, 'Use default values from samconfig') - quickPick.acceptItem(quickPick.items[2]) - }) - .build() - - const parameters = await (await getSyncWizard('infra', templateFile, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.SamConfig) - assert(!parameters.region) - assert(!parameters.stackName) - assert.strictEqual(parameters.deployType, 'infra') - assert(!parameters.bucketName) - assert.strictEqual(parameters.skipDependencyLayer, true) - assert(!parameters.syncFlags) - prompterTester.assertCallAll() - }) - }) - - describe('entry: appBuilder', () => { - let appNode: AppNode - - beforeEach(async () => { - const expectedSamAppLocation = { - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - } - appNode = new AppNode(expectedSamAppLocation) - }) - - it('happy path with invalid samconfig.toml', async () => { - /** - * Selection: - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 2. 'stack2' - * - bucketSource : [Select] 2. BucketSource.UserProvided - * - bucketName : [select] 3. stack-3-bucket - * - syncFlags : [Select] ["--save-params"] - */ - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a region', async (picker) => { - await picker.untilReady() - const select = picker.items.filter((i) => i.detail === 'us-west-2')[0] - picker.acceptItem(select || picker.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack1') - assert.strictEqual(picker.items[1].label, 'stack2') - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.deepStrictEqual(picker.items[0], { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }) - assert.deepStrictEqual(picker.items[1], { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }) - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack-1-bucket') - assert.strictEqual(picker.items[1].label, 'stack-2-bucket') - assert.strictEqual(picker.items[2].label, 'stack-3-bucket') - picker.acceptItem(picker.items[2]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const saveParam = picker.items.filter((item) => item.label === 'Save parameters')[0] - picker.acceptItems(saveParam) - }) - .build() - - const parameters = await (await getSyncWizard('infra', appNode, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.path, templateFile.path) - assert.strictEqual(parameters.projectRoot.path, projectRoot.path) - assert.strictEqual(parameters.paramsSource, ParamsSource.Specify) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack2') - assert.strictEqual(parameters.bucketSource, BucketSource.UserProvided) - assert.strictEqual(parameters.bucketName, 'stack-3-bucket') - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--save-params"]') - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] null; will use value from samconfig file - * - stackName : [Skip] null; will use value from samconfig file - * - bucketSource : [Skip] null; - * - bucketName : [Skip] null; automatically set for bucketSource option 1 - * - syncFlags : [Skip] null; will use flags from samconfig - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - const parameters = await (await getSyncWizard('infra', appNode, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.path, templateFile.path) - assert.strictEqual(parameters.projectRoot.path, projectRoot.path) - assert.strictEqual(parameters.paramsSource, ParamsSource.SamConfig) - assert.strictEqual(parameters.deployType, 'infra') - assert(!parameters.region) - assert(!parameters.stackName) - assert(!parameters.bucketSource) - assert(!parameters.bucketName) - assert.strictEqual(parameters.skipDependencyLayer, true) - prompterTester.assertCallAll() - }) - }) - - describe('entry: region node', () => { - const expectedRegionId = 'us-west-2' - let regionNode: RegionNode - - beforeEach(async () => { - // Create RegionNode as entry point - regionNode = new RegionNode( - { id: expectedRegionId, name: 'US West (N. California)' }, - createTestRegionProvider() - ) - }) - - it('happy path with invalid samconfig.toml', async () => { - /** - * Selection: - * - template : [Select] template/yaml set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 2. ('Specify required parameters') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Select] 2. 'stack2' - * - bucketName : [select] 2. stack-2-bucket - * - syncFlags : [Select] ["--dependency-layer","--use-container"] - */ - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack1') - assert.strictEqual(picker.items[1].label, 'stack2') - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.deepStrictEqual(picker.items[0], { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }) - assert.deepStrictEqual(picker.items[1], { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }) - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack-1-bucket') - assert.strictEqual(picker.items[1].label, 'stack-2-bucket') - assert.strictEqual(picker.items[2].label, 'stack-3-bucket') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - picker.acceptItems(dependencyLayer, useContainer) - }) - .build() - - const parameters = await (await getSyncWizard('infra', regionNode, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.Specify) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack2') - assert.strictEqual(parameters.bucketSource, BucketSource.UserProvided) - assert.strictEqual(parameters.bucketName, 'stack-2-bucket') - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--dependency-layer","--use-container"]') - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - template : [Select] template.yaml - * - projectRoot : [Skip] automatically set - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Skip] null; will use value from samconfig file - * - bucketName : [Skip] automatically set for bucketSource option 1 - * - syncFlags : [Skip] null; will use flags from samconfig - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - const parameters = await (await getSyncWizard('infra', regionNode, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.SamConfig) - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.region, 'us-west-2') - assert(!parameters.stackName) - assert(!parameters.bucketSource) - assert.strictEqual(parameters.skipDependencyLayer, true) - prompterTester.assertCallAll() - }) - }) - - describe('entry: samconfig file context menu', () => { - it('sad path with invalid samconfig.toml should throw parsing config file error', async () => { - // generate samconfig.toml in temporary test folder - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigInvalidData)) - try { - await (await getSyncWizard('infra', samconfigFile, false, false)).run() - } catch (error: any) { - assert.strictEqual(error.code, 'samConfigParseError') - } - }) - - it('happy path with valid samconfig.toml', async () => { - // generate samconfig.toml in temporary test folder - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigCompleteData)) - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - const parameters = await (await getSyncWizard('infra', samconfigFile, false, false)).run() - assert(parameters) - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.SamConfig) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'project-1') - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.bucketName, 'aws-sam-cli-managed-default-samclisourcebucket-lftqponsaxsr') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert(!parameters.syncFlags) - prompterTester.assertCallAll() - }) - - it('happy path with empty samconfig.toml', async () => { - // generate samconfig.toml in temporary test folder - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', '')) - /** - * Selection: - * - template : [Skip] automatically set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 1. ('Specify required parameters and save as defaults') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 2. 'stack2' - * - bucketName : [select] 2. stack-2-bucket - * - syncFlags : [Select] ["--dependency-layer","--use-container","--watch"] - */ - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack1') - assert.strictEqual(picker.items[1].label, 'stack2') - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.deepStrictEqual(picker.items[0], { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }) - assert.deepStrictEqual(picker.items[1], { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }) - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack-1-bucket') - assert.strictEqual(picker.items[1].label, 'stack-2-bucket') - assert.strictEqual(picker.items[2].label, 'stack-3-bucket') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - const watch = picker.items.filter((item) => item.label === 'Watch')[0] - picker.acceptItems(dependencyLayer, useContainer, watch) - }) - .build() - - const parameters = await (await getSyncWizard('infra', samconfigFile, false, false)).run() - assert(parameters) - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.Specify) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack2') - assert.strictEqual(parameters.bucketSource, BucketSource.UserProvided) - assert.strictEqual(parameters.bucketName, 'stack-2-bucket') - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--dependency-layer","--use-container","--watch"]') - prompterTester.assertCallAll() - }) - - it('happy path with empty samconfig.toml with backward click', async () => { - // generate samconfig.toml in temporary test folder - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', '')) - - const prompterTester = PrompterTester.init({ handlerTimeout: 300000 }) - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - // 1st - await quickPick.untilReady() - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', async (inputBox) => { - // 2nd - // 6th - await inputBox.untilReady() - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox( - 'Specify SAM Template parameter value for DestinationBucketName', - (() => { - return createPromptHandler({ - // 3rd - // 7th - // 9th - default: async (input: TestInputBox) => { - await input.untilReady() - input.acceptValue('my-destination-bucket-name') - }, - numbered: [ - { - // 5th - order: [2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick( - 'Specify parameter source for sync', - (() => { - return createPromptHandler({ - // 10th - default: async (picker: TestQuickPick) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }, - numbered: [ - { - // 4th - // 8th - order: [1, 2], - handler: clickBackButton, - }, - ], - }) - })() - ) - .handleQuickPick('Select a region', (quickPick) => { - // 11th - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - await quickPick.untilReady() - assert.strictEqual(quickPick.items[1].label, 'stack2') - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[1].label, 'stack-2-bucket') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - const watch = picker.items.filter((item) => item.label === 'Watch')[0] - picker.acceptItems(dependencyLayer, useContainer, watch) - }) - .build() - - const parameters = await (await getSyncWizard('infra', samconfigFile, false, false)).run() - assert(parameters) - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.Specify) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack2') - assert.strictEqual(parameters.bucketSource, BucketSource.UserProvided) - assert.strictEqual(parameters.bucketName, 'stack-2-bucket') - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--dependency-layer","--use-container","--watch"]') - prompterTester.assertCallAll() - const expectedCallOrder = [ - 'Select a SAM/CloudFormation Template', - 'Specify SAM Template parameter value for SourceBucketName', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify parameter source for sync', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify SAM Template parameter value for SourceBucketName', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify parameter source for sync', - 'Specify SAM Template parameter value for DestinationBucketName', - 'Specify parameter source for sync', - 'Select a region', - 'Select a CloudFormation Stack', - 'Specify S3 bucket for deployment artifacts', - 'Select an S3 Bucket', - 'Specify parameters for sync', - ] - for (const [index, title] of expectedCallOrder.entries()) { - prompterTester.assertCallOrder(title, index + 1) - } - }) - - describe('entry: command palette', () => { - it('happy path with invalid samconfig.toml', async () => { - /** - * Selection: - * - template : [Select] template/yaml set - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 1. ('Specify required parameters and save as defaults') - * - region : [Select] 'us-west-2' - * - stackName : [Select] 3. 'stack3' - * - bucketName : [select] 3. stack-3-bucket - * - syncFlags : [Select] all - */ - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'stack1') - assert.strictEqual(picker.items[1].label, 'stack2') - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[2]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.deepStrictEqual(picker.items[0], { - label: 'Create a SAM CLI managed S3 bucket', - data: BucketSource.SamCliManaged, - }) - assert.deepStrictEqual(picker.items[1], { - label: 'Specify an S3 bucket', - data: BucketSource.UserProvided, - }) - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - picker.acceptItems(dependencyLayer, useContainer) - }) - .build() - - const parameters = await (await getSyncWizard('infra', undefined, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.SpecifyAndSave) - assert.strictEqual(parameters.region, 'us-west-2') - assert.strictEqual(parameters.stackName, 'stack3') - assert.strictEqual(parameters.bucketSource, BucketSource.SamCliManaged) - assert(!parameters.bucketName) - assert.strictEqual(parameters.deployType, 'infra') - assert.strictEqual(parameters.skipDependencyLayer, true) - assert.strictEqual(parameters.syncFlags, '["--dependency-layer","--use-container"]') - prompterTester.assertCallAll() - }) - - it('happy path with valid samconfig.toml', async () => { - /** - * Selection: - * - template : [Select] template.yaml - * - projectRoot : [Skip] automatically set - * - * - SourceBucketName : [Select] prefill value - * - DestinationBucketName : [Select] prefill value - * - * - paramsSource : [Select] 3. ('Use default values from samconfig') - * - region : [Skip] automatically set from region node 'us-west-2' - * - stackName : [Skip] null; will use value from samconfig file - * - bucketName : [Skip] automatically set for bucketSource option 1 - * - syncFlags : [Skip] null; will use flags from samconfig - */ - - // generate samconfig.toml in temporary test folder - await testFolder.write('samconfig.toml', samconfigCompleteData) - - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items.length, 1) - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - const parameters = await (await getSyncWizard('infra', undefined, false, false)).run() - - assert(parameters) - - assert.strictEqual(parameters.template.uri.fsPath, templateFile.fsPath) - assert.strictEqual(parameters.projectRoot.fsPath, projectRoot.fsPath) - assert.strictEqual(parameters.paramsSource, ParamsSource.SamConfig) - assert.strictEqual(parameters.deployType, 'infra') - assert(!parameters.region) - assert(!parameters.stackName) - assert(!parameters.bucketSource) - assert(!parameters.syncFlags) - assert.strictEqual(parameters.skipDependencyLayer, true) - prompterTester.assertCallAll() - }) - }) - }) -}) -describe('SAM runSync', () => { - let sandbox: sinon.SinonSandbox - let testFolder: TestFolder - let projectRoot: vscode.Uri - let workspaceFolder: vscode.WorkspaceFolder - let templateFile: vscode.Uri - - let mockGetSpawnEnv: sinon.SinonStub - let mockGetSamCliPath: sinon.SinonStub - let mockChildProcessClass: sinon.SinonStub - let mockSamSyncChildProcess: sinon.SinonStub - - let spyWriteSamconfigGlobal: sinon.SinonSpy - let spyRunInterminal: sinon.SinonSpy - - let mockDefaultCFNClient: sinon.SinonStubbedInstance - let mockDefaultS3Client: sinon.SinonStubbedInstance - let registry: CloudFormationTemplateRegistry - - // Dependency clients - beforeEach(async function () { - testFolder = await TestFolder.create() - projectRoot = vscode.Uri.file(testFolder.path) - workspaceFolder = getWorkspaceFolder(testFolder.path) - sandbox = sinon.createSandbox() - registry = await globals.templateRegistry - - // Create template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - - await registry.addItem(templateFile) - - // Simulate return of deployed stacks - mockDefaultCFNClient = sandbox.createStubInstance(CloudFormationClientModule.CloudFormationClient) - sandbox.stub(CloudFormationClientModule, 'CloudFormationClient').returns(mockDefaultCFNClient) - mockDefaultCFNClient.listAllStacks.returns(intoCollection(stackSummaries)) - - // Simulate return of list bucket - mockDefaultS3Client = sandbox.createStubInstance(S3ClientModule.S3Client) - sandbox.stub(S3ClientModule, 'S3Client').returns(mockDefaultS3Client) - mockDefaultS3Client.listBucketsIterable.returns(intoCollection(s3BucketListSummary)) - - // Create Spy for validation - spyWriteSamconfigGlobal = sandbox.spy(SamConfigModule, 'writeSamconfigGlobal') - spyRunInterminal = sandbox.spy(ProcessTerminalUtils, 'runInTerminal') - - // generate template.yaml in temporary test folder and add to registery - templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - await (await globals.templateRegistry).addItem(templateFile) - - mockGetSpawnEnv = sandbox.stub(ResolveEnvModule, 'getSpawnEnv').callsFake( - sandbox.stub().resolves({ - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }) - ) - }) - - afterEach(() => { - sandbox.restore() - registry.reset() - }) - - describe(':) path', () => { - beforeEach(() => { - mockGetSamCliPath = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ path: 'sam-cli-path' })) - - // Confirm confirmDevStack message - // getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'OK')?.select()) - getTestWindow().onDidShowMessage((message) => { - message.selectItem("OK, and don't show this again") - }) - - // Mock child process with required properties that get called in ProcessTerminal - mockSamSyncChildProcess = Object.create(ProcessUtilsModule.ChildProcess.prototype, { - stopped: { get: sandbox.stub().returns(false) }, - stop: { value: sandbox.stub().resolves({}) }, - run: { - value: sandbox.stub().resolves({ - exitCode: 0, - stdout: 'Mock successful sync command execution ', - stderr: '', - }), - }, - }) - mockChildProcessClass = sandbox.stub(ProcessUtilsModule, 'ChildProcess').returns(mockSamSyncChildProcess) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('[entry: command palette] specify and save flag should instantiate correct process in terminal', async () => { - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[2]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[1].label, 'Specify an S3 bucket') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 3) - assert.strictEqual(picker.items[2].label, 'stack-3-bucket') - picker.acceptItem(picker.items[2]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - picker.acceptItems(dependencyLayer, useContainer) - }) - .build() - - // Invoke sync command from command palette - await runSync('code', undefined) - - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcessClass.calledOnce) - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'sync', - '--code', - '--template', - `${templateFile.fsPath}`, - '--s3-bucket', - 'stack-3-bucket', - '--stack-name', - 'stack3', - '--region', - 'us-west-2', - '--no-dependency-layer', - '--save-params', - '--parameter-overrides', - 'ParameterKey=SourceBucketName,ParameterValue=my-source-bucket-name ParameterKey=DestinationBucketName,ParameterValue=my-destination-bucket-name', - '--dependency-layer', - '--use-container', - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(mockGetSpawnEnv.calledOnce) - assert(spyRunInterminal.calledOnce) - assert.deepEqual(spyRunInterminal.getCall(0).args, [mockSamSyncChildProcess, 'sync']) - assert(spyWriteSamconfigGlobal.calledOnce) - // Check telementry - assertTelemetry('sam_sync', { result: 'Succeeded', source: undefined }) - assertTelemetryCurried('sam_sync')({ - syncedResources: 'CodeOnly', - source: undefined, - }) - prompterTester.assertCallAll() - }) - - it('[entry: command palette] specify and save flag (with --watch) should save params before starting SAM process', async () => { - const prompterTester = PrompterTester.init() - .handleQuickPick('Select a SAM/CloudFormation Template', async (quickPick) => { - // Need sometime to wait for the template to search for template file - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, templateFile.fsPath) - quickPick.acceptItem(quickPick.items[0]) - }) - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'Specify required parameters and save as defaults') - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[2].label, 'stack3') - picker.acceptItem(picker.items[2]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[0].label, 'Create a SAM CLI managed S3 bucket') - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - const watch = picker.items.filter((item) => item.label === 'Watch')[0] - picker.acceptItems(dependencyLayer, useContainer, watch) - }) - .build() - - // Invoke sync command from command palette - await runSync('code', undefined) - - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcessClass.calledOnce) - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'sync', - '--code', - '--template', - `${templateFile.fsPath}`, - '--stack-name', - 'stack3', - '--region', - 'us-west-2', - '--no-dependency-layer', - '--save-params', - '--parameter-overrides', - 'ParameterKey=SourceBucketName,ParameterValue=my-source-bucket-name ParameterKey=DestinationBucketName,ParameterValue=my-destination-bucket-name', - '--dependency-layer', - '--use-container', - '--watch', - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(mockGetSpawnEnv.calledOnce) - assert(spyRunInterminal.calledOnce) - assert.deepEqual(spyRunInterminal.getCall(0).args, [mockSamSyncChildProcess, 'sync']) - assert.strictEqual(spyWriteSamconfigGlobal.callCount, 2) - assert(spyWriteSamconfigGlobal.calledBefore(spyRunInterminal)) - // Check telementry - assertTelemetry('sam_sync', { result: 'Succeeded', source: undefined }) - assertTelemetryCurried('sam_sync')({ - syncedResources: 'CodeOnly', - source: undefined, - }) - prompterTester.assertCallAll() - }) - - it('[entry: template file] specify flag should instantiate correct process in terminal', async () => { - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - assert.strictEqual(picker.items[1].label, 'Specify required parameters') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select a region', (quickPick) => { - const select = quickPick.items.filter((i) => i.detail === 'us-west-2')[0] - quickPick.acceptItem(select || quickPick.items[0]) - }) - .handleQuickPick('Select a CloudFormation Stack', async (quickPick) => { - // The prompt will need some time to generate option - await quickPick.untilReady() - assert.strictEqual(quickPick.items[0].label, 'stack1') - quickPick.acceptItem(quickPick.items[0]) - }) - .handleQuickPick('Specify S3 bucket for deployment artifacts', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 2) - assert.strictEqual(picker.items[1].label, 'Specify an S3 bucket') - picker.acceptItem(picker.items[1]) - }) - .handleQuickPick('Select an S3 Bucket', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items[0].label, 'stack-1-bucket') - picker.acceptItem(picker.items[0]) - }) - .handleQuickPick('Specify parameters for sync', async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - const dependencyLayer = picker.items.filter((item) => item.label === 'Dependency layer')[0] - const useContainer = picker.items.filter((item) => item.label === 'Use container')[0] - picker.acceptItems(dependencyLayer, useContainer) - }) - .build() - - await runSync('infra', templateFile) - - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcessClass.calledOnce) - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'sync', - '--template', - `${templateFile.fsPath}`, - '--s3-bucket', - 'stack-1-bucket', - '--stack-name', - 'stack1', - '--region', - 'us-west-2', - '--no-dependency-layer', - '--parameter-overrides', - 'ParameterKey=SourceBucketName,ParameterValue=my-source-bucket-name ParameterKey=DestinationBucketName,ParameterValue=my-destination-bucket-name', - '--dependency-layer', - '--use-container', - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(mockGetSpawnEnv.calledOnce) - assert(spyRunInterminal.calledOnce) - assert.deepEqual(spyRunInterminal.getCall(0).args, [mockSamSyncChildProcess, 'sync']) - assert(spyWriteSamconfigGlobal.calledOnce) - - // Check telementry - assertTelemetry('sam_sync', { result: 'Succeeded', source: 'template' }) - assertTelemetryCurried('sam_sync')({ - syncedResources: 'AllResources', - source: 'template', - }) - prompterTester.assertCallAll() - }) - - it('[entry: appBuilder] use samconfig should instantiate correct process in terminal', async () => { - const expectedSamAppLocation = { - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - } - const appNode = new AppNode(expectedSamAppLocation) - const samconfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', samconfigCompleteData)) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - await runSync('infra', appNode) - - assert(mockGetSamCliPath.calledOnce) - assert(mockChildProcessClass.calledOnce) - assert.deepEqual(mockChildProcessClass.getCall(0).args, [ - 'sam-cli-path', - [ - 'sync', - '--template', - `${templateFile.fsPath}`, - '--no-dependency-layer', - '--config-file', - `${samconfigFile.fsPath}`, - '--parameter-overrides', - 'ParameterKey=SourceBucketName,ParameterValue=my-source-bucket-name ParameterKey=DestinationBucketName,ParameterValue=my-destination-bucket-name', - ], - { - spawnOptions: { - cwd: projectRoot?.fsPath, - env: { - AWS_TOOLING_USER_AGENT: 'AWS-Toolkit-For-VSCode/testPluginVersion', - SAM_CLI_TELEMETRY: '0', - }, - }, - }, - ]) - assert(mockGetSpawnEnv.calledOnce) - assert(spyRunInterminal.calledOnce) - assert.deepEqual(spyRunInterminal.getCall(0).args, [mockSamSyncChildProcess, 'sync']) - assert(spyWriteSamconfigGlobal.notCalled) - - // Check telementry - assertTelemetry('sam_sync', { result: 'Succeeded', source: 'appBuilderDeploy' }) - assertTelemetryCurried('sam_sync')({ - syncedResources: 'AllResources', - source: 'appBuilderDeploy', - }) - prompterTester.assertCallAll() - }) - }) - - describe(':( path', () => { - let appNode: AppNode - beforeEach(async () => { - mockGetSamCliPath = sandbox - .stub(SamUtilsModule, 'getSamCliPathAndVersion') - .callsFake(sandbox.stub().resolves({ path: 'sam-cli-path' })) - - appNode = new AppNode({ - workspaceFolder: workspaceFolder, - samTemplateUri: templateFile, - projectRoot: projectRoot, - }) - await testFolder.write('samconfig.toml', samconfigCompleteData) - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should abort when customer cancel sync dev stack agreement', async () => { - // Set the - await ToolkitPromptSettings.instance.update('samcliConfirmDevStack', false) - // Confirm confirmDevStack message - getTestWindow().onDidShowMessage((message) => { - message.dispose() - }) - - try { - await runSync('infra', appNode) - assert.fail('should have thrown CancellationError') - } catch (error: any) { - assert(error instanceof CancellationError) - assert.strictEqual(error.agent, 'user') - } - }) - - it('should abort when customer cancel sync wizard', async () => { - // Confirm confirmDevStack message - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'OK')?.select()) - sandbox.stub(SyncWizard.prototype, 'run').resolves(undefined) - - try { - await runSync('infra', appNode) - assert.fail('should have thrown CancellationError') - } catch (error: any) { - assert(error instanceof CancellationError) - assert.strictEqual(error.agent, 'user') - } - }) - - it('should throw ToolkitError when sync command fail', async () => { - // Confirm confirmDevStack message - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'OK')?.select()) - - const prompterTester = PrompterTester.init() - .handleInputBox('Specify SAM Template parameter value for SourceBucketName', (inputBox) => { - inputBox.acceptValue('my-source-bucket-name') - }) - .handleInputBox('Specify SAM Template parameter value for DestinationBucketName', (inputBox) => { - inputBox.acceptValue('my-destination-bucket-name') - }) - .handleQuickPick('Specify parameter source for sync', async (picker) => { - // Need time to check samconfig.toml file and generate options - await picker.untilReady() - assert.strictEqual(picker.items[2].label, 'Use default values from samconfig') - picker.acceptItem(picker.items[2]) - }) - .build() - - // Mock child process with required properties that get called in ProcessTerminal - mockSamSyncChildProcess = Object.create(ProcessUtilsModule.ChildProcess.prototype, { - stopped: { get: sandbox.stub().returns(false) }, - stop: { value: sandbox.stub().resolves({}) }, - run: { - value: sandbox.stub().resolves({ - exitCode: -1, - stdout: 'Mock sync command execution failure', - stderr: '', - }), - }, - }) - mockChildProcessClass = sandbox.stub(ProcessUtilsModule, 'ChildProcess').returns(mockSamSyncChildProcess) - - try { - await runSync('infra', appNode) - assert.fail('should have thrown ToolkitError') - } catch (error: any) { - assert(error instanceof ToolkitError) - assert.strictEqual(error.message, 'Failed to sync SAM application') - } - prompterTester.assertCallAll() - }) - }) -}) - -describe('SAM sync helper functions', () => { - describe('prepareSyncParams', function () { - let tempDir: vscode.Uri - - beforeEach(async function () { - tempDir = vscode.Uri.file(await makeTemporaryToolkitFolder()) - }) - - afterEach(async function () { - await fs.delete(tempDir, { recursive: true }) - }) - - it('uses region if given a tree node', async function () { - const params = await prepareSyncParams( - new (class extends AWSTreeNodeBase { - public override readonly regionCode = 'foo' - })('') - ) - - assert.strictEqual(params.region, 'foo') - }) - - async function makeTemplateItem(dir: vscode.Uri) { - const uri = vscode.Uri.joinPath(dir, 'template.yaml') - const data = makeSampleSamTemplateYaml(true) - await fs.writeFile(uri, JSON.stringify(data)) - - return { uri, data } - } - - it('loads template if given a URI', async function () { - const template = await makeTemplateItem(tempDir) - - const params = await prepareSyncParams(template.uri) - assert.strictEqual(params.template?.uri.fsPath, template.uri.fsPath) - assert.deepStrictEqual(params.template?.data, template.data) - }) - - it('skips dependency layers by default', async function () { - const template = await makeTemplateItem(tempDir) - - const params = await prepareSyncParams(template.uri) - assert.strictEqual(params.skipDependencyLayer, true) - }) - - describe('samconfig.toml', function () { - async function makeDefaultConfig(dir: vscode.Uri, body: string) { - const uri = vscode.Uri.joinPath(dir, 'samconfig.toml') - const data = ` - [default.sync.parameters] - ${body} -` - await fs.writeFile(uri, data) - - return uri - } - - async function getParams(body: string, dir = tempDir) { - const config = await makeDefaultConfig(dir, body) - - return prepareSyncParams(config) - } - - it('throws on non-string values', async function () { - await assert.rejects(() => getParams(`region = 0`), ToolkitError) - }) - - it('does not fail on missing values', async function () { - const params = await getParams(`region = "bar"`) - assert.strictEqual(params.region, 'bar') - }) - - it('sets the project root as the parent directory', async function () { - const params = await getParams(`region = "bar"`, tempDir) - assert.strictEqual(params.projectRoot?.fsPath, tempDir.fsPath) - }) - - it('uses the depdency layer option if provided', async function () { - const params = await getParams(`dependency_layer = true`, tempDir) - assert.strictEqual(params.skipDependencyLayer, false) - }) - - it('can load a relative template param', async function () { - const template = await makeTemplateItem(tempDir) - const params = await getParams(`template = "./template.yaml"`) - assert.deepStrictEqual(params.template?.data, template.data) - }) - - it('can load an absolute template param', async function () { - const template = await makeTemplateItem(tempDir) - const params = await getParams(`template = '${template.uri.fsPath}'`) - assert.deepStrictEqual(params.template?.data, template.data) - }) - - it('can load a relative template param without a path seperator', async function () { - const template = await makeTemplateItem(tempDir) - const params = await getParams(`template = "template.yaml"`) - assert.deepStrictEqual(params.template?.data, template.data) - }) - - it('can load a template param using an alternate key', async function () { - const template = await makeTemplateItem(tempDir) - const params = await getParams(`template_file = "template.yaml"`) - assert.deepStrictEqual(params.template?.data, template.data) - }) - - it('can use global params', async function () { - const params = await getParams(` - region = "bar" - [default.global.parameters] - stack_name = "my-app" - `) - assert.strictEqual(params.stackName, 'my-app') - }) - - it('prefers using the sync section over globals', async function () { - const params = await getParams(` - stack_name = "my-sync-app" - [default.global.parameters] - stack_name = "my-app" - `) - assert.strictEqual(params.stackName, 'my-sync-app') - }) - - it('loads all values if found', async function () { - const params = await getParams(` - region = "bar" - stack_name = "my-app" - s3_bucket = "my-bucket" - image_repository = "12345679010.dkr.ecr.bar.amazonaws.com/repo" - `) - assert.strictEqual(params.region, 'bar') - assert.strictEqual(params.stackName, 'my-app') - assert.strictEqual(params.bucketName, 'my-bucket') - assert.strictEqual(params.ecrRepoUri, '12345679010.dkr.ecr.bar.amazonaws.com/repo') - }) - }) - }) - - describe('syncFlagsPrompter', () => { - let sandbox: sinon.SinonSandbox - let acceptedItems: DataQuickPickItem[] - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() // Restore all stubs after each test - }) - - it('should return selected flags from buildFlagsPrompter', async () => { - getTestWindow().onDidShowQuickPick(async (picker) => { - await picker.untilReady() - assert.strictEqual(picker.items.length, 9) - assert.strictEqual(picker.title, 'Specify parameters for sync') - assert.deepStrictEqual(picker.items, syncFlagItems) - const buildInSource = picker.items[0] - const code = picker.items[1] - const dependencyLayer = picker.items[2] - assert.strictEqual(buildInSource.data, '--build-in-source') - assert.strictEqual(code.data, '--code') - assert.strictEqual(dependencyLayer.data, '--dependency-layer') - acceptedItems = [buildInSource, code, dependencyLayer] - picker.acceptItems(...acceptedItems) - }) - - const flags = await createMultiPick(syncFlagItems, { - title: 'Specify parameters for sync', - placeholder: 'Press enter to proceed with highlighted option', - }).prompt() - - assert.deepStrictEqual(flags, JSON.stringify(acceptedItems.map((i) => i.data))) - }) - }) - - describe('createEnvironmentPrompter', () => { - let sandbox: sinon.SinonSandbox - let config: SamConfig - let listEnvironmentsStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - // Create a stub for the SamConfig instance - config = new SamConfig(vscode.Uri.parse('dummy://uri')) - listEnvironmentsStub = sandbox.stub(config, 'listEnvironments') - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should create a prompter with existing samconfig env', () => { - // Arrange - const defaultEnv: Environment = { - name: 'default', - commands: {}, - } - const stagingEnv: Environment = { - name: 'staging', - commands: {}, - } - const prodEnv: Environment = { - name: 'prod', - commands: {}, - } - const envs: Environment[] = [defaultEnv, stagingEnv, prodEnv] - - listEnvironmentsStub.returns(envs) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) - - // Act - const prompter = createEnvironmentPrompter(config) - - // Assert - assert.ok(listEnvironmentsStub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select an Environment to Use') - assert.strictEqual(prompter.quickPick.placeholder, 'Select an environment') - assert.strictEqual(prompter.quickPick.items.length, 3) - assert.deepStrictEqual(prompter.quickPick.items, [ - { - label: 'default', - data: defaultEnv, - recentlyUsed: false, - }, - { - label: 'staging', - data: stagingEnv, - recentlyUsed: false, - }, - { - label: 'prod', - data: prodEnv, - recentlyUsed: false, - }, - ]) - }) - }) - - describe('prepareSyncParams', () => { - let sandbox: sinon.SinonSandbox - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - afterEach(() => { - sandbox.restore() - }) - - it('should return correct params from region node', async () => { - const regionNode = new RegionNode({ name: 'us-east-1', id: 'IAD' } as Region, {} as RegionProvider) - const result = await prepareSyncParams(regionNode) - assert.deepStrictEqual(result, { skipDependencyLayer: true, region: 'IAD' }) - }) - - it('should return correct params from appBuilder', async () => { - // setup appNode - const workspaceFolder = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspaceFolder) - const templateUri = vscode.Uri.file('file://mock/path/project/file') - const projectRootUri = getProjectRootUri(templateUri) - const samAppLocation = { - samTemplateUri: templateUri, - workspaceFolder: workspaceFolder, - projectRoot: projectRootUri, - } - const appNode = new AppNode(samAppLocation) - const tryLoadStub = sandbox.stub(Cfn, 'load') - - tryLoadStub.resolves({} as Cfn.Template) - - const templateItem = { - uri: templateUri, - data: {}, - } - - // Act - const result = await prepareSyncParams(appNode) - - // Assert - assert.deepStrictEqual(result, { - skipDependencyLayer: true, - template: templateItem, - projectRoot: projectRootUri, - }) - }) - - it('should return correct params for undefined input', async () => { - const result = await prepareSyncParams(undefined) - assert.deepStrictEqual(result, { skipDependencyLayer: true }) - }) - }) - - describe('getSyncParamsFromConfig', () => { - let sandbox: sinon.SinonSandbox - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should return correct params from config', async () => { - const configUri = vscode.Uri.file('file://mock/path/project/file') - const contents = ` - [default] - [default.global.parameters] - stack_name = "TestApp" - [default.build.parameters] - cached = true - parallel = true - [default.deploy.parameters] - capabilities = "CAPABILITY_IAM" - confirm_changeset = true - resolve_s3 = true - [default.sync.parameters] - watch = true - template_file = "/Users/mbfreder/TestApp/JavaSamApp/serverless-patterns/s3-lambda-resizing-python/template.yaml" - s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-1o6ke33w96qag" - stack_name = "s3-lambda-resizing-java-4" - dependency_layer = false` - - const config = await parseConfig(contents) - const samconfig = new SamConfig(configUri, config) - - const result = getSyncParamsFromConfig(samconfig) - assert.strictEqual( - result['templatePath'], - '/Users/mbfreder/TestApp/JavaSamApp/serverless-patterns/s3-lambda-resizing-python/template.yaml' - ) - assert.strictEqual(result['bucketName'], 'aws-sam-cli-managed-default-samclisourcebucket-1o6ke33w96qag') - assert.strictEqual(result['stackName'], 's3-lambda-resizing-java-4') - }) - - it('should return correct params from config with no template file', async () => { - const configUri = vscode.Uri.file('file://mock/path/project/file') - const contents = ` - [default] - [default.global.parameters] - stack_name = "TestApp" - [default.build.parameters] - cached = true - parallel = true - [default.deploy.parameters] - capabilities = "CAPABILITY_IAM" - confirm_changeset = true - resolve_s3 = true - [default.sync.parameters] - watch = true - s3_bucket = "bucket-from-samconfig" - stack_name = "s3-lambda-resizing-java-4" - dependency_layer = false` - - const config = await parseConfig(contents) - const samconfig = new SamConfig(configUri, config) - - const result = getSyncParamsFromConfig(samconfig) - assert.strictEqual(result['templatePath'], undefined) - assert.strictEqual(result['bucketName'], 'bucket-from-samconfig') - assert.strictEqual(result['stackName'], 's3-lambda-resizing-java-4') - }) - }) - - describe('saveAndBindArgs', () => { - let sandbox: sinon.SinonSandbox - let getConfigFileUriStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - getConfigFileUriStub = sandbox.stub() - - // Replace the real implementations with stubs - sandbox.stub(SamUtilsModule, 'updateRecentResponse').resolves() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should bind arguments correctly for code deployment', async () => { - const testFolder = await TestFolder.create() - const templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - - const args = { - deployType: 'code', - template: { - uri: templateFile, - data: {}, - } as TemplateItem, - bucketName: 'myBucket', - ecrRepoUri: 'myEcrRepo', - stackName: 'myStack', - region: 'us-east-1', - skipDependencyLayer: false, - paramsSource: ParamsSource.SpecifyAndSave, - } as SyncParams - - const result = await saveAndBindArgs(args) - - assert.ok(result.boundArgs.includes('--template')) - assert.ok(result.boundArgs.includes('--s3-bucket')) - assert.ok(result.boundArgs.includes('--image-repository')) - assert.ok(result.boundArgs.includes('--stack-name')) - assert.ok(result.boundArgs.includes('--region')) - assert.ok(result.boundArgs.includes('--code')) - assert.ok(result.boundArgs.includes('--save-params')) - assert.ok(result.boundArgs.includes(templateFile.fsPath)) - assert.ok(result.boundArgs.includes('myBucket')) - assert.ok(result.boundArgs.includes('myEcrRepo')) - assert.ok(result.boundArgs.includes('myStack')) - assert.ok(result.boundArgs.includes('us-east-1')) - }) - - it('should handle SamConfig paramsSource', async () => { - const testFolder = await TestFolder.create() - const projectRoot = vscode.Uri.file(testFolder.path) - const templateFile = vscode.Uri.file(await testFolder.write('template.yaml', validTemplateData)) - const samConfigFile = vscode.Uri.file(await testFolder.write('samconfig.toml', '[default]')) - - const args = { - deployType: 'code', - template: { uri: templateFile, data: {} } as TemplateItem, - bucketName: 'myBucket', - ecrRepoUri: 'myEcrRepo', - stackName: 'myStack', - region: 'us-east-1', - skipDependencyLayer: false, - paramsSource: ParamsSource.SamConfig, - projectRoot: projectRoot, - } as SyncParams - - getConfigFileUriStub.resolves(samConfigFile) - - const result = await saveAndBindArgs(args) - - assert.ok(result.boundArgs.includes('--config-file')) - assert.ok(result.boundArgs.includes(samConfigFile.fsPath)) - }) - }) - - describe('ensureBucket', () => { - let sandbox: sinon.SinonSandbox - let createBucketStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() // Restore original behavior after each test - }) - - it('should return the bucket name when it does not match newbucket:', async () => { - const resp = { region: 'us-east-1', bucketName: 'existing-bucket' } - const result = await ensureBucket(resp) - assert.strictEqual(result, 'existing-bucket') - }) - - it('should create a new bucket and return its name when bucketName matches newbucket:', async () => { - const resp = { region: 'us-east-1', bucketName: 'newbucket:my-new-bucket' } - - // Stub the S3 client's createBucket method - createBucketStub = sandbox.stub(S3Client.prototype, 'createBucket').resolves() - - const result = await ensureBucket(resp) - assert.ok(createBucketStub.calledOnce) - assert.strictEqual(createBucketStub.firstCall.args[0].bucketName, 'my-new-bucket') - assert.strictEqual(result, 'my-new-bucket') - }) - - it('should throw a ToolkitError when bucket creation fails', async () => { - const resp = { region: 'us-east-1', bucketName: 'newbucket:my-failing-bucket' } - - // Stub the S3 client's createBucket method to throw an error - createBucketStub = sandbox - .stub(S3Client.prototype, 'createBucket') - .rejects(new Error('Failed to create S3 bucket')) - - await assert.rejects(ensureBucket(resp)).catch((err) => { - assert.ok(err instanceof ToolkitError) - assert.ok(err.message, 'Failed to create S3 bucket') - }) - }) - }) - - describe('ensureRepo', () => { - let createRepositoryStub: sinon.SinonStub - let sandbox: sinon.SinonSandbox - - beforeEach(() => { - sandbox = sinon.createSandbox() - createRepositoryStub = sandbox.stub() - sandbox.stub(DefaultEcrClient.prototype, 'createRepository').callsFake(createRepositoryStub) - }) - - afterEach(() => { - sandbox.restore() - }) - - const createInput = (ecrRepoUri: string | undefined) => ({ - region: 'us-west-2', - ecrRepoUri, - }) - - const createNewRepoInput = (repoName: string) => createInput(`newrepo:${repoName}`) - - describe('when not creating new repository', () => { - it('should return original ecrRepoUri when not matching newrepo pattern', async () => { - const input = createInput('existing-repo:latest') - const result = await ensureRepo(input) - - assert(createRepositoryStub.notCalled) - assert.strictEqual(result, input.ecrRepoUri) - }) - - it('should return original ecrRepoUri when ecrRepoUri is undefined', async () => { - const input = createInput(undefined) - const result = await ensureRepo(input) - - assert(!result) - assert(createRepositoryStub.notCalled) - }) - }) - - describe('when creating new repository', () => { - const repoName = 'test-repo' - const input = createNewRepoInput(repoName) - - it('should create new repository successfully', async () => { - const expectedUri = 'aws.ecr.test/test-repo' - createRepositoryStub.resolves({ - repository: { - repositoryUri: expectedUri, - }, - }) - - const result = await ensureRepo(input) - - assert.strictEqual(result, expectedUri) - assert(createRepositoryStub.calledOnceWith(repoName)) - }) - - it('should handle repository creation failure', async () => { - createRepositoryStub.rejects(new Error('Repository creation failed')) - - try { - await ensureRepo(input) - assert.fail('Should have thrown an error') - } catch (err) { - assert(err instanceof ToolkitError) - assert.strictEqual(err.message, `Failed to create new ECR repository "${repoName}"`) - } - }) - - const testCases = [ - { - name: 'undefined repositoryUri', - response: { repository: { repositoryUri: undefined } }, - }, - { - name: 'empty repository response', - response: {}, - }, - ] - - for (const { name, response } of testCases) { - it(`should handle ${name}`, async () => { - createRepositoryStub.resolves(response) - - const result = await ensureRepo(input) - - assert(!result) - assert(createRepositoryStub.calledOnceWith(repoName)) - }) - } - }) - }) -}) - -const s3BucketListSummary: Array< - RequiredProps & { - readonly region: string - } -> = [ - { Name: 'stack-1-bucket', region: 'us-west-2' }, - { Name: 'stack-2-bucket', region: 'us-west-2' }, - { Name: 'stack-3-bucket', region: 'us-west-2' }, -] - -const stackSummaries: CloudFormationClientModule.StackSummary[][] = [ - [ - { - StackName: 'stack1', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - { - StackName: 'stack2', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - { - StackName: 'stack3', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as CloudFormationClientModule.StackSummary, - ], -] diff --git a/packages/core/src/test/shared/sam/utils.test.ts b/packages/core/src/test/shared/sam/utils.test.ts deleted file mode 100644 index 991e664447..0000000000 --- a/packages/core/src/test/shared/sam/utils.test.ts +++ /dev/null @@ -1,314 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import assert from 'assert' -import sinon from 'sinon' -import { - getProjectRootUri, - getProjectRoot, - getSource, - isDotnetRuntime, - getSamCliPathAndVersion, - getRecentResponse, - updateRecentResponse, - getSamCliErrorMessage, - throwIfErrorMatches, -} from '../../../shared/sam/utils' - -import { RegionNode } from '../../../awsexplorer/regionNode' -import { Region } from '../../../shared/regions/endpoints' -import { RegionProvider, ToolkitError } from '../../../shared' -import { DeployedResource, DeployedResourceNode } from '../../../awsService/appBuilder/explorer/nodes/deployedNode' -import { TemplateItem } from '../../../shared/ui/sam/templatePrompter' -import { SamCliInfoInvocation } from '../../../shared/sam/cli/samCliInfo' -import { SamCliSettings } from '../../../shared/sam/cli/samCliSettings' -import { telemetry } from '../../../shared/telemetry' -import globals from '../../../shared/extensionGlobals' -import { assertLogsContain } from '../../globalSetup.test' -import { ChildProcessResult } from '../../../shared/utilities/processUtils' - -describe('SAM utils', async function () { - it('returns the projectRoot', async function () { - const templateItem: TemplateItem = { - uri: vscode.Uri.file('file://mock/path/project/file'), - data: {}, - } - const response = getProjectRoot(templateItem) - assert.deepStrictEqual(response, vscode.Uri.file('file://mock/path/project')) - }) - it('returns the projectRootUri', async function () { - const template: vscode.Uri = vscode.Uri.file('file://mock/path/project/uri') - const response = getProjectRootUri(template) - assert.deepStrictEqual(response, vscode.Uri.file('file://mock/path/project')) - }) - - describe('getSource', async function () { - const testScenarios = [ - { - name: 'vscode.Uri', - value: vscode.Uri.file('file://file'), - expected: 'template', - }, - { - name: 'AWSTreeNode', - value: new RegionNode({ name: 'us-east-1', id: 'IAD' } as Region, {} as RegionProvider), - expected: 'regionNode', - }, - { - name: 'TreeNode', - value: new DeployedResourceNode({ arn: 'aws:arn:...', contextValue: '' } as DeployedResource), - expected: 'appBuilderDeploy', - }, - { - name: 'undefined', - value: undefined, - expected: undefined, - }, - ] - for (const scenario of testScenarios) { - it(`returns Source for ${scenario.name}`, async () => { - assert.strictEqual(getSource(scenario.value), scenario.expected) - }) - } - }) - - describe('checks if it is DotNet', async function () { - let sandbox: sinon.SinonSandbox - const noUri = vscode.Uri.parse('untitled://') - const testScenarios = [ - { - name: 'DotNet function', - template: ` - Transform: 'AWS::Serverless-2016-10-31' - Resources: - Func1: - Type: 'AWS::Serverless::Function' - Properties: - Runtime: 'dotnet8' - `, - expected: true, - }, - { - name: 'Global DotNet property', - template: ` - Transform: 'AWS::Serverless-2016-10-31' - Globals: - Function: - Runtime: 'dotnet8' - Resources: - Func1: - Type: 'AWS::Serverless::Function' - Properties: {} - `, - expected: true, - }, - { - name: 'different runtime', - template: ` - Transform: 'AWS::Serverless-2016-10-31' - Resources: - Func1: - Type: 'AWS::Serverless::Function' - Properties: - Runtime: 'nodejs20.x' - `, - expected: false, - }, - { - name: 'two functions, one DotNet', - template: ` - Transform: 'AWS::Serverless-2016-10-31' - Resources: - Func1: - Type: 'AWS::Serverless::Function' - Properties: - Runtime: 'nodejs20.x' - Func2: - Type: 'AWS::Serverless::Function' - Properties: - Runtime: 'dotnet8' - `, - expected: true, - }, - { - name: 'no function', - template: ` - Transform: 'AWS::Serverless-2016-10-31' - Resources: - Func1: - Type: 'AWS::S3::Bucket' - Properties: - Runtime: 'nodejs20.x' - `, - expected: false, - }, - { - name: 'no resources', - template: ``, - expected: false, - }, - ] - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - afterEach(() => { - sandbox.restore() - }) - - for (const scenario of testScenarios) { - it(`returns isDotNetRuntime for ${scenario.name}`, async () => { - const response = await isDotnetRuntime(noUri, scenario.template) - assert.strictEqual(response, scenario.expected) - }) - } - }) - - describe('getSamCliPathAndVersion', async function () { - let sandbox: sinon.SinonSandbox - let getOrDetectSamCliStub: sinon.SinonStub - let executeStub: sinon.SinonStub - let telemetryStub: sinon.SinonStub - - beforeEach(() => { - sandbox = sinon.createSandbox() - getOrDetectSamCliStub = sandbox.stub(SamCliSettings.instance, 'getOrDetectSamCli') - executeStub = sandbox.stub(SamCliInfoInvocation.prototype, 'execute') - telemetryStub = sandbox.stub(telemetry, 'record') - }) - - afterEach(() => { - sandbox.restore() - }) - - it('returns path and version when SAM CLI is found and version is valid', async () => { - const expectedPath = '/usr/local/bin/sam' - const expectedVersion = '1.99.0' - getOrDetectSamCliStub.resolves({ path: expectedPath }) - executeStub.resolves({ version: expectedVersion }) - - const result = await getSamCliPathAndVersion() - - assert.strictEqual(result.path, expectedPath) - assert.strictEqual(result.parsedVersion?.version, expectedVersion) - assert(telemetryStub.calledOnceWith({ version: expectedVersion })) - }) - - it('throws MissingExecutable error when SAM CLI path is undefined', async () => { - getOrDetectSamCliStub.resolves({ path: undefined }) - - try { - await getSamCliPathAndVersion() - assert.fail('Should have thrown an error') - } catch (error) { - assert(error instanceof ToolkitError) - assert.strictEqual(error.code, 'MissingExecutable') - assert.strictEqual(error.message, 'SAM CLI could not be found') - assert(telemetryStub.notCalled) - } - }) - - it('throws VersionTooLow error when SAM CLI version is below 1.53.0', async () => { - const lowVersion = '1.52.0' - getOrDetectSamCliStub.resolves({ path: '/usr/local/bin/sam' }) - executeStub.resolves({ version: lowVersion }) - - try { - await getSamCliPathAndVersion() - assert.fail('Should have thrown an error') - } catch (error) { - assert(error instanceof ToolkitError) - assert.strictEqual(error.code, 'VersionTooLow') - assert.strictEqual(error.message, 'SAM CLI version 1.53.0 or higher is required') - // records telemetry even when version is too low - assert(telemetryStub.calledOnceWith({ version: lowVersion })) - } - }) - }) - - describe('get/update recent response', () => { - // Create stub for globals.context.workspaceState.get - const mementoRootKey = 'samcli.utils.key' - const nonExistingMementoRootKey = 'samcli.utils.no.key' - const identifier = 'us-east-1' - const key1 = 'stackName' - const key2 = 'bucketName' - const value1 = 'myStackName' - const value2 = 'myBucketName' - - after(async () => { - await globals.context.workspaceState.update(mementoRootKey, {}) - }) - - it('1. getRecentResponse should return undefined when mementoRootKey does not exist', async () => { - assert(!getRecentResponse(nonExistingMementoRootKey, identifier, key1)) - }) - - it('2. updateRecentResponse should return the undefined when mementoRootKey does not exist', async () => { - try { - await updateRecentResponse(mementoRootKey, identifier, key1, value1) - } catch (err) { - assert.fail('The execution should have succeeded yet encounter unexpected exception') - } - }) - - it('3. getRecentResponse should return the correct value', async () => { - const result = getRecentResponse(mementoRootKey, identifier, key1) - assert.strictEqual(result, value1) - }) - - it('4. updateRecentResponse should only update the specified key', async () => { - await updateRecentResponse(mementoRootKey, identifier, key2, value2) - const result1 = getRecentResponse(mementoRootKey, identifier, key1) - const result2 = getRecentResponse(mementoRootKey, identifier, key2) - assert.strictEqual(result1, value1) - assert.strictEqual(result2, value2) - }) - - it('5. updateRecentResponse should log and swallow exception when fails to update value', async () => { - sinon.stub(globals.context.workspaceState, 'update').rejects(new Error('Error updating value')) - try { - await updateRecentResponse(mementoRootKey, identifier, key2, value2) - } catch (err) { - assert.fail('The target function should have handled exception internally') - } - assertLogsContain(`sam: unable to save response at key`, false, 'warn') - sinon.restore() - }) - }) - - describe('gets the SAM CLI error from stderr', async function () { - it('returns the error message', async function () { - const stderr = - 'Starting Build use cache\nStarting Build inside a container\nCache is invalid, running build and copying resources for following functions (ResizerFunction)\nBuilding codeuri: /Users/mbfreder/TestApp/JavaSamApp/serverless-patterns/s3lambda-resizing-python/src runtime: python3.12 metadata: {} architecture: x86_64 functions: ResizerFunction\nError: Docker is unreachable. Docker needs to be running to build inside a container.' - const response = getSamCliErrorMessage(stderr) - assert.deepStrictEqual( - response, - 'Error: Docker is unreachable. Docker needs to be running to build inside a container.' - ) - }) - }) - - describe('throwIfErrorMatches', async function () { - it('should throw a ToolkitError with the correct code when an error message matches', () => { - const mockError = new Error('Mock Error') - const mockResult: ChildProcessResult = { - exitCode: 1, - error: mockError, - stdout: '', - stderr: 'Docker is unreachable.', - } - assert.throws( - () => throwIfErrorMatches(mockResult), - (e: any) => { - assert.strictEqual(e instanceof ToolkitError, true) - assert.strictEqual(e.code, 'DockerUnreachable') - return true - } - ) - }) - }) -}) diff --git a/packages/core/src/test/shared/settings.test.ts b/packages/core/src/test/shared/settings.test.ts index a868d6fbe6..55510b2653 100644 --- a/packages/core/src/test/shared/settings.test.ts +++ b/packages/core/src/test/shared/settings.test.ts @@ -352,44 +352,6 @@ describe('DevSetting', function () { }) describe('getServiceConfig()', function () { - describe('get CodeCatalyst config', function () { - const devSettingName = 'aws.dev.codecatalystService' - const defaultConfig = { - region: 'default', - endpoint: 'default', - hostname: 'default', - gitHostname: 'default', - } - - it('throws an error for incomplete dev configuration', async function () { - const testSetting = { - // missing region - endpoint: 'test_endpoint', - hostname: 'test_hostname', - gitHostname: 'test_githostname', - } - - await settings.update(devSettingName, testSetting) - assert.throws(() => sut.getServiceConfig('codecatalystService', defaultConfig), ToolkitError) - }) - - it('returns dev settings configuration when provided', async function () { - const testSetting = { - region: 'test_region', - endpoint: 'test_endpoint', - hostname: 'test_hostname', - gitHostname: 'test_githostname', - } - - await settings.update(devSettingName, testSetting) - assert.deepStrictEqual(sut.getServiceConfig('codecatalystService', defaultConfig), testSetting) - }) - - it('returns default configuration when dev settings are not provided', function () { - assert.deepStrictEqual(sut.getServiceConfig('codecatalystService', defaultConfig), defaultConfig) - }) - }) - describe('get Codewhisperer config', function () { const devSettingName = 'aws.dev.codewhispererService' const defaultConfig = { diff --git a/packages/core/src/test/shared/sshConfig.test.ts b/packages/core/src/test/shared/sshConfig.test.ts deleted file mode 100644 index 87a816e925..0000000000 --- a/packages/core/src/test/shared/sshConfig.test.ts +++ /dev/null @@ -1,309 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import * as sinon from 'sinon' -import * as path from 'path' -import * as http from 'http' -import { ToolkitError } from '../../shared/errors' -import { Result } from '../../shared/utilities/result' -import { ChildProcess, ChildProcessResult } from '../../shared/utilities/processUtils' -import { SshConfig, ensureConnectScript, sshLogFileLocation } from '../../shared/sshConfig' -import { fileExists, makeTemporaryToolkitFolder } from '../../shared/filesystemUtilities' -import { - DevEnvironmentId, - bearerTokenCacheLocation, - connectScriptPrefix, - getCodeCatalystSsmEnv, -} from '../../codecatalyst/model' -import { StartDevEnvironmentSessionRequest } from '@aws-sdk/client-codecatalyst' -import { mkdir, readFile } from 'fs/promises' -import fs from '../../shared/fs/fs' -import { globals } from '../../shared' - -class MockSshConfig extends SshConfig { - // State variables to track logic flow. - public testIsWin: boolean = false - public configSection: string = '' - public exitCodeOverride: number = 0 - - public async getProxyCommandWrapper(command: string): Promise> { - return await this.getProxyCommand(command) - } - - public async testMatchSshSection(testSection: string) { - this.configSection = testSection - const result = await this.matchSshSection() - this.configSection = '' - return result - } - - public async testVerifySshHostWrapper(proxyCommand: string, testSection: string) { - this.configSection = testSection - const result = this.verifySSHHost(proxyCommand) - this.configSection = '' - return result - } - - protected override isWin() { - return this.testIsWin - } - - protected override async checkSshOnHost(): Promise { - return { - exitCode: this.exitCodeOverride, - error: undefined, - stdout: this.configSection, - stderr: '', - } - } - - public createSSHConfigSectionWrapper(proxyCommand: string): string { - return this.createSSHConfigSection(proxyCommand) - } -} - -describe('VscodeRemoteSshConfig', async function () { - let config: MockSshConfig - - const testCommand = 'test_connect' - const testProxyCommand = `'${testCommand}' '%h'` - - before(function () { - config = new MockSshConfig('sshPath', 'testHostNamePrefix', testCommand) - config.testIsWin = false - }) - - describe('getProxyCommand', async function () { - it('returns correct proxyCommand on non-windows', async function () { - config.testIsWin = false - const result = await config.getProxyCommandWrapper(testCommand) - assert.ok(result.isOk()) - const command = result.unwrap() - assert.strictEqual(command, testProxyCommand) - }) - - it('uses %n token for sagemaker_connect to preserve hostname case', async function () { - const sagemakerConfig = new MockSshConfig('sshPath', 'testHostNamePrefix', 'sagemaker_connect') - sagemakerConfig.testIsWin = false - - const result = await sagemakerConfig.getProxyCommandWrapper('sagemaker_connect') - assert.ok(result.isOk()) - const command = result.unwrap() - assert.strictEqual(command, `'sagemaker_connect' '%n'`) - }) - }) - - describe('matchSshSection', async function () { - beforeEach(function () { - config.exitCodeOverride = 0 - }) - - it('returns ok with match when proxycommand is present', async function () { - const testSection = `proxycommandfdsafdsafd${testProxyCommand}sa342432` - const result = await config.testMatchSshSection(testSection) - assert.ok(result.isOk()) - const match = result.unwrap() - assert.ok(match) - }) - - it('returns ok result with undefined inside when proxycommand is not present', async function () { - const testSection = `fdsafdsafdsa342432` - const result = await config.testMatchSshSection(testSection) - assert.ok(result.isOk()) - const match = result.unwrap() - assert.strictEqual(match, undefined) - }) - - it('returns error when ssh check fails with non-zero exit code', async function () { - config.exitCodeOverride = 255 - const testSection = `some config` - const result = await config.testMatchSshSection(testSection) - assert.ok(result.isErr()) - const error = result.err() - assert.ok(error.message.includes('ssh check against host failed')) - assert.ok(error.message.includes('255')) - }) - }) - - describe('verifySSHHost', async function () { - let promptUserToConfigureSshConfigStub: sinon.SinonStub< - [configSection: string | undefined, proxyCommand: string], - Promise - > - before(function () { - promptUserToConfigureSshConfigStub = sinon.stub(SshConfig.prototype, 'promptUserToConfigureSshConfig') - }) - - beforeEach(function () { - config.exitCodeOverride = 0 - promptUserToConfigureSshConfigStub.resetHistory() - }) - - after(function () { - sinon.restore() - }) - - it('writes to ssh config if command not found.', async function () { - const testSection = 'no-command-here' - const result = await config.testVerifySshHostWrapper(testCommand, testSection) - - assert.ok(result.isOk()) - sinon.assert.calledOn(promptUserToConfigureSshConfigStub, config) - sinon.assert.calledOnce(promptUserToConfigureSshConfigStub) - }) - - it('does not write to ssh config if command is find', async function () { - const testSection = `this is some text that doesn't matter, but here proxycommand ${testProxyCommand}` - const result = await config.testVerifySshHostWrapper(testCommand, testSection) - - assert.ok(result.isOk()) - sinon.assert.notCalled(promptUserToConfigureSshConfigStub) - }) - }) - - describe('createSSHConfigSection', async function () { - const testKeyPath = 'path/to/keys' - const newConfig = new MockSshConfig('sshPath', 'testHostNamePrefix', 'someScript', testKeyPath) - const expectedUserString = `User '%r'` - const expectedIdentityFileString = `IdentityFile '${testKeyPath}'` - - it('section includes relevant script prefix', function () { - const testScriptName = 'testScript' - const section = config.createSSHConfigSectionWrapper(testScriptName) - assert.ok(section.includes(testScriptName)) - }) - - it('includes keyPath if included in the class', function () { - const section = newConfig.createSSHConfigSectionWrapper('proxyCommand') - assert.ok(section.match(expectedIdentityFileString)) - }) - - it('parses the remote username from the ssh execution', function () { - const section = newConfig.createSSHConfigSectionWrapper('proxyCommand') - assert.ok(section.match(expectedUserString)) - }) - - it('omits User and IdentityFile fields when keyPath not given', function () { - const section = config.createSSHConfigSectionWrapper('proxyCommand') - assert.ok(!section.match(expectedUserString)) - assert.ok(!section.match(expectedIdentityFileString)) - }) - }) - - describe('sshLogFileLocation', async function () { - it('combines service and id into proper log file', function () { - const testService = 'testScript' - const testId = 'id' - const result = sshLogFileLocation(testService, testId) - - assert.ok(result.includes(testService)) - assert.ok(result.includes(testId)) - assert.ok(result.endsWith('.log')) - }) - }) -}) - -describe('CodeCatalyst Connect Script', function () { - function isWithin(path1: string, path2: string): boolean { - const rel = path.relative(path1, path2) - return !path.isAbsolute(rel) && !rel.startsWith('..') && !!rel - } - - it('can get a connect script path, adding a copy to global storage', async function () { - const script = (await ensureConnectScript(connectScriptPrefix, globals.context)).unwrap().fsPath - assert.ok(await fileExists(script)) - assert.ok(isWithin(globals.context.globalStorageUri.fsPath, script)) - }) - - function createFakeServer(testDevEnv: DevEnvironmentId) { - return http.createServer(async (req, resp) => { - try { - const data = await new Promise((resolve, reject) => { - req.on('error', reject) - req.on('data', (d) => resolve(d.toString())) - }) - - const body = JSON.parse(data) - const expected: Pick = { - sessionConfiguration: { sessionType: 'SSH' }, - } - - const expectedPath = `/v1/spaces/${testDevEnv.org.name}/projects/${testDevEnv.project.name}/devEnvironments/${testDevEnv.id}/session` - - assert.deepStrictEqual(body, expected) - assert.strictEqual(req.url, expectedPath) - } catch (e) { - resp.writeHead(400, { 'Content-Type': 'application/json' }) - resp.end(JSON.stringify({ name: 'ValidationException', message: (e as Error).message })) - - return - } - - resp.writeHead(200, { 'Content-Type': 'application/json' }) - resp.end( - JSON.stringify({ - tokenValue: 'a token', - streamUrl: 'some url', - sessionId: 'an id', - }) - ) - }) - } - - it('can run the script with environment variables', async function () { - const testDevEnv: DevEnvironmentId = { - id: '01234567890', - project: { name: 'project' }, - org: { name: 'org' }, - } - - const server = createFakeServer(testDevEnv) - const address = await new Promise((resolve, reject) => { - server.on('error', reject) - server.listen({ host: 'localhost', port: 28142 }, () => resolve(`http://localhost:28142`)) - }) - - await fs.writeFile(bearerTokenCacheLocation(testDevEnv.id), 'token') - const script = (await ensureConnectScript(connectScriptPrefix, globals.context)).unwrap().fsPath - const env = getCodeCatalystSsmEnv('us-weast-1', 'echo', testDevEnv) - env.CODECATALYST_ENDPOINT = address - - // This could be de-duped - const isWindows = process.platform === 'win32' - const cmd = isWindows ? 'powershell.exe' : script - const args = isWindows ? ['-ExecutionPolicy', 'Bypass', '-File', script, 'bar'] : [script, 'bar'] - - const output = await new ChildProcess(cmd, args).run({ spawnOptions: { env } }) - if (output.exitCode !== 0) { - const logOutput = sshLogFileLocation('codecatalyst', testDevEnv.id) - const message = `stderr:\n${output.stderr}\n\nlogs:\n${await readFile(logOutput)}` - - assert.fail(`Connect script should exit with a zero status:\n${message}`) - } - }) - - describe('~/.ssh', function () { - let tmpDir: string - - beforeEach(async function () { - tmpDir = await makeTemporaryToolkitFolder() - sinon.stub(fs, 'getUserHomeDir').returns(tmpDir) - }) - - afterEach(async function () { - sinon.restore() - await fs.delete(tmpDir, { recursive: true }) - }) - - it('works if the .ssh directory is missing', async function () { - ;(await ensureConnectScript(connectScriptPrefix, globals.context)).unwrap() - }) - - it('works if the .ssh directory exists but has different perms', async function () { - await mkdir(path.join(tmpDir, '.ssh'), 0o777) - ;(await ensureConnectScript(connectScriptPrefix, globals.context)).unwrap() - }) - }) -}) diff --git a/packages/core/src/test/shared/ui/sam/bucketPrompter.test.ts b/packages/core/src/test/shared/ui/sam/bucketPrompter.test.ts deleted file mode 100644 index ae437c3105..0000000000 --- a/packages/core/src/test/shared/ui/sam/bucketPrompter.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { S3Client } from '../../../../shared/clients/s3' -import * as SamUtilsModule from '../../../../shared/sam/utils' -import { createBucketNamePrompter } from '../../../../shared/ui/sam/bucketPrompter' -import { AsyncCollection } from '../../../../shared/utilities/asyncCollection' -import { RequiredProps } from '../../../../shared/utilities/tsUtils' -import { samDeployUrl } from '../../../../shared/constants' -import { Bucket } from '@aws-sdk/client-s3' - -describe('createBucketNamePrompter', () => { - let sandbox: sinon.SinonSandbox - const s3Client = new S3Client('us-east-1', 'aws') - const mementoRootKey = 'samcli.deploy.params' - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should create a prompter with existing buckets', () => { - // Arrange - const buckets = [ - { Name: 'bucket1', region: 'us-east-1' }, - { Name: 'bucket2', region: 'us-east-1' }, - { Name: 'bucket3', region: 'us-east-1' }, - ] as unknown as AsyncCollection & { readonly region: string }> - - const stub = sandbox.stub(s3Client, 'listBucketsIterable').callsFake(() => { - return buckets - }) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) // Mock recent bucket - - // Act - const prompter = createBucketNamePrompter(s3Client, mementoRootKey, samDeployUrl) - - // Assert - assert.ok(stub.calledOnce) - const expectedItems = buckets.map((b) => [ - { - label: b.Name, - data: b.Name, - recentlyUsed: false, - }, - ]) - assert.strictEqual(prompter.quickPick.title, 'Select an S3 Bucket') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a bucket (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 3) - assert.deepStrictEqual(prompter.quickPick.items, expectedItems) - }) - - it('should include no items found message if no stacks exist', () => { - const stub = sandbox.stub(s3Client, 'listBucketsIterable').callsFake(() => { - return [] as unknown as AsyncCollection & { readonly region: string }> - }) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) // Mock recent bucket - - // Act - const prompter = createBucketNamePrompter(s3Client, mementoRootKey, samDeployUrl) - - // Assert - assert.ok(stub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select an S3 Bucket') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a bucket (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 1) - assert.strictEqual( - prompter.quickPick.items[0].label, - 'No S3 buckets for region "us-east-1". Enter a name to create a new one.' - ) - }) -}) diff --git a/packages/core/src/test/shared/ui/sam/ecrPrompter.test.ts b/packages/core/src/test/shared/ui/sam/ecrPrompter.test.ts deleted file mode 100644 index e981bf1396..0000000000 --- a/packages/core/src/test/shared/ui/sam/ecrPrompter.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import * as AwsConsoleModule from '../../../../shared/awsConsole' -import { DefaultEcrClient, EcrRepository } from '../../../../shared/clients/ecrClient' -import { samSyncUrl } from '../../../../shared/constants' -import * as SamUtilsModule from '../../../../shared/sam/utils' -import * as ButtonsModule from '../../../../shared/ui/buttons' -import { createEcrPrompter } from '../../../../shared/ui/sam/ecrPrompter' -import { intoCollection } from '../../../../shared/utilities/collectionUtils' -import { sleep } from '../../../../shared/utilities/timeoutUtils' - -describe('createEcrPrompter', () => { - let sandbox: sinon.SinonSandbox - const ecrClient = new DefaultEcrClient('us-east-1') - const mementoRootKey = 'samcli.sync.params' - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should create a prompter with existing repos', async () => { - // Arrange - const ecrRepos: EcrRepository[][] = [ - [ - { - repositoryName: 'repo1', - repositoryUri: 'repoUri1', - repositoryArn: 'repoArn1', - } as EcrRepository, - { - repositoryName: 'repo2', - repositoryUri: 'repoUri2', - repositoryArn: 'repoArn2', - } as EcrRepository, - { - repositoryName: 'repo3', - repositoryUri: 'repoUri3', - repositoryArn: 'repoArn3', - } as EcrRepository, - ], - ] - const expectedItems = [ - { - label: 'repo1', - data: 'repoUri1', - detail: 'repoArn1', - recentlyUsed: false, - }, - { - label: 'repo2', - data: 'repoUri2', - detail: 'repoArn2', - recentlyUsed: false, - }, - { - label: 'repo3', - data: 'repoUri3', - detail: 'repoArn3', - recentlyUsed: false, - }, - ] - const listAllRepositoriesStub = sandbox.stub(ecrClient, 'listAllRepositories').returns(intoCollection(ecrRepos)) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) - const createCommonButtonsStub = sandbox.stub(ButtonsModule, 'createCommonButtons') - sandbox - .stub(AwsConsoleModule, 'getAwsConsoleUrl') - .returns(vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`)) - - // Act - const prompter = createEcrPrompter(ecrClient, mementoRootKey) - await sleep(50) - - // Assert - assert.ok(createCommonButtonsStub.calledOnce) - assert.ok( - createCommonButtonsStub.calledWithExactly( - samSyncUrl, - vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`) - ) - ) - assert.ok(listAllRepositoriesStub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select an ECR Repository') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a repository (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 3) - assert.deepStrictEqual(prompter.quickPick.items, expectedItems) - }) - - it('should include no items found message if no repos exist', async () => { - const listAllStacksStub = sandbox.stub(ecrClient, 'listAllRepositories').returns(intoCollection([])) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) - const createCommonButtonsStub = sandbox.stub(ButtonsModule, 'createCommonButtons') - sandbox - .stub(AwsConsoleModule, 'getAwsConsoleUrl') - .returns(vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`)) - - // Act - const prompter = createEcrPrompter(ecrClient, mementoRootKey) - await sleep(50) - - // Assert - assert.ok(createCommonButtonsStub.calledOnce) - assert.ok( - createCommonButtonsStub.calledWithExactly( - samSyncUrl, - vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`) - ) - ) - assert.ok(listAllStacksStub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select an ECR Repository') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a repository (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 1) - assert.deepStrictEqual( - prompter.quickPick.items[0].label, - 'No ECR repositories in region "us-east-1". Enter a name to create a new one.' - ) - assert.deepStrictEqual(prompter.quickPick.items[0].data, undefined) - }) -}) diff --git a/packages/core/src/test/shared/ui/sam/paramsSourcePrompter.test.ts b/packages/core/src/test/shared/ui/sam/paramsSourcePrompter.test.ts deleted file mode 100644 index e935e59acc..0000000000 --- a/packages/core/src/test/shared/ui/sam/paramsSourcePrompter.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - createDeployParamsSourcePrompter, - createSyncParamsSourcePrompter, - ParamsSource, -} from '../../../../shared/ui/sam/paramsSourcePrompter' -import { DataQuickPickItem } from '../../../../shared/ui/pickerPrompter' - -describe('createSyncParamsSourcePrompter', () => { - it('should return a prompter with the correct items with no valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify required parameters and save as defaults', - data: ParamsSource.SpecifyAndSave, - }, - { - label: 'Specify required parameters', - data: ParamsSource.Specify, - }, - ] - const prompter = createSyncParamsSourcePrompter(false) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for sync') - assert.strictEqual(quickPick.placeholder, 'Press enter to proceed with highlighted option') - assert.strictEqual(quickPick.items.length, 2) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) - - it('should return a prompter with the correct items with valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify required parameters and save as defaults', - data: ParamsSource.SpecifyAndSave, - }, - { - label: 'Specify required parameters', - data: ParamsSource.Specify, - }, - { - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - }, - ] - const prompter = createSyncParamsSourcePrompter(true) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for sync') - assert.strictEqual(quickPick.placeholder, 'Press enter to proceed with highlighted option') - assert.strictEqual(quickPick.items.length, 3) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) -}) - -describe('createDeployParamsSourcePrompter', () => { - it('should return a prompter with the correct items with no valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify required parameters and save as defaults', - data: ParamsSource.SpecifyAndSave, - }, - { - label: 'Specify required parameters', - data: ParamsSource.Specify, - }, - ] - const prompter = createDeployParamsSourcePrompter(false) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for deploy') - assert.strictEqual(quickPick.placeholder, 'Press enter to proceed with highlighted option') - assert.strictEqual(quickPick.items.length, 2) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) - - it('should return a prompter with the correct items with valid samconfig', () => { - const expectedItems: DataQuickPickItem[] = [ - { - label: 'Specify required parameters and save as defaults', - data: ParamsSource.SpecifyAndSave, - }, - { - label: 'Specify required parameters', - data: ParamsSource.Specify, - }, - { - label: 'Use default values from samconfig', - data: ParamsSource.SamConfig, - }, - ] - const prompter = createDeployParamsSourcePrompter(true) - const quickPick = prompter.quickPick - assert.strictEqual(quickPick.title, 'Specify parameter source for deploy') - assert.strictEqual(quickPick.placeholder, 'Press enter to proceed with highlighted option') - assert.strictEqual(quickPick.items.length, 3) - assert.deepStrictEqual(quickPick.items, expectedItems) - }) -}) diff --git a/packages/core/src/test/shared/ui/sam/stackPrompter.test.ts b/packages/core/src/test/shared/ui/sam/stackPrompter.test.ts deleted file mode 100644 index 206d26db9e..0000000000 --- a/packages/core/src/test/shared/ui/sam/stackPrompter.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import * as vscode from 'vscode' -import * as AwsConsoleModule from '../../../../shared/awsConsole' -import * as SamUtilsModule from '../../../../shared/sam/utils' -import * as ButtonsModule from '../../../../shared/ui/buttons' -import { CloudFormationClient, StackSummary } from '../../../../shared/clients/cloudFormation' -import { samSyncUrl } from '../../../../shared/constants' -import { createStackPrompter } from '../../../../shared/ui/sam/stackPrompter' -import { intoCollection } from '../../../../shared/utilities/collectionUtils' -import { sleep } from '../../../../shared/utilities/timeoutUtils' - -describe('createStackPrompter', () => { - let sandbox: sinon.SinonSandbox - const cfnClient = new CloudFormationClient('us-east-1') - const mementoRootKey = 'samcli.sync.params' - - beforeEach(() => { - sandbox = sinon.createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should create a prompter with existing stacks', async () => { - // Arrange - const stackSummaries: StackSummary[][] = [ - [ - { - StackName: 'stack1', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as StackSummary, - { - StackName: 'stack2', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as StackSummary, - { - StackName: 'stack3', - StackStatus: 'CREATE_COMPLETE', - CreationTime: new Date(), - } as StackSummary, - ], - ] - const expectedItems = [ - { - label: 'stack1', - data: 'stack1', - description: undefined, - invalidSelection: false, - recentlyUsed: false, - }, - { - label: 'stack2', - data: 'stack2', - description: undefined, - invalidSelection: false, - recentlyUsed: false, - }, - { - label: 'stack3', - data: 'stack3', - description: undefined, - invalidSelection: false, - recentlyUsed: false, - }, - ] - const listAllStacksStub = sandbox.stub(cfnClient, 'listAllStacks').returns(intoCollection(stackSummaries)) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) - const createCommonButtonsStub = sandbox.stub(ButtonsModule, 'createCommonButtons') - sandbox - .stub(AwsConsoleModule, 'getAwsConsoleUrl') - .returns(vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`)) - - // Act - const prompter = createStackPrompter(cfnClient, mementoRootKey, samSyncUrl) - await sleep(50) - - // Assert - assert.ok(createCommonButtonsStub.calledOnce) - assert.ok( - createCommonButtonsStub.calledWithExactly( - samSyncUrl, - vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`) - ) - ) - assert.ok(listAllStacksStub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select a CloudFormation Stack') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a stack (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 3) - assert.deepStrictEqual(prompter.quickPick.items, expectedItems) - }) - - it('should include no items found message if no stacks exist', async () => { - const listAllStacksStub = sandbox.stub(cfnClient, 'listAllStacks').returns(intoCollection([])) - sandbox.stub(SamUtilsModule, 'getRecentResponse').returns(undefined) - const createCommonButtonsStub = sandbox.stub(ButtonsModule, 'createCommonButtons') - sandbox - .stub(AwsConsoleModule, 'getAwsConsoleUrl') - .returns(vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`)) - - // Act - const prompter = createStackPrompter(cfnClient, mementoRootKey, samSyncUrl) - await sleep(50) - - // Assert - assert.ok(createCommonButtonsStub.calledOnce) - assert.ok( - createCommonButtonsStub.calledWithExactly( - samSyncUrl, - vscode.Uri.parse(`https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1`) - ) - ) - assert.ok(listAllStacksStub.calledOnce) - assert.strictEqual(prompter.quickPick.title, 'Select a CloudFormation Stack') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a stack (or enter a name to create one)') - assert.strictEqual(prompter.quickPick.items.length, 1) - assert.deepStrictEqual( - prompter.quickPick.items[0].label, - 'No stacks in region "us-east-1". Enter a name to create a new one.' - ) - assert.deepStrictEqual(prompter.quickPick.items[0].data, undefined) - }) -}) diff --git a/packages/core/src/test/shared/ui/sam/templatePrompter.test.ts b/packages/core/src/test/shared/ui/sam/templatePrompter.test.ts deleted file mode 100644 index 483b4fe8bc..0000000000 --- a/packages/core/src/test/shared/ui/sam/templatePrompter.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' - -import sinon from 'sinon' -import * as Cfn from '../../../../shared/cloudformation/cloudformation' -import { CloudFormationTemplateRegistry } from '../../../../shared/fs/templateRegistry' -import { WatchedItem } from '../../../../shared/fs/watchedFiles' -import * as SamUtilsModule from '../../../../shared/sam/utils' -import { createTemplatePrompter } from '../../../../shared/ui/sam/templatePrompter' -import { assertEqualPaths } from '../../../testUtil' -import { samDeployUrl } from '../../../../shared/constants' - -describe('createTemplatePrompter', () => { - let registry: CloudFormationTemplateRegistry - let sandbox: sinon.SinonSandbox - const mementoRootKey = 'samcli.sync.params' - - beforeEach(() => { - sandbox = sinon.createSandbox() - // Create a mock instance of CloudFormationTemplateRegistry - registry = { - items: [ - { path: '/path/to/template1.yaml', item: {} } as WatchedItem, - { path: '/path/to/template2.yaml', item: {} } as WatchedItem, - ], - } as CloudFormationTemplateRegistry // Typecasting to match expected type - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should create quick pick items from registry items', () => { - // Arrange - const recentTemplatePathStub = sandbox.stub().returns(undefined) - sandbox.replace(SamUtilsModule, 'getRecentResponse', recentTemplatePathStub) - const workspaceFolder = vscode.workspace.workspaceFolders?.[0] - assert.ok(workspaceFolder) - - const prompter = createTemplatePrompter(registry, mementoRootKey, samDeployUrl) - - assert.strictEqual(prompter.quickPick.items.length, 2) - assertEqualPaths(prompter.quickPick.items[0].label, '/path/to/template1.yaml') - assertEqualPaths(prompter.quickPick.items[1].label, '/path/to/template2.yaml') - assert.strictEqual(prompter.quickPick.title, 'Select a SAM/CloudFormation Template') - assert.strictEqual(prompter.quickPick.placeholder, 'Select a SAM/CloudFormation Template') - }) -}) diff --git a/packages/core/src/test/ssmDocument/commands/createDocumentFromTemplate.test.ts b/packages/core/src/test/ssmDocument/commands/createDocumentFromTemplate.test.ts deleted file mode 100644 index c9cacb0908..0000000000 --- a/packages/core/src/test/ssmDocument/commands/createDocumentFromTemplate.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import vscode from 'vscode' -import assert from 'assert' -import sinon from 'sinon' -import * as picker from '../../../shared/ui/picker' -import { - createSsmDocumentFromTemplate, - SsmDocumentTemplateQuickPickItem, -} from '../../../ssmDocument/commands/createDocumentFromTemplate' -import * as ssmDocumentUtil from '../../../ssmDocument/util/util' -import * as fsUtilities from '../../../shared/filesystemUtilities' - -import { FakeExtensionContext } from '../../fakeExtensionContext' - -describe('createDocumentFromTemplate', async function () { - let mockContext: vscode.ExtensionContext - let sandbox: sinon.SinonSandbox - before(async function () { - mockContext = await FakeExtensionContext.create() - }) - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - const fakeContentYaml = `--- -schemaVersion: '2.2' -mainSteps: [] -` - - const fakeContentJson = `{ - "schemaVersion": "2.2", - "mainSteps": [] -}` - - const fakeSelectionResult: SsmDocumentTemplateQuickPickItem = { - label: 'test template', - description: 'an example to test creating from template', - filename: 'test.command.ssm.json', - docType: 'command', - } - - const fakeSelection: SsmDocumentTemplateQuickPickItem[] = [] - fakeSelection.push(fakeSelectionResult) - - it('open and save document based on selected template', async function () { - sandbox.stub(picker, 'promptUser').returns(Promise.resolve(fakeSelection)) - sandbox.stub(picker, 'verifySinglePickerOutput').returns(fakeSelectionResult) - sandbox.stub(fsUtilities, 'readFileAsString').returns(Promise.resolve(fakeContentYaml)) - sandbox.stub(ssmDocumentUtil, 'promptUserForDocumentFormat').returns(Promise.resolve('JSON')) - - const openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument') - - await createSsmDocumentFromTemplate(mockContext) - - assert.strictEqual(openTextDocumentStub.getCall(0).args[0]?.content, fakeContentJson) - assert.strictEqual(openTextDocumentStub.getCall(0).args[0]?.language, 'ssm-json') - }) -}) diff --git a/packages/core/src/test/ssmDocument/commands/deleteDocument.test.ts b/packages/core/src/test/ssmDocument/commands/deleteDocument.test.ts deleted file mode 100644 index f79d217028..0000000000 --- a/packages/core/src/test/ssmDocument/commands/deleteDocument.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as sinon from 'sinon' -import * as vscode from 'vscode' -import { DocumentItemNodeWriteable } from '../../../ssmDocument/explorer/documentItemNodeWriteable' -import { SsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import { deleteDocument } from '../../../ssmDocument/commands/deleteDocument' -import { RegistryItemNode } from '../../../ssmDocument/explorer/registryItemNode' -import { DocumentFormat, DocumentIdentifier } from '@aws-sdk/client-ssm' -import { getTestWindow } from '../../shared/vscode/window' -import { stub } from '../../utilities/stubber' - -describe('deleteDocument', async function () { - let ssmClient: SsmDocumentClient - let node: DocumentItemNodeWriteable - let parentNode: RegistryItemNode - let sandbox: sinon.SinonSandbox - let spyExecuteCommand: sinon.SinonSpy - const fakeName: string = 'testDocument' - - const fakeDoc: DocumentIdentifier = { - Name: fakeName, - DocumentFormat: DocumentFormat.JSON, - DocumentType: 'Automation', - Owner: 'Amazon', - } - - const fakeRegion: string = 'us-east-1' - - beforeEach(function () { - sandbox = sinon.createSandbox() - spyExecuteCommand = sandbox.spy(vscode.commands, 'executeCommand') - - ssmClient = { - deleteDocument: async (doc: string) => { - return {} - }, - } as any as SsmDocumentClient - parentNode = stub(RegistryItemNode, { - regionCode: '', - registryName: '', - documentType: '', - serviceId: undefined, - label: undefined, - id: undefined, - iconPath: undefined, - description: undefined, - resourceUri: undefined, - tooltip: undefined, - command: undefined, - collapsibleState: undefined, - contextValue: undefined, - accessibilityInformation: undefined, - checkboxState: undefined, - }) - node = new DocumentItemNodeWriteable(fakeDoc, ssmClient, fakeRegion, parentNode) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('confirms deletion, deletes file, and refreshes parent node', async function () { - getTestWindow().onDidShowMessage((m) => m.items.find((i) => i.title === 'Delete')?.select()) - await deleteDocument(node) - getTestWindow().getFirstMessage().assertWarn('Are you sure you want to delete document testDocument?') - sandbox.assert.calledWith(spyExecuteCommand, 'aws.refreshAwsExplorerNode', parentNode) - }) -}) diff --git a/packages/core/src/test/ssmDocument/commands/openDocumentItem.test.ts b/packages/core/src/test/ssmDocument/commands/openDocumentItem.test.ts deleted file mode 100644 index a02403be4d..0000000000 --- a/packages/core/src/test/ssmDocument/commands/openDocumentItem.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DocumentFormat, DocumentIdentifier, GetDocumentResult } from '@aws-sdk/client-ssm' - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' - -import { openDocumentItem } from '../../../ssmDocument/commands/openDocumentItem' -import { DocumentItemNode } from '../../../ssmDocument/explorer/documentItemNode' - -import * as picker from '../../../shared/ui/picker' -import { FakeAwsContext } from '../../utilities/fakeAwsContext' -import { DefaultSsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import { stub } from '../../utilities/stubber' -import { getTestWindow } from '../../shared/vscode/window' - -describe('openDocumentItem', async function () { - afterEach(function () { - sinon.restore() - }) - - const rawContent: GetDocumentResult = { - DocumentFormat: DocumentFormat.JSON, - DocumentType: 'Command', - Name: 'testDocument', - Content: `{ - "schemaVersion": "2.2", - "mainSteps": [ - - ] - }`, - } - - const fakeDoc: DocumentIdentifier = { - Name: 'testDocument', - DocumentFormat: DocumentFormat.JSON, - DocumentType: 'Command', - Owner: 'Amazon', - } - - const fakeAwsContext = new FakeAwsContext() - - const fakeRegion = 'us-east-1' - - const fakeFormatSelection = [ - { - label: 'JSON', - description: 'Open document in JSON format', - }, - ] - - const fakeFormatSelectionResult = { - label: 'JSON', - description: 'Open document with format JSON', - } - - it('create DocumentItemNode and openDocumentItem functionality', async function () { - getTestWindow().onDidShowDialog((dialog) => dialog.selectItem(vscode.Uri.file('test'))) - sinon.stub(picker, 'promptUser').onFirstCall().returns(Promise.resolve(fakeFormatSelection)) - sinon.stub(picker, 'verifySinglePickerOutput').onFirstCall().returns(fakeFormatSelectionResult) - - const documentNode = generateDocumentItemNode() - const openTextDocumentStub = sinon.stub(vscode.workspace, 'openTextDocument') - await openDocumentItem(documentNode, fakeAwsContext, DocumentFormat.JSON) - assert.strictEqual(openTextDocumentStub.getCall(0).args[0]?.content, rawContent.Content) - assert.strictEqual(openTextDocumentStub.getCall(0).args[0]?.language, 'ssm-json') - }) - - function generateDocumentItemNode(): DocumentItemNode { - const client = stub(DefaultSsmDocumentClient, { regionCode: fakeRegion }) - client.getDocument.resolves(rawContent) - - return new DocumentItemNode(fakeDoc, client, fakeRegion) - } -}) diff --git a/packages/core/src/test/ssmDocument/commands/publishDocument.test.ts b/packages/core/src/test/ssmDocument/commands/publishDocument.test.ts deleted file mode 100644 index c715d11ad1..0000000000 --- a/packages/core/src/test/ssmDocument/commands/publishDocument.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - CreateDocumentRequest, - CreateDocumentResult, - UpdateDocumentRequest, - UpdateDocumentResult, -} from '@aws-sdk/client-ssm' - -import assert from 'assert' -import * as sinon from 'sinon' -import * as vscode from 'vscode' - -import { DefaultSsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import * as publish from '../../../ssmDocument/commands/publishDocument' -import * as ssmUtils from '../../../ssmDocument/util/util' -import { - PublishSSMDocumentAction, - PublishSSMDocumentWizardResponse, -} from '../../../ssmDocument/wizards/publishDocumentWizard' -import { closeAllEditors } from '../../testUtil' -import { stub } from '../../utilities/stubber' -import { getTestWindow } from '../../shared/vscode/window' -import { SeverityLevel } from '../../shared/vscode/message' - -describe('publishDocument', async function () { - let wizardResponse: PublishSSMDocumentWizardResponse - let textDocument: vscode.TextDocument - let result: CreateDocumentResult | UpdateDocumentResult - - const fakeCreateRequest: CreateDocumentRequest = { - Content: 'foo', - DocumentFormat: 'JSON', - DocumentType: 'Automation', - Name: 'test', - } - const fakeUpdateRequest: UpdateDocumentRequest = { - Content: 'foo', - DocumentFormat: 'JSON', - DocumentVersion: '$LATEST', - Name: 'test', - } - - beforeEach(async function () { - wizardResponse = { - action: PublishSSMDocumentAction.QuickUpdate, - name: 'test', - documentType: 'Automation', - region: '', - } - result = { - DocumentDescription: { - Name: 'testName', - }, - } - textDocument = await vscode.workspace.openTextDocument({ content: 'foo', language: 'ssm-json' }) - }) - - afterEach(async function () { - sinon.restore() - await closeAllEditors() - }) - - describe('createDocument', async function () { - it('createDocument API returns successfully', async function () { - wizardResponse = { - action: PublishSSMDocumentAction.QuickCreate, - name: 'test', - documentType: 'Automation', - region: '', - } - - const client = stub(DefaultSsmDocumentClient, { regionCode: 'region-1' }) - client.createDocument.resolves(result) - - await publish.createDocument(wizardResponse, textDocument, client) - - assert(client.createDocument.calledOnce) - assert.deepStrictEqual(client.createDocument.args, [[fakeCreateRequest]]) - }) - - it('createDocument API failed', async function () { - const client = stub(DefaultSsmDocumentClient, { regionCode: 'region-1' }) - client.createDocument.rejects(new Error('Create Error')) - - await publish.createDocument(wizardResponse, textDocument, client) - const errorMessage = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Error)[0] - assert.ok(errorMessage) - errorMessage.assertMessage("Failed to create Systems Manager Document 'test'. \nCreate Error") - }) - }) - - describe('updateDocument', async function () { - it('updateDocument API returns successfully', async function () { - const client = stub(DefaultSsmDocumentClient, { regionCode: 'region-1' }) - client.updateDocument.resolves(result) - - sinon.stub(ssmUtils, 'showConfirmationMessage').resolves(false) - await publish.updateDocument(wizardResponse, textDocument, client) - - assert(client.updateDocument.calledOnce) - assert.deepStrictEqual(client.updateDocument.args, [[fakeUpdateRequest]]) - }) - - it('updateDocument API failed', async function () { - const client = stub(DefaultSsmDocumentClient, { regionCode: 'region-1' }) - client.updateDocument.rejects(new Error('Update Error')) - - sinon.stub(ssmUtils, 'showConfirmationMessage').resolves(false) - await publish.updateDocument(wizardResponse, textDocument, client) - const errorMessage = getTestWindow().shownMessages.filter((m) => m.severity === SeverityLevel.Error)[0] - assert.ok(errorMessage) - errorMessage.assertMessage("Failed to update Systems Manager Document 'test'. \nUpdate Error") - }) - }) -}) diff --git a/packages/core/src/test/ssmDocument/commands/updateDocumentVersion.test.ts b/packages/core/src/test/ssmDocument/commands/updateDocumentVersion.test.ts deleted file mode 100644 index da941e43d6..0000000000 --- a/packages/core/src/test/ssmDocument/commands/updateDocumentVersion.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { DocumentFormat, DocumentIdentifier, DocumentVersionInfo } from '@aws-sdk/client-ssm' -import * as sinon from 'sinon' -import assert from 'assert' - -import { updateDocumentVersion } from '../../../ssmDocument/commands/updateDocumentVersion' -import { DocumentItemNodeWriteable } from '../../../ssmDocument/explorer/documentItemNodeWriteable' - -import * as picker from '../../../shared/ui/picker' -import { FakeAwsContext } from '../../utilities/fakeAwsContext' -import { DefaultSsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import { stub } from '../../utilities/stubber' -import { RegistryItemNode } from '../../../ssmDocument/explorer/registryItemNode' - -describe('openDocumentItem', async function () { - afterEach(function () { - sinon.restore() - }) - - const fakeDoc: DocumentIdentifier = { - Name: 'testDocument', - DocumentFormat: DocumentFormat.JSON, - DocumentType: 'Command', - Owner: 'Amazon', - } - - const fakeAwsContext = new FakeAwsContext() - - const fakeRegion = 'us-east-1' - - const fakeSchemaList: DocumentVersionInfo[] = [ - { - Name: 'testDocument', - DocumentVersion: '1', - }, - { - Name: 'testDocument', - DocumentVersion: '2', - }, - ] - - const fakeVersionSelectionResult = { - label: '2', - description: 'default', - } - - it('updateDocumentVersion with correct name and version', async function () { - sinon.stub(picker, 'promptUser').onFirstCall().resolves(undefined) - sinon.stub(picker, 'verifySinglePickerOutput').onFirstCall().returns(fakeVersionSelectionResult) - sinon.stub(fakeAwsContext, 'getCredentialAccountId').returns('Amazon') - const ssmClient = stub(DefaultSsmDocumentClient, { regionCode: 'region-1' }) - const documentNode = new DocumentItemNodeWriteable( - fakeDoc, - ssmClient, - fakeRegion, - // jscpd:ignore-start - stub(RegistryItemNode, { - documentType: '', - regionCode: '', - registryName: '', - serviceId: undefined, - label: undefined, - id: undefined, - iconPath: undefined, - description: undefined, - resourceUri: undefined, - tooltip: undefined, - command: undefined, - collapsibleState: undefined, - contextValue: undefined, - accessibilityInformation: undefined, - checkboxState: undefined, - }) - // jscpd:ignore-end - ) - sinon.stub(documentNode, 'listSchemaVersion').resolves(fakeSchemaList) - const updateVersionStub = sinon.stub(documentNode, 'updateDocumentVersion') - await updateDocumentVersion(documentNode, fakeAwsContext) - assert.strictEqual(updateVersionStub.getCall(0).args[0], '2') - }) -}) diff --git a/packages/core/src/test/ssmDocument/explorer/documentItemNode.test.ts b/packages/core/src/test/ssmDocument/explorer/documentItemNode.test.ts deleted file mode 100644 index 3fa5dedef2..0000000000 --- a/packages/core/src/test/ssmDocument/explorer/documentItemNode.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { DocumentIdentifier } from '@aws-sdk/client-ssm' -import { DefaultSsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import { DocumentItemNode } from '../../../ssmDocument/explorer/documentItemNode' -import { stub } from '../../utilities/stubber' - -describe('DocumentItemNode', async function () { - let testNode: DocumentItemNode - const testDoc: DocumentIdentifier = { - Name: 'testDoc', - Owner: 'Amazon', - } - const fakeRegion = 'us-east-1' - - beforeEach(function () { - const client = stub(DefaultSsmDocumentClient, { regionCode: fakeRegion }) - testNode = new DocumentItemNode(testDoc, client, fakeRegion) - }) - - it('initializes name, owner, context value', async function () { - assert.strictEqual(testNode.label, testDoc.Name) - assert.strictEqual(testNode.documentName, testDoc.Name) - assert.strictEqual(testNode.documentOwner, testDoc.Owner) - assert.strictEqual(testNode.contextValue, 'awsDocumentItemNode') - }) - - it('has no children', async function () { - const childNode = await testNode.getChildren() - assert(childNode !== undefined) - assert.strictEqual(childNode.length, 0) - }) -}) diff --git a/packages/core/src/test/ssmDocument/explorer/documentTypeNode.test.ts b/packages/core/src/test/ssmDocument/explorer/documentTypeNode.test.ts deleted file mode 100644 index 771dff9a5d..0000000000 --- a/packages/core/src/test/ssmDocument/explorer/documentTypeNode.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as sinon from 'sinon' -import { DocumentTypeNode } from '../../../ssmDocument/explorer/documentTypeNode' - -import { assertNodeListOnlyHasErrorNode } from '../../utilities/explorerNodeAssertions' - -describe('DocumentTypeNode', function () { - let sandbox: sinon.SinonSandbox - - const fakeRegion = 'testRegion' - const expectedChildNodeNames = ['Owned by Amazon', 'Owned by me', 'Shared with me'] - const documentType = 'Automation' - - beforeEach(function () { - sandbox = sinon.createSandbox() - }) - - afterEach(function () { - sandbox.restore() - }) - - it('has correct child nodes', async function () { - const testNode: DocumentTypeNode = new DocumentTypeNode(fakeRegion, documentType) - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, expectedChildNodeNames.length) - for (const [index, child] of childNodes.entries()) { - assert.strictEqual(child.label, expectedChildNodeNames[index]) - } - }) - - it('handles error', async function () { - const testNode: DocumentTypeNode = new DocumentTypeNode(fakeRegion, documentType) - sandbox.stub(testNode, 'updateChildren').callsFake(() => { - throw new Error('Update child error') - }) - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasErrorNode(childNodes) - }) -}) diff --git a/packages/core/src/test/ssmDocument/explorer/registryItemNode.test.ts b/packages/core/src/test/ssmDocument/explorer/registryItemNode.test.ts deleted file mode 100644 index 2c44bc4531..0000000000 --- a/packages/core/src/test/ssmDocument/explorer/registryItemNode.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { RegistryItemNode } from '../../../ssmDocument/explorer/registryItemNode' -import { assertNodeListOnlyHasErrorNode } from '../../utilities/explorerNodeAssertions' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import { DefaultSsmDocumentClient } from '../../../shared/clients/ssmDocumentClient' -import { stub } from '../../utilities/stubber' - -describe('RegistryItemNode', function () { - const regionCode = 'testRegion' - const documentType = 'Automation' - const registryNames = ['Owned by me', 'Owned by Amazon', 'Shared with me'] - - it('handles error', async function () { - const client = stub(DefaultSsmDocumentClient, { regionCode }) - client.listDocuments.throws('error') - - const testNode = new RegistryItemNode(regionCode, 'Owned by me', documentType, client) - const childNodes = await testNode.getChildren() - - assertNodeListOnlyHasErrorNode(childNodes) - }) - - it('puts documents into right registry', async function () { - return Promise.all( - registryNames.map(async (registry) => { - let owner: string - if (registry === 'Owned by Amazon') { - owner = 'Amazon' - } else if (registry === 'Owned by me') { - owner = '123456789012' - } else { - owner = '987654321012' - } - - const client = stub(DefaultSsmDocumentClient, { regionCode }) - client.listDocuments.callsFake(() => { - return asyncGenerator([ - { - Name: `${owner}doc`, - Owner: `${owner}`, - DocumentType: `${documentType}`, - }, - ]) - }) - - const testNode: RegistryItemNode = new RegistryItemNode(regionCode, registry, documentType, client) - const childNode = await testNode.getChildren() - const expectedNodeNames = [`${owner}doc`] - - assert.strictEqual(childNode.length, expectedNodeNames.length) - for (const [index, node] of childNode.entries()) { - assert.strictEqual(node.label, expectedNodeNames[index]) - } - }) - ) - }) -}) diff --git a/packages/core/src/test/ssmDocument/explorer/ssmDocumentNode.test.ts b/packages/core/src/test/ssmDocument/explorer/ssmDocumentNode.test.ts deleted file mode 100644 index 29eb906b7a..0000000000 --- a/packages/core/src/test/ssmDocument/explorer/ssmDocumentNode.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { SsmDocumentNode } from '../../../ssmDocument/explorer/ssmDocumentNode' -import { DocumentTypeNode } from '../../../ssmDocument/explorer/documentTypeNode' -import { DEFAULT_TEST_REGION_CODE } from '../../shared/regions/testUtil' - -describe('SsmDocumentNode', function () { - it('always has 1 node: Automation Documents, if any child exists', async function () { - const node = new SsmDocumentNode(DEFAULT_TEST_REGION_CODE) - const childNodes = await node.getChildren() - const expectedChildNodeNames: string[] = ['Automation Documents'] - - assert.strictEqual(childNodes.length, expectedChildNodeNames.length, 'Unexpected child node length') - - for (const [index, node] of childNodes.entries()) { - assert.ok(node instanceof DocumentTypeNode, 'Expected child node to be RegistryItemNode') - assert.strictEqual(node.label, expectedChildNodeNames[index]) - } - }) -}) diff --git a/packages/core/src/test/ssmDocument/util/validateDocumentName.test.ts b/packages/core/src/test/ssmDocument/util/validateDocumentName.test.ts deleted file mode 100644 index 7aee7f6164..0000000000 --- a/packages/core/src/test/ssmDocument/util/validateDocumentName.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { validateDocumentName } from '../../../ssmDocument/util/validateDocumentName' - -describe('validateDocumenttName', function () { - const invalidErrors: { documentNames: string[]; error: string }[] = [ - { documentNames: [''], error: 'Document name cannot be empty' }, - { - documentNames: ['AmazonTestDocument', 'AWS-TestDocument'], - error: 'Document name cannot start with Amazon or AWS-', - }, - { - documentNames: ['til~de', 'quo"te', 'paren(theses', 'sla/sh', 'brac[et', '?mark', 'mone$'], - error: 'Document name contains invalid characters, only a-z, A-Z, 0-9, and _, -, and . are allowed', - }, - { - documentNames: ['a', 'aa', 'a'.repeat(129), 'a'.repeat(256)], - error: 'Document name length must be between 3 and 128 characters', - }, - ] - it('returns undefined for a valid document name', function () { - assert.strictEqual(validateDocumentName('Aaaaa'), undefined) - }) - - for (const invalid of invalidErrors) { - describe(invalid.error, () => { - for (const documentName of invalid.documentNames) { - it(documentName, () => { - assert.strictEqual(validateDocumentName(documentName), invalid.error) - }) - } - }) - } -}) diff --git a/packages/core/src/test/ssmDocument/wizards/publishDocumentWizard.test.ts b/packages/core/src/test/ssmDocument/wizards/publishDocumentWizard.test.ts deleted file mode 100644 index d861bc3adb..0000000000 --- a/packages/core/src/test/ssmDocument/wizards/publishDocumentWizard.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' -import { - PublishSSMDocumentWizard, - PublishSSMDocumentWizardResponse, -} from '../../../ssmDocument/wizards/publishDocumentWizard' - -describe('AppRunnerCodeRepositoryWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - const wizard = new PublishSSMDocumentWizard() - tester = await createWizardTester(wizard) - }) - - it('has 3 steps by default', function () { - tester.assertShowCount(3) - }) - - it('never prompts for document type and uses a default value instead', function () { - tester.documentType.assertDoesNotShow() - tester.documentType.assertValue('Automation') - }) - - it('prompts for region, action, and a document name', function () { - tester.region.assertShowFirst() - tester.action.assertShowSecond() - tester.name.assertShowThird() - }) - - it('skips the region prompt if provided a region', async function () { - const wizard = new PublishSSMDocumentWizard('us-west-2') - tester = await createWizardTester(wizard) - tester.region.assertDoesNotShow() - tester.assertShowCount(2) - }) -}) diff --git a/packages/core/src/test/stepFunctions/commands/getStateMachineDefinitionFromCfnTemplate.test.ts b/packages/core/src/test/stepFunctions/commands/getStateMachineDefinitionFromCfnTemplate.test.ts deleted file mode 100644 index 3a2cb4243f..0000000000 --- a/packages/core/src/test/stepFunctions/commands/getStateMachineDefinitionFromCfnTemplate.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as getCfnDefinition from '../../../stepFunctions/commands/visualizeStateMachine/getStateMachineDefinitionFromCfnTemplate' - -import { normalize } from '../../../shared/utilities/pathUtils' - -const unescapedJsonString: string = `{"StartAt":"Submit Job","States":{"Submit Job":{"Next":"Wait X Seconds","Type":"Task","Resource":"","ResultPath":"$.guid"},"Wait X Seconds":{"Type":"Wait","SecondsPath":"$.wait_time","Next":"Get Job Status"},"Get Job Status":{"Next":"Job Complete?","InputPath":"$.guid","Type":"Task","Resource":"","ResultPath":"$.status"},"Job Complete?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"FAILED","Next":"Job Failed"},{"Variable":"$.status","StringEquals":"SUCCEEDED","Next":"Get Final Job Status"}],"Default":"Wait X Seconds"},"Job Failed":{"Type":"Fail","Error":"DescribeJob returned FAILED","Cause":"AWS Batch Job Failed"},"Get Final Job Status":{"End":true,"InputPath":"$.guid","Type":"Task","Resource":""}},"TimeoutSeconds":30}` -const unescapedJsonString2: string = `{"StartAt":"Submit Job2","States":{"Submit Job2":{"Next":"Wait X Seconds","Type":"Task","Resource":"","ResultPath":"$.guid"},"Wait X Seconds":{"Type":"Wait","SecondsPath":"$.wait_time","Next":"Get Job Status"},"Get Job Status":{"Next":"Job Complete?","InputPath":"$.guid","Type":"Task","Resource":"","ResultPath":"$.status"},"Job Complete?":{"Type":"Choice","Choices":[{"Variable":"$.status","StringEquals":"FAILED","Next":"Job Failed"},{"Variable":"$.status","StringEquals":"SUCCEEDED","Next":"Get Final Job Status"}],"Default":"Wait X Seconds"},"Job Failed":{"Type":"Fail","Error":"DescribeJob returned FAILED","Cause":"AWS Batch Job Failed"},"Get Final Job Status":{"End":true,"InputPath":"$.guid","Type":"Task","Resource":""}},"TimeoutSeconds":30}` -const uniqueIdendifier = 'MyStateMachine' -const cdkOutPath = normalize(normalize(__dirname).replace('/dist', '') + '/resources') -const templatePath = normalize(`${cdkOutPath}/templateJsonTester.template.json`) - -describe('Get State Machine Definition from Cfn Template', function () { - it('get the correct cfn definition for state machine with correct inputs', function () { - let data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate(uniqueIdendifier, templatePath) - data = getCfnDefinition.toUnescapedAslJsonString(data) - assert.strictEqual(data, unescapedJsonString) - }) - - it('fetch the correct state machine with similar state machine names', function () { - let data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate('MyStateMachine2', templatePath) - data = getCfnDefinition.toUnescapedAslJsonString(data) - assert.strictEqual(data, unescapedJsonString2) - }) - - it('return empty string with wrong templatePath', function () { - const data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate( - uniqueIdendifier, - templatePath + '/wrongpath' - ) - assert.strictEqual(data, '') - }) - - it('return empty string with non-existing uniqueIdentifier', function () { - const data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate( - uniqueIdendifier + 'wrongidentifier', - templatePath - ) - assert.strictEqual(data, '') - }) - - it('escaped json string not containing any of refRegExp, refRegExp2, fnGetAttRegExp, and fnGetAttRegExp2', function () { - const unescapedTesterString = '{"StartAt":"GreetedWorld","States":{"GreetedWorld":{"Type":"Succeed"}}}' - let data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate('NoRefNoFnGetStateMachine', templatePath) - data = getCfnDefinition.toUnescapedAslJsonString(data) - assert.strictEqual(data, unescapedTesterString) - }) - - it('escaped json string containing refRegExp, refRegExp2, and fnGetAttRegExp', function () { - const unescapedTesterString = - '{"StartAt":"Convert to seconds","States":{"Convert to seconds":{"Next":"Publish message","Type":"Task","ResultPath":"$.waitSeconds","Resource":"randomString","Parameters":{"expression":"$.waitMilliseconds / 1000","expressionAttributeValues":{"$.waitMilliseconds.$":"$.waitMilliseconds"}}},"Publish message":{"End":true,"Type":"Task","ResultPath":"$.sns","Resource":"arn::states:::sns:publish","Parameters":{"TopicArn":"","Message.$":"$.message"}}}}' - let data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate('RefandFnStateMachine', templatePath) - data = getCfnDefinition.toUnescapedAslJsonString(data) - assert.strictEqual(data, unescapedTesterString) - }) - - it('escaped json string containing refRegExp, refRegExp2, and fnGetAttRegExp2', function () { - const unescapedTesterString = - '{"StartAt":"Convert to seconds","States":{"Convert to seconds":{"Next":"Publish message","Type":"Task","ResultPath":"$.waitSeconds","Resource":"","Parameters":{"expression":"$.waitMilliseconds / 1000","expressionAttributeValues":{"$.waitMilliseconds.$":"$.waitMilliseconds"}}},"Publish message":{"End":true,"Type":"Task","ResultPath":"$.sns","Resource":"arn::states:::sns:publish","Parameters":{"TopicArn":"","Message.$":"$.message"}}}}' - let data = getCfnDefinition.getStateMachineDefinitionFromCfnTemplate('Ref2andFnStateMachine', templatePath) - data = getCfnDefinition.toUnescapedAslJsonString(data) - assert.strictEqual(data, unescapedTesterString) - }) -}) diff --git a/packages/core/src/test/stepFunctions/commands/resources/templateJsonTester.template.json b/packages/core/src/test/stepFunctions/commands/resources/templateJsonTester.template.json deleted file mode 100644 index 4522fb0eb7..0000000000 --- a/packages/core/src/test/stepFunctions/commands/resources/templateJsonTester.template.json +++ /dev/null @@ -1,480 +0,0 @@ -{ - "Resources": { - "SubmitJobFB773A16": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterSubmitJob293E36D9" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/SubmitJob/Resource" - } - }, - "CheckJob5FFC1D6F": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterCheckJobE79CEC2E" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CheckJob/Resource" - } - }, - "SubmitJob2215322E1": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterSubmitJob2F34CA86A" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/SubmitJob2/Resource" - } - }, - "CheckJob25E896F6F": { - "Type": "AWS::StepFunctions::Activity", - "Properties": { - "Name": "templateJsonTesterCheckJob2A285E37A" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CheckJob2/Resource" - } - }, - "MyStateMachineRoleD59FFEBC": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine/Role/Resource" - } - }, - "MyStateMachine6C968CA5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachineRoleD59FFEBC", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Submit Job\",\"States\":{\"Submit Job\":{\"Next\":\"Wait X Seconds\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "SubmitJobFB773A16" - }, - "\",\"ResultPath\":\"$.guid\"},\"Wait X Seconds\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status\"},\"Get Job Status\":{\"Next\":\"Job Complete?\",\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status\"}],\"Default\":\"Wait X Seconds\"},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status\":{\"End\":true,\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\"}},\"TimeoutSeconds\":30}" - ] - ] - } - }, - "DependsOn": ["MyStateMachineRoleD59FFEBC"], - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine/Resource" - } - }, - "MyStateMachine26C968CA5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachineRoleD59FFEBC", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Submit Job2\",\"States\":{\"Submit Job2\":{\"Next\":\"Wait X Seconds\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "SubmitJobFB773A16" - }, - "\",\"ResultPath\":\"$.guid\"},\"Wait X Seconds\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status\"},\"Get Job Status\":{\"Next\":\"Job Complete?\",\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\",\"ResultPath\":\"$.status\"},\"Job Complete?\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status\"}],\"Default\":\"Wait X Seconds\"},\"Job Failed\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status\":{\"End\":true,\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob5FFC1D6F" - }, - "\"}},\"TimeoutSeconds\":30}" - ] - ] - } - }, - "DependsOn": ["MyStateMachineRoleD59FFEBC"], - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine/Resource" - } - }, - "NoRefNoFnGetStateMachine16DBBC93E": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachine1Role819380C3", "Arn"] - }, - "DefinitionString": "{\"StartAt\":\"GreetedWorld\",\"States\":{\"GreetedWorld\":{\"Type\":\"Succeed\"}}}" - }, - "DependsOn": ["MyStateMachine1Role819380C3"], - "Metadata": { - "aws:cdk:path": "aws-stepfunctions-integ/MyStateMachine1/Resource" - } - }, - "RefandFnStateMachine2E01A3A5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["StateMachineRoleB840431D", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Convert to seconds\",\"States\":{\"Convert to seconds\":{\"Next\":\"Publish message\",\"Type\":\"Task\",\"ResultPath\":\"$.waitSeconds\",\"Resource\":\"", - { - "Fn::GetAtt": ["Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn"] - }, - "randomString\",\"Parameters\":{\"expression\":\"$.waitMilliseconds / 1000\",\"expressionAttributeValues\":{\"$.waitMilliseconds.$\":\"$.waitMilliseconds\"}}},\"Publish message\":{\"End\":true,\"Type\":\"Task\",\"ResultPath\":\"$.sns\",\"Resource\":\"arn:", - { - "Ref": "AWS::Partition" - }, - ":states:::sns:publish\",\"Parameters\":{\"TopicArn\":\"", - { - "Ref": "cooltopic4736778A" - }, - "\",\"Message.$\":\"$.message\"}}}}" - ] - ] - } - }, - "DependsOn": ["StateMachineRoleDefaultPolicyDF1E6607", "StateMachineRoleB840431D"], - "Metadata": { - "aws:cdk:path": "aws-stepfunctions-integ/StateMachine/Resource" - } - }, - "Ref2andFnStateMachine2E01A3A5": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["StateMachineRoleB840431D", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Convert to seconds\",\"States\":{\"Convert to seconds\":{\"Next\":\"Publish message\",\"Type\":\"Task\",\"ResultPath\":\"$.waitSeconds\",\"Resource\":\"", - { - "Fn::GetAtt": ["Evalda2d1181604e4a4586941a6abd7fe42dF371675D", "Arn"] - }, - "\",\"Parameters\":{\"expression\":\"$.waitMilliseconds / 1000\",\"expressionAttributeValues\":{\"$.waitMilliseconds.$\":\"$.waitMilliseconds\"}}},\"Publish message\":{\"End\":true,\"Type\":\"Task\",\"ResultPath\":\"$.sns\",\"Resource\":\"arn:", - { - "Ref": "AWS::Partition" - }, - ":states:::sns:publish\",\"Parameters\":{\"TopicArn\":\"", - { - "Ref": "cooltopic4736778A" - }, - "\",\"Message.$\":\"$.message\"}}}}" - ] - ] - } - }, - "DependsOn": ["StateMachineRoleDefaultPolicyDF1E6607", "StateMachineRoleB840431D"], - "Metadata": { - "aws:cdk:path": "aws-stepfunctions-integ/StateMachine/Resource" - } - }, - - "MyStateMachine2Role9449E860": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": { - "Fn::Join": [ - "", - [ - "states.", - { - "Ref": "AWS::Region" - }, - ".amazonaws.com" - ] - ] - } - } - } - ], - "Version": "2012-10-17" - } - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine2/Role/Resource" - } - }, - "MyStateMachine268DAC43E": { - "Type": "AWS::StepFunctions::StateMachine", - "Properties": { - "RoleArn": { - "Fn::GetAtt": ["MyStateMachine2Role9449E860", "Arn"] - }, - "DefinitionString": { - "Fn::Join": [ - "", - [ - "{\"StartAt\":\"Submit Job2\",\"States\":{\"Submit Job2\":{\"Next\":\"Wait X Seconds2\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "SubmitJob2215322E1" - }, - "\",\"ResultPath\":\"$.guid\"},\"Wait X Seconds2\":{\"Type\":\"Wait\",\"SecondsPath\":\"$.wait_time\",\"Next\":\"Get Job Status2\"},\"Get Job Status2\":{\"Next\":\"Job Complete?2\",\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob25E896F6F" - }, - "\",\"ResultPath\":\"$.status\"},\"Job Complete?2\":{\"Type\":\"Choice\",\"Choices\":[{\"Variable\":\"$.status\",\"StringEquals\":\"FAILED\",\"Next\":\"Job Failed2\"},{\"Variable\":\"$.status\",\"StringEquals\":\"SUCCEEDED\",\"Next\":\"Get Final Job Status2\"}],\"Default\":\"Wait X Seconds2\"},\"Job Failed2\":{\"Type\":\"Fail\",\"Error\":\"DescribeJob returned FAILED\",\"Cause\":\"AWS Batch Job Failed\"},\"Get Final Job Status2\":{\"End\":true,\"InputPath\":\"$.guid\",\"Type\":\"Task\",\"Resource\":\"", - { - "Ref": "CheckJob25E896F6F" - }, - "\"}},\"TimeoutSeconds\":30}" - ] - ] - } - }, - "DependsOn": ["MyStateMachine2Role9449E860"], - "Metadata": { - "aws:cdk:path": "templateJsonTester/MyStateMachine2/Resource" - } - }, - "CDKMetadata": { - "Type": "AWS::CDK::Metadata", - "Properties": { - "Analytics": "v2:deflate64:H4sIAAAAAAAAE02OTwvCMAzFP4v3LttAEG/KwJuXKXguXceyP6006URKv7vtBPH0XvKSX1JDXR2h2p3kiwrVTWVQ1mkIN5ZqEo01xM4rFk1vWk3WO6WzT0GHjNZEkRcDsX723qjcIgjnZFbkdx79+bukSTwkJthgMXEuEmeRDrG+SjWg2cj/dRQoFwitnbcoa4xRGNtpGKlc6z3Uh/T8SIiF84Zx0dB+9QO3SjCU2QAAAA==" - }, - "Metadata": { - "aws:cdk:path": "templateJsonTester/CDKMetadata/Default" - }, - "Condition": "CDKMetadataAvailable" - } - }, - "Conditions": { - "CDKMetadataAvailable": { - "Fn::Or": [ - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "af-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-northeast-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-northeast-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-southeast-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ap-southeast-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "ca-central-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "cn-north-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "cn-northwest-1" - ] - } - ] - }, - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-central-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-north-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-2" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "eu-west-3" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "me-south-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "sa-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-east-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-east-2" - ] - } - ] - }, - { - "Fn::Or": [ - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-west-1" - ] - }, - { - "Fn::Equals": [ - { - "Ref": "AWS::Region" - }, - "us-west-2" - ] - } - ] - } - ] - } - } -} diff --git a/packages/core/src/test/stepFunctions/explorer/stepFunctionNodes.test.ts b/packages/core/src/test/stepFunctions/explorer/stepFunctionNodes.test.ts deleted file mode 100644 index 61b4f2b181..0000000000 --- a/packages/core/src/test/stepFunctions/explorer/stepFunctionNodes.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { - contextValueStateMachine, - StateMachineNode, - StepFunctionsNode, -} from '../../../stepFunctions/explorer/stepFunctionsNodes' -import { - assertNodeListOnlyHasErrorNode, - assertNodeListOnlyHasPlaceholderNode, -} from '../../utilities/explorerNodeAssertions' -import { asyncGenerator } from '../../../shared/utilities/collectionUtils' -import globals from '../../../shared/extensionGlobals' -import { StepFunctionsClient } from '../../../shared/clients/stepFunctions' -import { stub } from '../../utilities/stubber' - -const regionCode = 'someregioncode' - -describe('StepFunctionsNode', function () { - function createStatesClient(...stateMachineNames: string[]) { - const client = stub(StepFunctionsClient, { regionCode }) - client.listStateMachines.returns( - asyncGenerator( - stateMachineNames.map((name) => { - return { - name: name, - stateMachineArn: 'arn:aws:states:us-east-1:123412341234:stateMachine:' + name, - type: 'STANDARD', - creationDate: new globals.clock.Date(), - } - }) - ) - ) - - return client - } - - it('returns placeholder node if no children are present', async function () { - const node = new StepFunctionsNode(regionCode, createStatesClient()) - - assertNodeListOnlyHasPlaceholderNode(await node.getChildren()) - }) - - it('has StateMachineNode child nodes', async function () { - const client = createStatesClient('stateMachine1', 'stateMachine2') - const testNode = new StepFunctionsNode(regionCode, client) - const childNodes = await testNode.getChildren() - - assert.strictEqual(childNodes.length, 2, 'Unexpected child count') - - for (const node of childNodes) { - assert.ok(node instanceof StateMachineNode, 'Expected child node to be StateMachineNode') - assert.strictEqual( - node.contextValue, - contextValueStateMachine, - 'expected the node to have a State Machine contextValue' - ) - } - }) - - it('sorts child nodes', async function () { - const client = createStatesClient('c', 'a', 'b') - const testNode = new StepFunctionsNode(regionCode, client) - const childNodes = await testNode.getChildren() - - const actualChildOrder = childNodes.map((node) => node.label) - assert.deepStrictEqual(actualChildOrder, ['a', 'b', 'c'], 'Unexpected child sort order') - }) - - it('has an error node for a child if an error happens during loading', async function () { - const client = createStatesClient('state-machine') - client.listStateMachines.throws() - const testNode = new StepFunctionsNode(regionCode, client) - - assertNodeListOnlyHasErrorNode(await testNode.getChildren()) - }) -}) diff --git a/packages/core/src/test/stepFunctions/utils.test.ts b/packages/core/src/test/stepFunctions/utils.test.ts deleted file mode 100644 index 2616316b6f..0000000000 --- a/packages/core/src/test/stepFunctions/utils.test.ts +++ /dev/null @@ -1,121 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { isDocumentValid, isStepFunctionsRole } from '../../stepFunctions/utils' -import { IamRole } from '../../shared/clients/iam' - -describe('isStepFunctionsRole', function () { - const baseIamRole: IamRole = { - Path: '', - RoleName: '', - RoleId: 'myRole', - Arn: 'arn:aws:iam::123456789012:role/myRole', - CreateDate: new Date(), - } - - it('return true if the Step Functions service principal is in the AssumeRolePolicyDocument', function () { - const role: IamRole = { - ...baseIamRole, - AssumeRolePolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: ['states.amazonaws.com'], - }, - Action: ['sts:AssumeRole'], - }, - ], - }), - } - assert.ok(isStepFunctionsRole(role)) - }) - - it('returns false if the role does not have an AssumeRolePolicyDocument', function () { - assert.ok(!isStepFunctionsRole(baseIamRole)) - }) - - it("returns false if the AssumeRolePolicyDocument does not contain Step Functions' service principal", () => { - const role: IamRole = { - ...baseIamRole, - AssumeRolePolicyDocument: JSON.stringify({ - Version: '2012-10-17', - Statement: [ - { - Effect: 'Allow', - Principal: { - Service: ['lambda.amazonaws.com'], - }, - Action: ['sts:AssumeRole'], - }, - ], - }), - } - assert.ok(!isStepFunctionsRole(role)) - }) -}) - -describe('isDocumentValid', async function () { - it('returns true for valid ASL', async function () { - const aslText = ` - { - "StartAt": "FirstMatchState", - "States": { - "FirstMatchState": { - "Type": "Task", - "Resource": "arn:aws:lambda:us-west-2:000000000000:function:OnFirstMatch", - "End": true - } - } - } ` - - const textDocument = await vscode.workspace.openTextDocument({ language: 'asl' }) - - const isValid = await isDocumentValid(aslText, textDocument) - assert.ok(isValid) - }) - - it('returns true for ASL with invalid arns', async function () { - const aslText = ` - { - "StartAt": "FirstMatchState", - "States": { - "FirstMatchState": { - "Type": "Task", - "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:OnFirstMatch", - "End": true - } - } - } ` - - const textDocument = await vscode.workspace.openTextDocument({ language: 'asl' }) - - const isValid = await isDocumentValid(aslText, textDocument) - assert.ok(isValid) - }) - - it('returns false for invalid ASL', async function () { - const aslText = ` - { - "StartAt": "Does not exist", - "States": { - "FirstMatchState": { - "Type": "Task", - "Resource": "arn:aws:lambda:us-west-2:000000000000:function:OnFirstMatch", - "End": true - } - } - } ` - - const textDocument = await vscode.workspace.openTextDocument({ language: 'asl' }) - - const isValid = await isDocumentValid(aslText, textDocument) - - assert.ok(!isValid) - }) -}) diff --git a/packages/core/src/test/stepFunctions/wizards/createStateMachineWizard.test.ts b/packages/core/src/test/stepFunctions/wizards/createStateMachineWizard.test.ts deleted file mode 100644 index 8ddad069fd..0000000000 --- a/packages/core/src/test/stepFunctions/wizards/createStateMachineWizard.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { CreateStateMachineWizard } from '../../../stepFunctions/wizards/createStateMachineWizard' -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' - -describe('CreateStateMachineWizard', function () { - let tester: WizardTester - - beforeEach(async function () { - tester = await createWizardTester(new CreateStateMachineWizard()) - }) - - it('prompts for a file name and format', async function () { - tester.templateFile.assertShowFirst() - tester.templateFormat.assertShowSecond() - tester.assertShowCount(2) - }) -}) diff --git a/packages/core/src/test/stepFunctions/wizards/previewStateMachineCDKWizard.test.ts b/packages/core/src/test/stepFunctions/wizards/previewStateMachineCDKWizard.test.ts deleted file mode 100644 index 9281f73948..0000000000 --- a/packages/core/src/test/stepFunctions/wizards/previewStateMachineCDKWizard.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { ConstructTreeEntity } from '../../../awsService/cdk/explorer/tree/types' -import { - getStateMachines, - PreviewStateMachineCDKWizard, -} from '../../../stepFunctions/wizards/previewStateMachineCDKWizard' -import { createWizardTester, WizardTester } from '../../shared/wizards/wizardTestUtils' - -const stateMachine1: ConstructTreeEntity = { - id: 'MyStateMachine', - path: 'aws-tester/MyTopLevelNode/MyStateMachine', - children: { - Resource: { - id: 'Resource', - path: 'aws-tester/MyTopLevelNode/MyStateMachine/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::StateMachine', - }, - }, - }, -} - -const stateMachine2: ConstructTreeEntity = { - id: 'MyOtherStateMachine', - path: 'aws-tester/MyTopLevelNode/NestedNode/MyOtherStateMachine', - children: { - Resource: { - id: 'Resource', - path: 'aws-tester/MyTopLevelNode/NestedNode/MyOtherStateMachine/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::StateMachine', - }, - }, - }, -} - -const nested = { - id: 'NestedNode', - path: 'aws-tester/MyTopLevelNode', - children: { - MyOtherStateMachine: stateMachine2, - }, -} - -const root: ConstructTreeEntity = { - id: 'MyTopLevelNode', - path: 'aws-tester/MyTopLevelNode', - children: { - Resource: { - id: 'Resource', - path: 'aws-tester/MyTopLevelNode/Resource', - attributes: { - 'aws:cdk:cloudformation:type': 'AWS::StepFunctions::ConstructNode', - }, - }, - MyStateMachine: stateMachine1, - NestedNode: nested, - }, -} - -describe('getStateMachines', function () { - it('returns the input if it already is a state machine', function () { - const result = getStateMachines(stateMachine1) - assert.deepStrictEqual(result, [stateMachine1]) - }) - - it('lists all state machines from a construct (depth=1)', function () { - const result = getStateMachines(nested) - assert.deepStrictEqual(result, [stateMachine2]) - }) - - it('lists all state machines from a construct (depth=2)', function () { - const result = getStateMachines(root) - assert.strictEqual(result.length, 2) - assert.deepStrictEqual(result, [stateMachine1, stateMachine2]) - }) -}) - -describe('PreviewStateMachineCDKWizard', async function () { - let tester: WizardTester - - beforeEach(async function () { - tester = await createWizardTester(new PreviewStateMachineCDKWizard()) - }) - - it('prompts for location then a state machine', function () { - tester.location.assertShowFirst() - tester.resource.assertShowSecond() - tester.assertShowCount(2) - }) -}) diff --git a/packages/core/src/test/stepFunctions/wizards/publishStateMachineWizard.test.ts b/packages/core/src/test/stepFunctions/wizards/publishStateMachineWizard.test.ts deleted file mode 100644 index 5d4b50d25b..0000000000 --- a/packages/core/src/test/stepFunctions/wizards/publishStateMachineWizard.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - PublishStateMachineAction, - PublishStateMachineWizard, -} from '../../../stepFunctions/wizards/publishStateMachineWizard' -import { WizardTester, createWizardTester } from '../../shared/wizards/wizardTestUtils' - -describe('PublishStateMachineWizard', async function () { - let tester: WizardTester - - beforeEach(async function () { - tester = await createWizardTester(new PublishStateMachineWizard()) - }) - - it('only shows two steps until an action is selected', function () { - tester.assertShowCount(2) - }) - - it('always prompts for region and action', function () { - tester.region.assertShowFirst() - tester.publishAction.assertShowSecond() - }) - - it('prompts for state machine arn if updating an existing state machine', function () { - tester.region.applyInput('us-east-1') - tester.publishAction.applyInput(PublishStateMachineAction.QuickUpdate) - tester.createResponse.assertDoesNotShowAny() - tester.updateResponse.stateMachineArn.assertShow() - }) - - it('prompts for name and role arn if creating a new state machine', function () { - tester.region.applyInput('us-east-1') - tester.publishAction.applyInput(PublishStateMachineAction.QuickCreate) - tester.updateResponse.assertDoesNotShowAny() - tester.createResponse.name.assertShow() - tester.createResponse.roleArn.assertShow() - }) -}) diff --git a/packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts b/packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts deleted file mode 100644 index c16534abc4..0000000000 --- a/packages/core/src/test/stepFunctions/workflowStudio/workflowStudioApiHandler.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import sinon from 'sinon' -import { WorkflowStudioApiHandler } from '../../../stepFunctions/workflowStudio/workflowStudioApiHandler' -import { MockDocument } from '../../fake/fakeDocument' -import { - ApiAction, - Command, - MessageType, - WebviewContext, - WorkflowMode, -} from '../../../stepFunctions/workflowStudio/types' -import * as vscode from 'vscode' -import { assertTelemetry } from '../../testUtil' -import { StepFunctionsClient } from '../../../shared/clients/stepFunctions' -import { IamClient } from '../../../shared/clients/iam' - -describe('WorkflowStudioApiHandler', function () { - let postMessageStub: sinon.SinonStub - let apiHandler: WorkflowStudioApiHandler - let testState: sinon.SinonStub - - async function assertTestApiResponse(expectedResponse: any) { - await apiHandler.performApiCall({ - apiName: ApiAction.SFNTestState, - params: { - definition: '', - roleArn: '', - }, - requestId: 'test-request-id', - command: Command.API_CALL, - messageType: MessageType.REQUEST, - }) - - assertTelemetry('ui_click', { - elementId: 'stepfunctions_testState', - }) - assert(postMessageStub.firstCall.calledWithExactly(expectedResponse)) - } - - beforeEach(() => { - const panel = vscode.window.createWebviewPanel('WorkflowStudioMock', 'WorkflowStudioMockTitle', { - viewColumn: vscode.ViewColumn.Active, - preserveFocus: true, - }) - - postMessageStub = sinon.stub(panel.webview, 'postMessage') - - const context: WebviewContext = { - stateMachineName: '', - mode: WorkflowMode.Editable, - defaultTemplateName: '', - defaultTemplatePath: '', - disposables: [], - panel, - textDocument: new MockDocument('', 'foo', async () => true), - workSpacePath: '', - fileStates: {}, - loaderNotification: undefined, - fileId: '', - } - - const sfnClient = new StepFunctionsClient('us-east-1') - apiHandler = new WorkflowStudioApiHandler('us-east-1', context, { - sfn: sfnClient, - iam: new IamClient('us-east-1'), - }) - - testState = sinon.stub(sfnClient, 'testState') - }) - - it('should handle request and response for success', async function () { - testState.resolves({ - output: 'Test state output', - }) - - await assertTestApiResponse({ - messageType: MessageType.RESPONSE, - command: Command.API_CALL, - apiName: ApiAction.SFNTestState, - response: { - output: 'Test state output', - }, - requestId: 'test-request-id', - isSuccess: true, - }) - }) - - it('should handle request and response for error', async function () { - testState.rejects(new Error('Error testing state')) - - await assertTestApiResponse({ - messageType: MessageType.RESPONSE, - command: Command.API_CALL, - apiName: ApiAction.SFNTestState, - error: { - message: 'Error testing state', - name: 'Error', - stack: sinon.match.string, - }, - isSuccess: false, - requestId: 'test-request-id', - }) - }) -}) diff --git a/packages/core/src/test/templates/sam/samTemplateGenerator.test.ts b/packages/core/src/test/templates/sam/samTemplateGenerator.test.ts deleted file mode 100644 index 615cc752c7..0000000000 --- a/packages/core/src/test/templates/sam/samTemplateGenerator.test.ts +++ /dev/null @@ -1,190 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as path from 'path' -import fs from '../../../shared/fs/fs' -import { Architecture } from '../../../lambda/models/samLambdaRuntime' -import * as CloudFormation from '../../../shared/cloudformation/cloudformation' -import { makeTemporaryToolkitFolder } from '../../../shared/filesystemUtilities' -import { SamTemplateGenerator } from '../../../shared/templates/sam/samTemplateGenerator' - -describe('SamTemplateGenerator', function () { - const sampleCodeUriValue: string = 'sampleCodeUri' - const sampleFunctionHandlerValue: string = 'sampleFunctionHandler' - const sampleResourceNameValue: string = 'sampleResourceName' - const sampleMemorySize: number = 256 - const sampleTimeout: number = 321 - const sampleRuntimeValue: string = 'sampleRuntime' - const sampleArchitecture: Architecture = 'arm64' - const sampleEnvironment: CloudFormation.Environment = {} - let templateFilename: string - let tempFolder: string - - beforeEach(async function () { - tempFolder = await makeTemporaryToolkitFolder() - templateFilename = path.join(tempFolder, 'template.yml') - }) - - afterEach(async function () { - await fs.delete(tempFolder, { recursive: true }) - }) - - function makeMinimalTemplate(): SamTemplateGenerator { - return new SamTemplateGenerator() - .withCodeUri(sampleCodeUriValue) - .withFunctionHandler(sampleFunctionHandlerValue) - .withRuntime(sampleRuntimeValue) - .withResourceName(sampleResourceNameValue) - } - - it('Produces a minimal template', async function () { - await makeMinimalTemplate().generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Resources) - assert.notStrictEqual(Object.keys(template.Resources!).length, 0) - - const resource = template.Resources![sampleResourceNameValue] - assert.ok(resource) - assert.ok(CloudFormation.isZipLambdaResource(resource.Properties)) - assert.strictEqual(resource!.Properties!.CodeUri, sampleCodeUriValue) - assert.strictEqual(resource!.Properties!.Handler, sampleFunctionHandlerValue) - assert.strictEqual(resource!.Properties!.Runtime, sampleRuntimeValue) - }) - - it('Produces a template containing MemorySize', async function () { - await makeMinimalTemplate().withMemorySize(sampleMemorySize).generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Resources) - assert.notStrictEqual(Object.keys(template.Resources!).length, 0) - - const resource = template.Resources![sampleResourceNameValue] - assert.ok(resource) - assert.strictEqual(resource!.Properties!.MemorySize, sampleMemorySize) - }) - - it('Produces a template containing Timeout', async function () { - await makeMinimalTemplate().withTimeout(sampleTimeout).generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Resources) - assert.notStrictEqual(Object.keys(template.Resources!).length, 0) - - const resource = template.Resources![sampleResourceNameValue] - assert.ok(resource) - assert.strictEqual(resource!.Properties!.Timeout, sampleTimeout) - }) - - it('Produces a template containing Environment', async function () { - await makeMinimalTemplate().withEnvironment(sampleEnvironment).generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Resources) - assert.notStrictEqual(Object.keys(template.Resources!).length, 0) - - const resource = template.Resources![sampleResourceNameValue] - assert.ok(resource) - assert.deepStrictEqual(resource!.Properties!.Environment, sampleEnvironment) - }) - - it('Produces a template containing Architectures', async function () { - await makeMinimalTemplate().withArchitectures([sampleArchitecture]).generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Resources) - assert.notStrictEqual(Object.keys(template.Resources!).length, 0) - - const resource = template.Resources![sampleResourceNameValue] - assert.ok(resource) - assert.deepStrictEqual(resource!.Properties?.Architectures, [sampleArchitecture]) - }) - - it('Produces a template with a Globals section', async function () { - await makeMinimalTemplate() - .withGlobals({ - Function: { - Timeout: 5, - }, - }) - .generate(templateFilename) - - assert.strictEqual(await fs.exists(templateFilename), true) - - const template: CloudFormation.Template = await CloudFormation.load(templateFilename) - assert.ok(template.Globals, 'Expected loaded template to have a Globals section') - const globals = template.Globals - assert.notStrictEqual(Object.keys(globals).length, 0, 'Expected Template Globals to be not empty') - - const functionKey = 'Function' - const timeoutKey = 'Timeout' - assert.ok(globals[functionKey], 'Expected Globals to contain Function') - assert.ok(globals[functionKey]![timeoutKey], 'Expected Globals.Function to contain Timeout') - assert.strictEqual(globals[functionKey]![timeoutKey], 5, 'Unexpected Globals.Function.Timeout value') - }) - - it('errs if resource name is missing', async function () { - await assert.rejects( - new SamTemplateGenerator() - .withCodeUri(sampleCodeUriValue) - .withFunctionHandler(sampleFunctionHandlerValue) - .withRuntime(sampleRuntimeValue) - .generate(templateFilename), - new Error('Missing value: at least one of ResourceName or TemplateResources') - ) - - assert.strictEqual(await fs.exists(templateFilename), false) - }) - - it('errs if function handler is missing', async function () { - await assert.rejects( - new SamTemplateGenerator() - .withCodeUri(sampleCodeUriValue) - .withRuntime(sampleRuntimeValue) - .withResourceName(sampleResourceNameValue) - .generate(templateFilename), - new Error('Missing value: Handler') - ) - - assert.strictEqual(await fs.exists(templateFilename), false) - }) - - it('errs if code uri is missing', async function () { - await assert.rejects( - new SamTemplateGenerator() - .withFunctionHandler(sampleFunctionHandlerValue) - .withRuntime(sampleRuntimeValue) - .withResourceName(sampleResourceNameValue) - .generate(templateFilename), - new Error('Missing value: CodeUri') - ) - - assert.strictEqual(await fs.exists(templateFilename), false) - }) - - it('errs if runtime is missing', async function () { - await assert.rejects( - new SamTemplateGenerator() - .withCodeUri(sampleCodeUriValue) - .withFunctionHandler(sampleFunctionHandlerValue) - .withResourceName(sampleResourceNameValue) - .generate(templateFilename), - new Error('Missing value: Runtime') - ) - - assert.strictEqual(await fs.exists(templateFilename), false) - }) -}) diff --git a/packages/core/src/testE2E/codecatalyst/client.test.ts b/packages/core/src/testE2E/codecatalyst/client.test.ts deleted file mode 100644 index 84e9309120..0000000000 --- a/packages/core/src/testE2E/codecatalyst/client.test.ts +++ /dev/null @@ -1,639 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { - CodeCatalystClient, - CodeCatalystOrg, - CodeCatalystProject, - createClient as createCodeCatalystClient, - DevEnvironment, -} from '../../shared/clients/codecatalystClient' -import { getThisDevEnv, prepareDevEnvConnection } from '../../codecatalyst/model' -import { Auth } from '../../auth/auth' -import { CodeCatalystAuthenticationProvider } from '../../codecatalyst/auth' -import { CodeCatalystCommands, DevEnvironmentSettings, UpdateDevEnvironmentSettings } from '../../codecatalyst/commands' -import globals from '../../shared/extensionGlobals' -import { CodeCatalystCreateWebview, SourceResponse } from '../../codecatalyst/vue/create/backend' -import { waitUntil } from '../../shared/utilities/timeoutUtils' -import { AccessDeniedException } from '@aws-sdk/client-sso-oidc' -import { GetDevEnvironmentRequest, _InstanceType } from '@aws-sdk/client-codecatalyst' -import { getTestWindow } from '../../test/shared/vscode/window' -import { patchObject, registerAuthHook, skipTest, using } from '../../test/setupUtil' -import { isExtensionInstalled } from '../../shared/utilities/vsCodeUtils' -import { VSCODE_EXTENSION_ID } from '../../shared/extensions' -import { captureEventOnce } from '../../test/testUtil' -import { toStream } from '../../shared/utilities/collectionUtils' -import { toCollection } from '../../shared/utilities/asyncCollection' -import { getLogger } from '../../shared/logger' -import { isAwsError, ToolkitError } from '../../shared/errors' -import { - scopesCodeCatalyst, - createBuilderIdProfile, - isValidCodeCatalystConnection, - SsoConnection, -} from '../../auth/connection' -import { hasKey } from '../../shared/utilities/tsUtils' - -let spaceName: CodeCatalystOrg['name'] -let projectName: CodeCatalystProject['name'] - -/** - * Key Information: - * - * This test can be run locally under the following conditions: - * - * - You have previously configured BuilderId using the vscode extension. - * This is because this test uses BuilderId/SSO information - * that the extension would have naturally created in that process. - * - * - A Space already exists in your CodeCatalyst account. - * We cannot currently automate this due to space creation - * not being available in the CodeCatalyst API. - * - * TODO: Create a new space instead of using an existing one, - * if the api eventually allows us to do so. - * - * - You do not have a CodeCatalyst project with the same name - * as {@link projectName} and {@link isolatedProjectName}. - * These projects are modified when this test is run. - * - * The test project cannot be torn down. - * - * - The CodeCatalyst api does not provide a way to delete - * a Project. So the projects will exist in the Space unless - * deleted manually. - * - * TODO: Delete the project when the test is done, - * if the api eventually allows us to do so. - * - * This provides a best effort to test the api functionality. - * - * - There are more complexities involved in testing api functionality - * as if we were using the vscode UI, so this tests the underlying CC - * api functionliaty. - * - * There are different clients in this test that are used in different - * parts of the CC process. Choose the best client for your usecase. - * - * TODO: We can borrow some components from `src/test/globalSetup.test.ts` - * to be able to leverage `getTestWindow()` and then use that - * for UI actions testing. - * - * TODO: Create separate tests that spin up a vscode extensionHost - * integ tests, but using the ssh hostname that we get from - * {@link prepareDevEnvConnection}. - */ -describe('Test how this codebase uses the CodeCatalyst API', function () { - let client: CodeCatalystClient - let commands: CodeCatalystCommands - let webviewClient: CodeCatalystCreateWebview - - /** - * Stores all dev environments created during this test suite - */ - const testDevEnvironments: DevEnvironment[] = [] - - before(async function () { - await using(registerAuthHook('codecatalyst-test-account'), async () => { - // These instances all interact with the CC API at some point. - // Use the right one for your use case. - commands = await createTestCodeCatalystCommands() - client = await createTestCodeCatalystClient(Auth.instance) - webviewClient = new CodeCatalystCreateWebview(client, CodeCatalystCommands.declared, () => {}) - - spaceName = (await getCurrentUsersSpace()).name - projectName = (await tryCreateTestProject(spaceName)).name - }) - }) - - beforeEach(function () { - registerAuthHook('codecatalyst-test-account') - }) - - describe('Dev Environment functionality', function () { - let defaultDevEnv: DevEnvironment - let defaultDevEnvSettings: DevEnvironmentSettings - - const isolatedProjectName = 'aws-toolkit-integ-test-project-isolated' - - before(async function () { - // Attempt to reap older dev environments associated with the test projects - await Promise.all([deleteOldDevEnvironments(projectName), deleteOldDevEnvironments(isolatedProjectName)]) - - defaultDevEnvSettings = buildDevEnvSettings() - defaultDevEnv = await createDevEnv(defaultDevEnvSettings) - }) - - after(async function () { - await deleteTestDevEnvs() - }) - - beforeEach(function () { - getTestWindow().onDidShowMessage((m) => { - const updateSshConfigItem = m.items.find((i) => i.title === 'Update SSH config') - updateSshConfigItem?.select() - }) - }) - - describe('getThisDevEnv', function () { - let ccAuth: CodeCatalystAuthenticationProvider - - before(function () { - ccAuth = CodeCatalystAuthenticationProvider.fromContext(globals.context) - }) - - it('returns `undefined` if not in a dev environment', async function () { - const result = await getThisDevEnv(ccAuth) - assert.strictEqual(result, undefined) - }) - - it('returns an error if in a dev environment but the API calls fail', async function () { - const oldEnv = { ...process.env } - process.env['__DEV_ENVIRONMENT_ID'] = defaultDevEnv.id - process.env['__DEV_ENVIRONMENT_SPACE_NAME'] = defaultDevEnv.org.name - process.env['__DEV_ENVIRONMENT_PROJECT_NAME'] = 'wrong-project-name' - - try { - const result = await getThisDevEnv(ccAuth) - assert.ok(result?.isErr(), 'Expected an error to be returned') - } finally { - process.env = oldEnv - } - }) - }) - - it('creates an empty Dev Environment', async function () { - const emptyDevEnvSettings = buildDevEnvSettings() - const emptyDevEnv = await createDevEnvFromWebview(emptyDevEnvSettings, { - type: 'none', - selectedSpace: { name: spaceName }, - selectedProject: { name: projectName, org: { name: spaceName }, type: 'project' }, - }) - - assert.strictEqual(emptyDevEnv.project.name, projectName) - assert.strictEqual(emptyDevEnv.org.name, spaceName) - }) - - it('creates a Dev Environment with a different instance type + storage', async function () { - const differentDevEnvSettings = buildDevEnvSettings('dev.standard1.medium', { sizeInGiB: 32 }) - - const actualDevEnv = await createDevEnvFromWebview(differentDevEnvSettings, { - type: 'none', - selectedSpace: { name: spaceName }, - selectedProject: { name: projectName, org: { name: spaceName }, type: 'project' }, - }) - - assert.strictEqual(actualDevEnv.project.name, projectName) - assert.strictEqual(actualDevEnv.org.name, spaceName) - assert.strictEqual(actualDevEnv.alias, differentDevEnvSettings.alias) - assert.strictEqual(actualDevEnv.instanceType, 'dev.standard1.medium') - assert.strictEqual(actualDevEnv.persistentStorage.sizeInGiB, 32) - }) - - it.skip('creates a Dev Environment using an existing branch', async function () { - // TODO: Write this test now that an API is available in the SDK: - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CodeCatalyst.html#createSourceRepository-property - // https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CodeCatalyst.html#createSourceRepositoryBranch-property - // For now, the repository is manually created in the account. - }) - - it('prompts to install the ssh extension if not available', async function () { - if (isExtensionInstalled(VSCODE_EXTENSION_ID.remotessh)) { - skipTest(this, 'remote ssh already installed') - } - - await assert.rejects( - prepareDevEnvConnection(client, { ...defaultDevEnv }), - /Remote SSH extension not installed/ - ) - const notification = await getTestWindow().waitForMessage( - /Connecting to Dev Environment requires the Remote SSH extension/ - ) - - // Normally selecting `Install...` would open up the extensions tab - // But for this test we actually do want to install the extension programtically - const executeCommand = vscode.commands.executeCommand - const patchInstall = patchObject(vscode.commands, 'executeCommand', (command, ...args) => { - if (command === 'extension.open') { - patchInstall.dispose() - return executeCommand('workbench.extensions.installExtension', ...args) - } else { - return executeCommand(command, ...args) - } - }) - - notification.selectItem('Install...') - await captureEventOnce(vscode.extensions.onDidChange, 60_000).catch(() => { - throw new Error('Timed out waiting to install remote SSH extension') - }) - }) - - it('connects to a running Dev Environment', async function () { - // Get necessary objects to run the ssh command. - const { SessionProcess, hostname, sshPath } = await prepareDevEnvConnection(client, { - ...defaultDevEnv, - }) - - // Through the actual ssh connection, run 'ls' command in the dev env. - const lsOutput = (await new SessionProcess(sshPath, [hostname, 'ls', '/projects']).run()).stdout - - // Assert that a certain file exists in the dev env. - const expectedFile = 'devfile.yaml' // File automatically created by CC - assert(lsOutput.split('\n').includes(expectedFile)) - }) - - it('has environment variables that identify the Dev Environment', async function () { - const { SessionProcess, hostname, sshPath } = await prepareDevEnvConnection(client, { - ...defaultDevEnv, - }) - - const output = ( - await new SessionProcess(sshPath, [hostname, 'printenv', '|', 'grep', '__DEV_ENVIRONMENT']).run() - ).stdout - - const parsed: Record = {} - for (const line of output.split('\n')) { - const [_, key, value] = line.match(/(.*)=(.*)/) ?? [] - if (!key || !value) { - throw new Error(`Failed to parse environment variables from dev env`) - } - - parsed[key] = value - } - - assert.deepStrictEqual(parsed['__DEV_ENVIRONMENT_ID'], defaultDevEnv.id) - assert.deepStrictEqual(parsed['__DEV_ENVIRONMENT_PROJECT_NAME'], defaultDevEnv.project.name) - assert.deepStrictEqual(parsed['__DEV_ENVIRONMENT_ORGANIZATION_NAME'], defaultDevEnv.org.name) - }) - - /** - * IMPORTANT: This test takes a while to run. Stopping a Dev Env takes time. - */ - it('stops a running Dev Environment, then starts it up again', async function () { - // Ensure existing Dev Env is running - const devEnvData = { - spaceName: defaultDevEnv.org.name, - projectName: defaultDevEnv.project.name, - id: defaultDevEnv.id, - } - await waitUntilDevEnvStatus(devEnvData, ['RUNNING']) - - // Stop Dev Env - await client.stopDevEnvironment(devEnvData) - await waitUntilDevEnvStatus(devEnvData, ['STOPPED']) - - // Start Dev Env - await prepareDevEnvConnection(client, { ...defaultDevEnv }) - await waitUntilDevEnvStatus(devEnvData, ['RUNNING']) - }) - - it('updates the properties of an existing dev environment', async function () { - const newDevEnvSettings = { - alias: createAlias(), - instanceType: 'dev.standard1.medium', - } satisfies UpdateDevEnvironmentSettings - - // Ensure current properties do not equal the updated properties - assert.notStrictEqual(defaultDevEnv.alias, newDevEnvSettings.alias) - assert.notStrictEqual(defaultDevEnv.instanceType, newDevEnvSettings.instanceType) - - // Sanity Check due to: https://issues.amazon.com/Velox-Bug-42 - assert.ok(defaultDevEnv.id, 'Dev Env ID should not be empty.') - - // Update dev env - const updatedDevEnv = await commands.updateDevEnv(defaultDevEnv, newDevEnvSettings) - - // Ensure our update succeeded by checking for new properties - assert.strictEqual(updatedDevEnv?.alias, newDevEnvSettings.alias) - assert.strictEqual(updatedDevEnv?.instanceType, newDevEnvSettings.instanceType) - }) - - it('creates multiple dev envs, lists them, then deletes them', async function () { - // We want a clean project for this test due to additional dev envs - // from previous tests polluting the 'list dev env' output. - await tryCreateTestProject(spaceName, isolatedProjectName) - await deleteTestDevEnvs(isolatedProjectName) - - // Create multiple Dev Envs - const projectSource: SourceResponse = { - type: 'none', - selectedSpace: { name: spaceName }, - selectedProject: { name: isolatedProjectName, org: { name: spaceName }, type: 'project' }, - } - const createdDevEnvs = await Promise.all([ - createDevEnvFromWebview(buildDevEnvSettings(), projectSource), - createDevEnvFromWebview(buildDevEnvSettings(), projectSource), - ]) - assert.strictEqual(createdDevEnvs.length, 2) - - // List all Dev Envs - const listedDevEnvs = await getAllTestDevEnvironments(isolatedProjectName) - - const actualDevEnvIds = listedDevEnvs.map((d) => d.id) - const expectedDevEnvIds = createdDevEnvs.map((d) => d.id) - assert.deepStrictEqual( - actualDevEnvIds.sort((a, b) => a.localeCompare(b)), - expectedDevEnvIds.sort((a, b) => a.localeCompare(b)), - 'The list dev envs dont match' - ) - - // Delete all Dev Envs - await Promise.all(listedDevEnvs.map((devEnv) => commands.deleteDevEnv(devEnv))) - const remainingDevEnvs = await getAllTestDevEnvironments(isolatedProjectName) - if (remainingDevEnvs.length > 0) { - // Dev Envs may still be returned if they are still in the process of being deleted. - // Just ensure they are in the process or fully deleted. - for (const devEnv of remainingDevEnvs) { - assert.ok(['DELETING', 'DELETED'].includes(devEnv.status), 'Dev Env was not successfully deleted') - } - } - }) - - it('lists all spaces for the given user', async function () { - const spaces = await client.listSpaces().flatten().promise() - assert.ok(spaces.find((space) => space.name === spaceName)) - }) - - // TODO: Re-add this test when the CoCa SDK offers a way to delete projects. - it.skip('lists all projects for the given user', async function () { - // Create lots of projects - const ephemeralProjectNames = [] - for (let index = 0; index < 30; index++) { - ephemeralProjectNames.push(`ephemeral-project-${index}`) - } - await Promise.all(ephemeralProjectNames.map((name) => tryCreateTestProject(spaceName, name))) - await client.listProjects({ spaceName }).flatten().promise() - }) - - function buildDevEnvSettings( - instanceType: DevEnvironmentSettings['instanceType'] = 'dev.standard1.small', - persistentStorage: DevEnvironmentSettings['persistentStorage'] = { sizeInGiB: 16 } - ): DevEnvironmentSettings { - return { - instanceType, - persistentStorage, - alias: createAlias(), - inactivityTimeoutMinutes: 15, - } - } - - /** Creates a unique alias for a dev environment. */ - function createAlias(): NonNullable { - return `test-alias-${Date.now() + Math.random()}` - } - - async function createDevEnv(devEnvSettings: DevEnvironmentSettings): Promise { - const env = await client.createDevEnvironment({ - spaceName, - projectName, - ides: [{ name: 'VSCode' }], - ...devEnvSettings, - }) - - testDevEnvironments.push(env) - return env - } - - async function createDevEnvFromWebview( - ...args: Parameters - ): Promise { - const env = await webviewClient.createDevEnvOfType(...args) - testDevEnvironments.push(env) - - return env - } - }) - - /** - * Creates a CodeCatalyst commands instance. - * - * This holds the underlying functions that are triggered - * when the user interacts with the vscode UI model. - * - * The goal is to test using this object as much as we can, - * so we can test as close as we can to the user experience. - * This can have interactions with vscode UI model. - */ - async function createTestCodeCatalystCommands(): Promise { - const ccAuthProvider = CodeCatalystAuthenticationProvider.fromContext(globals.context) - return new CodeCatalystCommands(ccAuthProvider) - } - - /** - * Creates a CodeCatalyst api client. - */ - async function createTestCodeCatalystClient(auth: Auth): Promise { - const conn = await useCodeCatalystSsoConnection(auth) - return await createCodeCatalystClient(conn, undefined, undefined) - } - - /** - * Returns the existing Sso connection that has been - * verified to work with CodeCatalyst. - * - * A new connection will be created if one is not available. - * The returned connection is set as the active connection if not already. - */ - async function useCodeCatalystSsoConnection(auth: Auth): Promise { - const builderIdSsoConnection = (await auth.listConnections()).find(isValidCodeCatalystConnection) - const conn = builderIdSsoConnection ?? (await auth.createConnection(createBuilderIdProfile(scopesCodeCatalyst))) - - return auth.useConnection(conn) - } - - /** - * Creates a specific CC Project if it does not already exist in the space. - * @param projectName The name of the test project. Be careful changing this if you plan to run locally. - */ - async function tryCreateTestProject( - spaceName: CodeCatalystOrg['name'], - projectName: CodeCatalystProject['name'] = 'aws-vscode-toolkit-integ-test-project' - ): Promise { - try { - return await client.createProject({ - spaceName, - displayName: projectName, - description: 'This project is autogenerated by the AWS Toolkit VSCode Integ Test.', - }) - } catch (e) { - if ((isAwsError(e) || e instanceof ToolkitError) && e.code === 'ConflictException') { - getLogger().debug(`Tried to create test project but it already exists: ${spaceName}/${projectName}`) - return client.getProject({ - spaceName, - name: projectName, - }) - } - - throw e - } - } - - /** - * Gets the first CodeCatalyst space it finds. - * - * The intention for this is to require no setup of a Space by the - * user if they want to run this test locally. - */ - async function getCurrentUsersSpace(): Promise { - const firstPageOfSpaces = (await client.listSpaces().iterator().next()).value - - if (firstPageOfSpaces === undefined || firstPageOfSpaces.length === 0) { - // Space must already exist due to CC not providing an api to create a space. - throw new Error( - 'No spaces found in account. A CodeCatalyst Space must be created manually before running this test.' - ) - } - - const firstSpaceFound = firstPageOfSpaces[0] - return firstSpaceFound - } - - /** - * Deletes any existing dev envs created by this test suite, resolves only - * once all are fully deleted on the server side. Can optionally filter per-project. - */ - async function deleteTestDevEnvs(projectName?: CodeCatalystProject['name']): Promise { - const environments = await getAllTestDevEnvironments(projectName) - - // Deleting a dev env that has already been deleted will throw an error. - // We need to be selective about which dev envs get explicitly deleted. - const devEnvsToDelete = environments - .filter((devEnv) => !['DELETING', 'DELETED'].includes(devEnv.status)) - .map(async (devEnv) => deleteDevEnv(devEnv.project.name, devEnv.id)) - - // These dev envs are already in the process of being deleted, so we just need to wait until they are fully deleted. - const devEnvsToWaitForDeletion = environments - .filter((devEnv) => ['DELETING'].includes(devEnv.status)) - .map(async (devEnv) => waitUntilDevEnvDeleted(devEnv.project.name, devEnv.id)) - - await Promise.all([...devEnvsToDelete, ...devEnvsToWaitForDeletion]) - } - - /** - * Deletes all dev environments associated with the project that are older than one day - */ - async function deleteOldDevEnvironments(projectName: CodeCatalystProject['name']): Promise { - const environments = await getAllDevEnvs(projectName) - - // Deleting a dev env that has already been deleted will throw an error. - // We need to be selective about which dev envs get explicitly deleted. - const oneDayInMs = 60 * 60 * 24 * 1000 - await Promise.all( - environments - .filter((devEnv) => Date.now() - devEnv.lastUpdatedTime.getTime() >= oneDayInMs) - .filter((devEnv) => !['DELETING', 'DELETED'].includes(devEnv.status)) - .map((devEnv) => - deleteDevEnv(devEnv.project.name, devEnv.id).catch((err) => { - getLogger().warn(`tests: failed to deleted old dev environment "${devEnv.id}": %s`, err) - }) - ) - ) - } - - async function getAllDevEnvs(projectName: CodeCatalystProject['name']): Promise { - const currentDevEnvs = await client - .listDevEnvironments({ name: projectName, org: { name: spaceName }, type: 'project' }) - .flatten() - .promise() - return currentDevEnvs - } - - function getAllTestDevEnvironments(projectName?: CodeCatalystProject['name']) { - const projects = Array.from( - testDevEnvironments.map((env) => env.project.name).reduce((set, name) => set.add(name), new Set()) - ) - - const currentDevEnvs = toCollection(() => toStream(projects.map((name) => getAllDevEnvs(name)))) - .flatten() - .filter((env) => !!testDevEnvironments.some((other) => other.id === env.id)) - .filter((env) => !projectName || env.project.name === projectName) - - return currentDevEnvs.promise() - } - - /** - * Deletes a specific dev env and only returns when it is fully - * deleted on the server - */ - async function deleteDevEnv(projectName: CodeCatalystProject['name'], id: DevEnvironment['id']): Promise { - await commands.deleteDevEnv({ - org: { name: spaceName }, - project: { name: projectName }, - id: id, - }) - - return waitUntilDevEnvDeleted(projectName, id) - } - - /** - * waits until the dev env is fully deleted in the server. - * - * This function is needed specifically for a dev env in the deletion - * process since an error will be thrown when we try to get the dev environment - * but it has been successfully deleted. - */ - async function waitUntilDevEnvDeleted(projectName: CodeCatalystProject['name'], id: DevEnvironment['id']) { - const result = await waitUntil( - async function () { - let devEnvBeingDeleted: DevEnvironment - try { - devEnvBeingDeleted = await client.getDevEnvironment({ spaceName, projectName, id }) - } catch (e: any) { - // Cannot use isAwsError() because the client actually returns a regular Error - // with a 'code' property for this call. - if (hasKey(e, 'code') && e.code === AccessDeniedException.name) { - // This error is thrown because the dev env does not exist anymore - // on the server. The name doesn't make it obvious IMO. - return true - } - throw e - } - return devEnvBeingDeleted.status === 'DELETED' - }, - { - interval: 1000, - timeout: 60000, - } - ) - if (!result) { - throw new Error(`Dev env ${id} did not transition to DELETED status before timeout.`) - } - } - - /** - * Asserts that a given dev env has one of the given statuses, it will fail - * if the timeout is reached. - */ - async function waitUntilDevEnvStatus( - devEnv: GetDevEnvironmentRequest, - status: DevEnvironment['status'][] - ): Promise { - const result = await waitUntil( - async function () { - if (!devEnv.spaceName || !devEnv.projectName) { - return false - } - const devEnvData = await client.getDevEnvironment({ - spaceName: devEnv.spaceName, - projectName: devEnv.projectName, - id: devEnv.id, - }) - return status.includes(devEnvData.status) - }, - { - interval: 1000, - timeout: 120000, - } - ) - - if (!result) { - throw new Error( - `Dev env ${devEnv.id} did not transition to one of the following statuses before timeout: "${status}"` - ) - } - } -}) diff --git a/packages/core/src/testInteg/appBuilder/serverlessLand/main.test.ts b/packages/core/src/testInteg/appBuilder/serverlessLand/main.test.ts deleted file mode 100644 index 6ca07ca691..0000000000 --- a/packages/core/src/testInteg/appBuilder/serverlessLand/main.test.ts +++ /dev/null @@ -1,148 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import path from 'path' -import { PrompterTester } from '../../../test/shared/wizards/prompterTester' -import { describe } from 'mocha' -import { ProjectMetadata } from '../../../awsService/appBuilder/serverlessLand/metadataManager' -import fs from '../../../shared/fs/fs' -import { AppBuilderRootNode } from '../../../awsService/appBuilder/explorer/nodes/rootNode' -import * as sinon from 'sinon' -import { AppNode } from '../../../awsService/appBuilder/explorer/nodes/appNode' -import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode' -import { getTestWindow } from '../../../test/shared/vscode/window' - -describe('Serverless Land Integration', async () => { - const metadataPath = path.resolve( - __dirname, - '../../../../../src/awsService/appBuilder/serverlessLand/metadata.json' - ) - const metadataContent = await fs.readFileText(metadataPath) - const parseMetadata = JSON.parse(metadataContent) as ProjectMetadata - const workspaceFolder = vscode.workspace.workspaceFolders![0] - const projectFolder = 'my-project-from-Serverless-Land' - - // Additional layer of describe() needed here to prevent side effect from - // `sandbox.spy(AppBuilderRootNode.instance)` - describe('Happy Path', async () => { - let rootNode: sinon.SinonSpiedInstance - let sandbox: sinon.SinonSandbox - - beforeEach(async () => { - sandbox = sinon.createSandbox() - rootNode = sandbox.spy(AppBuilderRootNode.instance) - await fs.delete(path.join(workspaceFolder.uri.fsPath, projectFolder), { recursive: true }) - }) - - afterEach(async () => { - await fs.delete(path.join(workspaceFolder.uri.fsPath, projectFolder), { recursive: true }) - sandbox.restore() - }) - - it('creates project from Serverless Land integration', async () => { - /** - * Selection: - * - pattern : [Select] 2 apigw-rest-api-lambda-sam - * - runtime : [Select] 3 dotnet - * - iac : [Select] 1 sam - * - location : [Input] From TestFolder.uri - * - name : [Input] "my-project-from-Serverless-Land" - */ - - const testWindow = getTestWindow() - const prompterTester = PrompterTester.init({ testWindow }) - .handleQuickPick('Select a Pattern for your application', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - Object.entries(parseMetadata.patterns).map(([key, pattern]) => { - options.find((option) => option.label === key && option.detail === pattern.description) - }) - quickPick.acceptItem(quickPick.items[1]) - }) - .handleQuickPick('Select Runtime', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - assert.strictEqual(options[0].label, 'python') - assert.strictEqual(options[1].label, 'javascript') - assert.strictEqual(options[2].label, 'java') - assert.strictEqual(options[3].label, 'dotnet') - quickPick.acceptItem(options[3]) - }) - .handleQuickPick('Select IaC', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - assert.strictEqual(options[0].label, 'sam') - quickPick.acceptItem(options[0]) - }) - .handleQuickPick('Select Project Location', async (quickPick) => { - await quickPick.untilReady() - const options = quickPick.items - assert.strictEqual(options[0].label, '$(folder) workspaceFolder') - assert.strictEqual(options[1].label, '$(folder-opened) Select a folder...') - quickPick.acceptItem(options[0]) - }) - .handleInputBox('Enter Project Name', (inputBox) => { - inputBox.acceptValue('my-project-from-Serverless-Land') - }) - .build() - - // Validate that the README.md is shown. - testWindow.onDidChangeActiveTextEditor((editors) => { - assert(editors) - const readMe = path.join(workspaceFolder.uri.fsPath, projectFolder, 'README.md') - assert.strictEqual(editors?.document.fileName, readMe) - }) - - await vscode.commands.executeCommand('aws.toolkit.lambda.createServerlessLandProject') - - // projectNodes set from previous step - - const projectNode = await rootNode - .getChildren() - .then( - (children) => - children.find( - (node) => - node instanceof AppNode && - node.label === 'workspaceFolder/my-project-from-Serverless-Land' - ) as AppNode | undefined - ) - - assert.ok(projectNode, 'Expect Serverless Land project node in Application Builder') - - // Check App Builder resources - const resourceNodes = await projectNode.getChildren() - assert.strictEqual(resourceNodes.length, 1) - assert.ok(resourceNodes[0] instanceof ResourceNode) - - // Validate Lambda resource configuration - const lambdaResource = resourceNodes[0] as ResourceNode - assert.strictEqual(lambdaResource.resource.resource.Type, 'AWS::Serverless::Function') - assert( - 'Runtime' in lambdaResource.resource.resource && lambdaResource.resource.resource.Runtime === 'dotnet8' - ) - assert.strictEqual(lambdaResource.resource.resource.Id, 'HelloWorldFunction') - assert('Events' in lambdaResource.resource.resource) - assert.deepStrictEqual(lambdaResource.resource.resource.Events, [ - { - Id: 'HelloWorld', - Type: 'Api', - Path: '/hello', - Method: 'get', - }, - ]) - assert('Environment' in lambdaResource.resource.resource) - assert.deepStrictEqual(lambdaResource.resource.resource.Environment, { - Variables: { - PARAM1: 'VALUE', - }, - }) - - prompterTester.assertCallAll() - }) - }) -}) diff --git a/packages/core/src/testInteg/appBuilder/sidebar/appBuilderNode.test.ts b/packages/core/src/testInteg/appBuilder/sidebar/appBuilderNode.test.ts deleted file mode 100644 index cd9416f015..0000000000 --- a/packages/core/src/testInteg/appBuilder/sidebar/appBuilderNode.test.ts +++ /dev/null @@ -1,194 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import { AppBuilderRootNode } from '../../../awsService/appBuilder/explorer/nodes/rootNode' -import { detectSamProjects } from '../../../awsService/appBuilder/explorer/detectSamProjects' -import { SamAppLocation } from '../../../awsService/appBuilder/explorer/samProject' -import { AppNode } from '../../../awsService/appBuilder/explorer/nodes/appNode' -import { ResourceNode } from '../../../awsService/appBuilder/explorer/nodes/resourceNode' -import * as sinon from 'sinon' -import { writeSamconfigGlobal, SamConfig } from '../../../shared/sam/config' -import { globals, sleep } from '../../../shared' -import path from 'path' - -describe('Application Builder', async () => { - let rootNode: sinon.SinonSpiedInstance - let projects: SamAppLocation[] - let originalWalkThroughState: boolean - let projectNodes: any[] - let sandbox: sinon.SinonSandbox - - before(async () => { - sandbox = sinon.createSandbox() - // Set the workspace to the testFixtures folder to avoid side effects from other tests. - sandbox.stub(vscode.workspace, 'workspaceFolders').value([ - { - index: 0, - name: 'workspaceFolder', - uri: vscode.Uri.file(path.join(__dirname, '../../../../src/testFixtures/workspaceFolder')), - }, - ]) - rootNode = sandbox.spy(AppBuilderRootNode.instance) - - projects = await detectSamProjects() - - // Set the walkthrough status to true to ensure the root node has a walkthrough node - originalWalkThroughState = (await globals.globalState.get('aws.toolkit.lambda.walkthroughCompleted')) || false - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', false) - }) - - after(async () => { - // Restore original status of walkthroughCompleted status - await globals.globalState.update('aws.toolkit.lambda.walkthroughCompleted', originalWalkThroughState) - sandbox.restore() - }) - - describe('root node', async () => { - it('creates an AppBuilderRootNode with correct label', async () => { - const appBuidlerNode = rootNode.getTreeItem() - assert.strictEqual(appBuidlerNode.label, 'APPLICATION BUILDER') - }) - - it('generates correct number of children nodes: walkthrough node + project nodes', async () => { - const rootNodeChildern = await rootNode.getChildren() - - // walkthrough node should be at the first position in the list - const walkthroughNode = rootNodeChildern[0] - assert.strictEqual(walkthroughNode.id, 'walkthrough') - - // project nodes in the workspace should be AppNode instances for all project in the workspace - projectNodes = rootNodeChildern.filter((node) => node instanceof AppNode) - assert.strictEqual(projectNodes.length, projects.length) - }) - }) - - describe('application nodes in workspace (Test in order)', async () => { - // The following `it()` statement within this block is meant to be executed in order - let appBuilderTestApp: AppNode - let appBuilderTestAppResourceNodes - let samConfig: SamConfig - let projectRoot: vscode.Uri - - it('1: contains application node for appbuilder-test-app', async () => { - // projectNodes set from previous step - const filterResult = projectNodes.filter( - (node): node is AppNode => - node instanceof AppNode && node.label === 'workspaceFolder/appbuilder-test-app' - ) - assert.strictEqual(filterResult.length, 1) - - // Set properties for succeeding tests - appBuilderTestApp = filterResult[0] - projectRoot = appBuilderTestApp.resource.projectRoot - samConfig = await SamConfig.fromProjectRoot(projectRoot) - }) - - it('2: contains correct application node properties', async () => { - // Validate App Node - const appBuilderTestAppTreeItem = appBuilderTestApp.getTreeItem() - assert.strictEqual(appBuilderTestAppTreeItem.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert(appBuilderTestAppTreeItem.label, 'workspaceFolder/appbuilder-test-app') - assert(appBuilderTestAppTreeItem.contextValue, 'awsAppBuilderAppNode') - }) - - it('3: contains correct resource node properties', async () => { - // Validate Resource Node - appBuilderTestAppResourceNodes = await appBuilderTestApp.getChildren() - - // Expect 4 undeployed resources in this example projects - assert(appBuilderTestAppResourceNodes.length === 4) - assert(appBuilderTestAppResourceNodes.every((resourceNode) => resourceNode instanceof ResourceNode)) - - assert( - appBuilderTestAppResourceNodes.every( - (resourceNode): resourceNode is ResourceNode => resourceNode instanceof ResourceNode - ) - ) - - const expectedStackName = await samConfig.getCommandParam('global', 'stack_name') - const expectedRegion = await samConfig.getCommandParam('global', 'region') - for (const node of appBuilderTestAppResourceNodes) { - if (node instanceof ResourceNode) { - assert.strictEqual(node.resource.region, expectedRegion) - assert.strictEqual(node.resource.stackName, expectedStackName) - assert(!node.resource.deployedResource) - assert(!node.resourceLogicalId) - } else { - assert.fail('Node is not an instance of ResourceNode') - } - } - - // Validate Lambda resource node - const lambdaResourceNode = getResourceNodeByType( - appBuilderTestAppResourceNodes, - 'AWS::Serverless::Function' - ) - assert.strictEqual(lambdaResourceNode.id, 'AppBuilderProjectLambda') - const lambdaTreeItemProperties = lambdaResourceNode.getTreeItem() - assert.strictEqual(lambdaTreeItemProperties.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(lambdaTreeItemProperties.iconPath?.toString(), '$(aws-lambda-function)') - - // Validate s3 bucket - const s3BucketResourceNode = getResourceNodeByType(appBuilderTestAppResourceNodes, 'AWS::S3::Bucket') - assert.strictEqual(s3BucketResourceNode.id, 'AppBuilderProjectBucket') - const s3BucketTreeItemProperties = s3BucketResourceNode.getTreeItem() - assert.strictEqual(s3BucketTreeItemProperties.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(s3BucketTreeItemProperties.iconPath?.toString(), '$(aws-s3-bucket)') - - // Validate s3 policy - const s3PolicyResourceNode = getResourceNodeByType(appBuilderTestAppResourceNodes, 'AWS::S3::BucketPolicy') - assert.strictEqual(s3PolicyResourceNode.id, 'AppBuilderProjectBucketBucketPolicy') - const s3PolicyTreeItemProperties = s3PolicyResourceNode.getTreeItem() - assert.strictEqual(s3PolicyTreeItemProperties.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(s3PolicyTreeItemProperties.iconPath?.toString(), '$(info)') - - // Validate api gateway resource node - const apigwResourceNode = getResourceNodeByType(appBuilderTestAppResourceNodes, 'AWS::Serverless::Api') - assert.strictEqual(apigwResourceNode.id, 'AppBuilderProjectAPI') - const apigwTreeItemProperties = apigwResourceNode.getTreeItem() - assert.strictEqual(apigwTreeItemProperties.collapsibleState, vscode.TreeItemCollapsibleState.Collapsed) - assert.strictEqual(apigwTreeItemProperties.iconPath?.toString(), '$(info)') - }) - - it('4: has registered refresh command successfully', async () => { - const originalCount = rootNode.refresh.callCount - let accumulateCount: number - - await vscode.commands.executeCommand('aws.appBuilderForFileExplorer.refresh') - accumulateCount = rootNode.refresh.callCount - assert.strictEqual(accumulateCount - originalCount, 1) - - await vscode.commands.executeCommand('aws.appBuilder.refresh') - accumulateCount = rootNode.refresh.callCount - assert.strictEqual(accumulateCount - originalCount, 2) - }) - - it('5: triggers auto refresh when there a file getting updated', async () => { - // Get existing stack name and region information - const orignalStackName = `${await samConfig.getCommandParam('global', 'stack_name')}` - const originalRegion = `${await samConfig.getCommandParam('global', 'region')}` - const updateStackName = `${orignalStackName}-updated` - const originalCount = rootNode.refresh.callCount - - // Update stack name in samconfig.toml - await writeSamconfigGlobal(projectRoot, updateStackName, originalRegion) - await sleep(500) - const accumulateCount = rootNode.refresh.callCount - assert.strictEqual(accumulateCount - originalCount, 2) - - // Restore region to us-west-2 - await writeSamconfigGlobal(projectRoot, orignalStackName, originalRegion) - }) - }) -}) - -function getResourceNodeByType(resourceNodes: any[], type: string) { - const matches = resourceNodes.filter((node) => node.resource.resource.Type === type) - // Expect only 1 resource node per resource type specifically for this appbuilder-test-app - assert(matches.length === 1) - return matches[0] -} diff --git a/packages/core/src/testInteg/appBuilder/walkthrough.test.ts b/packages/core/src/testInteg/appBuilder/walkthrough.test.ts deleted file mode 100644 index a8097101b1..0000000000 --- a/packages/core/src/testInteg/appBuilder/walkthrough.test.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { appMap } from '../../awsService/appBuilder/walkthrough' -import assert from 'assert' - -describe('Walkthrough pattern URL exists', function () { - const serverlessLandOwner = 'aws-samples' - const serverlessLandRepo = 'serverless-patterns' - - for (const [key, app] of appMap.entries()) { - it(`Walkthrough pattern URL exists for ${key}`, async function () { - const url = `https://github.com/${serverlessLandOwner}/${serverlessLandRepo}/releases/latest/download/${app.asset}` - const response = await fetch(url, { - method: 'HEAD', - }) - // ignore if too frequent - assert.ok(response.status === 200 || response.status === 429) - }) - } -}) diff --git a/packages/core/src/testInteg/codecatalyst/devEnv.test.ts b/packages/core/src/testInteg/codecatalyst/devEnv.test.ts deleted file mode 100644 index 7a96ce79ec..0000000000 --- a/packages/core/src/testInteg/codecatalyst/devEnv.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { InactivityMessage, shouldSendActivity } from '../../codecatalyst/devEnv' -import * as sinon from 'sinon' -import { sleep, waitUntil } from '../../shared/utilities/timeoutUtils' -import { TestWindow, getTestWindow } from '../../test/shared/vscode/window' -import { DevEnvActivity } from '../../shared/clients/devenvClient' - -describe('shouldSendActivity', function () { - it('returns true when inactivity timeout > 0', function () { - assert.strictEqual(shouldSendActivity(1), true) - assert.strictEqual(shouldSendActivity(15), true) - }) - - it('returns false when inactivity timeout <== 0', function () { - assert.strictEqual(shouldSendActivity(0), false) - assert.strictEqual(shouldSendActivity(-1), false) - }) -}) - -describe('InactivityMessage', function () { - /** Actual minute in prod is this value instead so testing is faster. */ - let relativeMinuteMillis: number - let testWindow: TestWindow - let devEnvActivity: sinon.SinonStubbedInstance - let actualMessages: { message: string; minute: number }[] = [] - let inactivityMsg: InactivityMessage - let userActivity: number - - beforeEach(function () { - relativeMinuteMillis = 100 - testWindow = getTestWindow() - inactivityMsg = new InactivityMessage() - userActivity = 0 - setInitialOffset(0) - - devEnvActivity = sinon.createStubInstance(DevEnvActivity) - devEnvActivity.sendActivityUpdate.callsFake(async () => { - userActivity += 1 - const timestamp = getLatestTimestamp() - return timestamp - }) - devEnvActivity.onActivityUpdate.callsFake((activityCallback) => { - const timestamp = getLatestTimestamp() - activityCallback(timestamp) - }) - devEnvActivity.isLocalActivityStale.callsFake(async () => { - return false - }) - - startCapturingMessages() - }) - - afterEach(function () { - inactivityMsg.dispose() - testWindow.dispose() - }) - - it('shows warning 5 minutes before shutdown for 15 minute timeout', async function () { - setInitialOffset(9) - await inactivityMsg.init(15, devEnvActivity as unknown as DevEnvActivity, relativeMinuteMillis) - - await assertMessagesShown([ - ['Your CodeCatalyst Dev Environment has been inactive for 10 minutes, and will stop soon.', 10], - ]) - }) - - it('shows warning 5 minutes before shutdown for 60 minute timeout', async function () { - setInitialOffset(54) - await inactivityMsg.init(60, devEnvActivity as unknown as DevEnvActivity, relativeMinuteMillis) - - await assertMessagesShown([ - ['Your CodeCatalyst Dev Environment has been inactive for 55 minutes, and will stop soon.', 55], - ]) - }) - - it.skip('resets inactivity countdown when a user confirms the message', async function () { - await inactivityMsg.init(10, devEnvActivity as unknown as DevEnvActivity, relativeMinuteMillis) - const msg = await testWindow.waitForMessage(/Dev Environment has been inactive/) - assert.deepStrictEqual(userActivity, 1) - msg.selectItem("I'm here!") - await waitUntil(async () => userActivity > 1, { truthy: true, interval: 100, timeout: 5_000 }) - assert.deepStrictEqual(userActivity, 2, 'confirming the message should trigger user activity') - - await assertMessagesShown([ - ['Your CodeCatalyst Dev Environment has been inactive for 5 minutes, and will stop soon.', 5], - ]) - }) - - it('offsets when 2.5 minutes have already passed for an inactive external client', async function () { - setInitialOffset(2.5) - await inactivityMsg.init(9, devEnvActivity as unknown as DevEnvActivity, relativeMinuteMillis) - - await assertMessagesShown([ - ['Your CodeCatalyst Dev Environment has been inactive for 4 minutes, and will stop soon.', 4], - ]) - }) - - it('does not show inactivity message if user activity is found using the API', async function () { - // This gets checked each time before we decide to show the message. - // If a new user activity exists then we abort showing the message. - devEnvActivity.isLocalActivityStale.callsFake(async () => { - return true - }) - - await inactivityMsg.init( - inactivityMsg.shutdownWarningThreshold + 1, - devEnvActivity as unknown as DevEnvActivity, - relativeMinuteMillis - ) - await devEnvActivity.sendActivityUpdate() - await sleep(relativeMinuteMillis * 3) - assert.strictEqual(testWindow.shownMessages.length, 0) - assert.strictEqual(devEnvActivity.isLocalActivityStale.calledOnce, true) - }) - - /** - * Assert the expected inactivity message was shown at the - * right time. - * - * @param text The actual message itself - * @param minute The minute the message was expected to be shown at - */ - async function assertMessagesShown(expectedMessages: [text: string, minute: number][]) { - await waitUntil( - async () => { - return expectedMessages.length === actualMessages.length - }, - { truthy: true, interval: 200, timeout: 10_000 } - ) - if (expectedMessages.length !== actualMessages.length) { - assert.fail(`Expected ${expectedMessages.length} messages, but got ${actualMessages.length}`) - } - - for (let i = 0; i < expectedMessages.length; i++) { - const expected = { - message: expectedMessages[i][0], - minute: expectedMessages[i][1], - } - assert.deepStrictEqual(actualMessages[i].message, expected.message) - // Avoid flakiness in the timing by looking within a minute rather than exact. - assert.ok( - Math.abs(actualMessages[i].minute - expected.minute) <= 1, - `Expected to be within 60 seconds of minute ${expected.minute}, but instead was at minute ${actualMessages[i].minute}` - ) - } - } - - let _initialOffset = 0 - /** - * This is used for the edge case where the MDE was previously updated with an activity - * timestamp, but once our client retrieves this value some time has already passed. - */ - function setInitialOffset(minutes: number) { - _initialOffset = minutes * relativeMinuteMillis - } - - function getLatestTimestamp() { - let timestamp = Date.now() - timestamp -= _initialOffset - - return timestamp - } - - /** - * Starts capturing all vscode messages shown and records them in {@link actualMessages}. - * - * The `minute` field in {@link actualMessages} records the minute the message was shown. - * This value is relative to {@link relativeMinuteMillis}. - */ - function startCapturingMessages() { - const start = Date.now() - const messages: { message: string; minute: number }[] = [] - testWindow.onDidShowMessage(async (message) => { - const now = Date.now() - messages.push({ - message: message.message, - minute: Math.floor((now + _initialOffset - start) / relativeMinuteMillis), - }) - }) - actualMessages = messages - } -}) diff --git a/packages/core/src/testInteg/codelens.js.test.ts b/packages/core/src/testInteg/codelens.js.test.ts deleted file mode 100644 index c7f3582a05..0000000000 --- a/packages/core/src/testInteg/codelens.js.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { dirname, join } from 'path' -import * as vscode from 'vscode' -import { getAddConfigCodeLens, getTestWorkspaceFolder } from './integrationTestsUtilities' -import { AddSamDebugConfigurationInput } from '../shared/sam/debugger/commands/addSamDebugConfiguration' -import * as testUtils from './integrationTestsUtilities' - -const activateExtensionTimeoutMillis = 30000 -const codelensTestTimeoutMillis = 10000 -const codelensRetryInterval = 1000 - -const workspaceFolder = getTestWorkspaceFolder() - -describe('SAM Local CodeLenses (JS)', async function () { - // TODO : Extend this test suite out to work for different projects with different file configurations - before(async function () { - this.timeout(activateExtensionTimeoutMillis) - await testUtils.configureAwsToolkitExtension() - }) - - it('appear when manifest in subfolder and app is beside manifest', async function () { - const appRoot = join(workspaceFolder, 'js-plain-sam-app') - const appCodePath = join(appRoot, 'src', 'app.js') - const expectedHandlerName = 'app.handlerBesidePackageJson' - const document = await vscode.workspace.openTextDocument(appCodePath) - - const codeLenses = await getAddConfigCodeLens(document.uri, codelensTestTimeoutMillis, codelensRetryInterval) - - assertAddDebugConfigCodeLensExists(codeLenses, expectedHandlerName, dirname(appCodePath)) - }).timeout(codelensTestTimeoutMillis) - - it('appear when manifest in root', async function () { - const appRoot = join(workspaceFolder, 'js-manifest-in-root') - const appCodePath = join(appRoot, 'src', 'subfolder', 'app.mjs') - const expectedHandlerName = 'src/subfolder/app.handlerTwoFoldersDeep' - const document = await vscode.workspace.openTextDocument(appCodePath) - - const codeLenses = await getAddConfigCodeLens(document.uri, codelensTestTimeoutMillis, codelensRetryInterval) - - assertAddDebugConfigCodeLensExists(codeLenses, expectedHandlerName, join(dirname(appCodePath), '..', '..')) - }).timeout(codelensTestTimeoutMillis) - - it('appear when manifest in subfolder and app in subfolder to manifest', async function () { - const appRoot = join(workspaceFolder, 'js-manifest-in-subfolder') - const appCodePath = join(appRoot, 'src', 'subfolder', 'app.js') - const expectedHandlerName = 'subfolder/app.handlerInManifestSubfolder' - const document = await vscode.workspace.openTextDocument(appCodePath) - - const codeLenses = await getAddConfigCodeLens(document.uri, codelensTestTimeoutMillis, codelensRetryInterval) - - assertAddDebugConfigCodeLensExists(codeLenses, expectedHandlerName, join(dirname(appCodePath), '..')) - }).timeout(codelensTestTimeoutMillis) - - it('appear when project is a few folders deep in the workspace', async function () { - const appRoot = join(workspaceFolder, 'deeper-projects', 'js-plain-sam-app') - const appCodePath = join(appRoot, 'src', 'app.js') - const expectedHandlerName = 'app.projectDeepInWorkspace' - const document = await vscode.workspace.openTextDocument(appCodePath) - - const codeLenses = await getAddConfigCodeLens(document.uri, codelensTestTimeoutMillis, codelensRetryInterval) - - assertAddDebugConfigCodeLensExists(codeLenses, expectedHandlerName, dirname(appCodePath)) - }) - - function assertAddDebugConfigCodeLensExists( - codeLenses: vscode.CodeLens[] | undefined, - expectedHandlerName: string, - manifestPath: string - ) { - assert.ok(codeLenses !== undefined, 'Did not expect undefined when requesting CodeLenses') - - const debugCodeLenses = getLocalInvokeCodeLenses(codeLenses).filter((codeLens) => - hasLocalInvokeArguments(codeLens, expectedHandlerName, manifestPath) - ) - - assert.strictEqual(debugCodeLenses.length, 2, 'Add Debug Config CodeLenses were not found') - } - - function getLocalInvokeCodeLenses(codeLenses: vscode.CodeLens[]): vscode.CodeLens[] { - return codeLenses.filter( - (codeLens) => - codeLens.command && - codeLens.command.command === 'aws.pickAddSamDebugConfiguration' && - codeLens.command.arguments && - codeLens.command.arguments.length === 3 - ) - } - - function hasLocalInvokeArguments(codeLens: vscode.CodeLens, handlerName: string, manifestPath: string): boolean { - if (!codeLens.command || !codeLens.command.arguments || codeLens.command.arguments.length !== 3) { - return false - } - - const commandArguments = codeLens.command.arguments[0] as AddSamDebugConfigurationInput - - return ( - commandArguments.resourceName === handlerName && dirname(commandArguments.rootUri.fsPath) === manifestPath - ) - } -}) diff --git a/packages/core/src/testInteg/sam.test.ts b/packages/core/src/testInteg/sam.test.ts deleted file mode 100644 index 4f80d550df..0000000000 --- a/packages/core/src/testInteg/sam.test.ts +++ /dev/null @@ -1,644 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { Runtime } from '@aws-sdk/client-lambda' -import { mkdtempSync } from 'fs' // eslint-disable-line no-restricted-imports -import * as path from 'path' -import * as semver from 'semver' -import * as vscode from 'vscode' -import * as vscodeUtils from '../../src/shared/utilities/vsCodeUtils' -import { DependencyManager } from '../../src/lambda/models/samLambdaRuntime' -import { helloWorldTemplate } from '../../src/lambda/models/samTemplates' -import { getSamCliContext } from '../../src/shared/sam/cli/samCliContext' -import { runSamCliInit, SamCliInitArgs } from '../../src/shared/sam/cli/samCliInit' -import { Language } from '../shared/codelens/codeLensUtils' -import { VSCODE_EXTENSION_ID } from '../shared/extensions' -import { fileExists, tryRemoveFolder } from '../shared/filesystemUtilities' -import { AddSamDebugConfigurationInput } from '../shared/sam/debugger/commands/addSamDebugConfiguration' -import { findParentProjectFile } from '../shared/utilities/workspaceUtils' -import * as testUtils from './integrationTestsUtilities' -import { waitUntil } from '../shared/utilities/timeoutUtils' -import { AwsSamDebuggerConfiguration } from '../shared/sam/debugger/awsSamDebugConfiguration.gen' -import { AwsSamTargetType } from '../shared/sam/debugger/awsSamDebugConfiguration' -import { insertTextIntoFile } from '../shared/utilities/textUtilities' -import globals from '../shared/extensionGlobals' -import { closeAllEditors, getWorkspaceFolder } from '../test/testUtil' -import { ToolkitError } from '../shared/errors' -import { SamAppLocation } from '../awsService/appBuilder/explorer/samProject' -import { AppNode } from '../awsService/appBuilder/explorer/nodes/appNode' -import sinon from 'sinon' -import { getTestWindow } from '../test/shared/vscode/window' -import { ParamsSource, runBuild } from '../shared/sam/build' -import { DataQuickPickItem } from '../shared/ui/pickerPrompter' -import fs from '../shared/fs/fs' - -const projectFolder = testUtils.getTestWorkspaceFolder() - -/* Test constants go here */ -const codelensTimeout: number = 60000 -const codelensRetryInterval: number = 5000 -const noDebugSessionTimeout: number = 5000 -const noDebugSessionInterval: number = 100 - -/** Go can't handle API tests yet */ -const skipLanguagesOnApi = ['go'] - -interface TestScenarioDefaults { - path: string - debugSessionType: string - language: Language - dependencyManager: DependencyManager - /** Minimum vscode version required by the relevant third-party extension. */ - vscodeMinimum: string -} -type TestScenario = { - displayName: string - runtime: Runtime - baseImage?: string -} & TestScenarioDefaults - -const nodeDefaults = { - path: 'hello-world/app.mjs', - debugSessionType: 'pwa-node', - dependencyManager: 'npm' as DependencyManager, - language: 'javascript' as Language, - vscodeMinimum: '1.50.0', -} -// https://github.com/microsoft/vscode-python/blob/main/package.json -const pythonDefaults = { - path: 'hello_world/app.py', - debugSessionType: 'python', - dependencyManager: 'pip' as DependencyManager, - language: 'python' as Language, - vscodeMinimum: '1.77.0', -} - -const javaDefaults = { - path: 'HelloWorldFunction/src/main/java/helloworld/App.java', - debugSessionType: 'java', - dependencyManager: 'gradle' as DependencyManager, - language: 'java' as Language, - vscodeMinimum: '1.50.0', -} - -const dotnetDefaults = { - path: 'src/HelloWorld/Function.cs', - debugSessionType: 'coreclr', - language: 'csharp' as Language, - dependencyManager: 'cli-package' as DependencyManager, - vscodeMinimum: '1.80.0', -} - -const defaults: Record = { - nodejs: nodeDefaults, - java: javaDefaults, - python: pythonDefaults, - dotnet: dotnetDefaults, -} - -function generateScenario( - runtime: string, - version: string, - options: Partial = {}, - fromImage: boolean = false -): TestScenario { - if (fromImage && !options.baseImage) { - throw new Error('baseImage property must be specified when testing from image') - } - const { sourceTag, ...defaultOverride } = options - const source = `(${options.sourceTag ? `${options.sourceTag} ` : ''}${fromImage ? 'Image' : 'ZIP'})` - const fullName = `${runtime}${version}` as Runtime - return { - runtime: fullName, - displayName: `${fullName} ${source}`, - ...defaults[runtime], - ...defaultOverride, - } -} -// When testing additional runtimes, consider pulling the docker container in buildspec\linuxIntegrationTests.yml -// to reduce the chance of automated tests timing out. - -const scenarios: TestScenario[] = [ - // zips - generateScenario('nodejs', '20.x'), - generateScenario('nodejs', '22.x', { vscodeMinimum: '1.78.0' }), - generateScenario('python', '3.10'), - generateScenario('python', '3.11', { vscodeMinimum: '1.78.0' }), - generateScenario('python', '3.12', { vscodeMinimum: '1.78.0' }), - generateScenario('python', '3.13', { vscodeMinimum: '1.78.0' }), - generateScenario('dotnet', '6'), - generateScenario('java', '8.al2', { sourceTag: 'Maven', dependencyManager: 'maven' }), - generateScenario('java', '11', { sourceTag: 'Gradle' }), - generateScenario('java', '17', { sourceTag: 'Gradle' }), - // images - generateScenario('nodejs', '20.x', { baseImage: 'amazon/nodejs20.x-base' }, true), - generateScenario('nodejs', '22.x', { baseImage: 'amazon/nodejs22.x-base', vscodeMinimum: '1.78.0' }, true), - generateScenario('python', '3.10', { baseImage: 'amazon/python3.10-base' }, true), - generateScenario('python', '3.11', { baseImage: 'amazon/python3.11-base', vscodeMinimum: '1.78.0' }, true), - generateScenario('python', '3.12', { baseImage: 'amazon/python3.12-base', vscodeMinimum: '1.78.0' }, true), - generateScenario('python', '3.13', { baseImage: 'amazon/python3.13-base', vscodeMinimum: '1.78.0' }, true), - generateScenario('dotnet', '6', { baseImage: 'amazon/dotnet6-base' }, true), - generateScenario( - 'java', - '.al2', - { baseImage: 'amazon/java8.al2-base', sourceTag: 'Maven', dependencyManager: 'maven' }, - true - ), - generateScenario('java', '11', { baseImage: 'amazon/java11-base', sourceTag: 'Gradle' }, true), - generateScenario('java', '17', { baseImage: 'amazon/java17-base', sourceTag: 'Gradle' }, true), -] - -async function openSamAppFile(applicationPath: string): Promise { - const document = await vscode.workspace.openTextDocument(applicationPath) - - return document.uri -} - -/** - * Returns a string if there is a validation issue, undefined if there is no issue. - */ -function validateSamDebugSession( - debugSession: vscode.DebugSession, - expectedName: string, - expectedRuntime: string -): string | undefined { - const runtime = (debugSession.configuration as any).runtime - const name = (debugSession.configuration as any).name - if (name !== expectedName || runtime !== expectedRuntime) { - const failMsg = - `Unexpected DebugSession (expected name="${expectedName}" runtime="${expectedRuntime}"):` + - `\n${JSON.stringify(debugSession)}` - return failMsg - } -} - -/** - * Simulates pressing 'F5' to start debugging. Sets up events to see if debugging was successful - * or not. Since we are not checking outputs we treat a successful operation as the debug session - * closing on its own (i.e. the container executable terminated) - * - * @param scenario Scenario to run, used for logging information - * @param scenarioIndex Scenario number, used for logging information - * @param testConfig Debug configuration to start the debugging with - * @param testDisposables All events registered by this function are pushed here to be removed later - * @param sessionLog An array where session logs are stored - */ -async function startDebugger( - scenario: TestScenario, - scenarioIndex: number, - target: AwsSamTargetType, - testConfig: vscode.DebugConfiguration, - testDisposables: vscode.Disposable[], - sessionLog: string[] -) { - function logSession(startEnd: 'START' | 'END' | 'EXIT' | 'FAIL', name: string) { - sessionLog.push( - `scenario ${scenarioIndex}.${target.toString()[0]} ${startEnd.padEnd(5, ' ')} ${target}/${ - scenario.displayName - }: ${name}` - ) - } - - // Create a Promise that encapsulates our success critera - const success = new Promise((resolve, reject) => { - testDisposables.push( - vscode.debug.onDidTerminateDebugSession(async (session) => { - logSession('END', session.name) - const sessionRuntime = (session.configuration as any).runtime - if (!sessionRuntime) { - // It's a coprocess, ignore it. - return - } - const failMsg = validateSamDebugSession(session, testConfig.name, scenario.runtime) - if (failMsg) { - reject(new Error(failMsg)) - } - resolve() - await stopDebugger(`${scenario.runtime} / onDidTerminateDebugSession`) - }) - ) - }) - - // Executes the 'F5' action to start debugging - await vscode.debug.startDebugging(undefined, testConfig) - if (!vscode.debug.activeDebugSession) { - logSession('EXIT', `${testConfig.name} (exited immediately)`) - return - } - logSession('START', vscode.debug.activeDebugSession.name) - - // Some tests need to hit debug continue to the debugger to complete. - // Testing locally seemed to take 600-800 millis to run. - const result = await waitUntil( - async () => { - try { - await vscode.commands.executeCommand('workbench.action.debug.continue') - return true - } catch { - return false - } - }, - { interval: 500, timeout: 10_000 } - ) - if (result === undefined) { - throw new ToolkitError(`Toolkit: Debug session did not stop. Something may have gotten stuck.`) - } - - return success -} - -async function stopDebugger(logMsg: string | undefined): Promise { - if (logMsg) { - console.log(`sam.test.ts: stopDebugger(): ${logMsg}`) - } - await vscode.commands.executeCommand('workbench.action.debug.stop') -} - -async function activateExtensions(): Promise { - console.log('Activating extensions...') - await vscodeUtils.activateExtension(VSCODE_EXTENSION_ID.python, false) - // TODO: Must be reactivated when go tests are enabled above. - // Caveat: v0.40.1 of the go extension breaks this line (see changelog for this version) - // await vscodeUtils.activateExtension(VSCODE_EXTENSION_ID.go, false) - await vscodeUtils.activateExtension(VSCODE_EXTENSION_ID.java, false) - await vscodeUtils.activateExtension(VSCODE_EXTENSION_ID.javadebug, false) - console.log('Extensions activated') -} - -describe('SAM Integration Tests', async function () { - const samApplicationName = 'testProject' - /** - * Breadcrumbs from each process, printed at end of all scenarios to give - * us an idea of the timeline. - */ - const sessionLog: string[] = [] - let javaLanguageSetting: string | undefined - const config = vscode.workspace.getConfiguration('java') - let testSuiteRoot: string - - before(async function () { - javaLanguageSetting = config.get('server.launchMode') - await config.update('server.launchMode', 'Standard') - - await activateExtensions() - await testUtils.configureAwsToolkitExtension() - // await testUtils.configureGoExtension() - - testSuiteRoot = mkdtempSync(path.join(projectFolder, 'inttest')) - console.log('testSuiteRoot: ', testSuiteRoot) - await fs.mkdir(testSuiteRoot) - }) - - after(async function () { - await tryRemoveFolder(testSuiteRoot) - // Print a summary of session that were seen by `onDidStartDebugSession`. - const sessionReport = sessionLog.map((x) => ` ${x}`).join('\n') - await config.update('server.launchMode', javaLanguageSetting) - console.log(`DebugSessions seen in this run:\n${sessionReport}`) - }) - - describe('SAM install test', async () => { - let runtimeTestRoot: string - let randomTestScenario: TestScenario - - before(async function () { - if (scenarios.length === 0) { - throw new Error('There are no scenarios available.') - } - randomTestScenario = scenarios[0] - - runtimeTestRoot = path.join(testSuiteRoot, 'randomScenario') - await fs.mkdir(runtimeTestRoot) - }) - - after(async function () { - await tryRemoveFolder(runtimeTestRoot) - }) - - it('produces an error when creating a SAM Application to the same location', async function () { - await createSamApplication(runtimeTestRoot, randomTestScenario) - await assert.rejects(createSamApplication(runtimeTestRoot, randomTestScenario), 'Promise was not rejected') - }) - }) - - function assertCodeLensReferencesHasSameRoot(codeLens: vscode.CodeLens, expectedUri: vscode.Uri) { - assert.ok(codeLens.command, 'CodeLens did not have a command') - const command = codeLens.command! - - assert.ok(command.arguments, 'CodeLens command had no arguments') - const commandArguments = command.arguments! - - assert.strictEqual(commandArguments.length, 3, 'CodeLens command had unexpected arg count') - const params: AddSamDebugConfigurationInput = commandArguments[0] - assert.ok(params, 'unexpected non-defined command argument') - - assert.strictEqual(path.dirname(params.rootUri.fsPath), path.dirname(expectedUri.fsPath)) - } - - for (let scenarioIndex = 0; scenarioIndex < scenarios.length; scenarioIndex++) { - const scenario = scenarios[scenarioIndex] - - describe(`SAM runtime: ${scenario.displayName}`, async function () { - let runtimeTestRoot: string - - before(async function () { - runtimeTestRoot = path.join(testSuiteRoot, scenario.runtime) - console.log('runtimeTestRoot: ', runtimeTestRoot) - await fs.mkdir(runtimeTestRoot) - }) - - after(async function () { - // don't clean up after java tests so the java language server doesn't freak out - if (scenario.language !== 'java') { - await tryRemoveFolder(runtimeTestRoot) - } - }) - - function log(o: any) { - console.log(`sam.test.ts: scenario ${scenarioIndex} (${scenario.displayName}): ${o}`) - } - - /** - * This suite makes a sam app that all tests operate on. - * Cleanup happens at the end of the suite. - */ - describe(`Starting with a newly created ${scenario.displayName} SAM Application...`, async function () { - let testDisposables: vscode.Disposable[] - - let testDir: string - let samAppCodeUri: vscode.Uri - let appPath: string - let cfnTemplatePath: string - - before(async function () { - testDir = mkdtempSync(path.join(runtimeTestRoot, 'samapp-')) - log(`testDir: ${testDir}`) - - await createSamApplication(testDir, scenario) - appPath = path.join(testDir, samApplicationName, scenario.path) - - cfnTemplatePath = path.join(testDir, samApplicationName, 'template.yaml') - const readmePath = path.join(testDir, samApplicationName, 'README.md') - assert.ok(await fileExists(cfnTemplatePath), `Expected SAM template to exist at ${cfnTemplatePath}`) - assert.ok(await fileExists(readmePath), `Expected SAM App readme to exist at ${readmePath}`) - - samAppCodeUri = await openSamAppFile(appPath) - }) - - beforeEach(async function () { - testDisposables = [] - await closeAllEditors() - }) - - afterEach(async function () { - for (const d of testDisposables) { - d.dispose() - } - await stopDebugger(undefined) - }) - - after(async function () { - // don't clean up after java tests so the java language server doesn't freak out - if (scenario.language !== 'java') { - await tryRemoveFolder(testDir) - } - }) - - it('produces an Add Debug Configuration codelens', async function () { - if ( - scenario.language === 'csharp' || // TODO - semver.lt(vscode.version, scenario.vscodeMinimum) - ) { - this.skip() - } - const codeLenses = await testUtils.getAddConfigCodeLens( - samAppCodeUri, - codelensTimeout, - codelensRetryInterval - ) - assert.ok(codeLenses, 'No CodeLenses provided') - assert.strictEqual(codeLenses.length, 2, 'Incorrect amount of CodeLenses provided') - - let manifestFile: RegExp - switch (scenario.language) { - case 'javascript': - manifestFile = /^package\.json$/ - break - case 'python': - manifestFile = /^requirements\.txt$/ - break - // case 'csharp': - // manifestFile = /^.*\.csproj$/ - // break - case 'go': - manifestFile = /^go\.mod$/ - break - case 'java': - if (scenario.dependencyManager === 'maven') { - manifestFile = /^.*pom\.xml$/ - break - } else if (scenario.dependencyManager === 'gradle') { - manifestFile = /^.*build\.gradle$/ - break - } - assert.fail(`invalid dependency manager for java: ${scenario.dependencyManager}`) - break - default: - assert.fail('invalid scenario language') - } - - const projectRoot = await findParentProjectFile(samAppCodeUri, manifestFile) - assert.ok(projectRoot, 'projectRoot not found') - for (const codeLens of codeLenses) { - assertCodeLensReferencesHasSameRoot(codeLens, projectRoot!) - } - }) - - it('target=api: invokes and attaches on debug request (F5)', async function () { - if ( - skipLanguagesOnApi.includes(scenario.language) || - semver.lt(vscode.version, scenario.vscodeMinimum) - ) { - this.skip() - } - - await testTarget('api', { - api: { - path: '/hello', - httpMethod: 'get', - headers: { 'accept-language': 'fr-FR' }, - }, - }) - }) - - it('target=template: invokes and attaches on debug request (F5)', async function () { - if (semver.lt(vscode.version, scenario.vscodeMinimum)) { - this.skip() - } - - await testTarget('template') - }) - - async function testTarget(target: AwsSamTargetType, extraConfig: any = {}) { - // Allow previous sessions to go away. - await waitUntil(async () => vscode.debug.activeDebugSession === undefined, { - timeout: noDebugSessionTimeout, - interval: noDebugSessionInterval, - truthy: true, - }) - - // We exclude the Node debug type since it causes the most erroneous failures with CI. - // However, the fact that there are sessions from previous tests is still an issue, so - // a warning will be logged under the current session. - if (vscode.debug.activeDebugSession) { - assert.strictEqual( - vscode.debug.activeDebugSession.type, - 'pwa-node', - `unexpected debug session in progress: ${JSON.stringify( - vscode.debug.activeDebugSession, - undefined, - 2 - )}` - ) - - sessionLog.push(`(WARNING) Unexpected debug session ${vscode.debug.activeDebugSession.name}`) - } - - const testConfig = { - type: 'aws-sam', - request: 'direct-invoke', - name: `test-config-${scenarioIndex}`, - invokeTarget: { - target: target, - // Resource defined in `src/testFixtures/.../template.yaml`. - logicalId: 'HelloWorldFunction', - templatePath: cfnTemplatePath, - }, - sam: { - containerBuild: true, - }, - ...extraConfig, - } as AwsSamDebuggerConfiguration - - // runtime is optional for ZIP, but required for image-based - if (scenario.baseImage) { - testConfig.lambda = { - runtime: scenario.runtime, - } - - // HACK: set GOPROXY=direct or it will fail to build. https://golang.org/ref/mod#module-proxy - // This only applies for our internal systems - if (scenario.language === 'go') { - const dockerfilePath: string = path.join(path.dirname(appPath), 'Dockerfile') - await insertTextIntoFile('ENV GOPROXY=direct', dockerfilePath, 1) - } - } - - // XXX: force load since template registry seems a bit flakey - await (await globals.templateRegistry).addItem(vscode.Uri.file(cfnTemplatePath)) - - await startDebugger(scenario, scenarioIndex, target, testConfig, testDisposables, sessionLog) - } - }) - }) - } - - describe('SAM Build', async function () { - let workspaceFolder: vscode.WorkspaceFolder - // We're testing only one case (python 3.10 (ZIP)) on the integration tests. More niche cases are handled as unit tests. - const scenarioIndex = 3 - const scenario = scenarios[scenarioIndex] - let testRoot: string - let sandbox: sinon.SinonSandbox - let testDir: string - let cfnTemplatePath: string - - before(async function () { - workspaceFolder = getWorkspaceFolder(testSuiteRoot) - testRoot = path.join(testSuiteRoot, scenario.runtime) - await fs.mkdir(testRoot) - - testDir = mkdtempSync(path.join(testRoot, 'samapp-')) - console.log(`testDir: ${testDir}`) - - await createSamApplication(testDir, scenario) - - cfnTemplatePath = path.join(testDir, samApplicationName, 'template.yaml') - }) - - after(async function () { - await tryRemoveFolder(testSuiteRoot) - // don't clean up after java tests so the java language server doesn't freak out - if (scenario.language !== 'java') { - await tryRemoveFolder(testRoot) - } - }) - - beforeEach(async function () { - sandbox = sinon.createSandbox() - await closeAllEditors() - }) - - afterEach(async function () { - sandbox.restore() - }) - - it('can build a SAM Template', async function () { - // Instantiate AppNode - const samAppLocation = { - samTemplateUri: vscode.Uri.file(cfnTemplatePath), - workspaceFolder: workspaceFolder, - } as SamAppLocation - const appNode = new AppNode(samAppLocation) - - getTestWindow().onDidShowQuickPick((input) => { - if (input.title?.includes('Specify parameter source for build')) { - input.acceptItem(input.items[0]) - const item = input.items[0] as DataQuickPickItem - assert.deepStrictEqual(item.data as ParamsSource, ParamsSource.Specify) - } else if (input.title?.includes('Select build flags')) { - const item1 = input.items[2] as DataQuickPickItem - const item2 = input.items[4] as DataQuickPickItem - const item3 = input.items[7] as DataQuickPickItem - input.acceptItems(item1, item2, item3) // --cached, --parallel, --use-container - - assert.strictEqual(item1.data as string, '--cached') - assert.strictEqual(item2.data as string, '--parallel') - assert.strictEqual(item3.data as string, '--use-container') - } - }) - - const response = await runBuild(appNode) - - assert.deepStrictEqual(response, { - isSuccess: true, - }) - - const builtTemplatePath = path.join(testDir, samApplicationName, '.aws-sam', 'build', 'template.yaml') - assert.ok(await fs.existsFile(builtTemplatePath)) - }) - }) - - async function createSamApplication(location: string, scenario: TestScenario): Promise { - const initArguments: SamCliInitArgs = { - name: samApplicationName, - location: location, - dependencyManager: scenario.dependencyManager, - } - if (scenario.baseImage) { - initArguments.baseImage = scenario.baseImage - } else { - initArguments.runtime = scenario.runtime - initArguments.template = helloWorldTemplate - } - const samCliContext = getSamCliContext() - await runSamCliInit(initArguments, samCliContext) - // XXX: Fixes flakiness. Ensures the files from creation of sam - // app are processed by code lens file watcher. Otherwise, potential - // issues of file not in registry before it is found. - await globals.codelensRootRegistry.rebuild() - } -}) diff --git a/packages/core/src/testInteg/shared/codelens/samTemplateCodeLensProvider.test.ts b/packages/core/src/testInteg/shared/codelens/samTemplateCodeLensProvider.test.ts deleted file mode 100644 index 45167136a0..0000000000 --- a/packages/core/src/testInteg/shared/codelens/samTemplateCodeLensProvider.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import * as vscode from 'vscode' -import * as semver from 'semver' -import { TemplateSymbolResolver } from '../../../shared/cloudformation/templateSymbolResolver' -import { SamTemplateCodeLensProvider } from '../../../shared/codelens/samTemplateCodeLensProvider' -import { LaunchConfiguration } from '../../../shared/debug/launchConfiguration' -import { API_TARGET_TYPE, TEMPLATE_TARGET_TYPE } from '../../../shared/sam/debugger/awsSamDebugConfiguration' -import * as workspaceUtils from '../../../shared/utilities/workspaceUtils' -import { VSCODE_EXTENSION_ID } from '../../../shared/extensions' -import { activateExtension } from '../../../shared/utilities/vsCodeUtils' -import { stub } from '../../../test/utilities/stubber' - -describe('SamTemplateCodeLensProvider', async function () { - let codeLensProvider: SamTemplateCodeLensProvider = new SamTemplateCodeLensProvider() - let document: vscode.TextDocument - let launchConfig: LaunchConfiguration - let templateUri: vscode.Uri - // not used in `provideCodeLenses` - const mockCancellationToken: vscode.CancellationToken = {} as any as vscode.CancellationToken - - beforeEach(async function () { - codeLensProvider = new SamTemplateCodeLensProvider() - document = (await workspaceUtils.openTextDocument('python3.7-plain-sam-app/template.yaml'))! - templateUri = document.uri - launchConfig = new LaunchConfiguration(document.uri) - }) - - it('provides a CodeLens for a file with a new resource', async function () { - // - redhat.vscode-yaml requires vscode 1.63: https://github.com/redhat-developer/vscode-yaml/blob/main/package.json - // - the codelens behavior changed with the vscode 1.66 release - if (semver.lt(vscode.version, '1.66.0')) { - this.skip() - } - await activateExtension(VSCODE_EXTENSION_ID.yaml, false) - - const codeLenses = await codeLensProvider.provideCodeLenses( - document, - mockCancellationToken, - new TemplateSymbolResolver(document), - launchConfig, - true - ) - - const expectedCodeLens = [ - new vscode.CodeLens(new vscode.Range(25, 4, 31, 0), { - title: 'AWS: Add Debug Configuration', - command: 'aws.addSamDebugConfiguration', - arguments: [{ resourceName: 'Function2NotInLaunchJson', rootUri: templateUri }, TEMPLATE_TARGET_TYPE], - }), - new vscode.CodeLens(new vscode.Range(31, 4, 43, 0), { - title: 'AWS: Add Debug Configuration', - command: 'aws.addSamDebugConfiguration', - arguments: [{ resourceName: 'Function3NotInLaunchJson', rootUri: templateUri }, TEMPLATE_TARGET_TYPE], - }), - ] - - expectedCodeLens.splice( - 0, - 0, - new vscode.CodeLens(new vscode.Range(37, 12, 43, 0), { - title: 'AWS: Add API Debug Configuration', - command: 'aws.addSamDebugConfiguration', - arguments: [{ resourceName: 'Function3NotInLaunchJson', rootUri: templateUri }, API_TARGET_TYPE], - }) - ) - - assert.deepStrictEqual(codeLenses, expectedCodeLens) - }) - - it('provides no code lenses for a file with no resources', async function () { - const resolver = stub(TemplateSymbolResolver) - resolver.getResourcesOfKind.resolves([]) - - const codeLenses = await codeLensProvider.provideCodeLenses( - document, - mockCancellationToken, - resolver, - launchConfig - ) - - assert.deepStrictEqual(codeLenses, []) - }) -}) diff --git a/packages/core/src/testInteg/stepFunctions/init.test.ts b/packages/core/src/testInteg/stepFunctions/init.test.ts deleted file mode 100644 index 894fcedb35..0000000000 --- a/packages/core/src/testInteg/stepFunctions/init.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import assert from 'assert' -import { createTestWorkspaceFolder, toTextEditor } from '../../test/testUtil' -import vscode from 'vscode' -import { ASLLanguageClient } from '../../stepFunctions/asl/client' -import { waitUntil } from '../../shared' - -describe('stepFunctions ASL LSP', async function () { - let tempFolder: string - - beforeEach(async function () { - // Make a temp folder for all these tests - const d = await createTestWorkspaceFolder() - tempFolder = d.uri.fsPath - }) - - it('init', async function () { - const stateMachineFileText = `{ - -}` - const fileName = 'stepfunction.asl' - const editor = await toTextEditor(stateMachineFileText, fileName, tempFolder) - await waitUntil(async () => ASLLanguageClient.isReady, { - timeout: 30000, - interval: 500, - }) - const result = (await vscode.commands.executeCommand( - 'vscode.executeCompletionItemProvider', - editor.document.uri, - new vscode.Position(1, 4) - )) as vscode.CompletionList - assert.deepStrictEqual( - result.items.map((item) => item.label), - ['Comment', 'QueryLanguage', 'StartAt', 'States', 'TimeoutSeconds', 'Version'] - ) - }) -}) diff --git a/packages/core/src/threatComposer/activation.ts b/packages/core/src/threatComposer/activation.ts deleted file mode 100644 index b3e644959f..0000000000 --- a/packages/core/src/threatComposer/activation.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import { ThreatComposerEditorProvider } from './threatComposerEditorProvider' -import { CreateNewThreatComposer, NewThreatComposerFile } from './commands/createNewThreatComposerFile' - -/** - * Activates the extension and registers all necessary components. - * @param extensionContext The extension context object. - */ -export async function activate(extensionContext: vscode.ExtensionContext): Promise { - extensionContext.subscriptions.push( - ThreatComposerEditorProvider.register(extensionContext), - CreateNewThreatComposer.register(), - NewThreatComposerFile.register() - ) -} diff --git a/packages/core/src/threatComposer/commands/createNewThreatComposerFile.ts b/packages/core/src/threatComposer/commands/createNewThreatComposerFile.ts deleted file mode 100644 index dbcf6c2904..0000000000 --- a/packages/core/src/threatComposer/commands/createNewThreatComposerFile.ts +++ /dev/null @@ -1,62 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import path from 'path' -import { Commands } from '../../shared/vscode/commands2' -import vscode from 'vscode' -import { ThreatComposerEditorProvider } from '../threatComposerEditorProvider' -import fs from '../../shared/fs/fs' - -/** - * This is a helper function to create a new Threat Composer file. - * It first checks if a workspace has been opened, as we would need a workspace to save the file. - * User is then prompted for a file name that is saved in the workspace as .tc.json. - * The new file is then opened in a Threat Composer view. - */ -const createNewThreatComposerFile = async () => { - if (vscode.workspace.workspaceFolders) { - const rootFolder = vscode.workspace.workspaceFolders[0].uri.fsPath - - const title = await vscode.window.showInputBox({ - prompt: 'Enter name for file', - validateInput: async (text) => { - if (text && (await fs.existsFile(path.join(rootFolder, `${text}.tc.json`)))) { - return 'The specified file already exists' - } - }, - }) - - if (!title) { - return - } - - const fileContent = '' // Empty content would be accepted by TC which will save default structure automatically - const filePath = path.join(rootFolder, `${title}.tc.json`) - await fs.writeFile(filePath, fileContent) - - await vscode.commands.executeCommand( - 'vscode.openWith', - vscode.Uri.file(filePath), - ThreatComposerEditorProvider.viewType - ) - } else { - void vscode.window.showErrorMessage('Workspace folder not defined') - } -} - -/** - * Command to Create a new Threat Composer File through the command pallet. - * The only difference with `NewThreatComposerFile` is in the text that is displayed - */ -export const CreateNewThreatComposer = Commands.declare( - 'aws.createNewThreatComposer', - () => createNewThreatComposerFile -) - -/** - * Command to Create a new Threat Composer File through the New File option. - * The only difference with `CreateNewThreatComposer` is in the text that is displayed - */ -export const NewThreatComposerFile = Commands.declare('aws.newThreatComposerFile', () => createNewThreatComposerFile) diff --git a/packages/core/src/threatComposer/handleMessage.ts b/packages/core/src/threatComposer/handleMessage.ts deleted file mode 100644 index d12db251c7..0000000000 --- a/packages/core/src/threatComposer/handleMessage.ts +++ /dev/null @@ -1,59 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - Command, - LoadStageMessage, - LogMessage, - Message, - MessageType, - SaveFileRequestMessage, - WebviewContext, -} from './types' -import { autoSaveFileMessageHandler, saveFileMessageHandler } from './handlers/saveFileMessageHandler' -import { logMessageHandler } from './handlers/logMessageHandler' -import { openFeedbackMessageHandler } from './handlers/openFeedbackMessageHandler' -import { initMessageHandler, reloadMessageHandler } from './handlers/initMessageHandler' -import { loadStageMessageHandler } from './handlers/loadStageMessageHandler' - -/** - * Handles messages received from the webview. Depending on the message type and command, it - * calls the appropriate handler function - * @param message The message received from the webview - * @param context The context object containing information about the webview environment - */ -export async function handleMessage(message: unknown, context: WebviewContext) { - const composerMessage = message as Message - - const { command, messageType } = composerMessage - - if (messageType === MessageType.REQUEST) { - switch (command) { - case Command.AUTO_SAVE_FILE: - void autoSaveFileMessageHandler(message as SaveFileRequestMessage, context) - break - case Command.SAVE_FILE: - void saveFileMessageHandler(message as SaveFileRequestMessage, context) - break - case Command.INIT: - void initMessageHandler(context) - break - case Command.RELOAD: - void reloadMessageHandler(context) - } - } else if (messageType === MessageType.BROADCAST) { - switch (command) { - case Command.LOG: - void logMessageHandler(message as LogMessage, context) - break - case Command.OPEN_FEEDBACK: - openFeedbackMessageHandler() - break - case Command.LOAD_STAGE: - void loadStageMessageHandler(message as LoadStageMessage, context) - break - } - } -} diff --git a/packages/core/src/threatComposer/handlers/initMessageHandler.ts b/packages/core/src/threatComposer/handlers/initMessageHandler.ts deleted file mode 100644 index aed5f73a8b..0000000000 --- a/packages/core/src/threatComposer/handlers/initMessageHandler.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Command, MessageType, WebviewContext } from '../types' -import { broadcastFileChange } from './onFileChangedHandler' - -/** - * Handler for when the Threat Composer view is ready. - * This handler is used to initialize the webview with the contents of the Threat Composer file - * selected. - * @param context The context object containing the necessary information for the webview. - */ -export async function initMessageHandler(context: WebviewContext) { - const filePath = context.defaultTemplatePath - - try { - const fileContents = context.textDocument.getText().toString() - context.fileStates[filePath] = { fileContents: fileContents } - context.autoSaveFileState[filePath] = { fileContents: fileContents } - await broadcastFileChange(context.defaultTemplateName, filePath, fileContents, context.panel) - if (context.loaderNotification) { - context.loaderNotification.progress.report({ increment: 20 }) - } - } catch (e) { - await context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.INIT, - filePath: filePath, - isSuccess: false, - failureReason: (e as Error).message, - }) - } -} - -/** - * Handler for reloading the Threat Composer file. - * This handler is used to reload the Threat Composer file with the latest contents. - * @param context The context object containing the necessary information for the webview. - */ -export async function reloadMessageHandler(context: WebviewContext) { - const filePath = context.defaultTemplatePath - - try { - if (!context.fileStates[filePath]) { - const fileContents = context.textDocument.getText().toString() - context.fileStates[filePath] = { fileContents: fileContents } - } - - if (!context.autoSaveFileState[filePath]) { - context.autoSaveFileState[filePath] = context.fileStates[filePath] - } - - const fileContents = context.autoSaveFileState[filePath].fileContents - await broadcastFileChange(context.defaultTemplateName, filePath, fileContents, context.panel) - } catch (e) { - await context.panel.webview.postMessage({ - messageType: MessageType.RESPONSE, - command: Command.RELOAD, - filePath: filePath, - isSuccess: false, - failureReason: (e as Error).message, - }) - } -} diff --git a/packages/core/src/threatComposer/handlers/loadStageMessageHandler.ts b/packages/core/src/threatComposer/handlers/loadStageMessageHandler.ts deleted file mode 100644 index 044180ab4f..0000000000 --- a/packages/core/src/threatComposer/handlers/loadStageMessageHandler.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { LoadStageMessage as LoadStageMessage, WebviewContext } from '../types' - -/** - * Handler for managing the various stages of the webview. - * As the webview completes each stage, it sends a message to the extension, which updates the - * Threat Composer load notification. - * @param message The message containing the load stage. - * @param context The context object containing the necessary information for the webview. - */ -export async function loadStageMessageHandler(message: LoadStageMessage, context: WebviewContext) { - switch (message.loadStage) { - case 'API_LOADED': - if (context.loaderNotification) { - context.loaderNotification.progress.report({ increment: 20 }) - } - break - case 'RENDER_COMPLETE': - if (context.loaderNotification) { - context.loaderNotification.progress.report({ increment: 30 }) - if (context.loaderNotification.promiseResolve) { - context.loaderNotification.promiseResolve() - } - } - break - } -} diff --git a/packages/core/src/threatComposer/handlers/logMessageHandler.ts b/packages/core/src/threatComposer/handlers/logMessageHandler.ts deleted file mode 100644 index 85ed05af10..0000000000 --- a/packages/core/src/threatComposer/handlers/logMessageHandler.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Command, LogMessage, Message, MessageType, WebviewContext } from '../types' -import { getLogger } from '../../shared/logger/logger' -import * as vscode from 'vscode' -import { sendThreatComposerErrored } from './webviewTelemetryHandler' - -/** - * Handler for logging messages from the webview. - * @param message The message containing the log message and other metadata. - * @param context The context object containing the necessary information for the webview. - */ -export async function logMessageHandler(message: LogMessage, context: WebviewContext) { - const logger = getLogger() - switch (message.logType) { - case 'INFO': - logger.info(message.logMessage) - if (message.showNotification) { - void vscode.window.showInformationMessage(message.logMessage) - } - return - case 'WARNING': - logger.warn(message.logMessage) - if (message.showNotification) { - void vscode.window.showWarningMessage(message.logMessage) - } - return - case 'ERROR': - logger.error(message.logMessage) - sendThreatComposerErrored({ - reason: message.logMessage, - id: context.fileId, - }) - if (message.showNotification) { - if (message.notificationType === 'INVALID_JSON') { - const selection = await vscode.window.showErrorMessage( - `${message.logMessage}. Please re-open the file in a text editor or overwrite the contents of the file with Threat Composer JSON.`, - 'Open in default editor', - 'Overwrite' - ) - - if (selection === 'Open in default editor') { - context.panel.dispose() - await vscode.commands.executeCommand('vscode.openWith', context.textDocument.uri, 'default') - } else if (selection === 'Overwrite') { - const broadcastMessage: Message = { - messageType: MessageType.BROADCAST, - command: Command.OVERWRITE_FILE, - } - await context.panel.webview.postMessage(broadcastMessage) - } - } else { - void vscode.window.showErrorMessage(message.logMessage) - } - } - return - } -} diff --git a/packages/core/src/threatComposer/handlers/onFileChangedHandler.ts b/packages/core/src/threatComposer/handlers/onFileChangedHandler.ts deleted file mode 100644 index 6f40b8ddbd..0000000000 --- a/packages/core/src/threatComposer/handlers/onFileChangedHandler.ts +++ /dev/null @@ -1,77 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Command, FileChangedMessage, MessageType, WebviewContext } from '../types' -import vscode from 'vscode' -import fs from '../../shared/fs/fs' -import { getLogger } from '../../shared/logger/logger' - -/** - * Function to call when the text document has been modified - * If the change occurs due to - * a user save action, file change is ignored. - * an auto save that is persisted as a result of the 'AutoSave' setting turned on in VSCode, - * update file state. - * an external file change, - * if a local, unsaved change exist in Threat Composer, the file change is ignored and the - * user is warned. - * if no unsaved local changes exist, the web view is notified of the external file change. - * @param context: The Webview Context that contain the details of the file and the webview - */ -export async function onFileChanged(context: WebviewContext) { - const filePath = context.defaultTemplatePath - const fileName = context.defaultTemplateName - - const fileContents = await fs.readFileText(filePath) - - // If the change event is due to a save action by the user, this trigger can be ignored. - if (fileContents !== context.fileStates[filePath].fileContents) { - if (fileContents === context.autoSaveFileState[filePath].fileContents) { - // Contents of the file are the same as that of the 'autoSaveFileState'. This - // means that the file change was triggered as a result of auto save, we - // don't have to notify the webview However, 'fileStates' need to be updated. - context.fileStates[filePath] = { fileContents: fileContents } - } else if (context.fileStates[filePath].fileContents === context.autoSaveFileState[filePath].fileContents) { - // There are no unsaved local changes in the file, so the file change is - // triggered due to an external change in the file. This must be propagated - // to the webview, so that it can be updated. - getLogger().debug('DocumentChanged') - await broadcastFileChange(fileName, filePath, fileContents, context.panel) - context.fileStates[filePath] = { fileContents: fileContents } - context.autoSaveFileState[filePath] = { fileContents: fileContents } - } else { - // There are unsaved local changes in the file, and the file has been - // changed externally. The file is currently in a dirty state. This file - // change trigger is ignored. When the user decides to save the local - // changes, they can decide to overwrite the file. - void vscode.window.showWarningMessage(`${fileName} has been modified externally.`) - getLogger().warn('Document Changed externally before local changes are saved') - } - } -} - -/** - * Helper Function to broadcast the file change to the Threat Composer view - * @param fileName: Name of the file that was changed - * @param filePath: The path to the file - * @param fileContents: The updated file contents - * @param panel: the panel which contains the webview to be notified. - */ -export async function broadcastFileChange( - fileName: string, - filePath: string, - fileContents: string, - panel: vscode.WebviewPanel -) { - const fileChangedMessage: FileChangedMessage = { - messageType: MessageType.BROADCAST, - command: Command.FILE_CHANGED, - fileName: fileName, - fileContents: fileContents, - filePath: filePath, - } - - await panel.webview.postMessage(fileChangedMessage) -} diff --git a/packages/core/src/threatComposer/handlers/onThemeChangedHandler.ts b/packages/core/src/threatComposer/handlers/onThemeChangedHandler.ts deleted file mode 100644 index 697f87d72c..0000000000 --- a/packages/core/src/threatComposer/handlers/onThemeChangedHandler.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { Command, MessageType, ThemeChangedMessage } from '../types' -import { ColorTheme, ColorThemeKind, WebviewPanel } from 'vscode' - -/** - * Handler for theme change event. It will broadcast the theme change event to the - * Threat Composer webview, so that it can update the UI accordingly. - * @param colorTheme The updated color theme - * @param panel The panel which contains the webview to be notified. - */ -export async function onThemeChanged(colorTheme: ColorTheme, panel: WebviewPanel) { - const colorThemeKind = colorTheme.kind - const newTheme = - colorThemeKind === ColorThemeKind.Dark || colorThemeKind === ColorThemeKind.HighContrast ? 'dark' : 'light' - await broadcastThemeChange(newTheme, panel) -} - -/** - * Helper Function to broadcast the theme change to the Threat Composer view - * @param newTheme: The updated theme - * @param panel: the panel which contains the webview to be notified. - */ -export async function broadcastThemeChange(newTheme: string, panel: WebviewPanel) { - const themeChangedMessage: ThemeChangedMessage = { - messageType: MessageType.BROADCAST, - command: Command.THEME_CHANGED, - newTheme: newTheme, - } - - await panel.webview.postMessage(themeChangedMessage) -} diff --git a/packages/core/src/threatComposer/handlers/openFeedbackMessageHandler.ts b/packages/core/src/threatComposer/handlers/openFeedbackMessageHandler.ts deleted file mode 100644 index 4d8886f8f9..0000000000 --- a/packages/core/src/threatComposer/handlers/openFeedbackMessageHandler.ts +++ /dev/null @@ -1,17 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { submitFeedback } from '../../feedback/vue/submitFeedback' -import { placeholder } from '../../shared/vscode/commands2' - -/** - * Handler for opening the feedback message. - * This function is responsible for submitting the feedback message - * to the feedback service. - * @returns {Promise} A promise that resolves when the feedback message has been submitted. - */ -export function openFeedbackMessageHandler() { - void submitFeedback(placeholder, 'Threat Composer') -} diff --git a/packages/core/src/threatComposer/handlers/saveFileMessageHandler.ts b/packages/core/src/threatComposer/handlers/saveFileMessageHandler.ts deleted file mode 100644 index dc042305aa..0000000000 --- a/packages/core/src/threatComposer/handlers/saveFileMessageHandler.ts +++ /dev/null @@ -1,181 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import vscode from 'vscode' -import { - Command, - MessageType, - SaveCompleteSubType, - SaveFileRequestMessage, - SaveFileResponseMessage, - WebviewContext, -} from '../types' -import { telemetry } from '../../shared/telemetry/telemetry' -import { ToolkitError } from '../../shared/errors' -import _ from 'lodash' - -/** - * Handler for saving a file message from the webview. - * This function performs the following tasks: - * 1. It saves the file contents to the workspace. - * 2. It saves the file using the `vscode` command `workbench.action.files.save`. - * 3. It handles the case where the file has been externally modified. - * 4. It sends a response message back to the webview with the save status and any failure reason. - * @returns {Promise} A promise that resolves when the file has been saved. - * @param request The request message containing the file contents and path. - * @param context The webview context containing the necessary information for saving the file. - */ -export async function saveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) { - let saveCompleteSubType = SaveCompleteSubType.SAVE_FAILED - let saveSuccess = false - let errorMessage: string | undefined = undefined - let previousAutoSaveFileContent: string | undefined = undefined - - // If filePath is empty, save contents in default template file - const filePath = context.defaultTemplatePath - - await telemetry.threatComposer_fileSaved.run(async (span) => { - span.record({ - id: context.fileId, - saveType: 'MANUAL_SAVE', - }) - - try { - if (context.fileStates[filePath] && context.autoSaveFileState[filePath]) { - previousAutoSaveFileContent = context.autoSaveFileState[filePath].fileContents - - if (context.fileStates[filePath].fileContents !== request.fileContents) { - context.autoSaveFileState[filePath] = { fileContents: request.fileContents } - - await saveWorkspace(context, request.fileContents) - - await vscode.commands.executeCommand('workbench.action.files.save') - - if (context.textDocument.isDirty) { - throw new Error('Document has been modified externally') - } - - void vscode.window.showInformationMessage('Threat Composer JSON has been saved') - - saveCompleteSubType = SaveCompleteSubType.SAVED - } else { - saveCompleteSubType = SaveCompleteSubType.SAVE_SKIPPED_SAME_CONTENT - } - saveSuccess = true - } else { - throw new Error('Previous state of file not found') - } - } catch (e) { - if (previousAutoSaveFileContent !== undefined) { - context.autoSaveFileState[filePath] = { fileContents: previousAutoSaveFileContent } - } - - errorMessage = (e as Error).message - saveSuccess = false - saveCompleteSubType = SaveCompleteSubType.SAVE_FAILED - void vscode.window.showErrorMessage(errorMessage) - throw new ToolkitError(errorMessage, { code: 'Failed to Save' }) - } finally { - const saveFileResponseMessage: SaveFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.SAVE_FILE, - filePath: filePath, - isSuccess: saveSuccess, - failureReason: errorMessage, - saveCompleteSubType: saveCompleteSubType, - } - await context.panel.webview.postMessage(saveFileResponseMessage) - } - }) -} - -/** - * Handler for auto saving a file message from the webview. - * This function performs the following tasks: - * 1. It saves the file contents to the workspace. - * 2. It handles the case when the change has resulted in a similar JSON (will not save if the - * change is due to formatting or ordering changes that do not affect the JSON structure). - * 3. It sends a response message back to the webview with the save status and any failure reason. - * @returns {Promise} A promise that resolves when the file has been saved. - * @param request The request message containing the file contents and path. - * @param context The webview context containing the necessary information for saving the file. - */ -export async function autoSaveFileMessageHandler(request: SaveFileRequestMessage, context: WebviewContext) { - let saveCompleteSubType = SaveCompleteSubType.SAVE_FAILED - let saveSuccess = false - let errorMessage: string | undefined = undefined - let previousAutoSaveFileContent: string | undefined = undefined - - // If filePath is empty, save contents in default template file - const filePath = context.defaultTemplatePath - - try { - if (context.autoSaveFileState[filePath]) { - previousAutoSaveFileContent = context.autoSaveFileState[filePath].fileContents - - if (previousAutoSaveFileContent !== request.fileContents) { - let previousState - - try { - previousState = JSON.parse(previousAutoSaveFileContent) - } catch (e) { - previousState = {} - } - - const currentState = JSON.parse(request.fileContents) - - if (!_.isEqual(previousState, currentState)) { - context.autoSaveFileState[filePath] = { fileContents: request.fileContents } - - await saveWorkspace(context, request.fileContents) - saveCompleteSubType = SaveCompleteSubType.SAVED - } else { - saveCompleteSubType = SaveCompleteSubType.SAVE_SKIPPED_SAME_JSON - } - } else { - saveCompleteSubType = SaveCompleteSubType.SAVE_SKIPPED_SAME_CONTENT - } - saveSuccess = true - } else { - throw new Error('Previous state of file not found') - } - } catch (e) { - if (previousAutoSaveFileContent !== undefined) { - context.autoSaveFileState[filePath] = { fileContents: previousAutoSaveFileContent } - } - - errorMessage = (e as Error).message - saveSuccess = false - saveCompleteSubType = SaveCompleteSubType.SAVE_FAILED - void vscode.window.showErrorMessage(errorMessage) - throw new ToolkitError(errorMessage, { code: 'Failed to Auto Save' }) - } finally { - const saveFileResponseMessage: SaveFileResponseMessage = { - messageType: MessageType.RESPONSE, - command: Command.AUTO_SAVE_FILE, - filePath: filePath, - isSuccess: saveSuccess, - failureReason: errorMessage, - saveCompleteSubType: saveCompleteSubType, - } - await context.panel.webview.postMessage(saveFileResponseMessage) - } -} - -/** - * Saves to the workspace with the provided file contents. - * @param context The webview context containing the necessary information for saving the file. - * @param fileContents The file contents to save. - */ -async function saveWorkspace(context: WebviewContext, fileContents: string) { - context.autoSaveFileState[context.defaultTemplatePath] = { fileContents: fileContents } - - const edit = new vscode.WorkspaceEdit() - // Just replace the entire document every time for this example extension. - // A more complete extension should compute minimal edits instead. - edit.replace(context.textDocument.uri, new vscode.Range(0, 0, context.textDocument.lineCount, 0), fileContents) - - await vscode.workspace.applyEdit(edit) -} diff --git a/packages/core/src/threatComposer/handlers/webviewTelemetryHandler.ts b/packages/core/src/threatComposer/handlers/webviewTelemetryHandler.ts deleted file mode 100644 index cd5ef8a36d..0000000000 --- a/packages/core/src/threatComposer/handlers/webviewTelemetryHandler.ts +++ /dev/null @@ -1,33 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import { telemetry, ThreatComposerClosed, ThreatComposerError } from '../../shared/telemetry/telemetry' - -/** - * Function to send an error occurred telemetry, for errors that have occurred - * in the Threat Composer view. - * @param metadata: Error metadata, including the error reason and result - */ -export function sendThreatComposerErrored(metadata: ThreatComposerError) { - telemetry.threatComposer_error.emit({ - result: metadata.result ?? 'Succeeded', - reason: metadata.reason ?? '', - id: metadata.id, - }) -} - -/** - * Function to send a cancelled telemetry when the open action is cancelled by - * the user. This is usually when Threat Composer takes longer than usual to open, - * and the user has cancelled the open action. - * @param metadata: Cancelled metadata, including the error reason and result - */ -export function sendThreatComposerOpenCancelled(metadata: ThreatComposerClosed) { - telemetry.threatComposer_closed.emit({ - result: metadata.result ?? 'Succeeded', - reason: metadata.reason ?? '', - id: metadata.id, - }) -} diff --git a/packages/core/src/threatComposer/threatComposerEditor.ts b/packages/core/src/threatComposer/threatComposerEditor.ts deleted file mode 100644 index 6bfb9ce973..0000000000 --- a/packages/core/src/threatComposer/threatComposerEditor.ts +++ /dev/null @@ -1,233 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as nls from 'vscode-nls' -import * as path from 'path' -import * as vscode from 'vscode' -import { handleMessage } from './handleMessage' -import { FileWatchInfo, WebviewContext } from './types' -import { telemetry } from '../shared/telemetry/telemetry' -import { onFileChanged } from './handlers/onFileChangedHandler' -import { onThemeChanged } from './handlers/onThemeChangedHandler' -import { sendThreatComposerOpenCancelled } from './handlers/webviewTelemetryHandler' -import { getLogger } from '../shared/logger/logger' - -const localize = nls.loadMessageBundle() - -/** - * The main class for the Threat Composer Editor. This class handles the creation and management - * of the webview panel for the Threat Composer Editor. It also handles the communication - * between the webview and the extension context. This class also stores the state of the - * tc.json file, that is being edited in the webview panel, in the property 'fileStates'. The - * 'autoSaveFileStates' property is used to store local changes that are being made in the - * webview panel. - */ -export class ThreatComposerEditor { - public readonly documentUri: vscode.Uri - public webviewPanel: vscode.WebviewPanel - protected readonly disposables: vscode.Disposable[] = [] - protected isPanelDisposed = false - private readonly onVisualizationDisposeEmitter = new vscode.EventEmitter() - private fileId: string - public workSpacePath: string - public defaultTemplatePath: string - public defaultTemplateName: string - // fileStates is used to store the state of the file being edited and achieve bi-direction sync - public fileStates: Record - // autoSaveFileStates is used to store local changes that are being made in the webview panel. - public autoSaveFileStates: Record - private getWebviewContent: () => Promise - - public constructor( - textDocument: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - context: vscode.ExtensionContext, - fileId: string, - getWebviewContent: () => Promise - ) { - this.getWebviewContent = getWebviewContent - this.documentUri = textDocument.uri - this.webviewPanel = webviewPanel - this.fileStates = {} - this.autoSaveFileStates = {} - this.workSpacePath = path.dirname(textDocument.uri.fsPath) - this.defaultTemplatePath = textDocument.uri.fsPath - this.defaultTemplateName = path.basename(this.defaultTemplatePath) - this.fileId = fileId - - this.setupWebviewPanel(textDocument, context) - } - - public get onVisualizationDisposeEvent(): vscode.Event { - return this.onVisualizationDisposeEmitter.event - } - - public getPanel(): vscode.WebviewPanel | undefined { - if (!this.isPanelDisposed) { - return this.webviewPanel - } - } - - public showPanel(): void { - this.getPanel()?.reveal() - } - - public async refreshPanel(context: vscode.ExtensionContext) { - if (!this.isPanelDisposed) { - this.webviewPanel.dispose() - const document = await vscode.workspace.openTextDocument(this.documentUri) - this.setupWebviewPanel(document, context) - } - } - - protected getText(textDocument: vscode.TextDocument): string { - return textDocument.getText() - } - - /** - * Sets up the webview panel for the Threat Composer Editor. This includes creating the - * panel, setting up the webview content, and handling the communication between the webview - * and the extension context. - * @param textDocument The text document to be displayed in the webview panel. - * @param context The extension context. - * @private - */ - private setupWebviewPanel(textDocument: vscode.TextDocument, context: vscode.ExtensionContext) { - const documentUri = textDocument.uri - - const contextObject: WebviewContext = { - panel: this.webviewPanel, - textDocument: textDocument, - disposables: this.disposables, - workSpacePath: this.workSpacePath, - defaultTemplatePath: this.defaultTemplatePath, - defaultTemplateName: this.defaultTemplateName, - fileStates: this.fileStates, - autoSaveFileState: this.autoSaveFileStates, - loaderNotification: undefined, - fileId: this.fileId, - } - - async function cancelOpenInThreatComposer(reason: string) { - contextObject.panel.dispose() - await vscode.commands.executeCommand('vscode.openWith', documentUri, 'default') - sendThreatComposerOpenCancelled({ - reason: reason, - id: contextObject.fileId, - }) - } - - void vscode.window.withProgress( - { - location: vscode.ProgressLocation.Notification, - title: 'Opening file in Threat Composer', - cancellable: true, - }, - (progress, token) => { - token.onCancellationRequested(async () => { - getLogger().debug('User canceled opening in TC operation') - await cancelOpenInThreatComposer('User canceled opening in THreatComposer operation') - }) - - progress.report({ increment: 0 }) - - return new Promise(async (resolve) => { - contextObject.loaderNotification = { - progress: progress, - cancellationToken: token, - promiseResolve: () => { - resolve() - }, - } - - // Initialise the panel panel - this.initialiseVisualizationWebviewPanel(documentUri, context) - - // Hook up event handlers so that we can synchronize the webview with the text document. - // - // The text document acts as our model, so we have to sync change in the document to our - // editor and sync changes in the editor back to the document. - // - // Remember that a single text document can also be shared between multiple custom - // editors (this happens for example when you split a custom editor) - - contextObject.disposables.push( - vscode.workspace.onDidChangeTextDocument(async () => { - await onFileChanged(contextObject) - }) - ) - - // Hook up event handler to update the UI on theme changes within VSCode. - contextObject.disposables.push( - vscode.window.onDidChangeActiveColorTheme(async (data) => { - await onThemeChanged(data, contextObject.panel) - }) - ) - - // Handle messages from the webview - this.disposables.push( - this.webviewPanel.webview.onDidReceiveMessage((message) => - handleMessage(message, contextObject) - ) - ) - - // When the panel is closed, dispose of any disposables/remove subscriptions - const disposePanel = async () => { - if (this.isPanelDisposed) { - return - } - - await telemetry.threatComposer_closed.run(async (span) => { - span.record({ - id: this.fileId, - }) - this.isPanelDisposed = true - contextObject.loaderNotification?.promiseResolve() - this.onVisualizationDisposeEmitter.fire() - for (const disposable of this.disposables) { - disposable.dispose() - } - this.onVisualizationDisposeEmitter.dispose() - }) - } - - this.disposables.push( - this.webviewPanel.onDidDispose(async () => { - await disposePanel() - }) - ) - - progress.report({ increment: 10 }) - - // Set the initial html for the webpage - this.webviewPanel.webview.html = await this.getWebviewContent() - - progress.report({ increment: 20 }) - }) - } - ) - } - - /** - * Initialises the webview panel for the Threat Composer Editor. This includes setting up the - * panel's title and webview options. - * @param documentUri - * @param context - * @private - */ - private initialiseVisualizationWebviewPanel(documentUri: vscode.Uri, context: vscode.ExtensionContext) { - // Setup initial content for the webview - this.webviewPanel.webview.options = { - enableScripts: true, - localResourceRoots: [context.extensionUri], - } - - this.webviewPanel.title = localize( - 'AWS.threatComposer.page.title', - '{0} (Threat Composer)', - path.basename(documentUri.fsPath) - ) - } -} diff --git a/packages/core/src/threatComposer/threatComposerEditorProvider.ts b/packages/core/src/threatComposer/threatComposerEditorProvider.ts deleted file mode 100644 index a742e17b40..0000000000 --- a/packages/core/src/threatComposer/threatComposerEditorProvider.ts +++ /dev/null @@ -1,186 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -import * as vscode from 'vscode' -import * as nls from 'vscode-nls' -import fs from '../shared/fs/fs' -import request from '../shared/request' -import { getLogger } from '../shared/logger/logger' -import { ThreatComposerEditor } from './threatComposerEditor' -import { ToolkitError } from '../shared/errors' -import { telemetry } from '../shared/telemetry/telemetry' -import { getClientId } from '../shared/telemetry/util' -import { getRandomString, getStringHash } from '../shared/utilities/textUtilities' -import globals from '../shared/extensionGlobals' - -const localize = nls.loadMessageBundle() - -// Change this to true for local dev -const isLocalDev = false -const localhost = 'http://127.0.0.1:3000' -const cdn = 'https://ide-toolkits.threat-composer.aws.dev' -let clientId = '' - -/** - * Provider for ThreatComposer editors. - * - * ThreatComposer editor is used for `.tc.json` files, which are just json files. - * To get started, run this extension and open an empty `.tc.json` file in VS Code. - */ -export class ThreatComposerEditorProvider implements vscode.CustomTextEditorProvider { - public static readonly viewType = 'threatComposer.tc.json' - - /** - * Registers a new custom editor provider for `.tc.json` files. - * @remarks This should only be called once per extension. - * @param context The extension context - */ - public static register(context: vscode.ExtensionContext): vscode.Disposable { - const provider = new ThreatComposerEditorProvider(context) - return vscode.window.registerCustomEditorProvider(ThreatComposerEditorProvider.viewType, provider, { - webviewOptions: { - enableFindWidget: true, - retainContextWhenHidden: true, // Set to 'false' to re-render on tab switch - }, - }) - } - - protected readonly name: string = 'ThreatComposerManager' - protected readonly managedVisualizations = new Map() - protected extensionContext: vscode.ExtensionContext - protected webviewHtml: string - protected readonly logger = getLogger() - - constructor(context: vscode.ExtensionContext) { - this.extensionContext = context - this.webviewHtml = '' - } - - /** - * Fetches the webview HTML from the CDN or local server. - * @private - */ - private async fetchWebviewHtml() { - const source = isLocalDev ? localhost : cdn - const response = await request.fetch('GET', `${source}/index.html`).response - this.webviewHtml = await response.text() - - for (const visualization of this.managedVisualizations.values()) { - await visualization.refreshPanel(this.extensionContext) - } - } - - /** - * Gets the webview content for the Threat Composer Editor. - * @private - */ - private getWebviewContent = async () => { - if (!this.webviewHtml) { - await this.fetchWebviewHtml() - } - let htmlFileSplit = this.webviewHtml.split('') - - // Set asset source to CDN - const source = isLocalDev ? localhost : cdn - const baseTag = `` - - // Set dark mode, locale, and feature flags - const locale = vscode.env.language - const localeTag = `` - const theme = vscode.window.activeColorTheme.kind - const isDarkMode = theme === vscode.ColorThemeKind.Dark || theme === vscode.ColorThemeKind.HighContrast - const darkModeTag = `` - let html = `${htmlFileSplit[0]} ${baseTag} ${localeTag} ${darkModeTag} ${htmlFileSplit[1]}` - - const nonce = getRandomString() - htmlFileSplit = html.split("script-src 'self'") - - let localDevURL = '' - if (isLocalDev) { - localDevURL = ` ${localhost}` - } - - html = `${htmlFileSplit[0]} script-src 'self' 'nonce-${nonce}' ${localDevURL} ${htmlFileSplit[1]}` - - htmlFileSplit = html.split('') - const script = await fs.readFileText( - vscode.Uri.joinPath(this.extensionContext.extensionUri, 'resources', 'js', 'vsCodeExtensionInterface.js') - ) - - return `${htmlFileSplit[0]} ${htmlFileSplit[1]}` - } - - /** - * Called when our custom editor is opened. - * @param document: The document to be displayed in the editor. - * @param webviewPanel: The webview panel that the editor should be displayed in. - * @param _token: A cancellation token that can be used to cancel the editor. - * @returns A promise that resolves when the editor is resolved. - */ - public async resolveCustomTextEditor( - document: vscode.TextDocument, - webviewPanel: vscode.WebviewPanel, - _token: vscode.CancellationToken - ): Promise { - await telemetry.threatComposer_opened.run(async (span) => { - if (!this.webviewHtml) { - await this.fetchWebviewHtml() - } - - if (clientId === '') { - clientId = getClientId(globals.globalState) - } - // Attempt to retrieve existing visualization if it exists. - const existingVisualization = this.getExistingVisualization(document.uri.fsPath) - if (existingVisualization) { - existingVisualization.showPanel() - } else { - // Existing visualization does not exist, construct new visualization - try { - const fileId = getStringHash(`${document.uri.fsPath}${clientId}`) - const newVisualization = new ThreatComposerEditor( - document, - webviewPanel, - this.extensionContext, - fileId, - this.getWebviewContent - ) - this.handleNewVisualization(document.uri.fsPath, newVisualization) - span.record({ id: fileId }) - } catch (err) { - this.handleErr(err as Error) - throw new ToolkitError((err as Error).message, { code: 'Failed to Open Threat Composer' }) - } - } - }) - } - - protected handleErr(err: Error): void { - void vscode.window.showInformationMessage( - localize( - 'AWS.threatComposer.visualisation.errors.rendering', - 'There was an error rendering Threat Composer, check logs for details.' - ) - ) - this.logger.error(`${this.name}: Unable to open in ThreatComposer view: ${err}`) - } - - protected handleNewVisualization(key: string, visualization: ThreatComposerEditor): void { - this.managedVisualizations.set(key, visualization) - - const visualizationDisposable = visualization.onVisualizationDisposeEvent(() => { - this.managedVisualizations.delete(key) - }) - this.pushToExtensionContextSubscriptions(visualizationDisposable) - } - - protected pushToExtensionContextSubscriptions(visualizationDisposable: vscode.Disposable): void { - this.extensionContext.subscriptions.push(visualizationDisposable) - } - - protected getExistingVisualization(key: string): ThreatComposerEditor | undefined { - return this.managedVisualizations.get(key) - } -} diff --git a/packages/core/src/threatComposer/types.ts b/packages/core/src/threatComposer/types.ts deleted file mode 100644 index c0358a67b8..0000000000 --- a/packages/core/src/threatComposer/types.ts +++ /dev/null @@ -1,144 +0,0 @@ -/*! - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -import * as vscode from 'vscode' - -export type WebviewContext = { - panel: vscode.WebviewPanel - textDocument: vscode.TextDocument - disposables: vscode.Disposable[] - workSpacePath: string - defaultTemplatePath: string - defaultTemplateName: string - fileStates: Record - autoSaveFileState: Record - loaderNotification: undefined | LoaderNotification - fileId: string -} - -export type LoaderNotification = { - progress: vscode.Progress<{ - message?: string | undefined - increment?: number | undefined - }> - cancellationToken: vscode.CancellationToken - promiseResolve: any -} - -export type FileWatchInfo = { - fileContents: string -} - -export interface Message { - command: Command - messageType: MessageType -} - -export enum Command { - AUTO_SAVE_FILE = 'AUTO_SAVE_FILE', - SAVE_FILE = 'SAVE_FILE', - LOG = 'LOG', - EMIT_TELEMETRY = 'EMIT_TELEMETRY', - OPEN_FEEDBACK = 'OPEN_FEEDBACK', - INIT = 'INIT', - RELOAD = 'RELOAD', - LOAD_STAGE = 'LOAD_STAGE', - - FILE_CHANGED = 'FILE_CHANGED', - THEME_CHANGED = 'THEME_CHANGED', - OVERWRITE_FILE = 'OVERWRITE_FILE', -} - -export enum MessageType { - REQUEST = 'REQUEST', - RESPONSE = 'RESPONSE', - BROADCAST = 'BROADCAST', -} - -enum TelemetryType { - GENERATE_CLICKED = 'GENERATE_CLICKED', - REGENERATE_CLICKED = 'REGENERATE_CLICKED', - GENERATE_ACCEPTED = 'GENERATE_ACCEPTED', - GENERATE_REJECTED = 'GENERATE_REJECTED', - INVALID_GENERATION = 'INVALID_GENERATION', - POST_PROCESS = 'POST_PROCESS', - CUSTOMER_READY = 'CUSTOMER_READY', - FPS = 'FPS', - ADD_RESOURCE = 'ADD_RESOURCE', - ADD_CONNECTION = 'ADD_CONNECTION', - OPEN_WFS = 'OPEN_WFS', - CLOSE_WFS = 'CLOSE_WFS', -} - -export enum SaveCompleteSubType { - SAVED = 'SAVED', - SAVE_SKIPPED_SAME_CONTENT = 'SAVE_SKIPPED_SAME_CONTENT', - SAVE_SKIPPED_SAME_JSON = 'SAVE_SKIPPED_SAME_JSON', - SAVE_FAILED = 'SAVE_FAILED', -} - -export interface SaveFileResponseMessage extends Message { - filePath: string - isSuccess: boolean - saveCompleteSubType: SaveCompleteSubType - failureReason?: string -} - -export interface FileChangedMessage extends Message { - fileName: string - fileContents: string - filePath: string -} - -export interface ThemeChangedMessage extends Message { - newTheme: string -} - -export interface SaveFileRequestMessage extends Message { - fileContents: string -} - -export interface AddFileWatchRequestMessage extends Message { - eventId: string - fileName: string -} - -export interface NotifyUserRequestMessage extends Message { - eventId?: string - notification: string - notificationType: 'INFO' | 'WARNING' | 'ERROR' -} - -export interface LogMessage extends Message { - eventId?: string - logMessage: string - logType: 'INFO' | 'WARNING' | 'ERROR' - showNotification: boolean - notificationType: 'INVALID_JSON' | undefined -} - -export interface EmitTelemetryMessage extends Message { - eventId?: string - eventType: TelemetryType - metadata?: string -} - -export interface LoadStageMessage extends Message { - loadStage: 'API_LOADED' | 'RENDER_COMPLETE' -} - -export interface ReferenceDetails { - title: string | undefined - url: string | undefined - snippet?: string -} - -export interface GenerateResourceResponseMessage extends Message { - chatResponse: string - references: ReferenceDetails[] - metadata: object - isSuccess: boolean - errorMessage?: string - traceId: string -} diff --git a/packages/core/webpack.config.js b/packages/core/webpack.config.js index b58c990704..e485ecd530 100644 --- a/packages/core/webpack.config.js +++ b/packages/core/webpack.config.js @@ -21,10 +21,7 @@ module.exports = (env, argv) => { const config = { ...baseConfig, - entry: { - 'src/stepFunctions/asl/aslServer': './src/stepFunctions/asl/aslServer.ts', - 'src/awsService/sagemaker/detached-server/server': './src/awsService/sagemaker/detached-server/server.ts', - }, + entry: {}, } const vueConfig = {