Create New Amplify App
src/commands/create-new-app.ts
six on the ordinary path, eight if a build limit sends it through JobCheckAndWait
Use your keyboard to navigate
Kit K
A COMMAND LINE, REBUILT Β· NOTHING HERE REACHES AWS
Seven commands I wrote to stand up and move AWS environments from a laptop, and the blast radius of each one read off the code.
It's a TypeScript CLI that drives Amplify, AmplifyBackend, CloudFormation, DynamoDB, SSM and Cognito against a live account through single sign-on. A command line is close to impossible to show, so I rebuilt it here: every prompt string, every choice list, every default and every line the program prints is quoted from the source, and the only thing I made up is what AWS would have said back.
SIMULATEDNothing on this page opens a connection, and every value AWS would have returned is invented and printed in violet.
REDACTEDThe project was never put under version control and its config folder still holds long-lived credentials in plaintext, which is why nothing identifying the account or the client reaches this page. The account id, the two role ARNs, the SSO start URL, the company and tenant domains, the Bitbucket workspace, the app and parameter names and the vendor products are replaced with visible tokens in the data file itself rather than at render time, so you can see the tokens sitting inside the prompts and the log lines.
01 Β· THE GUARDS
Every yes-or-no question in the program is built by one function, confirm({ message: query, default: true }), so Inquirer renders it as (Y/n) and pressing Enter without typing anything answers yes. Create New Amplify App and Create New Amplify Environment ask along the way; the other five go from the menu straight to the writes, and the two heaviest writers are both in that group: a full DynamoDB table copy that replaces items key for key and a pass across the Amplify apps the region lists back, writing each of their environment variables into SSM with Overwrite: true.
YES/NO QUESTIONS ON EACH COMMAND’S PATH
src/commands/create-new-app.ts
six on the ordinary path, eight if a build limit sends it through JobCheckAndWait
src/commands/create-amplify-environment.ts
four on the clone path, one if you answer no to the first
src/commands/create-new-customer.ts
no yes/no question anywhere on its path
src/commands/copy-env-db.ts
no yes/no question anywhere on its path
src/commands/copy-environment-variables-to-ssm.ts
no yes/no question, and no prompt of any kind
src/commands/copy-cognito-pool-users.ts
no yes/no question anywhere on its path
src/commands/copy-amplify-users.ts
no yes/no question anywhere on its path
Create New Customer Environment, the one that provisions for a paying customer, hardcodes preSelectedEnvironmentType: EnvironmentType.PRODUCTION with skipEnvironmentType: true, so it can only ever be pointed at production; it never says so, and between the domain you type and a live branch cut from the head of production there is no question at all.
02 Β· THE CONSOLE
Arrow keys move the cursor, Enter commits and a committed prompt collapses the way Inquirer collapses it. The manifest beside the terminal fills with the AWS calls that route issues, in call order, each traced to the file and line where the command object is constructed and sorted into reads, creates or overwrites.
click or tap the terminal to take the keyboard
ROUTE COMPLETE
03 Β· THE THREE CONFIGURATIONS
The environment type you pick at the first prompt selects a set of JSON files by name, and those files are spread wholesale into the Amplify request. The keys are quoted from src/config/*.json with the domains and image names taken out, and several of them turn out to hold the same value in all three. The environment.default.*.json files are each the two characters {}, placeholders nobody ever filled in.
| key | file | development | demo | production |
|---|---|---|---|---|
AMPLIFY_DESTRUCTIVE_UPDATES | app.default.*.json Β· environmentVariables | "true" | "true" | "false" |
AMPLIFY_ENABLE_DEBUG_OUTPUT | app.default.*.json Β· environmentVariables | "false" | "false" | "false" |
BUILD_ID | app.default.*.json Β· environmentVariables | "0.1.0" | "0.1.0" | not set |
enableBranchAutoBuild | app.default.*.json | true | true | true |
enableBranchAutoDeletion | app.default.*.json | true | false | false |
autoBranchCreationPatterns | app.default.*.json | <redacted:branch-pattern> Γ 4 | ["BRANCH_CREATION_PATTERNS"] | ["BRANCH_CREATION_PATTERNS"] |
autoBranchCreationConfig.stage | app.default.*.json | "DEVELOPMENT" | "DEVELOPMENT" | "PRODUCTION" |
branchName | branch.default.*.json | "dev" | "demo" | "stage" |
stage | branch.default.*.json | "PRODUCTION" | "PRODUCTION" | "PRODUCTION" |
subDomainSettings[0].branchName | domain.default.*.json | "dev" | "demo" | "stage" |
the whole file | environment.default.*.json | {} | {} | {} |
AMPLIFY_DESTRUCTIVE_UPDATES is "true" in development and demo and "false" in production, which is the way round you would want. The branch files hold two keys each and every one of them stages as PRODUCTION, including development, while the production file’s branch is called stage.
04 Β· THE SOURCE ITSELF
Each of these is a claim about the source and carries the file and line to check it against. I have not guessed at what AWS does with any of it; where the reading depends on a service behaviour I have left the behaviour out and quoted the code instead.
Before the menu appears, updateProfileInConfig reads ~/.aws/config, matches the profile block with new RegExp(`\\[profile ${AWS_PROFILE}\\][\\s\\S]*?(?=\\[|$)`, 'g') and writes the whole file back. Anything of yours that the regex swallowed is gone.
src/aws/login.ts:45
Login has a narrower default parameter, and runCLI calls Login('AdministratorAccess'), so the role written into the profile is administrator every time.
src/index.ts:59
All thirteen confirmations go through one function: confirm({ message: query, default: true }). Inquirer renders that as (Y/n) and a bare Enter answers yes.
src/helpers/command-line-inputs.ts:14
amplifyCopyDatabase performs the whole transfer at line 22. The command then recomputes both suffixes and calls DynamodbTransfer again at line 42 with no await, so the second pass runs outside the try/catch.
src/commands/copy-env-db.ts:42
clearEmptyIndexes sets a missing or empty index attribute to the literal string 'null' and then logs "Deleted empty index attribute β¦". Nothing is deleted and the destination now holds the four characters n-u-l-l.
src/helpers/dynamodb-transfer.ts:60
GetBackendEnvironment gives the stack name, DescribeStackResources finds the API resource, the physical id is regex-stripped back to a stack name, DescribeStacks reads GraphQLAPIIdOutput and the env parameter, and the suffix comes back as `-${graphqlId}-${envName}`. Every table whose name ends with that string is in scope.
src/helpers/amplify-get-env-dynamodb-suffix.ts:56
The name is built as region code plus environment short code plus the cleaned customer name, then cut with .slice(0, 10). A comment says "If the name is shorter than 2 characters, pad it with x" and the padding was never written.
src/helpers/amplify-create-environment-name.ts:23
Both fetchers send ListAppsCommand({}) and never follow nextToken, while listAllApps in the same codebase does paginate. Whichever apps fell off the first page do not exist as far as these commands are concerned.
src/helpers/amplify-fetch-region-apps.ts:8
SecureString encrypts the Bitbucket credential pair with AES-256-GCM and PBKDF2 at 100,000 iterations, then caches the instance in a Map keyed by the plaintext, so the plaintext stays resident for the life of the process.
src/helpers/secure-request.ts:20
secureHttpsRequest resolves on the response body without ever reading the status code and sets no timeout.
src/helpers/secure-request.ts:78
The user migration, the second database transfer and the third-party redirect-URI POST are all fired without await, so each one escapes the try/catch that was written to contain it.
src/commands/copy-amplify-users.ts:24
AmplifyWaitForJob only ends its loop on COMPLETED or FAILED and its caller only proceeds on SUCCEED (src/commands/create-new-app.ts:346), so the database clone at the end of Create New Amplify App cannot be reached.
src/helpers/amplify-wait-for-job.ts:35
Config paths are built with path.join(path.resolve(), 'src/config/β¦'), so dist/index.js looks for src/config relative to whatever directory you happen to be standing in.
src/helpers/read-json-file.ts:3
The files are called ampify-create-env-with-branch.ts and amplify-clone-envrionment.ts, and both are imported under those spellings, so both work.
src/helpers/
npm test is echo "Error: no test specified" && exit 1. There is no .git directory, no README and no documentation, and all 46 source files carry the same modification timestamp. It does type-check clean under strict.
package.json
There is a HeadlessCreateDevelopmentEnvironment and a second copy of CloneEnvironment with the prompts stripped out, and nothing in the repository imports either of them. The first also passes region.code where every other call site passes region.awsRegion.
src/headless/
zx is a declared dependency and is imported nowhere. @aws-sdk/credential-provider-sso is imported by the client provider and is not in the dependency list, so it resolves only as a transitive install.
package.json
The provider builds six AWS clients and caches them per ClassName-region, and getSecretsManagerClient is defined but called from nowhere.
src/aws/aws-client-provider.ts:52
05 Β· WHAT IS NOT HERE
Every one of these was replaced with a token in the JSON before the page was built, so the raw value has no path to the browser. On every row except the last three the count is how many times that string occurs in src/ and the package manifests, which is why the package scope is counting package.json and its lock file rather than any TypeScript. The three rows marked never read, never copied are the live credentials sitting in the untracked config/ folder, and their number counts separate secret values rather than occurrences. The third-party products are described by what they do rather than named, because a named vendor sitting next to everything else on this page would give the client away.
| what | how many | what you see instead |
|---|---|---|
| AWS account id | 6 | <redacted:aws-account-id> |
| npm package scope | 3 | <redacted:npm-scope> |
| SSO profile name | 2 | <redacted:aws-profile> |
| SSO start URL | 1 | <redacted:sso-start-url> |
| IAM service role ARN | 3 | <redacted:iam-role-arn> |
| Cognito import role ARN | 2 | <redacted:service-role-arn> |
| Company domain | 7 | <redacted:company-domain> |
| Tenant directory domain | 3 | <redacted:tenant-domain> |
| Bitbucket workspace | 4 | <redacted:bitbucket-workspace> |
| Repository slug | 4 | <redacted:repo> |
| Amplify app-name template | 2 | <redacted:app-name> |
| Hardcoded SSM parameter path | 2 | <redacted:ssm-app-path> |
| Amplify resource logical ids | 2 | <redacted:resource-id> |
| PreSignup allow-list parameter | 2 | <redacted:presignup-parameter> |
| Container registry image | 3 | <redacted:container-image> |
| Auto-branch creation patterns | 4 | <redacted:branch-pattern> |
| Third-party vendor names | 44 | described, never named |
| Long-lived AWS access key pair | 2 | never read, never copied |
| Third-party API keys and secrets | 4 | never read, never copied |
| Analytics container id | 1 | never read, never copied |
The credentials sitting in the config folder should be rotated whether or not this page exists, and there is no git history to scrub because there is no git.