I made an initial Amplify app by following the official Amplify Gen 2 walkthrough. I got it to work in the sandbox. It then fails to deploy.
To recreate the error:
- Use a freshly created Cloud9 environment (hint: use an upgraded EC2 instance, the free tier won’t work, needs more RAM and disk), combined with a newly bootstrapped Amplify app and a new CodeCommit repository – everything clean.
- Follow these instructions – https://docs.amplify.aws/gen2/start/quickstart/vite-react-app/
- Work through all the bugs in the instructions (beyond the scope of this question) and get the system to work locally in a sandbox.
- Get the whole thing loaded into a Git repository (in this case, CodeCommit) and link the Amplify app to the repository.
- Run the deployment (manually or automatic upon a Git push, if everything is working).
Deployment fails.
Default options and settings and versions are used in all cases, unless specifically mentioned in the linked walkthrough.
The resulting error (in the deployment console) looks like this:
170 2024-05-01T22:08:22.641Z [INFO]: src/components/TodoList.tsx(14,14): error TS2345: Argument of type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }[]' is not assignable to parameter of type 'SetStateAction<{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }[]>'.
171 Type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }[]' is not assignable to type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }[]'.
172 Property 'type' is missing in type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }' but required in type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }'.
173 2024-05-01T22:08:22.645Z [INFO]: src/components/TodoList.tsx(20,13): error TS2345: Argument of type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }[]' is not assignable to parameter of type 'SetStateAction<{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }[]>'.
174 Type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }[]' is not assignable to type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }[]'.
175 Type '{ readonly id: string; readonly createdAt: string; readonly updatedAt: string; content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; owner?: string | undefined; }' is not assignable to type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }'.
176 src/components/TodoList.tsx(42,25): error TS2339: Property 'id' does not exist on type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }'.
177 src/components/TodoList.tsx(42,35): error TS2339: Property 'content' does not exist on type '{ type: { readonly id: string; readonly createdAt: string; readonly updatedAt: string; } & { content?: Nullable<string> | undefined; done?: Nullable<boolean> | undefined; priority?: "low" | ... 3 more ... | undefined; } & { ...; }; }'.
178 2024-05-01T22:08:22.710Z [ERROR]: !!! Build failed
179 2024-05-01T22:08:22.711Z [ERROR]: !!! Error: Command failed with exit code 2
TodoList.tsx is a direct copy and paste from the tutorial:
import { useState, useEffect } from "react";
import { generateClient } from "aws-amplify/data";
import type { Schema } from "../../amplify/data/resource";
export default function TodoList() {
// generate your data client using the Schema from your backend
const client = generateClient<Schema>();
const [todos, setTodos] = useState<Schema["Todo"][]>([]);
async function listTodos() {
// fetch all todos
const { data } = await client.models.Todo.list();
setTodos(data);
}
useEffect(() => {
listTodos()
const sub = client.models.Todo.observeQuery().subscribe(({ items }) =>
setTodos([...items])
);
return () => sub.unsubscribe();
}, []);
return (
<div>
<h1>Todos</h1>
<button onClick={async () => {
// create a new Todo with the following attributes
const { errors, data: newTodo } = await client.models.Todo.create({
// prompt the user to enter the title
content: window.prompt("title"),
done: false,
priority: 'medium'
})
console.log(errors, newTodo);
}}>Create</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.content}</li>
))}
</ul>
</div>
);
}
../../amplify/data/resource is directly from AWS and looks like this:
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
/*== STEP 1 ===============================================================
The section below creates a Todo database table with a "content" field. Try
adding a new "isDone" field as a boolean. The authorization rule below
specifies that any user authenticated via an API key can "create", "read",
"update", and "delete" any "Todo" records.
=========================================================================*/
const schema = a.schema({
Todo: a
.model({
content: a.string(),
})
.authorization((allow) => [allow.guest()]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
schema,
authorizationModes: {
defaultAuthorizationMode: 'iam',
},
});
/*== STEP 2 ===============================================================
Go to your frontend source code. From your client-side code, generate a
Data client to make CRUDL requests to your table. (THIS SNIPPET WILL ONLY
WORK IN THE FRONTEND CODE FILE.)
Using JavaScript or Next.js React Server Components, Middleware, Server
Actions or Pages Router? Review how to generate Data clients for those use
cases: https://docs.amplify.aws/gen2/build-a-backend/data/connect-to-API/
=========================================================================*/
/*
"use client"
import { generateClient } from "aws-amplify/data";
import type { Schema } from "@/amplify/data/resource";
const client = generateClient<Schema>() // use this Data client for CRUDL requests
*/
/*== STEP 3 ===============================================================
Fetch records from the database and use them in your frontend component.
(THIS SNIPPET WILL ONLY WORK IN THE FRONTEND CODE FILE.)
=========================================================================*/
/* For example, in a React component, you can use this snippet in your
function's RETURN statement */
// const { data: todos } = await client.models.Todo.list()
// return <ul>{todos.map(todo => <li key={todo.id}>{todo.content}</li>)}</ul>
For completeness, here is package.json (autogenerated and untouched):
{
"name": "lpm-react-amplify-gen2",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"aws-amplify": "^6.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@aws-amplify/backend": "^0.15.0",
"@aws-amplify/backend-cli": "^0.15.0",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"@vitejs/plugin-react": "^4.2.1",
"aws-cdk": "^2.139.1",
"aws-cdk-lib": "^2.139.1",
"constructs": "^10.3.0",
"esbuild": "^0.20.2",
"eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"tsx": "^4.7.3",
"typescript": "^5.4.5",
"vite": "^5.2.0"
}
}
I would provide package-lock.json but it is just short of 20,000 lines. In case it matters, I excluded node_modules from the repository via .gitignore.
Since this all works in the sandbox, it seems like there must be some version difference between all the stuff installed for the sandbox compared to what gets deployed and used to build.
One problem I solved (as part of step 2, above) was the failure of npx amplify sandbox
, it was segfaulting and core dumping. This is apparently due to an issue with Node 20.12, so you must fall back to 20.11. I thought that might be the source of the issue, so I added some commands to do that fallback in the amplify.yml (Amplify app->Hosting->build options), with no success. I did not try doing the same thing by specifying a custom build container, as that seems redundant and unnecessary if the amplify.yml approach didn’t help. It was using the 20.11 version in the deployment during that test, according to the console printout.
For what it’s worth, the Cloud9 IDE is littered with warnings, mostly they seem to be similar type errors. An alleged fix for this is to change the Typescript version setting in the IDE to match the command-line/sandbox enviroment, but there doesn’t seem to be such a setting (most people only talk about using VSCode, not Cloud9). Regardless of these warnings (errors?) being left unresolved, the Amplify sandbox eventually worked as intended.
At this point, debugging this requires lots of iteration over random ideas and a >5 minute wait for the deployment to fail on each of those iterations. I could really use some focused ideas and direction.
- What is the likely culprit?
- Why would the sandbox differ from the deployment environment?
I am not sure if I need to be focusing on syntax problems in the code provided by the tutorial (there were others that had to be solved to get the sandboxed version to work) or if it’s something in the configuration of the deployment or something else entirely.
- Is it a syntax error in the specified file? If so, why did it work in the sandbox?
- Is it a language/Node/Typescript version issue?
- Is it a package version issue?
- Is it some other version issue?
- Is it a problem in the Amplify deployment configuration?
- Is it some other Amplify configuration issue?
How to I get the sandbox to actually align with the deployment so any errors show up locally?