├── .prettierrc ├── .husky └── pre-commit ├── docs ├── src │ ├── pages │ │ ├── cli │ │ │ ├── create.mdx │ │ │ ├── _meta.json │ │ │ └── intro.mdx │ │ ├── core │ │ │ ├── _meta.json │ │ │ └── intro.mdx │ │ ├── ui │ │ │ ├── _meta.json │ │ │ └── intro.mdx │ │ ├── infra │ │ │ ├── _meta.json │ │ │ ├── intro.mdx │ │ │ └── tips.mdx │ │ ├── index.mdx │ │ ├── overview │ │ │ ├── guides │ │ │ │ └── _meta.json │ │ │ ├── contributors │ │ │ │ ├── tips.mdx │ │ │ │ ├── _meta.json │ │ │ │ ├── resources.mdx │ │ │ │ ├── intro.mdx │ │ │ │ └── fix-audit-issues.mdx │ │ │ ├── _meta.json │ │ │ ├── common-error-resolutions.mdx │ │ │ ├── quick-start.mdx │ │ │ ├── packages.mdx │ │ │ └── faq.mdx │ │ ├── _app.mdx │ │ ├── learn │ │ │ ├── _meta.json │ │ │ └── resources.mdx │ │ └── _meta.json │ ├── globals.css │ └── components │ │ ├── utils.ts │ │ ├── Link.tsx │ │ └── Feature.tsx ├── public │ ├── og2.png │ ├── fast-forward-emoji.svg │ ├── green-boost-gradient.svg │ └── fast-forward-gradient.svg ├── postcss.config.cjs ├── CHANGELOG.md ├── IDEAS.md ├── tailwind.config.js ├── .lintstagedrc.js ├── tsconfig.json ├── .gitignore ├── next.config.js └── package.json ├── examples ├── widgets-dynamo │ ├── ui │ │ ├── .gitignore │ │ ├── vite-env.d.ts │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── components │ │ │ │ ├── NavAsideLink.css.ts │ │ │ │ ├── Footer.tsx │ │ │ │ ├── NavAsideLink.tsx │ │ │ │ ├── Header.tsx │ │ │ │ └── NavAside.tsx │ │ │ ├── pages │ │ │ │ ├── Root.tsx │ │ │ │ └── widgets │ │ │ │ │ └── WidgetsAdditionalActions.tsx │ │ │ ├── config.ts │ │ │ ├── utils │ │ │ │ ├── date.ts │ │ │ │ └── format.ts │ │ │ ├── main.tsx │ │ │ ├── theme.ts │ │ │ ├── trpc.ts │ │ │ └── App.css.ts │ │ ├── public │ │ │ ├── browserconfig.xml │ │ │ ├── site.webmanifest │ │ │ ├── og-icon.svg │ │ │ ├── favicon-16x16.png │ │ │ ├── safari-pinned-tab.svg │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-70x70.png │ │ │ └── android-chrome-192x192.png │ │ ├── .lintstagedrc.js.t │ │ ├── .eslintrc.cjs │ │ └── index.html │ ├── .gitignore │ ├── db │ │ ├── src │ │ │ ├── config.ts │ │ │ └── README.md │ │ ├── tsconfig.json │ │ ├── README.md │ │ ├── .lintstagedrc.js.t │ │ ├── .eslintrc.cjs │ │ └── package.json │ ├── core │ │ ├── src │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ ├── domain │ │ │ │ ├── models │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── base.ts │ │ │ │ │ ├── widget-component.ts │ │ │ │ │ ├── component.ts │ │ │ │ │ └── widget.ts │ │ │ │ └── schemas │ │ │ │ │ ├── index.ts │ │ │ │ │ └── common.ts │ │ │ ├── config.ts │ │ │ ├── entrypoints │ │ │ │ └── api │ │ │ │ │ ├── trpc.ts │ │ │ │ │ ├── router.ts │ │ │ │ │ ├── handler.ts │ │ │ │ │ └── routers │ │ │ │ │ ├── widget.ts │ │ │ │ │ └── components.ts │ │ │ ├── utils │ │ │ │ └── logger.ts │ │ │ ├── stage-name.ts │ │ │ └── services │ │ │ │ ├── widget-service.ts │ │ │ │ └── component-service.ts │ │ ├── tsconfig.json │ │ ├── .lintstagedrc.js.t │ │ └── .eslintrc.cjs │ ├── infra │ │ ├── .gitignore │ │ ├── README.md │ │ ├── vite.config.ts │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── pipeline │ │ │ │ └── pipeline-app.ts │ │ │ ├── local-app.ts │ │ │ └── app │ │ │ │ └── stateless │ │ │ │ └── monitor.ts │ │ ├── .lintstagedrc.js.t │ │ ├── .eslintrc.cjs │ │ └── cdk.json │ ├── pnpm-workspace.yaml │ ├── packages │ │ ├── tsconfig │ │ │ ├── tsconfig.ui.json │ │ │ ├── tsconfig.node.json │ │ │ └── package.json │ │ ├── utils │ │ │ ├── package.json │ │ │ └── index.js │ │ ├── eslint-config-react │ │ │ ├── package.json │ │ │ └── .eslintrc.cjs │ │ └── eslint-config-node │ │ │ ├── package.json │ │ │ └── .eslintrc.cjs │ ├── .vscode │ │ └── settings.json │ └── package.json ├── document-management │ └── README.md ├── cloud-broker │ └── README.md └── README.md ├── packages ├── gboost │ ├── templates │ │ ├── minimal │ │ │ ├── core │ │ │ │ ├── src │ │ │ │ │ ├── index.shared.ts │ │ │ │ │ └── index.shared.test.ts │ │ │ │ ├── tsconfig.json.t │ │ │ │ ├── .lintstagedrc.js.t │ │ │ │ ├── .eslintrc.cjs.t │ │ │ │ └── package.json.t │ │ │ ├── ui │ │ │ │ ├── .env.t │ │ │ │ ├── src │ │ │ │ │ └── app │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ ├── page.test.tsx │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── apple-touch-icon.png │ │ │ │ ├── next-env.d.ts.t │ │ │ │ ├── .eslintrc.cjs.t │ │ │ │ ├── tsconfig.json.t │ │ │ │ ├── .lintstagedrc.js.t │ │ │ │ ├── .gitignore │ │ │ │ ├── package.json.t │ │ │ │ └── next.config.js │ │ │ ├── .gitignore.t │ │ │ ├── infra │ │ │ │ ├── .gitignore │ │ │ │ ├── src │ │ │ │ │ ├── app-stage.test.ts │ │ │ │ │ └── app-stage.ts │ │ │ │ ├── tsconfig.json.t │ │ │ │ ├── .lintstagedrc.js.t │ │ │ │ ├── .eslintrc.cjs.t │ │ │ │ └── package.json.t │ │ │ ├── .husky │ │ │ │ └── pre-commit │ │ │ ├── pnpm-workspace.yaml │ │ │ ├── packages │ │ │ │ ├── tsconfig │ │ │ │ │ ├── tsconfig.node.json.t │ │ │ │ │ ├── tsconfig.next.json.t │ │ │ │ │ └── package.json.t │ │ │ │ ├── utils │ │ │ │ │ ├── package.json.t │ │ │ │ │ └── index.js │ │ │ │ ├── eslint-config-next │ │ │ │ │ └── package.json.t │ │ │ │ └── eslint-config-node │ │ │ │ │ ├── package.json.t │ │ │ │ │ └── .eslintrc.cjs.t │ │ │ ├── .vscode │ │ │ │ └── settings.json │ │ │ ├── README.md │ │ │ └── package.json.t │ │ ├── basic-ui │ │ │ ├── core │ │ │ │ └── src │ │ │ │ │ ├── index.server.ts │ │ │ │ │ ├── index.shared.ts │ │ │ │ │ └── config │ │ │ │ │ ├── stage-name.ts │ │ │ │ │ ├── server-config.ts │ │ │ │ │ └── shared-config.ts │ │ │ ├── ui │ │ │ │ └── src │ │ │ │ │ ├── app │ │ │ │ │ ├── globals.css │ │ │ │ │ ├── hello │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── [name] │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ └── Hello.tsx │ │ │ │ │ ├── page.tsx │ │ │ │ │ ├── goodbye │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ └── [name] │ │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ └── Goodbye.tsx │ │ │ │ │ └── layout.tsx │ │ │ │ │ ├── utils │ │ │ │ │ ├── kebab-to-title.ts │ │ │ │ │ └── kebab-to-title.test.ts │ │ │ │ │ ├── config │ │ │ │ │ ├── server-config.ts │ │ │ │ │ └── client-config.ts │ │ │ │ │ ├── components │ │ │ │ │ ├── LoadingH2.tsx │ │ │ │ │ ├── layout │ │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ │ ├── DrawerList.tsx │ │ │ │ │ │ └── Drawer.tsx │ │ │ │ │ └── theme │ │ │ │ │ │ ├── ThemeRegistry.tsx │ │ │ │ │ │ └── theme.tsx │ │ │ │ │ └── core-server.ts │ │ │ └── infra │ │ │ │ └── src │ │ │ │ ├── local-app.ts │ │ │ │ ├── pipeline │ │ │ │ └── pipeline-app.ts │ │ │ │ ├── app │ │ │ │ └── stateless │ │ │ │ │ └── waf-stack.ts │ │ │ │ └── app-stage.ts │ │ ├── crud-core │ │ │ ├── core │ │ │ │ ├── src │ │ │ │ │ ├── types │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── search-params.ts │ │ │ │ │ ├── modules │ │ │ │ │ │ ├── base │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── base.entity.ts │ │ │ │ │ │ │ └── base.schema.ts │ │ │ │ │ │ └── item │ │ │ │ │ │ │ ├── index.shared.ts │ │ │ │ │ │ │ ├── delete-item.use-case.ts │ │ │ │ │ │ │ ├── create-item.use-case.ts │ │ │ │ │ │ │ ├── get-item.use-case.ts │ │ │ │ │ │ │ ├── update-item.use-case.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── item.entity.ts │ │ │ │ │ │ │ └── list-items.use-case.ts │ │ │ │ │ ├── adapters │ │ │ │ │ │ └── secondary │ │ │ │ │ │ │ └── logger-adapter.ts │ │ │ │ │ ├── config │ │ │ │ │ │ ├── stage-name.ts │ │ │ │ │ │ └── shared-config.ts │ │ │ │ │ ├── index.server.ts │ │ │ │ │ └── index.shared.ts │ │ │ │ └── tsconfig.json.t │ │ │ ├── ui │ │ │ │ ├── src │ │ │ │ │ ├── app │ │ │ │ │ │ ├── page.test.tsx │ │ │ │ │ │ ├── globals.css │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ ├── items │ │ │ │ │ │ │ ├── new │ │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ │ └── create-item.action.ts │ │ │ │ │ │ │ ├── [id] │ │ │ │ │ │ │ │ ├── update-item.action.ts │ │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ │ │ ├── page.tsx │ │ │ │ │ │ │ └── ItemsToolbar.tsx │ │ │ │ │ │ └── layout.tsx │ │ │ │ │ ├── utils │ │ │ │ │ │ ├── format.ts │ │ │ │ │ │ ├── decode-search-params.ts │ │ │ │ │ │ └── generate-csp.ts │ │ │ │ │ ├── config │ │ │ │ │ │ ├── server-config.ts │ │ │ │ │ │ └── client-config.ts │ │ │ │ │ ├── types │ │ │ │ │ │ └── page-props.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── layout │ │ │ │ │ │ │ ├── Footer.tsx │ │ │ │ │ │ │ ├── DrawerList.tsx │ │ │ │ │ │ │ └── Drawer.tsx │ │ │ │ │ │ ├── theme │ │ │ │ │ │ │ ├── ThemeRegistry.tsx │ │ │ │ │ │ │ └── theme.tsx │ │ │ │ │ │ └── rhf-inputs │ │ │ │ │ │ │ └── RhfTextField.tsx │ │ │ │ │ ├── core-server.ts │ │ │ │ │ └── middleware.ts │ │ │ │ ├── .eslintrc.cjs.t │ │ │ │ └── package.json.t │ │ │ └── infra │ │ │ │ └── src │ │ │ │ ├── local-app.ts │ │ │ │ ├── pipeline │ │ │ │ └── pipeline-app.ts │ │ │ │ └── app │ │ │ │ └── stateless │ │ │ │ └── waf-stack.ts │ │ ├── crud-postgres │ │ │ ├── core │ │ │ │ ├── src │ │ │ │ │ ├── db │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── drizzle │ │ │ │ │ │ │ ├── 0002_calm_mandarin.sql │ │ │ │ │ │ │ ├── 0001_loud_serpent_society.sql │ │ │ │ │ │ │ ├── meta │ │ │ │ │ │ │ │ ├── 0000_snapshot.json │ │ │ │ │ │ │ │ └── _journal.json │ │ │ │ │ │ │ └── 0000_boring_maginty.sql │ │ │ │ │ │ ├── drizzle.config.ts │ │ │ │ │ │ ├── schema.ts │ │ │ │ │ │ ├── scripts │ │ │ │ │ │ │ └── seed-items.ts │ │ │ │ │ │ └── utils │ │ │ │ │ │ │ ├── get-db-auth-token.ts │ │ │ │ │ │ │ └── get-db-password.ts │ │ │ │ │ ├── adapters │ │ │ │ │ │ └── secondary │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── db-adapter.ts │ │ │ │ │ ├── modules │ │ │ │ │ │ └── item │ │ │ │ │ │ │ └── item.db.ts │ │ │ │ │ ├── config │ │ │ │ │ │ └── server-config.ts │ │ │ │ │ └── utils │ │ │ │ │ │ └── transform-null-to-undefined.ts │ │ │ │ └── tsconfig.json.t │ │ │ ├── .envrc │ │ │ ├── infra │ │ │ │ └── src │ │ │ │ │ └── utils │ │ │ │ │ ├── get-db-fn-props.ts │ │ │ │ │ └── connect-fn-to-db.ts │ │ │ └── README.md │ │ └── crud-dynamo │ │ │ ├── README.md │ │ │ └── core │ │ │ └── package.json.t │ ├── src │ │ ├── create │ │ │ ├── ask.test.ts │ │ │ ├── get-template-operations │ │ │ │ ├── base-operation-params.ts │ │ │ │ ├── get-minimal-operations.ts │ │ │ │ ├── get-crud-dynamo-operations.ts │ │ │ │ └── get-basic-ui-operations.ts │ │ │ ├── operations │ │ │ │ ├── operations.ts │ │ │ │ ├── common.ts │ │ │ │ ├── rename-files.ts │ │ │ │ └── rename-files.test.ts │ │ │ └── execute-operations.ts │ │ ├── utils │ │ │ └── handle-aborted.ts │ │ └── invoke │ │ │ └── inject-fn-config.ts │ ├── tsconfig.build.json │ ├── .lintstagedrc.js │ ├── tsconfig.json │ ├── scripts │ │ └── build.ts │ └── README.md ├── gboost-node │ ├── src │ │ ├── index.ts │ │ └── index.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── .lintstagedrc.js │ ├── scripts │ │ └── build.ts │ ├── README.md │ └── package.json ├── gboost-ui │ ├── src │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── use-trace-update.test.ts │ │ │ ├── download-link.ts │ │ │ └── use-trace-update.ts │ │ ├── index.ts │ │ └── types │ │ │ └── string-with-autocomplete.ts │ ├── tsconfig.build.json │ ├── .lintstagedrc.js │ ├── README.md │ └── tsconfig.json ├── gboost-infra │ ├── src │ │ ├── user-base │ │ │ ├── index.ts │ │ │ ├── pre-token-generation.ts │ │ │ └── post-confirmation.ts │ │ ├── aspects │ │ │ ├── index.ts │ │ │ ├── suppress-nags │ │ │ │ ├── suppress-cdk-custom-resource.ts │ │ │ │ ├── suppress-cdk-log-retention.ts │ │ │ │ ├── suppress-cdk-bucket-notifications.ts │ │ │ │ ├── suppress-cdk-bucket-deployment.ts │ │ │ │ ├── suppress-cdk-custom-resource-provider.ts │ │ │ │ └── suppress-cdk-monitoring-constructs.ts │ │ │ └── gov-cloud-compat.ts │ │ ├── web-deployment │ │ │ └── common.ts │ │ ├── user-management │ │ │ ├── function │ │ │ │ ├── validateUsernames.ts │ │ │ │ ├── getCognitoIdentity.ts │ │ │ │ ├── deleteUsers.ts │ │ │ │ ├── enableUsers.ts │ │ │ │ ├── disableUsers.ts │ │ │ │ └── resetPasswords.ts │ │ │ └── includeSelectionSetList.vtl │ │ ├── file-upload │ │ │ └── function │ │ │ │ └── findIndex.ts │ │ ├── static-site │ │ │ ├── rewrite-url.js │ │ │ └── response-headers.test.ts │ │ ├── table.ts │ │ ├── construct-default-props.ts │ │ └── index.ts │ ├── tsconfig.json │ ├── .lintstagedrc.js │ ├── tsconfig.build.json │ └── README.md └── gboost-common │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── .lintstagedrc.js │ ├── src │ ├── convert-case.test.ts │ ├── index.ts │ ├── convert-case.ts │ ├── merge-deep.ts │ └── get-error-message.ts │ ├── scripts │ └── build.ts │ └── README.md ├── .gitignore ├── NOTICE ├── .changeset ├── pretty-flies-smell.md ├── config.json ├── README.md └── neat-waves-impress.md ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── tsconfig.json ├── pnpm-workspace.yaml ├── CODE_OF_CONDUCT.md ├── README.md └── .github ├── actions └── install │ └── action.yml └── workflows └── pages-deploy.yml /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /docs/src/pages/cli/create.mdx: -------------------------------------------------------------------------------- 1 | # `gboost create` -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.local 3 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/src/index.shared.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE -------------------------------------------------------------------------------- /docs/src/pages/cli/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction" 3 | } -------------------------------------------------------------------------------- /docs/src/pages/core/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction" 3 | } -------------------------------------------------------------------------------- /docs/src/pages/ui/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction" 3 | } -------------------------------------------------------------------------------- /packages/gboost-node/src/index.ts: -------------------------------------------------------------------------------- 1 | export const hello = "world"; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | .env 5 | *.tsbuildinfo -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /examples/document-management/README.md: -------------------------------------------------------------------------------- 1 | # Document Management 2 | 3 | TODO -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/src/config.ts: -------------------------------------------------------------------------------- 1 | export const config = {}; 2 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/.env.t: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_STAGE_NAME=local -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/README.md: -------------------------------------------------------------------------------- 1 | # Core 2 | Add backend logic here -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./stage-name.js"; 2 | -------------------------------------------------------------------------------- /packages/gboost-ui/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./download-link.js"; 2 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/gboost-ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export { downloadLink } from "./utils/index"; 2 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/.gitignore.t: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_STORE 3 | .envrc -------------------------------------------------------------------------------- /docs/src/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /docs/public/og2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/green-boost/HEAD/docs/public/og2.png -------------------------------------------------------------------------------- /docs/src/pages/cli/intro.mdx: -------------------------------------------------------------------------------- 1 | # CLI Introduction 2 | 3 | Welcome to the docs for `gboost`. -------------------------------------------------------------------------------- /docs/src/pages/infra/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction", 3 | "tips": "Tips" 4 | } -------------------------------------------------------------------------------- /docs/src/pages/ui/intro.mdx: -------------------------------------------------------------------------------- 1 | # UI Introduction 2 | 3 | Welcome to the docs for `gboost-ui`. -------------------------------------------------------------------------------- /docs/src/pages/core/intro.mdx: -------------------------------------------------------------------------------- 1 | # Core Introduction 2 | 3 | Welcome to the docs for `gboost-node`. -------------------------------------------------------------------------------- /docs/src/pages/index.mdx: -------------------------------------------------------------------------------- 1 | import { Landing } from "../components/Landing"; 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/src/README.md: -------------------------------------------------------------------------------- 1 | # DB 2 | Add database scripts and utility functions here. -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/.gitignore: -------------------------------------------------------------------------------- 1 | # CDK asset staging directory 2 | .cdk.staging 3 | cdk.out -------------------------------------------------------------------------------- /docs/src/pages/infra/intro.mdx: -------------------------------------------------------------------------------- 1 | # Infra Introduction 2 | 3 | Welcome to the docs for `gboost-infra`. -------------------------------------------------------------------------------- /packages/gboost-node/src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "vitest"; 2 | 3 | test.todo("TODO"); 4 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@myapp/tsconfig/tsconfig.node.json", 3 | } 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/.gitignore: -------------------------------------------------------------------------------- 1 | # CDK asset staging directory 2 | .cdk.staging 3 | cdk.out -------------------------------------------------------------------------------- /docs/src/components/utils.ts: -------------------------------------------------------------------------------- 1 | export const gradientClasses = "bg-gradient-to-r from-lime-400 to-cyan-400"; 2 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@myapp/tsconfig/tsconfig.node.json", 3 | } 4 | -------------------------------------------------------------------------------- /.changeset/pretty-flies-smell.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gboost": patch 3 | --- 4 | 5 | infra: cdk destroy command correction 6 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/core/src/index.server.ts: -------------------------------------------------------------------------------- 1 | export { ServerConfig } from "./config/server-config"; 2 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { SearchParams } from "./search-params"; 2 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export { schema } from "./schema"; 3 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Page() { 2 | return <>; 3 | } 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./widget.js"; 2 | export * from "./component.js"; 3 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/src/app/page.test.tsx: -------------------------------------------------------------------------------- 1 | import { test } from "vitest"; 2 | 3 | test.todo("write tests"); 4 | -------------------------------------------------------------------------------- /docs/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/tsconfig.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@{{GB_APP_ID}}/tsconfig/tsconfig.node.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/page.test.tsx: -------------------------------------------------------------------------------- 1 | import { test } from "vitest"; 2 | 3 | test.todo("write tests"); 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/src/index.shared.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "vitest"; 2 | 3 | test.todo("write tests"); 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/tsconfig.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@{{GB_APP_ID}}/tsconfig/tsconfig.node.json", 3 | } 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/src/app-stage.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from "vitest"; 2 | 3 | test.todo("write tests"); 4 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | widgetsTableName: process.env["WIDGETS_TABLE_NAME"], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --app-bar-height: 60px; 3 | --drawer-width: 240px; 4 | } 5 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --app-bar-height: 60px; 3 | --drawer-width: 240px; 4 | } 5 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/schemas/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./widget-schemas.js"; 2 | export * from "./component-schemas.js"; 3 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-base/index.ts: -------------------------------------------------------------------------------- 1 | export {} from "./oidc-user-base"; 2 | export { type UserBaseProps } from "./user-base.js"; 3 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/entrypoints/api/trpc.ts: -------------------------------------------------------------------------------- 1 | import { initTRPC } from "@trpc/server"; 2 | 3 | export const t = initTRPC.create(); 4 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/README.md: -------------------------------------------------------------------------------- 1 | # /infra 2 | 3 | /infra is where you define your infrastructure with the AWS Cloud Development Kit (CDK) -------------------------------------------------------------------------------- /docs/public/fast-forward-emoji.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /docs/src/pages/overview/guides/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "aws-credentials": "AWS Credentials", 3 | "clean-code": "Clean Code", 4 | "config": "Configuration" 5 | } -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/base/index.ts: -------------------------------------------------------------------------------- 1 | export { BaseEntity } from "./base.entity"; 2 | export { baseSchema } from "./base.schema"; 3 | -------------------------------------------------------------------------------- /docs/src/pages/_app.mdx: -------------------------------------------------------------------------------- 1 | import "../globals.css"; 2 | 3 | export default function App({ Component, pageProps }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /docs/src/pages/infra/tips.mdx: -------------------------------------------------------------------------------- 1 | # Tips 2 | 3 | - Don't define all your infrastructure within class constructors, use methods to make your constructs easier to follow. -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@myapp/tsconfig/tsconfig.ui.json", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/green-boost/HEAD/packages/gboost/templates/minimal/ui/src/app/favicon.ico -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "usernamehw.errorlens" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/core/src/index.shared.ts: -------------------------------------------------------------------------------- 1 | export { SharedConfig } from "./config/shared-config"; 2 | export { StageName } from "./config/stage-name"; 3 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | export function formatDate(date?: string) { 2 | return date ? new Date(date).toLocaleString() : ""; 3 | } 4 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle/0002_calm_mandarin.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "{{GB_SQL_APP_ID}}"."item" ALTER COLUMN "id" SET DEFAULT gen_random_uuid(); -------------------------------------------------------------------------------- /docs/src/pages/overview/contributors/tips.mdx: -------------------------------------------------------------------------------- 1 | # Tips 2 | 3 | ## JavaScript 4 | 5 | - Avoid `export * from "./some-file"`. It makes tracking down imported functions/classes difficult. -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/src/app/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/awslabs/green-boost/HEAD/packages/gboost/templates/minimal/ui/src/app/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/types/search-params.ts: -------------------------------------------------------------------------------- 1 | type SearchParamValues = string | string[] | undefined; 2 | export type SearchParams = Record; 3 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | export default defineConfig({ 3 | resolve: { 4 | dedupe: ["aws-cdk-lib", "cdk-nag"], 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/.envrc: -------------------------------------------------------------------------------- 1 | # export AWS_PROFILE={{GB_APP_ID}} 2 | # export AWS_REGION=TODO 3 | export DB_SECRET_ARN=TODO 4 | export DB_BASTION_ID=TODO 5 | export DB_ENDPOINT=TODO -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/adapters/secondary/index.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export { dbWrite, dbRead } from "./db-adapter"; 3 | export { logger } from "./logger-adapter"; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | "noEmit": true, 5 | }, 6 | "include": ["scripts/**/*", "utils/**/*"], 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { redirect } from "next/navigation"; 3 | 4 | export default async function Page() { 5 | redirect("/items"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/hello/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { redirect } from "next/navigation"; 3 | 4 | export default function Page() { 5 | redirect("/hello/world"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { redirect } from "next/navigation"; 3 | 4 | export default async function Page() { 5 | redirect("/hello/world"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/new/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { CreateItem } from "./CreateItem"; 3 | 4 | export default function Page() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | - 'core' 2 | - 'db' 3 | - 'infra' 4 | - 'packages/tsconfig' 5 | - 'packages/eslint-config-node' 6 | - 'packages/eslint-config-react' 7 | - 'packages/utils' 8 | - 'ui' -------------------------------------------------------------------------------- /packages/gboost/src/create/ask.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | 3 | describe("ask.ts", () => { 4 | test("TODO", () => { 5 | expect("TODO").toEqual("TODO"); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/goodbye/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { redirect } from "next/navigation"; 3 | 4 | export default function Page() { 5 | redirect("/goodbye/world"); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/tsconfig.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@{{GB_APP_ID}}/tsconfig/tsconfig.node.json", 3 | "compilerOptions": { 4 | "exactOptionalPropertyTypes": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/gboost' 3 | - 'packages/gboost-common' 4 | - 'packages/gboost-infra' 5 | - 'packages/gboost-node' 6 | - 'packages/gboost-ui' 7 | - 'docs' 8 | - 'docs/demos/**' -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@myapp/tsconfig/tsconfig.node.json", 3 | "exclude": ["cdk.out"], 4 | "compilerOptions": { 5 | "exactOptionalPropertyTypes": false 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost-ui/src/types/string-with-autocomplete.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @link https://twitter.com/diegohaz/status/1524257274012876801 3 | */ 4 | export type StringWithAutocomplete = T | (string & Record); 5 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/tsconfig/tsconfig.ui.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/vite-react/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | }, 7 | } -------------------------------------------------------------------------------- /packages/gboost-node/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["src/**/*.test.ts", "scripts/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/gboost-ui/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noEmit": false, 5 | "outDir": "dist" 6 | }, 7 | "exclude": ["src/**/*.test.ts", "scripts/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/goodbye/[name]/loading.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { LoadingH2 } from "@/components/LoadingH2"; 3 | 4 | export default function Loading() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/hello/[name]/loading.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { LoadingH2 } from "@/components/LoadingH2"; 3 | 4 | export default function Loading() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost-common/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "noEmit": false 6 | }, 7 | "exclude": ["src/**/*.test.ts", "scripts/**/*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/gboost/src/create/get-template-operations/base-operation-params.ts: -------------------------------------------------------------------------------- 1 | export interface BaseOperationParams { 2 | appId: string; 3 | appTitle: string; 4 | destinationPath: string; 5 | templatesDirPath: string; 6 | } 7 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/README.md: -------------------------------------------------------------------------------- 1 | # /db 2 | 3 | When building a web app, you often will need to seed or fill your database with sample data for demos or to tests various functionality. Use this workspace to create scripts to do that. -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/index.ts: -------------------------------------------------------------------------------- 1 | export { GovCloudCompat } from "./gov-cloud-compat.js"; 2 | export { SuppressNags } from "./suppress-nags/suppress-nags.js"; 3 | export { Suppression } from "./suppress-nags/suppression.js"; 4 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # gboost-docs 2 | 3 | ## 0.1.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [[`4680cc1`](https://github.com/awslabs/green-boost/commit/4680cc1e2290e5838f4b613980b48591683dbe99)]: 8 | - gboost-ui@0.36.0 9 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from "@aws-lambda-powertools/logger"; 2 | 3 | // Environment variable POWERTOOLS_SERVICE_NAME will determine service name of logger 4 | export const logger = new Logger(); 5 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/index.shared.ts: -------------------------------------------------------------------------------- 1 | export { 2 | type ItemSchema, 3 | itemSchema, 4 | updateItemSchema, 5 | type ItemInputSchema, 6 | type UpdateItemInputSchema, 7 | } from "./item.schema"; 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/tsconfig.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@{{GB_APP_ID}}/tsconfig/tsconfig.node.json", 3 | "exclude": ["cdk.out"], 4 | "compilerOptions": { 5 | "exactOptionalPropertyTypes": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "core" 3 | - "infra" 4 | - "packages/eslint-config-next" 5 | - "packages/eslint-config-node" 6 | - "packages/tsconfig" 7 | - "packages/utils" 8 | - "ui" 9 | -------------------------------------------------------------------------------- /docs/IDEAS.md: -------------------------------------------------------------------------------- 1 | # Ideas 2 | 3 | This is a scratch pad for future GB Docs ideas: 4 | 5 | - [] Add overview/guides/editor on which extensions to add: ESLint, Error Lens 6 | - [] Add overview/guides/debugging on how to use JavaScript Debug Terminal -------------------------------------------------------------------------------- /packages/gboost-ui/src/utils/use-trace-update.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest"; 2 | import { useTraceUpdate } from "./use-trace-update.js"; 3 | 4 | test("use-trace-update", () => { 5 | expect(useTraceUpdate).toBeDefined(); 6 | }); 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { Config } from "drizzle-kit"; 3 | 4 | export default { 5 | out: `./src/db/drizzle`, 6 | schema: `./src/modules/**/*.db.ts`, 7 | } satisfies Config; 8 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/tsconfig/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18-strictest-esm/tsconfig.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "noEmit": true, 7 | }, 8 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/utils/kebab-to-title.ts: -------------------------------------------------------------------------------- 1 | export function kebabToTitle(value: string): string { 2 | const words = value 3 | .split("-") 4 | .map((w) => w.at(0)?.toUpperCase() + w.slice(1)); 5 | return words.join(" "); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/delete-item.use-case.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { itemRepo } from "./item.repo"; 3 | 4 | export async function deleteItemUseCase(id: string): Promise { 5 | await itemRepo.remove(id); 6 | } 7 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/tsconfig/tsconfig.node.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "noEmit": true 7 | } 8 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/utils", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": { 6 | ".": "./index.js" 7 | }, 8 | "dependencies": { 9 | "eslint": "^8.36.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/tsconfig/tsconfig.next.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/next"], 3 | "compilerOptions": { 4 | "target": "ES2017", 5 | "module": "ESNext", 6 | "moduleResolution": "Bundler" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/next-env.d.ts.t: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/models/base.ts: -------------------------------------------------------------------------------- 1 | export class Base { 2 | createdDate: string; 3 | updatedDate: string; 4 | constructor(params: Base) { 5 | this.createdDate = params.createdDate; 6 | this.updatedDate = params.updatedDate; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const config = { 3 | content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], 4 | darkMode: "class", 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/schemas/common.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const listSchema = z.object({ 4 | cursor: z.string().optional(), 5 | pageSize: z.number().max(100).default(10), 6 | }); 7 | export type ListSchema = z.infer; 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/utils/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/utils", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": { 6 | ".": "./index.js" 7 | }, 8 | "dependencies": { 9 | "eslint": "^8.53.0" 10 | } 11 | } -------------------------------------------------------------------------------- /examples/cloud-broker/README.md: -------------------------------------------------------------------------------- 1 | # Cloud Broker 2 | 3 | Cloud Broker is an example app built with Green Boost that gives organizations greater control over their AWS accounts. Cloud Broker provides a self-service platform with fine-grained access management and security controls. 4 | 5 | TODO -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/adapters/secondary/logger-adapter.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Logger } from "@aws-lambda-powertools/logger"; 3 | 4 | // Environment variable POWERTOOLS_SERVICE_NAME will determine service name of logger 5 | export const logger = new Logger(); 6 | -------------------------------------------------------------------------------- /packages/gboost-ui/src/utils/download-link.ts: -------------------------------------------------------------------------------- 1 | export async function downloadLink(link: string, fileName: string) { 2 | const element = document.createElement("a"); 3 | element.href = link; 4 | element.download = fileName; 5 | document.body.appendChild(element); 6 | element.click(); 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/.eslintrc.cjs.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@{{GB_APP_ID}}/eslint-config-next"], 7 | }); 8 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/components/NavAsideLink.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from "@vanilla-extract/css"; 2 | 3 | export const link = style({ 4 | textDecoration: "none", 5 | }); 6 | 7 | export const activeLink = style({ 8 | backgroundColor: "var(--amplify-colors-neutral-40)", 9 | }); 10 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "typescript.tsdk": "node_modules/typescript/lib", 6 | "search.exclude": { 7 | "**/node_modules": true, 8 | "**/cdk.out": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/src/app-stage.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Stage } from "aws-cdk-lib"; 3 | import type { Construct } from "constructs"; 4 | 5 | export class AppStage extends Stage { 6 | constructor(scope: Construct, id: string) { 7 | super(scope, id); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/gboost-node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationMap": true, 6 | "incremental": true, 7 | "noEmit": true 8 | }, 9 | "include": ["src/**/*", "scripts/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /docs/src/pages/overview/contributors/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction", 3 | "testing-locally": "Testing Locally", 4 | "development-workflow": "Development Workflow", 5 | "dependencies": "Dependencies", 6 | "fix-audit-issues": "Fix Audit Issues", 7 | "resources": "Resources", 8 | "tips": "Tips" 9 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/stage-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Each stage name corresponds to stage. Local is for developer to deploy locally where all other stages are pipeline stages. 3 | */ 4 | export enum StageName { 5 | Local = "local", 6 | Dev = "dev", 7 | Test = "test", 8 | Prod = "prod", 9 | } 10 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from "react"; 2 | import { View } from "@aws-amplify/ui-react"; 3 | 4 | export function Footer(): ReactElement { 5 | return ( 6 | 7 | Widgets App 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /docs/src/pages/overview/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction", 3 | "prereqs": "Prerequisites", 4 | "quick-start": "Quick Start", 5 | "packages": "Packages", 6 | "guides": "Guides", 7 | "faq": "FAQ", 8 | "common-error-resolutions": "Common Error Resolutions", 9 | "contributors": "Contributors" 10 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@myapp/utils"; 3 | 4 | export default { 5 | "*.ts": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [`eslint --fix --max-warnings=0 ${filesToLint}`, "tsc --noEmit"]; 8 | } 9 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/core/src/config/stage-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Stage (environment) identifier. Local is for developer to deploy locally where all other stages are pipeline stages. 3 | */ 4 | export enum StageName { 5 | Local = "local", 6 | Dev = "dev", 7 | Test = "test", 8 | Prod = "prod", 9 | } 10 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/config/stage-name.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Stage (environment) identifier. Local is for developer to deploy locally where all other stages are pipeline stages. 3 | */ 4 | export enum StageName { 5 | Local = "local", 6 | Dev = "dev", 7 | Test = "test", 8 | Prod = "prod", 9 | } 10 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/pages/Root.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from "react"; 2 | import { Navigate, ScrollRestoration } from "react-router-dom"; 3 | 4 | export function Root(): ReactElement { 5 | return ( 6 | <> 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/config/server-config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { ServerConfig as BaseServerConfig } from "@/core-server"; 3 | 4 | export class ServerConfig extends BaseServerConfig {} 5 | 6 | export const serverConfig = new ServerConfig( 7 | process.env[ServerConfig.envVarNames.STAGE_NAME], 8 | ); 9 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/create-item.use-case.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { itemRepo } from "./item.repo"; 3 | import type { ItemInputSchema } from "./item.schema"; 4 | 5 | export async function createItemUseCase(props: ItemInputSchema): Promise { 6 | await itemRepo.create(props); 7 | } 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/config/server-config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { ServerConfig as BaseServerConfig } from "@/core-server"; 3 | 4 | export class ServerConfig extends BaseServerConfig {} 5 | 6 | export const serverConfig = new ServerConfig( 7 | process.env[ServerConfig.envVarNames.STAGE_NAME], 8 | ); 9 | -------------------------------------------------------------------------------- /packages/gboost/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "noEmit": false 6 | }, 7 | "exclude": [ 8 | "templates/**/*", 9 | "examples/**/*", 10 | "src/**/*.test.ts", 11 | "tests/**/*", 12 | "scripts/**/*" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /docs/src/components/Link.tsx: -------------------------------------------------------------------------------- 1 | import NextLink from "next/link"; 2 | 3 | export function Link(props: Parameters[0]) { 4 | return ( 5 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/tsconfig", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": { 6 | "./*": "./*" 7 | }, 8 | "devDependencies": { 9 | "@tsconfig/node18-strictest-esm": "^1.0.1", 10 | "@tsconfig/vite-react": "^1.0.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/config.ts: -------------------------------------------------------------------------------- 1 | import { StageName } from "@myapp/core"; 2 | 3 | export const config = { 4 | apiUrl: String(import.meta.env.VITE_API_URL), 5 | mode: import.meta.env.MODE, 6 | stage: import.meta.env.VITE_STAGE, 7 | get isLocal() { 8 | return this.stage === StageName.Local; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Template: Minimal 2 | 3 | ## Deploy Infrastructure Locally 4 | 5 | 1. From the root of your monorepo, `cd infra` 6 | 2. `pnpm deploy:local` 7 | 8 | ## Clean Up Infrastructure Locally 9 | 10 | 1. From the root of your monorepo, `cd infra` 11 | 2. `pnpm destroy:local` 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/get-item.use-case.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { ItemEntity } from "./item.entity"; 3 | import { itemRepo } from "./item.repo"; 4 | 5 | export async function getItemUseCase( 6 | id: string, 7 | ): Promise { 8 | return itemRepo.get(id); 9 | } 10 | -------------------------------------------------------------------------------- /packages/gboost/src/utils/handle-aborted.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/terkelg/prompts/issues/252#issuecomment-778683666 2 | export function handleAborted({ aborted }: { aborted: boolean }) { 3 | if (aborted) { 4 | process.nextTick(() => { 5 | console.log("\nStopping. Goodbye 👋"); 6 | process.exit(0); 7 | }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "awslabs/green-boost" }], 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "typescript.preferences.importModuleSpecifierEnding": "js", 6 | "typescript.tsdk": "node_modules/typescript/lib", 7 | "search.exclude": { 8 | "**/node_modules": true, 9 | "**/cdk.out": true, 10 | } 11 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/src/pipeline/pipeline-app.ts: -------------------------------------------------------------------------------- 1 | import { App } from "aws-cdk-lib"; 2 | import { configs } from "../config/configs.js"; 3 | import { StageName } from "@myapp/core"; 4 | import { PipelineStack } from "./pipeline-stack.js"; 5 | 6 | const app = new App(); 7 | new PipelineStack(app, "myapp-pipeline", { env: configs[StageName.Dev] }); 8 | -------------------------------------------------------------------------------- /packages/gboost-common/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationMap": true, 6 | "exactOptionalPropertyTypes": false, 7 | "incremental": true, 8 | "noEmit": true 9 | }, 10 | "include": ["src/**/*", "scripts/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost-infra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | "declaration": true, 5 | "declarationMap": true, 6 | "exactOptionalPropertyTypes": false, 7 | "incremental": true, 8 | "noEmit": true 9 | }, 10 | "include": ["src/**/*", "scripts/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/config/client-config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { SharedConfig } from "@{{GB_APP_ID}}/core/shared"; 3 | 4 | class ClientConfig extends SharedConfig { 5 | appTitle = "{{GB_APP_TITLE}}"; 6 | } 7 | 8 | export const clientConfig = new ClientConfig( 9 | process.env["NEXT_PUBLIC_STAGE_NAME"], 10 | ); 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/index.server.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export { ServerConfig } from "./config/server-config"; 3 | export { 4 | createItemUseCase, 5 | deleteItemUseCase, 6 | getItemUseCase, 7 | listItemsUseCase, 8 | updateItemUseCase, 9 | } from "./modules/item"; 10 | export { logger } from "./adapters/secondary"; 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/config/client-config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { SharedConfig } from "@{{GB_APP_ID}}/core/shared"; 3 | 4 | class ClientConfig extends SharedConfig { 5 | appTitle = "{{GB_APP_TITLE}}"; 6 | } 7 | 8 | export const clientConfig = new ClientConfig( 9 | process.env["NEXT_PUBLIC_STAGE_NAME"], 10 | ); 11 | -------------------------------------------------------------------------------- /docs/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts?(x)": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/src/local-app.ts: -------------------------------------------------------------------------------- 1 | import { App } from "aws-cdk-lib"; 2 | import { AppStage } from "./app-stage.js"; 3 | import { configs } from "./config/configs.js"; 4 | import { StageName } from "@myapp/core"; 5 | 6 | const app = new App(); 7 | const localConfig = configs[StageName.Local]; 8 | new AppStage(app, localConfig.stageId, localConfig); 9 | -------------------------------------------------------------------------------- /packages/gboost/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/goodbye/[name]/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Goodbye } from "./Goodbye"; 3 | 4 | interface PageProps { 5 | params: { name: string }; 6 | } 7 | 8 | export default function Page(props: PageProps) { 9 | const { 10 | params: { name }, 11 | } = props; 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/update-item.use-case.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { itemRepo } from "./item.repo"; 3 | import type { UpdateItemInputSchema } from "./item.schema"; 4 | 5 | export async function updateItemUseCase( 6 | props: UpdateItemInputSchema, 7 | ): Promise { 8 | await itemRepo.update(props); 9 | } 10 | -------------------------------------------------------------------------------- /docs/src/pages/overview/common-error-resolutions.mdx: -------------------------------------------------------------------------------- 1 | # Common Error Resolutions 2 | 3 | ## CDK 4 | - Error: `ChangeSet [PipelineChange] does not exist (Service: AmazonCloudFormation; Status Code: 404; Error Code: ChangeSetNotFound; Request ID: ...)` 5 | - Resolution: Start the pipeline execution again by selecting "Release change" button at top of AWS Console CodePipeline UI. -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@myapp/utils"; 3 | 4 | export default { 5 | "*.ts": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [`eslint --fix --max-warnings=0 ${filesToLint}`, `vitest --passWithNoTests related ${files}`, "tsc --noEmit"]; 8 | } 9 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/entrypoints/api/router.ts: -------------------------------------------------------------------------------- 1 | import { widgetRouter } from "./routers/widget.js"; 2 | import { t } from "./trpc.js"; 3 | import { componentRouter } from "./routers/components.js"; 4 | 5 | export const router = t.router({ 6 | widget: widgetRouter, 7 | component: componentRouter, 8 | }); 9 | export type AppRouter = typeof router; 10 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@myapp/utils"; 3 | 4 | export default { 5 | "*.ts": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [`eslint --fix --max-warnings=0 ${filesToLint}`, `vitest --passWithNoTests related ${files}`, "tsc --noEmit"]; 8 | } 9 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@myapp/utils"; 3 | 4 | export default { 5 | "*.ts?(x)": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [`eslint --fix --max-warnings=0 ${filesToLint}`, `vitest --passWithNoTests related ${files}`, "tsc --noEmit"]; 8 | } 9 | } -------------------------------------------------------------------------------- /packages/gboost-common/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/gboost-infra/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/gboost-node/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/gboost-ui/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | export default { 2 | "*.ts?(x)": (files) => { 3 | const joinedFiles = files.join(" "); 4 | return [ 5 | `eslint --max-warnings=0 --no-warn-ignored ${joinedFiles}`, 6 | `prettier --write ${joinedFiles}`, 7 | `vitest related --run ${joinedFiles}`, 8 | "tsc --noEmit", 9 | ]; 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/LoadingH2.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use client"; 3 | 4 | import Skeleton from "@mui/material/Skeleton"; 5 | import Typography from "@mui/material/Typography"; 6 | 7 | export function LoadingH2() { 8 | return ( 9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/tsconfig/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/tsconfig", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": { 6 | "./*": "./*" 7 | }, 8 | "devDependencies": { 9 | "@tsconfig/next": "^2.0.1", 10 | "@tsconfig/node18": "^18.2.2", 11 | "@tsconfig/strictest": "^2.0.2" 12 | } 13 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/hello/[name]/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Hello } from "@/app/hello/[name]/Hello"; 3 | 4 | interface PageProps { 5 | params: { name: string }; 6 | } 7 | 8 | export default function Page(props: PageProps) { 9 | const { 10 | params: { name }, 11 | } = props; 12 | return ; 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/base/base.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | baseSchema, 3 | type BaseSchema, 4 | type BaseInputSchema, 5 | } from "./base.schema"; 6 | 7 | export abstract class BaseEntity { 8 | readonly props: BaseSchema; 9 | 10 | constructor(props: BaseInputSchema) { 11 | this.props = baseSchema.parse(props); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle/0001_loud_serpent_society.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS "{{GB_SQL_APP_ID}}"."item" ( 2 | "created_at" timestamp with time zone DEFAULT now() NOT NULL, 3 | "description" text NOT NULL, 4 | "id" uuid PRIMARY KEY NOT NULL, 5 | "name" text NOT NULL, 6 | "updated_at" timestamp with time zone DEFAULT now() NOT NULL 7 | ); 8 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/models/widget-component.ts: -------------------------------------------------------------------------------- 1 | import { Base } from "./base.js"; 2 | 3 | export class WidgetComponent extends Base { 4 | widgetId: string; 5 | componentId: string; 6 | constructor(params: WidgetComponent) { 7 | super(params); 8 | this.widgetId = params.widgetId; 9 | this.componentId = params.componentId; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/types/page-props.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { SearchParams } from "@{{GB_APP_ID}}/core/shared"; 3 | 4 | /** 5 | * Generic Next.js props for page.tsx function. 6 | */ 7 | export type PageProps< 8 | T extends Record = Record, 9 | > = { 10 | params: T; 11 | searchParams?: SearchParams; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/gboost-infra/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "noEmit": false 6 | }, 7 | "exclude": [ 8 | "src/**/*.test.ts", 9 | // custom resource functions are bundled by esbuild. see scripts/build.ts 10 | "./src/web-deployment/web-deploy-cr-handler/**", 11 | "scripts/**/*" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/pages/learn/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "intro": "Introduction", 3 | "m1-deploy-gb-app": "M1: Deploy Green Boost Template App", 4 | "m2-js-ts": "M2: JavaScript/TypeScript", 5 | "m3-infra": "M3: Infrastructure", 6 | "m4-backend": "M4: Backend", 7 | "m5-frontend": "M5: Frontend", 8 | "m6-tooling": "M6: Tooling", 9 | "m7-clean-code": "M7: Clean Code", 10 | "resources": "Resources" 11 | } -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/index.shared.ts: -------------------------------------------------------------------------------- 1 | export { SharedConfig } from "./config/shared-config"; 2 | export { StageName } from "./config/stage-name"; 3 | export { type SearchParams } from "./types"; 4 | export { 5 | type ItemSchema, 6 | type ItemInputSchema, 7 | itemSchema, 8 | updateItemSchema, 9 | type UpdateItemInputSchema, 10 | } from "./modules/item/index.shared"; 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle/meta/0000_snapshot.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "92d1c54f-e2c6-44d5-afdc-e5ae23453215", 3 | "prevId": "00000000-0000-0000-0000-000000000000", 4 | "version": "5", 5 | "dialect": "pg", 6 | "tables": {}, 7 | "enums": {}, 8 | "schemas": {}, 9 | "_meta": { 10 | "columns": {}, 11 | "schemas": {}, 12 | "tables": {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/next"], 3 | "compilerOptions": { 4 | "exactOptionalPropertyTypes": false, 5 | "paths": { 6 | "@/*": ["./src/*"], 7 | }, 8 | }, 9 | "include": [ 10 | "next-env.d.ts", 11 | "**/*.ts", 12 | "**/*.tsx", 13 | ".next/types/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@myapp/eslint-config-node"], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@myapp/eslint-config-node"], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@myapp/eslint-config-node"], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/[id]/update-item.action.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use server"; 3 | 4 | import { updateItemUseCase } from "@/core-server"; 5 | import { revalidatePath } from "next/cache"; 6 | 7 | export async function updateItemAction( 8 | props: Parameters[0], 9 | ) { 10 | await updateItemUseCase(props); 11 | revalidatePath("/items"); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/new/create-item.action.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use server"; 3 | 4 | import { createItemUseCase } from "@/core-server"; 5 | import { revalidatePath } from "next/cache"; 6 | 7 | export async function createItemAction( 8 | props: Parameters[0], 9 | ) { 10 | await createItemUseCase(props); 11 | revalidatePath("/items"); 12 | } 13 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/pages/widgets/WidgetsAdditionalActions.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@aws-amplify/ui-react"; 2 | import { ReactElement } from "react"; 3 | import { Link } from "react-router-dom"; 4 | 5 | export function WidgetsAdditionalActions(): ReactElement { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /packages/gboost-common/src/convert-case.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | import { camelToKebab } from "./convert-case.js"; 3 | 4 | describe("convertCase.ts", () => { 5 | const camelCase = "longVariableName"; 6 | const kebabCase = "long-variable-name"; 7 | test(`${camelCase} to kebabCase ${kebabCase}`, () => { 8 | expect(camelToKebab(camelCase)).toBe(kebabCase); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@GB_APP_ID/utils"; 3 | 4 | export default { 5 | "*.ts": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [ 8 | `eslint --fix --max-warnings=0 ${filesToLint}`, 9 | `vitest related --run ${files}`, 10 | "tsc --noEmit", 11 | ]; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/gboost/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/node18"], 3 | "compilerOptions": { 4 | // don't need declaration map b/c end user is only running as cli 5 | "exactOptionalPropertyTypes": false, 6 | "incremental": true, 7 | "noEmit": true 8 | }, 9 | "include": ["src/**/*", "tests/**/*", "scripts/**/*"], 10 | "exclude": ["templates/**/*", "examples/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Green Boost 2 | ![ci workflow](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 3 | 4 | Build Full Stack Serverless Web Apps on AWS Fast 5 | 6 | Docs: https://awslabs.github.io/green-boost/ 7 | 8 | ## Security 9 | 10 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 11 | 12 | ## License 13 | 14 | This project is licensed under the Apache-2.0 License. -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/utils/index.js: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint"; 2 | 3 | export async function removeIgnoredFiles(files) { 4 | const eslint = new ESLint(); 5 | const isIgnored = await Promise.all( 6 | files.map((file) => { 7 | return eslint.isPathIgnored(file); 8 | }) 9 | ); 10 | const filteredFiles = files.filter((_, i) => !isIgnored[i]); 11 | return filteredFiles.join(" "); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { clientConfig } from "@/config/client-config"; 3 | import { Typography } from "@mui/material"; 4 | import { type ReactElement } from "react"; 5 | 6 | export function Footer(): ReactElement { 7 | return ( 8 | 9 | {clientConfig.appTitle} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@GB_APP_ID/utils"; 3 | 4 | export default { 5 | "*.ts": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | return [ 8 | `eslint --fix --max-warnings=0 ${filesToLint}`, 9 | `vitest related --run ${files}`, 10 | "tsc --noEmit", 11 | ]; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/infra/src/local-app.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { App } from "aws-cdk-lib"; 3 | import { AppStage } from "./app-stage"; 4 | import { Config } from "./config/config"; 5 | import { userInfo } from "node:os"; 6 | 7 | const app = new App(); 8 | const localConfig = new Config( 9 | process.env["STAGE_NAME"] || userInfo().username, 10 | ); 11 | new AppStage(app, localConfig.stageId, localConfig); 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/infra/src/local-app.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { App } from "aws-cdk-lib"; 3 | import { AppStage } from "./app-stage"; 4 | import { Config } from "./config/config"; 5 | import { userInfo } from "node:os"; 6 | 7 | const app = new App(); 8 | const localConfig = new Config( 9 | process.env["STAGE_NAME"] || userInfo().username, 10 | ); 11 | new AppStage(app, localConfig.stageId, localConfig); 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/schema.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { pgSchema } from "drizzle-orm/pg-core"; 3 | 4 | // schema is created in custom migration file with {{GB_SQL_APP_ID}}_iam_user so that DB IAM 5 | // user has access to all tables within this schema 6 | // want to use config.appId but https://github.com/drizzle-team/drizzle-kit-mirror/issues/55 7 | export const schema = pgSchema("{{GB_SQL_APP_ID}}"); 8 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/.eslintrc.cjs.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@{{GB_APP_ID}}/eslint-config-node"], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/.eslintrc.cjs.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@{{GB_APP_ID}}/eslint-config-node"], 7 | rules: { 8 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { clientConfig } from "@/config/client-config"; 3 | import Typography from "@mui/material/Typography"; 4 | import { type ReactElement } from "react"; 5 | 6 | export function Footer(): ReactElement { 7 | return ( 8 | 9 | {clientConfig.appTitle} 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/utils/kebab-to-title.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | import { kebabToTitle } from "./kebab-to-title"; 3 | 4 | describe("kebab-to-title", () => { 5 | test("one word case", () => { 6 | expect(kebabToTitle("world")).toBe("World"); 7 | }); 8 | test("two words case", () => { 9 | expect(kebabToTitle("john-smith")).toBe("John Smith"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/utils/index.js: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint"; 2 | 3 | export async function removeIgnoredFiles(files) { 4 | const eslint = new ESLint(); 5 | const isIgnored = await Promise.all( 6 | files.map((file) => { 7 | return eslint.isPathIgnored(file); 8 | }), 9 | ); 10 | const filteredFiles = files.filter((_, i) => !isIgnored[i]); 11 | return filteredFiles.join(" "); 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/infra/src/pipeline/pipeline-app.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { App } from "aws-cdk-lib"; 3 | import { StageName } from "@{{GB_APP_ID}}/core/shared"; 4 | import { PipelineStack } from "./pipeline-stack"; 5 | import { Config } from "../config/config"; 6 | 7 | const app = new App(); 8 | const devConfig = new Config(StageName.Dev); 9 | new PipelineStack(app, "{{GB_APP_ID}}-pipeline", { env: devConfig.env }); 10 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/infra/src/pipeline/pipeline-app.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { App } from "aws-cdk-lib"; 3 | import { StageName } from "@{{GB_APP_ID}}/core/shared"; 4 | import { PipelineStack } from "./pipeline-stack"; 5 | import { Config } from "../config/config"; 6 | 7 | const app = new App(); 8 | const devConfig = new Config(StageName.Dev); 9 | new PipelineStack(app, "{{GB_APP_ID}}-pipeline", { env: devConfig.env }); 10 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/tsconfig.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@{{GB_APP_ID}}/tsconfig/tsconfig.next.json"], 3 | "compilerOptions": { 4 | "exactOptionalPropertyTypes": false, 5 | "paths": { 6 | "@/*": ["./src/*"], 7 | } 8 | }, 9 | "include": [ 10 | "next-env.d.ts", 11 | "**/*.ts", 12 | "**/*.tsx", 13 | ".next/types/**/*.ts" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/web-deployment/common.ts: -------------------------------------------------------------------------------- 1 | export interface InputResourceProperties { 2 | sourceBucketName: string; 3 | sourceObjectKey: string; 4 | destinationBucketName: string; 5 | destinationObjectKeyPrefix: string; 6 | environment: Record; 7 | distributionId?: string; 8 | prune: boolean; 9 | } 10 | 11 | export interface ResourceProperties extends InputResourceProperties { 12 | ServiceToken: string; 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process"; 2 | import { rmSync } from "node:fs"; 3 | import { resolve } from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const thisFilePath = fileURLToPath(import.meta.url); 7 | const outDir = resolve(thisFilePath, `../dist`); 8 | rmSync(outDir, { recursive: true, force: true }); 9 | execSync("pnpm tsc --project tsconfig.build.json", { stdio: "inherit" }); 10 | -------------------------------------------------------------------------------- /packages/gboost-common/scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process"; 2 | import { rmSync } from "node:fs"; 3 | import { resolve } from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const thisFilePath = fileURLToPath(import.meta.url); 7 | const outDir = resolve(thisFilePath, `../dist`); 8 | rmSync(outDir, { recursive: true, force: true }); 9 | execSync("pnpm tsc --project tsconfig.build.json", { stdio: "inherit" }); 10 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/validateUsernames.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export interface UsernameArgs { 4 | usernames: string[]; 5 | } 6 | 7 | let schema: undefined | Joi.ObjectSchema; 8 | export function validate(args: UsernameArgs) { 9 | if (!schema) { 10 | schema = Joi.object({ 11 | usernames: Joi.array().items(Joi.string().max(128)).required(), 12 | }); 13 | } 14 | Joi.assert(args, schema); 15 | } 16 | -------------------------------------------------------------------------------- /packages/gboost-node/scripts/build.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "node:child_process"; 2 | import { rmSync } from "node:fs"; 3 | import { resolve } from "node:path"; 4 | import { fileURLToPath } from "node:url"; 5 | 6 | const thisFilePath = fileURLToPath(import.meta.url); 7 | const outDir = resolve(thisFilePath, `../dist`); 8 | rmSync(outDir, { recursive: true, force: true }); 9 | execSync("pnpm tsc --project tsconfig.build.json", { stdio: "inherit" }); 10 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/base/base.schema.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { z } from "zod"; 3 | 4 | const datetime = z 5 | .string() 6 | .datetime() 7 | .default(() => new Date().toISOString()); 8 | 9 | export const baseSchema = z.object({ 10 | createdAt: datetime, 11 | updatedAt: datetime, 12 | }); 13 | export type BaseSchema = z.infer; 14 | export type BaseInputSchema = z.input; 15 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/file-upload/function/findIndex.ts: -------------------------------------------------------------------------------- 1 | export function findIndex( 2 | bucketMap: { bucket: string; baseKey: string }[], 3 | bucket: string, 4 | ) { 5 | let i = 0; 6 | let notFound = true; 7 | while (notFound && i < bucketMap.length) { 8 | if (bucketMap[i]?.bucket === bucket) { 9 | notFound = false; 10 | } else { 11 | i++; 12 | } 13 | } 14 | if (notFound) { 15 | i = -1; 16 | } 17 | return i; 18 | } 19 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/.lintstagedrc.js.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { removeIgnoredFiles } from "@GB_APP_ID/utils"; 3 | 4 | export default { 5 | "*.ts?(x)": async (files) => { 6 | const filesToLint = await removeIgnoredFiles(files); 7 | // TODO: use setup here: https://nextjs.org/docs/pages/building-your-application/configuring/eslint#lint-staged 8 | return [`next lint`, `vitest related --run ${files}`, "tsc --noEmit"]; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/.eslintrc.cjs.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@{{GB_APP_ID}}/eslint-config-next"], 7 | ignorePatterns: [".next/**"], 8 | rules: { 9 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/gboost-common/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Common Library 2 | 3 | ![ci](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 4 | 5 | Build Full Stack Serverless Web Apps on AWS Fast 6 | 7 | Documentation: https://awslabs.github.io/green-boost/ 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /docs/src/pages/overview/contributors/resources.mdx: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | Building a library whether for React components, CDK Constructs, or Node.js takes skill. 4 | 5 | # React 6 | - [Advanced React Component Composition Guide](https://frontendmastery.com/posts/advanced-react-component-composition-guide/) 7 | - [Building Future Facing Frontend Architectures](https://frontendmastery.com/posts/building-future-facing-frontend-architectures/) 8 | # AWS CDK 9 | - TODO 10 | # Node.js 11 | - TODO -------------------------------------------------------------------------------- /packages/gboost-node/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Node.js Library 2 | 3 | ![ci workflow](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 4 | 5 | Build Full Stack Serverless Web Apps on AWS Fast 6 | 7 | Documentation: https://awslabs.github.io/green-boost/ 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /packages/gboost/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Command Line Interface 2 | 3 | ![ci workflow](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 4 | 5 | Build Full Stack Serverless Web Apps on AWS Fast 6 | 7 | Documentation: https://awslabs.github.io/green-boost/ 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/index.ts: -------------------------------------------------------------------------------- 1 | export { createItemUseCase } from "./create-item.use-case"; 2 | export { deleteItemUseCase } from "./delete-item.use-case"; 3 | export { getItemUseCase } from "./get-item.use-case"; 4 | export { listItemsUseCase } from "./list-items.use-case"; 5 | export { updateItemUseCase } from "./update-item.use-case"; 6 | export { 7 | type ItemSchema, 8 | itemSchema, 9 | type ItemInputSchema, 10 | } from "./item.schema"; 11 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/item.entity.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from "../base"; 2 | import { 3 | itemSchema, 4 | type ItemSchema, 5 | type ItemInputSchema, 6 | } from "./item.schema"; 7 | 8 | /** 9 | * Items 10 | */ 11 | export class ItemEntity extends BaseEntity { 12 | override readonly props: ItemSchema; 13 | 14 | constructor(props: ItemInputSchema) { 15 | super(props); 16 | this.props = itemSchema.parse(props); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/gboost-ui/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost UI Component Library 2 | 3 | ![ci workflow](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 4 | 5 | Build Full Stack Serverless Web Apps on AWS Fast 6 | 7 | Documentation: https://awslabs.github.io/green-boost/ 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /.github/actions/install/action.yml: -------------------------------------------------------------------------------- 1 | name: Install 2 | description: Install PNPM, Node.js, and dependencies 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install PNPM 7 | uses: pnpm/action-setup@v2 8 | with: 9 | version: 8 10 | 11 | - name: Install Node.js 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: 18.x 15 | cache: pnpm 16 | 17 | - name: Install dependencies 18 | run: pnpm install 19 | shell: bash -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "editor.defaultFormatter": "esbenp.prettier-vscode", 6 | "editor.formatOnSave": true, 7 | "eslint.useFlatConfig": true, 8 | "files.associations": { 9 | "*.css": "tailwindcss" 10 | }, 11 | "typescript.tsdk": "node_modules/typescript/lib", 12 | "search.exclude": { 13 | "**/node_modules": true, 14 | "**/cdk.out": true, 15 | "packages/*/dist": true, 16 | } 17 | } -------------------------------------------------------------------------------- /packages/gboost-infra/src/static-site/rewrite-url.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 2 | // @ts-ignore handler is called by CloudFront function runtime 3 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 4 | function handler(event) { 5 | var request = event.request; 6 | var uri = request.uri; 7 | // check whether URI is missing file extension 8 | if (!uri.includes(".")) { 9 | request.uri = "/index.html"; 10 | } 11 | return request; 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost-common/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | camelToKebab, 3 | camelToSnake, 4 | lowerToPascal, 5 | pascalToKebabCase, 6 | } from "./convert-case.js"; 7 | export { 8 | type CognitoGroup, 9 | type CognitoUser, 10 | CognitoUserStatus, 11 | CreateCognitoUser, 12 | type Filter, 13 | type ListUsersArgs, 14 | type ListUsersInGroupArgs, 15 | } from "./user-management.js"; 16 | export { mergeDeep } from "./merge-deep.js"; 17 | export { getErrorMessage } from "./get-error-message.js"; 18 | -------------------------------------------------------------------------------- /packages/gboost-infra/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Infrastructure Construct Library 2 | 3 | ![ci workflow](https://github.com/awslabs/green-boost/actions/workflows/ci.yml/badge.svg) 4 | 5 | Build Full Stack Serverless Web Apps on AWS Fast 6 | 7 | Documentation: https://awslabs.github.io/green-boost/ 8 | 9 | ## Security 10 | 11 | See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. 12 | 13 | ## License 14 | 15 | This project is licensed under the Apache-2.0 License. 16 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/eslint-config-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/eslint-config-react", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": "./.eslintrc.cjs", 6 | "devDependencies": { 7 | "eslint": "^8.36.0", 8 | "eslint-config-prettier": "^8.7.0", 9 | "eslint-config-react-app": "^7.0.1", 10 | "eslint-define-config": "^1.16.0", 11 | "eslint-plugin-prettier": "^4.0.0", 12 | "prettier": "^2.8.0", 13 | "typescript": "^4.9.3" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-base/pre-token-generation.ts: -------------------------------------------------------------------------------- 1 | import type { PreTokenGenerationTriggerHandler } from "aws-lambda"; 2 | 3 | export const handler: PreTokenGenerationTriggerHandler = async (event) => { 4 | const groups = event.request.userAttributes["custom:groups"]; 5 | if (groups) { 6 | event.response = { 7 | claimsOverrideDetails: { 8 | groupOverrideDetails: { 9 | groupsToOverride: [groups], 10 | }, 11 | }, 12 | }; 13 | } 14 | return event; 15 | }; 16 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Examples 2 | 3 | Examples are production-grade web apps built with Green Boost that solve a business problem. They have specific requirements and may take many manual steps to deploy. They're not meant for use as a "quick start". Templates created from `gboost create` on the other hand are simple web apps meeting more generic use cases meant for developers to build on top off. Templates are easy to get started with. You're welcome to deploy these example apps and utilize patterns and code found within them. -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/scripts/seed-items.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { dbWrite } from "../../adapters/secondary"; 3 | import type { ItemInputSchema } from "../../index.shared"; 4 | import { item } from "../../modules/item/item.db"; 5 | 6 | const items: ItemInputSchema[] = []; 7 | 8 | for (const _i of Array(50).keys()) { 9 | items.push({ 10 | name: `Item ${_i}`, 11 | description: `Item ${_i}'s Description`, 12 | }); 13 | } 14 | 15 | await dbWrite.insert(item).values(items).execute(); 16 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle/0000_boring_maginty.sql: -------------------------------------------------------------------------------- 1 | -- Custom SQL migration file, put you code below! -- 2 | create schema {{GB_SQL_APP_ID}}; 3 | create role {{GB_SQL_APP_ID}}_iam_user with login; 4 | grant rds_iam to {{GB_SQL_APP_ID}}_iam_user; 5 | grant usage on schema {{GB_SQL_APP_ID}} to {{GB_SQL_APP_ID}}_iam_user; 6 | -- https://gist.github.com/zaenk/2e9c1936663caae71b212f056b5dfb5f 7 | alter default privileges in schema {{GB_SQL_APP_ID}} grant insert, update, delete, select on tables to {{GB_SQL_APP_ID}}_iam_user; -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-custom-resource.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkCustomResource(construct: IConstruct) { 5 | if (construct.node.path.match(/^.+\/AWS[a-z0-9]{32}\/Resource$/)) { 6 | NagSuppressions.addResourceSuppressions(construct, [ 7 | { 8 | id: "AwsSolutions-L1", 9 | reason: "AWS CDK team will update runtime if security issue arises", 10 | }, 11 | ]); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/db/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/db", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "lint": "eslint \"src/**/*.ts\"", 8 | "watch": "tsc -w", 9 | "typecheck": "tsc --noEmit" 10 | }, 11 | "devDependencies": { 12 | "@myapp/eslint-config-node": "workspace:^", 13 | "@myapp/tsconfig": "workspace:^", 14 | "@myapp/utils": "workspace:^", 15 | "eslint": "^8.36.0", 16 | "eslint-define-config": "^1.16.0", 17 | "typescript": "^4.9.4" 18 | } 19 | } -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/infra/src/utils/get-db-fn-props.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { SubnetType } from "aws-cdk-lib/aws-ec2"; 3 | import type { DbIamCluster, FunctionProps } from "gboost-infra"; 4 | 5 | /** 6 | * Get function props needed to connect Node.js Lambda Function to Aurora PostgreSQL DB 7 | */ 8 | export function getDbFnProps( 9 | cluster: DbIamCluster, 10 | ): Omit { 11 | return { 12 | vpc: cluster.vpc, 13 | vpcSubnets: { subnetType: SubnetType.PRIVATE_WITH_EGRESS }, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /.open-next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/utils/date.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Given UTC ISO timestamp, return local-datetime input value 3 | */ 4 | export function getLocalDateTimeInputValue(date: string) { 5 | const d = new Date(date); 6 | const year = d.getFullYear(); 7 | const month = String(d.getMonth() + 1).padStart(2, "0"); 8 | const day = String(d.getDate()).padStart(2, "0"); 9 | const hours = String(d.getHours()).padStart(2, "0"); 10 | const minutes = String(d.getMinutes()).padStart(2, "0"); 11 | return `${year}-${month}-${day}T${hours}:${minutes}`; 12 | } 13 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/eslint-config-next/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/eslint-config-next", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": "./.eslintrc.cjs", 6 | "devDependencies": { 7 | "eslint": "^8.53.0", 8 | "eslint-config-next": "^14.0.3", 9 | "eslint-config-prettier": "^9.0.0", 10 | "eslint-define-config": "^2.0.0", 11 | "eslint-plugin-prettier": "^5.0.1", 12 | "eslint-plugin-security": "^1.7.1", 13 | "prettier": "^3.1.0", 14 | "typescript": "^5.2.2" 15 | } 16 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/eslint-config-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/eslint-config-node", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": "./.eslintrc.cjs", 6 | "devDependencies": { 7 | "@typescript-eslint/eslint-plugin": "^5.55.0", 8 | "@typescript-eslint/parser": "^5.55.0", 9 | "eslint": "^8.36.0", 10 | "eslint-config-prettier": "^8.7.0", 11 | "eslint-define-config": "^1.16.0", 12 | "eslint-plugin-prettier": "^4.0.0", 13 | "prettier": "^2.8.0", 14 | "typescript": "^4.9.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/main.tsx: -------------------------------------------------------------------------------- 1 | import "@aws-amplify/ui-react/styles.css"; 2 | import "@fontsource/inter/variable.css"; 3 | import { createRoot } from "react-dom/client"; 4 | import { StrictMode } from "react"; 5 | import { RouterProvider } from "react-router-dom"; 6 | import { router } from "./router.js"; 7 | 8 | const container = document.getElementById("app"); 9 | if (container) { 10 | const root = createRoot(container); 11 | root.render( 12 | 13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | const currencyFormatter = Intl.NumberFormat("default", { 2 | currency: "USD", 3 | style: "currency", 4 | }); 5 | 6 | export function formatCurrency(n: number): string { 7 | return currencyFormatter.format(n); 8 | } 9 | 10 | const dateTimeFormatter = Intl.DateTimeFormat("default", { 11 | hour: "numeric", 12 | minute: "numeric", 13 | month: "numeric", 14 | year: "numeric", 15 | day: "numeric", 16 | }); 17 | export function formatDateTime(date: string) { 18 | return dateTimeFormatter.format(new Date(date)); 19 | } 20 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { getItemUseCase } from "@/core-server"; 3 | import type { PageProps } from "@/types/page-props"; 4 | import { UpdateItem } from "./UpdateItem"; 5 | import { redirect } from "next/navigation"; 6 | 7 | export default async function Page({ params }: PageProps<{ id: string }>) { 8 | const { id } = params; 9 | const item = await getItemUseCase(id); 10 | if (item) { 11 | return ; 12 | } else { 13 | return redirect("/item"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{GB_APP_NAME}}", 3 | "short_name": "{{GB_APP_NAME}}", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#18ab4b", 17 | "background_color": "#18ab4b", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/core-server.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // Purpose of this file to safeguard agaginst importing server code/configuration 3 | // into client components. To do this, we restrict imports to @{{GB_APP_ID}}/core/server 4 | // via ESLint but temporarily disable the rule for this file. Then we use 5 | // "server-only" package which ensures this file is only imported in server components. 6 | import "server-only"; // prevents server code from being bundled into client code 7 | // eslint-disable-next-line no-restricted-imports 8 | export * from "@{{GB_APP_ID}}/core/server"; 9 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/core-server.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // Purpose of this file to safeguard agaginst importing server code/configuration 3 | // into client components. To do this, we restrict imports to @{{GB_APP_ID}}/core/server 4 | // via ESLint but temporarily disable the rule for this file. Then we use 5 | // "server-only" package which ensures this file is only imported in server components. 6 | import "server-only"; // prevents server code from being bundled into client code 7 | // eslint-disable-next-line no-restricted-imports 8 | export * from "@{{GB_APP_ID}}/core/server"; 9 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /.open-next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/gov-cloud-compat.ts: -------------------------------------------------------------------------------- 1 | import type { IAspect } from "aws-cdk-lib"; 2 | import { CfnFunction } from "aws-cdk-lib/aws-lambda"; 3 | import type { IConstruct } from "constructs"; 4 | 5 | /** 6 | * Fix compatability issues with gboost-infra constructs and AWS GovCloud. For 7 | * example, change all arm64 Lambdas to x86_64 8 | */ 9 | export class GovCloudCompat implements IAspect { 10 | visit(node: IConstruct) { 11 | if (node instanceof CfnFunction) { 12 | const fn = node as CfnFunction; 13 | fn.architectures = ["x86_64"]; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/gboost-ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@tsconfig/strictest", "@tsconfig/next"], 3 | "compilerOptions": { 4 | "checkJs": false, // remove once fixAmplify.js is removed 5 | // uncomment below once stitches code is removed! 6 | // https://github.com/stitchesjs/stitches/issues/1055 7 | // "declaration": true, 8 | // "declarationMap": true, 9 | "exactOptionalPropertyTypes": false, 10 | "noEmit": true, 11 | "incremental": true, 12 | "target": "ES2015" // allows for of loop 13 | }, 14 | "include": ["src/**/*.ts", "src/**/*.tsx", "scripts/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/modules/item/list-items.use-case.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { ItemEntity } from "./item.entity"; 3 | import type { SearchParams } from "../../types"; 4 | import { itemRepo } from "./item.repo"; 5 | import { listItemsSchema, type ListItemsInputSchema } from "./item.schema"; 6 | 7 | type ListBatchesUseCaseProps = ListItemsInputSchema | SearchParams | undefined; 8 | export function listItemsUseCase( 9 | props: ListBatchesUseCaseProps, 10 | ): Promise { 11 | const result = listItemsSchema.parse(props); 12 | return itemRepo.list(result); 13 | } 14 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/core/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/core", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "exports": { 7 | "./server": "./src/index.server.ts", 8 | "./shared": "./src/index.shared.ts" 9 | }, 10 | "scripts": { 11 | "lint": "eslint \"src/**/*.ts\"", 12 | "test": "vitest run", 13 | "watch": "tsc -w", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "devDependencies": { 17 | "eslint": "^8.53.0", 18 | "eslint-define-config": "^2.0.0", 19 | "typescript": "^5.2.2", 20 | "vitest": "^0.34.6" 21 | } 22 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/hello/[name]/Hello.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use client"; 3 | 4 | import Typography from "@mui/material/Typography"; 5 | import { useMemo } from "react"; 6 | import { kebabToTitle } from "@/utils/kebab-to-title"; 7 | 8 | interface HelloProps { 9 | name: string; 10 | } 11 | 12 | export function Hello(props: HelloProps) { 13 | const { name } = props; 14 | const pascalCaseName = useMemo(() => kebabToTitle(name), [name]); 15 | return ( 16 | 17 | {`Hello, ${pascalCaseName}!`} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/eslint-config-node/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/eslint-config-node", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "exports": "./.eslintrc.cjs", 6 | "devDependencies": { 7 | "@typescript-eslint/eslint-plugin": "^6.11.0", 8 | "@typescript-eslint/parser": "^6.11.0", 9 | "eslint": "^8.53.0", 10 | "eslint-config-prettier": "^9.0.0", 11 | "eslint-define-config": "^2.0.0", 12 | "eslint-plugin-prettier": "^5.0.1", 13 | "eslint-plugin-security": "^1.7.1", 14 | "prettier": "^3.1.0", 15 | "typescript": "^5.2.2" 16 | } 17 | } -------------------------------------------------------------------------------- /docs/src/pages/_meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": { 3 | "display": "hidden", 4 | "title": "Green Boost", 5 | "theme": { 6 | "layout": "raw" 7 | } 8 | }, 9 | "overview": { 10 | "title": "Overview", 11 | "type": "page" 12 | }, 13 | "learn": { 14 | "title": "Learn", 15 | "type": "page" 16 | }, 17 | "cli": { 18 | "title": "CLI", 19 | "type": "page" 20 | }, 21 | "core": { 22 | "title": "Core", 23 | "type": "page" 24 | }, 25 | "infra": { 26 | "title": "Infra", 27 | "type": "page" 28 | }, 29 | "ui": { 30 | "title": "UI", 31 | "type": "page" 32 | } 33 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/entrypoints/api/handler.ts: -------------------------------------------------------------------------------- 1 | import { awsLambdaRequestHandler } from "@trpc/server/adapters/aws-lambda"; 2 | import { logger } from "../../utils/logger.js"; 3 | import { router } from "./router.js"; 4 | 5 | export const handler = awsLambdaRequestHandler({ 6 | router, 7 | onError: (opts) => logger.error("Error:", { ...opts, req: undefined }), 8 | responseMeta: () => ({ 9 | headers: { 10 | // TODO: get origin of frontend (from SSM Param?) and replace * 11 | "Access-Control-Allow-Origin": "*", 12 | "Access-Control-Allow-Methods": "GET,POST", // 13 | }, 14 | }), 15 | }); 16 | -------------------------------------------------------------------------------- /packages/gboost/src/create/get-template-operations/get-minimal-operations.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { type Operation, OperationType } from "../operations/operations.js"; 3 | import type { BaseOperationParams } from "./base-operation-params.js"; 4 | 5 | export function getMinimalOperations(params: BaseOperationParams): Operation[] { 6 | const { destinationPath, templatesDirPath } = params; 7 | return [ 8 | { 9 | type: OperationType.Copy, 10 | name: "CopyMinimalTemplate", 11 | sourcePath: resolve(templatesDirPath, "minimal"), 12 | destinationPath, 13 | }, 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/goodbye/[name]/Goodbye.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use client"; 3 | 4 | import Typography from "@mui/material/Typography"; 5 | import { useMemo } from "react"; 6 | import { kebabToTitle } from "@/utils/kebab-to-title"; 7 | 8 | interface HelloProps { 9 | name: string; 10 | } 11 | 12 | export function Goodbye(props: HelloProps) { 13 | const { name } = props; 14 | const pascalCaseName = useMemo(() => kebabToTitle(name), [name]); 15 | return ( 16 | 17 | {`Goodbye, ${pascalCaseName}!`} 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/utils/decode-search-params.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import type { SearchParams } from "@{{GB_APP_ID}}/core/shared"; 3 | 4 | export function decodeSearchParams( 5 | searchParams: SearchParams = {}, 6 | ): SearchParams { 7 | const decodedSearchParams: SearchParams = {}; 8 | for (const [key, value] of Object.entries(searchParams)) { 9 | if (Array.isArray(value)) { 10 | decodedSearchParams[key] = value.map(decodeURIComponent); 11 | } else if (value) { 12 | decodedSearchParams[key] = decodeURIComponent(value); 13 | } 14 | } 15 | return decodedSearchParams; 16 | } 17 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | extends: ["@myapp/eslint-config-react"], 7 | rules: { 8 | // @myapp/core/app-router: safe guard accidentally importing without type prefix 9 | // should only be imported from src/trpc.ts. 10 | // react-icons: only import from sub paths like react-icons/md to speed up 11 | // development server 12 | "no-restricted-imports": ["error", "@myapp/core/app-router", "react-icons"], 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /docs/src/pages/overview/contributors/intro.mdx: -------------------------------------------------------------------------------- 1 | # Contributor's Introduction 2 | 3 | Hello! Thank you for your interest in contributing to Green Boost. By contributing, you have the opportunity to scale your influence to AWS customers around the world! 4 | 5 | To get started contributing: 6 | 1. Fork the repository by clicking [here](https://github.com/awslabs/green-boost/fork) 7 | 2. Clone your forked repository like: `git clone https://github.com/bestickley/green-boost.git` 8 | 3. Change directory: `cd green-boost` 9 | 4. Install dependencies: `pnpm i` 10 | 5. Try running linting: `pnpm lint`, typechecking: `pnpm typecheck` or tests: `pnpm test`. -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/eslint-config-react/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | root: true, 7 | extends: [ 8 | "react-app", // use CRA linting rules 9 | "plugin:prettier/recommended", 10 | ], 11 | rules: { 12 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 13 | // reduces development bundle size speeding up dev server 14 | "no-restricted-imports": ["error", "react-icons"], 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-log-retention.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkLogRetention(construct: IConstruct) { 5 | if ( 6 | construct.node.path.match( 7 | /.+LogRetention[a-z0-9]{32}\/ServiceRole\/DefaultPolicy\/Resource$/, 8 | ) 9 | ) { 10 | NagSuppressions.addResourceSuppressions(construct, [ 11 | { 12 | id: "AwsSolutions-IAM5", 13 | reason: 14 | "LogRetention IAM policy requires wildcards to update log group expiration", 15 | }, 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-bucket-notifications.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkBucketNotifications(construct: IConstruct) { 5 | if (construct.node.path.includes("BucketNotificationsHandler")) { 6 | NagSuppressions.addResourceSuppressions(construct, [ 7 | { 8 | id: "AwsSolutions-IAM5", 9 | reason: 10 | "The policy only allows the custom resource Lambda to put notifications on S3 Buckets. The CDK uses this role to avoid circular dependencies.", 11 | }, 12 | ]); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "debug TS", 9 | "type": "node", 10 | "request": "launch", 11 | "runtimeExecutable": "/Users/stickb/Library/pnpm/node", 12 | "args": ["${relativeFile}"], 13 | "runtimeArgs": ["--loader", "tsx"], 14 | "skipFiles": ["/**", "node_modules/**"], 15 | "envFile": "${workspaceFolder}/.env" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/theme.ts: -------------------------------------------------------------------------------- 1 | import { createTheme as createStitchesTheme, config } from "gboost-ui"; 2 | import { createTheme } from "@aws-amplify/ui-react"; 3 | 4 | export const theme = createTheme({ 5 | name: "base", 6 | tokens: { 7 | colors: { 8 | brand: { 9 | // add primary/secondary brand colors 10 | primary: {}, 11 | secondary: {}, 12 | }, 13 | }, 14 | }, 15 | }); 16 | 17 | // Temporarily need Stitches Theme until GB migrates to Vanilla Extract 18 | export const stitchesTheme = createStitchesTheme({ 19 | ...config.theme, 20 | colors: { 21 | ...config.theme.colors, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/table.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Table as CdkTable, 3 | type TableProps as CdkTableProps, 4 | } from "aws-cdk-lib/aws-dynamodb"; 5 | import type { Construct } from "constructs"; 6 | import { mergeDeep } from "gboost-common"; 7 | import { constructDefaultProps } from "./construct-default-props.js"; 8 | 9 | export type TableProps = CdkTableProps; 10 | 11 | /** 12 | * DynamoDB Table 13 | */ 14 | export class Table extends CdkTable { 15 | constructor(scope: Construct, id: string, props: TableProps) { 16 | const newProps = mergeDeep(constructDefaultProps?.table, props); 17 | super(scope, id, newProps); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "./node_modules/.bin/vite-node src/local-app.ts", 3 | "watch": { 4 | "include": [ 5 | "src/**", 6 | "../core/**" 7 | ], 8 | "exclude": [ 9 | "node_modules", 10 | "cdk.out", 11 | "../core/node_modules" 12 | ] 13 | }, 14 | "context": { 15 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 16 | "@aws-cdk/core:stackRelativeExports": true, 17 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 18 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 19 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | import createWithNextra from "nextra"; 2 | 3 | const withNextra = createWithNextra({ 4 | theme: "nextra-theme-docs", 5 | themeConfig: "./src/theme.config.tsx", 6 | }); 7 | 8 | export default withNextra({ 9 | basePath: "/green-boost", 10 | images: { 11 | unoptimized: true, 12 | }, 13 | output: "export", 14 | modularizeImports: { 15 | // https://mui.com/material-ui/guides/minimizing-bundle-size/#development-environment 16 | // @mui/icons-material is automatically modularized by Next.js 17 | "@mui/material": { 18 | transform: "@mui/material/{{member}}", 19 | }, 20 | }, 21 | reactStrictMode: true, 22 | }); 23 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/components/NavAsideLink.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink } from "react-router-dom"; 2 | import * as styles from "./NavAsideLink.css.js"; 3 | import { clsx } from "clsx"; 4 | import { ReactElement } from "react"; 5 | 6 | export function NavAsideLink( 7 | props: Parameters[0] 8 | ): ReactElement { 9 | return ( 10 | 12 | clsx({ 13 | "amplify-flex": true, 14 | [styles.link]: true, 15 | [styles.activeLink]: isActive, 16 | }) 17 | } 18 | to={props.to} 19 | > 20 | {props.children} 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/og-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/drizzle/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "5", 3 | "dialect": "pg", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "5", 8 | "when": 1690829406629, 9 | "tag": "0000_boring_maginty", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "5", 15 | "when": 1690986344719, 16 | "tag": "0001_loud_serpent_society", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "5", 22 | "when": 1691000791477, 23 | "tag": "0002_calm_mandarin", 24 | "breakpoints": true 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/includeSelectionSetList.vtl: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2017-02-28", 3 | "operation": "Invoke", 4 | "payload": { 5 | "arguments": $utils.toJson($ctx.arguments), 6 | "identity": $utils.toJson($ctx.identity), 7 | "source": $utils.toJson($ctx.source), 8 | "result": $utils.toJson($ctx.result), 9 | "request": $utils.toJson($ctx.request), 10 | "info": { 11 | "fieldName": $utils.toJson($ctx.info.fieldName), 12 | "parentTypeName": $utils.toJson($ctx.info.parentTypeName), 13 | "selectionSetList": $utils.toJson($ctx.info.selectionSetList), 14 | "variables": $utils.toJson($ctx.info.variables) 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/core/src/config/server-config.ts: -------------------------------------------------------------------------------- 1 | import { SharedConfig } from "./shared-config"; 2 | import { StageName } from "./stage-name"; 3 | 4 | /** 5 | * Config used only on server 6 | */ 7 | export class ServerConfig extends SharedConfig { 8 | /** 9 | * Environment variable names. No [magic strings](https://deviq.com/antipatterns/magic-strings)! 10 | */ 11 | static envVarNames = { 12 | STAGE_NAME: "STAGE_NAME", 13 | }; 14 | 15 | constructor(stageName?: string) { 16 | super(stageName || StageName.Local); 17 | } 18 | } 19 | 20 | export const serverConfig = new ServerConfig( 21 | process.env[ServerConfig.envVarNames.STAGE_NAME], 22 | ); 23 | -------------------------------------------------------------------------------- /packages/gboost/src/create/operations/operations.ts: -------------------------------------------------------------------------------- 1 | import type { CopyOperation } from "./copy.js"; 2 | import type { ReplaceOperation } from "./replace.js"; 3 | import type { RenameFilesOperation } from "./rename-files.js"; 4 | import type { UpdatePackageJsonOperation } from "./update-package-json.js"; 5 | 6 | export { OperationType } from "./common.js"; 7 | export type Operation = 8 | | CopyOperation 9 | | ReplaceOperation 10 | | UpdatePackageJsonOperation 11 | | RenameFilesOperation; 12 | 13 | export { copy } from "./copy.js"; 14 | export { replace } from "./replace.js"; 15 | export { updatePackageJson } from "./update-package-json.js"; 16 | export { renameFiles } from "./rename-files.js"; 17 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/adapters/secondary/db-adapter.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { drizzle } from "drizzle-orm/postgres-js"; 3 | import { item } from "../../modules/item/item.db"; 4 | import type { DrizzleConfig } from "drizzle-orm"; 5 | import { sqlRead, sqlWrite } from "../../db/config"; 6 | 7 | const schema = { 8 | item, 9 | }; 10 | const drizzleConfig: DrizzleConfig = { 11 | logger: process.env["NODE_ENV"] !== "production", 12 | schema, 13 | }; 14 | 15 | /** 16 | * DB Write Instance 17 | */ 18 | export const dbWrite = drizzle(sqlWrite, drizzleConfig); 19 | 20 | /** 21 | * DB Reader Instance 22 | * 23 | */ 24 | export const dbRead = drizzle(sqlRead, drizzleConfig); 25 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, ReactElement, SetStateAction } from "react"; 2 | import { Header as GbHeader } from "gboost-ui"; 3 | import { Heading } from "@aws-amplify/ui-react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | interface HeaderProps { 7 | open: boolean; 8 | setOpen: Dispatch>; 9 | } 10 | 11 | export function Header(props: HeaderProps): ReactElement { 12 | const { open, setOpen } = props; 13 | return ( 14 | 17 | Widgets 18 | 19 | } 20 | open={open} 21 | setOpen={setOpen} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /docs/public/green-boost-gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Green Boost 14 | 15 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/getCognitoIdentity.ts: -------------------------------------------------------------------------------- 1 | import type { AppSyncIdentityCognito, AppSyncResolverEvent } from "aws-lambda"; 2 | 3 | // create custom type so consumer doesn't have to assert groups exist 4 | interface AppSyncIdentityCognitoCustom extends AppSyncIdentityCognito { 5 | groups: string[]; 6 | } 7 | 8 | export function getCognitoIdentity( 9 | e: AppSyncResolverEvent, 10 | ): AppSyncIdentityCognitoCustom { 11 | const identity = e.identity as AppSyncIdentityCognitoCustom; 12 | if (identity?.groups !== null) { 13 | return identity; 14 | } else { 15 | throw new Error( 16 | "AppSyncResolverEvent does not have cognito identity with groups", 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/static-site/response-headers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from "vitest"; 2 | import { getCsp, getCspSource } from "./response-headers.js"; 3 | 4 | describe("getCspSource", () => { 5 | test("CSP sources are properly quoted and spaced", () => { 6 | expect(getCspSource(["none", "self", "https://google.com"])).toBe( 7 | "'none' 'self' https://google.com", 8 | ); 9 | }); 10 | }); 11 | 12 | describe("getCsp", () => { 13 | test("default csp", () => { 14 | const result = getCsp({}); 15 | expect(result).toBe( 16 | "default-src 'self'; form-action 'none'; navigate-to 'none'; object-src 'none';", 17 | ); 18 | }); 19 | }); 20 | 21 | // TODO add tests for other functions 22 | -------------------------------------------------------------------------------- /docs/src/pages/overview/quick-start.mdx: -------------------------------------------------------------------------------- 1 | # Quick Start 2 | 3 | 1. Follow the [prerequisites](./prereqs). 4 | 2. Install the Green Boost CLI: `pnpm add -g gboost`. 5 | 3. Create a web app with: `gboost create`. 6 | 4. Follow the prompts to select a template, directory, app id, and app title. To learn more about `gboost create` and the templates available, learn more [here](../cli/create). 7 | 5. Change directory into your directory: `cd ` 8 | 6. Install dependencies: `pnpm i`. 9 | 7. Change directory into the infrastructure folder: `cd infra` 10 | 8. Deploy: `pnpm deploy:local` 11 | 9. View your web app the CloudFront URL printed in your terminal towards the end of output from the previous command. 12 | 10. Clean up: `pnpm destroy:local`. -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/favicon-16x16.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR(-SgAMA�� �a cHRMz&�����u0�`:�p��Q<�PLTE�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�J�J0�^'�W�I�P7�c�Ht̓���D�m�H)�X���~Л"�R�IwΕ������qː2�_���������=�h����Ԣ����������ج@�j���Z�~�N���aƄ�L(�W��עݷ�J@�j:�e"�SO�v�P�K���WtRNSa���c��e����d�g����72bKGD&Z��tIME�5J(w��IDAT�U���@ E����(���b�ޱ��7�(;�y�=�L.��"AԴJc�Y�a򈔊ɉ�@�r��j ��Qo4[�N��gR �p�'��_����|�9R���ow{�q8�Η��F'�������� ���+�C���z<-�LÖ�.Y�H���ƻ%tEXtdate:create2022-01-19T18:53:19+00:00����%tEXtdate:modify2022-01-19T18:53:19+00:00��2WzTXtRaw profile type iptcx��� qV((�O��I�R# .c #K� D�4�d#�T ��������ˀH�J.�t�B5�IEND�B`� -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-bucket-deployment.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkBucketDeployment(construct: IConstruct) { 5 | if (construct.node.path.includes("Custom::CDKBucketDeployment")) { 6 | NagSuppressions.addResourceSuppressions(construct, [ 7 | { 8 | id: "AwsSolutions-IAM5", 9 | reason: 10 | "Wildcards are used to succinctly define a specific set or permissions to defined resources.", 11 | }, 12 | { 13 | id: "AwsSolutions-L1", 14 | reason: 15 | "Ok for CDKBucketDeployment function to not use latest Lambda runtime", 16 | }, 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/infra/src/utils/connect-fn-to-db.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Port } from "aws-cdk-lib/aws-ec2"; 3 | import type { DbIamCluster, Function as GbFunction } from "gboost-infra"; 4 | 5 | /** 6 | * Connect Aurora PostgreSQL DB to Node.js Lambda Function. Add required environment 7 | * variables. 8 | */ 9 | export function connectFnToDb(cluster: DbIamCluster, fn: GbFunction) { 10 | // TODO: verify this works 11 | fn.connections.allowTo(cluster, Port.tcp(cluster.clusterEndpoint.port)); 12 | cluster.grantConnect(fn); 13 | fn.addEnvironment("PGHOST", String(cluster.clusterEndpoint.hostname)); 14 | // uncomment below if reader instance is added 15 | // fn.addEnvironment("PGHOST_RO", String(cluster.clusterReadEndpoint.hostname)); 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/components/Feature.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import type { Accessibility } from "lucide-react"; 3 | 4 | interface FeatureProps { 5 | children?: ReactNode; 6 | description: ReactNode; 7 | icon: typeof Accessibility; 8 | title: ReactNode; 9 | } 10 | 11 | export function Feature(props: FeatureProps) { 12 | return ( 13 |
16 |
17 | 18 |

{props.title}

19 |
20 | {props.description} 21 |
22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/models/component.ts: -------------------------------------------------------------------------------- 1 | import { Base } from "./base.js"; 2 | 3 | export class Component extends Base { 4 | description?: string | undefined; 5 | expirationDate: string; 6 | id: string; 7 | inStock: number; 8 | name: string; 9 | price: number; 10 | rating: number; 11 | 12 | constructor(params: Component) { 13 | super(params); 14 | this.description = params.description; 15 | this.expirationDate = params.expirationDate; 16 | this.id = params.id; 17 | this.name = params.name; 18 | this.price = params.price; 19 | this.rating = params.rating; 20 | this.inStock = params.inStock; 21 | } 22 | } 23 | 24 | export type CreateComponent = Omit< 25 | Component, 26 | "id" | "createdDate" | "updatedDate" 27 | >; 28 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/trpc.ts: -------------------------------------------------------------------------------- 1 | import { createTRPCReact, httpBatchLink } from "@trpc/react-query"; 2 | // eslint-disable-next-line no-restricted-imports 3 | import type { AppRouter as _AppRouter } from "@myapp/core/router"; 4 | import { config } from "./config.js"; 5 | 6 | // This type-only AppRouter is safe to import anywhere throughout ui code. 7 | // AppRouter from "@myapp/core/app-router" without type prefix could import 8 | // server code so beware 9 | export type AppRouter = _AppRouter; 10 | export const trpc = createTRPCReact(); 11 | export const trpcClient = trpc.createClient({ 12 | links: [ 13 | httpBatchLink({ 14 | url: config.apiUrl + "/trpc", 15 | fetch: (input, init) => fetch(input, { ...init, mode: "cors" }), 16 | }), 17 | ], 18 | }); 19 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/utils/get-db-auth-token.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Signer } from "@aws-sdk/rds-signer"; 3 | 4 | interface GetDbPasswordParams { 5 | host: string; 6 | port: number; 7 | user: string; 8 | } 9 | 10 | /** 11 | * Gets signed DB auth token for usage as IAM user within Aurora 12 | * Note, the IAM user must be created before this password can be used. 13 | * @link https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.html 14 | */ 15 | export async function getDbAuthToken(params: GetDbPasswordParams) { 16 | const { host, port, user } = params; 17 | const signer = new Signer({ 18 | hostname: host, 19 | port: port, 20 | username: user, 21 | }); 22 | return signer.getAuthToken(); 23 | } 24 | -------------------------------------------------------------------------------- /docs/src/pages/overview/packages.mdx: -------------------------------------------------------------------------------- 1 | # Packages 2 | 3 | Green Boost is published into 5 packages on NPM: 4 | - [gboost](https://www.npmjs.com/package/gboost): Command Line Interface (CLI) 5 | - [gboost-common](https://www.npmjs.com/package/gboost-common): Utility library to share commonly needed code between other libraries. 6 | - [gboost-infra](https://www.npmjs.com/package/gboost-infra): AWS CDK Constructs Library. 7 | - [gboost-node](https://www.npmjs.com/package/gboost-node): Node.js backend code typically including functions run in AWS Lambda. 8 | - [gboost-ui](https://www.npmjs.com/package/gboost-ui): React UI Component Library built on Material UI and Next.js. 9 | 10 | Each package is versioned and released differently according to semantic versioning. Packages are only published as ES modules. -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/page.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { listItemsUseCase, logger } from "@/core-server"; 3 | import type { PageProps } from "@/types/page-props"; 4 | import { redirect } from "next/navigation"; 5 | import { ItemsTable } from "./ItemsTable"; 6 | import { decodeSearchParams } from "@/utils/decode-search-params"; 7 | 8 | export const dynamic = "force-dynamic"; 9 | 10 | export default async function Page(props: PageProps) { 11 | try { 12 | const decodedSearchParams = decodeSearchParams(props.searchParams); 13 | const items = await listItemsUseCase(decodedSearchParams); 14 | return i.props)} />; 15 | } catch (err) { 16 | logger.error("Error at /items", { err }); 17 | } 18 | redirect("/items"); 19 | } 20 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/construct-default-props.ts: -------------------------------------------------------------------------------- 1 | import type { BucketProps } from "./bucket/bucket.js"; 2 | import type { FunctionProps } from "./function.js"; 3 | import type { TableProps } from "./table.js"; 4 | import { mergeDeep } from "gboost-common"; 5 | 6 | /** 7 | * Default props for all constructs from gboost-infra 8 | */ 9 | export interface ConstructDefaultProps { 10 | bucket?: Partial; 11 | function?: Partial; 12 | table?: Partial; 13 | } 14 | 15 | export let constructDefaultProps: ConstructDefaultProps = {}; 16 | 17 | /** 18 | * Set gboost-infra's default construct props by deep merging. 19 | */ 20 | export function setConstructDefaultProps(props: ConstructDefaultProps) { 21 | constructDefaultProps = mergeDeep(props, constructDefaultProps); 22 | } 23 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/infra/src/app/stateless/waf-stack.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Stack, type StackProps } from "aws-cdk-lib"; 3 | import type { Construct } from "constructs"; 4 | import { ManagedRuleName, WebAcl } from "./web-acl"; 5 | import type { Config } from "../../config/config"; 6 | 7 | interface WafStackProps extends StackProps { 8 | config: Config; 9 | } 10 | 11 | export class WafStack extends Stack { 12 | webAcl: WebAcl; 13 | 14 | constructor(scope: Construct, id: string, props: WafStackProps) { 15 | super(scope, id, props); 16 | this.webAcl = this.#createWebAcl(); 17 | } 18 | 19 | #createWebAcl() { 20 | return new WebAcl(this, "WebAcl", { 21 | managedRuleNames: Object.values(ManagedRuleName), 22 | scope: "CLOUDFRONT", 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/monorepo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "pnpm --recursive --parallel build", 8 | "lint": "pnpm --recursive --parallel lint", 9 | "test": "pnpm --recursive --parallel test", 10 | "typecheck": "pnpm --recursive --parallel typecheck", 11 | "prepare": "husky install", 12 | "preinstall": "npx only-allow pnpm" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^20.9.1", 16 | "eslint": "^8.53.0", 17 | "husky": "^8.0.3", 18 | "lint-staged": "^15.1.0", 19 | "typescript": "^5.2.2" 20 | }, 21 | "dependencies": { 22 | "esbuild": "^0.19.5" 23 | }, 24 | "pnpm": { 25 | "overrides": {}, 26 | "peerDependencyRules": {} 27 | } 28 | } -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/theme/ThemeRegistry.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use client"; 3 | 4 | import { Experimental_CssVarsProvider as CssVarsProvider } from "@mui/material/styles"; 5 | import CssBaseline from "@mui/material/CssBaseline"; 6 | import { NextAppDirEmotionCacheProvider } from "./EmotionCache"; 7 | import { theme } from "./theme"; 8 | 9 | interface ThemeRegistryProps { 10 | children: React.ReactNode; 11 | nonce: string | null; 12 | } 13 | export function ThemeRegistry(props: ThemeRegistryProps) { 14 | return ( 15 | 18 | 19 | 20 | {props.children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/theme/ThemeRegistry.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | "use client"; 3 | 4 | import { Experimental_CssVarsProvider as CssVarsProvider } from "@mui/material/styles"; 5 | import CssBaseline from "@mui/material/CssBaseline"; 6 | import { NextAppDirEmotionCacheProvider } from "./EmotionCache"; 7 | import { theme } from "./theme"; 8 | 9 | interface ThemeRegistryProps { 10 | children: React.ReactNode; 11 | nonce: string | null; 12 | } 13 | export function ThemeRegistry(props: ThemeRegistryProps) { 14 | return ( 15 | 18 | 19 | 20 | {props.children} 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/App.css.ts: -------------------------------------------------------------------------------- 1 | import { globalStyle } from "@vanilla-extract/css"; 2 | 3 | // https://www.joshwcomeau.com/css/custom-css-reset/ 4 | globalStyle("*, *::before, *::after", { 5 | boxSizing: "border-box", 6 | }); 7 | globalStyle("*", { 8 | margin: 0, 9 | fontFamily: "var(--amplify-fonts-default-variable)", 10 | }); 11 | globalStyle("html,body", { 12 | height: "100%", 13 | }); 14 | globalStyle("body", { 15 | lineHeight: 1.5, 16 | fontSmooth: "antialiased", 17 | }); 18 | globalStyle("img, picture, video, canvas, svg, iframe", { 19 | display: "block", 20 | maxWidth: "100%", 21 | }); 22 | globalStyle("p, h1, h2, h3, h4, h5, h6", { 23 | overflowWrap: "break-word", 24 | }); 25 | globalStyle("#root, #__next", { 26 | isolation: "isolate", 27 | }); 28 | // Green Boost Global Styles 29 | globalStyle("a", { 30 | all: "unset", 31 | }); 32 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/modules/item/item.db.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { text, timestamp, uuid } from "drizzle-orm/pg-core"; 3 | import { schema } from "../../db"; 4 | import type { InferModel } from "drizzle-orm"; 5 | 6 | export const item = schema.table("item", { 7 | createdAt: timestamp("created_at", { 8 | withTimezone: true, 9 | mode: "string", 10 | }) 11 | .notNull() 12 | .defaultNow(), 13 | description: text("description").notNull(), 14 | id: uuid("id").defaultRandom().primaryKey(), 15 | name: text("name").notNull(), 16 | updatedAt: timestamp("updated_at", { 17 | withTimezone: true, 18 | mode: "string", 19 | }) 20 | .notNull() 21 | .defaultNow(), 22 | }); 23 | 24 | export type SelectItem = InferModel; 25 | export type InsertItem = InferModel; 26 | -------------------------------------------------------------------------------- /.changeset/neat-waves-impress.md: -------------------------------------------------------------------------------- 1 | --- 2 | "gboost-ui": minor 3 | --- 4 | 5 | Remove deprecated exports from `gboost-ui`. This release migrates from Amplify UI and Vite to Material UI and Next.js. See discussions: [Frontend Tooling / Framework](https://github.com/awslabs/green-boost/discussions/214) and [Switch UI Component Library](https://github.com/awslabs/green-boost/discussions/213) for more details. For users currently on `gboost-ui`, we understand this is a very large change, but we believe these new technologies will enable Green Boost developers to build faster with React. All UI components have equivalents within Material UI that we recommend you upgrade to. If you cannot upgrade to Material UI, we recommend you extract the last `gboost-ui` source code from [here](https://github.com/awslabs/green-boost/tree/320f3e00d0fde2d86b570408648de260a5a3e2fd) and use it in your application. 6 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/layout/DrawerList.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { List } from "@mui/material"; 3 | import { type ReactElement } from "react"; 4 | import { DrawerListItem } from "./DrawerListItem"; 5 | import ListIcon from "@mui/icons-material/List"; 6 | import { useTheme } from "@mui/material/styles"; 7 | 8 | interface DrawerListProps { 9 | open: boolean; 10 | } 11 | 12 | export function DrawerList(props: DrawerListProps): ReactElement { 13 | const theme = useTheme(); 14 | const { open } = props; 15 | // TODO: highlight based on path 16 | return ( 17 | 18 | } 21 | open={open} 22 | text="Items" 23 | /> 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/pages-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Docs 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | paths: 9 | - docs/** 10 | - .github/workflows/pages-deploy.yml 11 | 12 | jobs: 13 | deploy_docs: 14 | name: Deploy Docs to GitHub Pages 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repo 18 | uses: actions/checkout@v3 19 | 20 | - name: Install 21 | uses: ./.github/actions/install 22 | 23 | - name: Build docs 24 | working-directory: docs 25 | run: pnpm build 26 | 27 | - name: Deploy to GitHub Pages 28 | uses: peaceiris/actions-gh-pages@v3 29 | with: 30 | github_token: ${{ secrets.GITHUB_TOKEN }} 31 | publish_dir: ./docs/out 32 | user_name: 'github-actions[bot]' 33 | user_email: 'github-actions[bot]@users.noreply.github.com' -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/domain/models/widget.ts: -------------------------------------------------------------------------------- 1 | import { Base } from "./base.js"; 2 | 3 | export enum WidgetStep { 4 | Step1 = "Step1", 5 | Step2 = "Step2", 6 | Step3 = "Step3", 7 | } 8 | 9 | export class Widget extends Base { 10 | active: boolean; 11 | description?: string | undefined; 12 | expirationDate: string; 13 | id: string; 14 | name: string; 15 | price: number; 16 | rating: number; 17 | step: WidgetStep; 18 | 19 | constructor(params: Widget) { 20 | super(params); 21 | this.active = params.active; 22 | this.description = params.description; 23 | this.expirationDate = params.expirationDate; 24 | this.id = params.id; 25 | this.name = params.name; 26 | this.price = params.price; 27 | this.rating = params.rating; 28 | this.step = params.step; 29 | } 30 | } 31 | 32 | export type CreateWidget = Omit; 33 | -------------------------------------------------------------------------------- /packages/gboost/src/create/operations/common.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from "node:fs"; 2 | import { resolve } from "node:path"; 3 | 4 | export enum OperationType { 5 | Copy = "Copy", 6 | Replace = "Replace", 7 | UpdatePackageJson = "UpdatePackageJson", 8 | RenameFiles = "UpdateFileName", 9 | } 10 | 11 | export interface BaseOperation { 12 | name: string; 13 | } 14 | 15 | /** 16 | * Given path of directory, returns array of all file paths within directory 17 | */ 18 | export function listFilePaths(dirPath: string): string[] { 19 | const filePaths: string[] = []; 20 | const directory = readdirSync(dirPath, { withFileTypes: true }); 21 | for (const d of directory) { 22 | const filePath = resolve(dirPath, d.name); 23 | if (d.isDirectory()) { 24 | filePaths.push(...listFilePaths(filePath)); 25 | } else { 26 | filePaths.push(filePath); 27 | } 28 | } 29 | return filePaths; 30 | } 31 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/items/ItemsToolbar.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Box, Button } from "@mui/material"; 3 | import RefreshIcon from "@mui/icons-material/Refresh"; 4 | import AddIcon from "@mui/icons-material/Add"; 5 | import Link from "next/link"; 6 | 7 | interface ItemsToolbarProps { 8 | isPending: boolean; 9 | handleRefresh: () => void; 10 | } 11 | 12 | export function ItemsToolbar(props: ItemsToolbarProps) { 13 | const { handleRefresh, isPending } = props; 14 | return ( 15 | t.spacing(2)} justifyContent="flex-end"> 16 | 23 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-dynamo/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Template: CRUD with DynamoDB 2 | 3 | ## Deploy Infrastructure Locally 4 | 5 | 1. From the root of your monorepo, `cd infra` 6 | 2. `pnpm deploy:local` 7 | 8 | ## Develop Frontend with Fast Refresh 9 | 10 | 1. Copy your API Gateway URL to VITE_API_URL environment variable in ui/.env.local 11 | 12 | - Example: `VITE_API_URL=https://0p9l5vkduj.execute-api.us-east-1.amazonaws.com/prod` 13 | 14 | 2. From the root of your monorepo, `cd ui` 15 | 3. `pnpm dev` 16 | 17 | ## Develop Backend with Hotswap 18 | 19 | 1. From the root of your monorepo, `cd infra` 20 | 2. `pnpm watch:api` 21 | 3. Make file changes in `core/` or `infra/` and updates will be "hotswapped" and function logs tailed. With small Lambda functions, deployment can ~5 seconds. 22 | 23 | ## Clean Up Infrastructure Locally 24 | 25 | 1. From the root of your monorepo, `cd infra` 26 | 2. `pnpm destroy:local` 27 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/README.md: -------------------------------------------------------------------------------- 1 | # Green Boost Template: CRUD with Postgres 2 | 3 | ## Deploy Infrastructure Locally 4 | 5 | 1. From the root of your monorepo, `cd infra` 6 | 2. `pnpm deploy:local` 7 | 8 | ## Develop Frontend with Fast Refresh 9 | 10 | 1. Copy your API Gateway URL to VITE_API_URL environment variable in ui/.env.local 11 | 12 | - Example: `VITE_API_URL=https://0p9l5vkduj.execute-api.us-east-1.amazonaws.com/prod` 13 | 14 | 2. From the root of your monorepo, `cd ui` 15 | 3. `pnpm dev` 16 | 17 | ## Develop Backend with Hotswap 18 | 19 | 1. From the root of your monorepo, `cd infra` 20 | 2. `pnpm watch:api` 21 | 3. Make file changes in `core/` or `infra/` and updates will be "hotswapped" and function logs tailed. With small Lambda functions, deployment can ~5 seconds. 22 | 23 | ## Clean Up Infrastructure Locally 24 | 25 | 1. From the root of your monorepo, `cd infra` 26 | 2. `pnpm destroy:local` 27 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Bucket, type BucketProps } from "./bucket/bucket.js"; 2 | export { Function, type FunctionProps } from "./function.js"; 3 | export { GraphqlApi } from "./graphql-api.js"; 4 | export { StaticSite, type StaticSiteProps } from "./static-site/static-site.js"; 5 | export { GovCloudCompat, SuppressNags, Suppression } from "./aspects/index.js"; 6 | export { Table, type TableProps } from "./table.js"; 7 | export { type UserManagementProps } from "./user-management/user-management.js"; 8 | export { type UserBaseProps } from "./user-base/index.js"; 9 | export { FileUpload, type FileUploadProps } from "./file-upload/file-upload.js"; 10 | export { WebDeployment } from "./web-deployment/web-deployment.js"; 11 | export { 12 | type ConstructDefaultProps, 13 | constructDefaultProps, 14 | setConstructDefaultProps, 15 | } from "./construct-default-props.js"; 16 | export { DbIamCluster } from "./db-iam-cluster.js"; 17 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gboost-docs", 3 | "version": "0.1.1", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "dev": "next dev", 9 | "format": "prettier --write .", 10 | "lint": "eslint --max-warnings=0 --no-warn-ignored src/**/*.{ts,tsx}", 11 | "start": "next start", 12 | "typecheck": "tsc --noEmit" 13 | }, 14 | "dependencies": { 15 | "gboost-ui": "workspace:^", 16 | "lucide-react": "^0.411.0", 17 | "next": "14.2.10", 18 | "nextra": "^2.13.4", 19 | "nextra-theme-docs": "^2.13.4", 20 | "react": "^18.3.1", 21 | "react-dom": "^18.3.1" 22 | }, 23 | "devDependencies": { 24 | "@tsconfig/next": "^2.0.1", 25 | "@tsconfig/strictest": "^2.0.5", 26 | "@types/react": "^18.2.37", 27 | "@types/react-dom": "^18.3.0", 28 | "autoprefixer": "^10.4.19", 29 | "postcss": "^8.4.39", 30 | "tailwindcss": "^3.4.6" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/infra/src/app/stateless/waf-stack.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Stack, type StackProps } from "aws-cdk-lib"; 3 | import type { Construct } from "constructs"; 4 | import { ManagedWebAclRuleName, WebAcl } from "./web-acl"; 5 | import type { Config } from "../../config/config"; 6 | 7 | interface WafStackProps extends StackProps { 8 | config: Config; 9 | } 10 | 11 | export class WafStack extends Stack { 12 | webAclArn: string; 13 | #props: WafStackProps; 14 | 15 | constructor(scope: Construct, id: string, props: WafStackProps) { 16 | super(scope, id, props); 17 | this.#props = props; 18 | this.#props; 19 | const webAcl = this.#createWebAcl(); 20 | this.webAclArn = webAcl.webAclArn; 21 | } 22 | 23 | #createWebAcl() { 24 | return new WebAcl(this, "WebAcl", { 25 | scope: "CLOUDFRONT", 26 | managedRuleNames: Object.values(ManagedWebAclRuleName), 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/gboost-common/src/convert-case.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * camelCase to kebab-case 3 | * @link https://gist.github.com/nblackburn/875e6ff75bc8ce171c758bf75f304707 4 | */ 5 | export function camelToKebab(s: string) { 6 | return s.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, "$1-$2").toLowerCase(); 7 | } 8 | 9 | /** 10 | * camelCase to snake_case 11 | * @link https://stackoverflow.com/questions/54246477/how-to-convert-camelcase-to-snake-case-in-javascript 12 | */ 13 | export function camelToSnake(s: string): string { 14 | return s.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); 15 | } 16 | 17 | /** 18 | * lowercase to Pascalcase 19 | */ 20 | export function lowerToPascal(s: string) { 21 | return s[0]?.toUpperCase() + s.slice(1); 22 | } 23 | 24 | /** 25 | * Converts WidgetsDynamo to widgets-dynamo 26 | */ 27 | export function pascalToKebabCase(pascal: string) { 28 | return pascal.replace(/([a-z0–9])([A-Z])/g, "$1-$2").toLowerCase(); 29 | } 30 | -------------------------------------------------------------------------------- /docs/src/pages/overview/faq.mdx: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How does Green Boost compare to Amplify? 4 | - Opinions: Green Boost is more opinionated than Amplify. 5 | - Clients: Green Boost supports web. Amplify supports web and mobile. 6 | - Languages/Frameworks: Green Boost only supports TypeScript and React. Amplify supports multiple languages and frontend frameworks. 7 | - Infrastructure: Green Boost uses the AWS CDK for infrastructure definition and deployment. Amplify uses CloudFormation templates for infrastructure definition (with second class support for CDK) and uses their own Amplify CLI for deployment. 8 | - UI Component Library: Green Boost uses Material UI. Amplify uses Amplify UI. 9 | - Hosting: Green Boost uses CDK constructs for transparent hosting. Amplify has proprietary hosting solution. 10 | - SLA: Green Boost is developed and maintained by AWS Professional Services consultants in between projects. Amplify is developer and maintained by a dedicated AWS Service team. -------------------------------------------------------------------------------- /packages/gboost/src/create/execute-operations.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "../utils/logger.js"; 2 | import { 3 | updatePackageJson, 4 | copy, 5 | type Operation, 6 | OperationType, 7 | replace, 8 | renameFiles, 9 | } from "./operations/operations.js"; 10 | 11 | /** 12 | * Executes all operations upon destination directory 13 | */ 14 | export function executeOperations(operations: Operation[]): void { 15 | for (const operation of operations) { 16 | logger.debug(`Executing operation: ${JSON.stringify(operation)}`); 17 | switch (operation.type) { 18 | case OperationType.Copy: 19 | copy(operation); 20 | break; 21 | case OperationType.Replace: 22 | replace(operation); 23 | break; 24 | case OperationType.UpdatePackageJson: 25 | updatePackageJson(operation); 26 | break; 27 | case OperationType.RenameFiles: 28 | renameFiles(operation); 29 | break; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import "./globals.css"; 3 | import type { ReactNode } from "react"; 4 | import { headers } from "next/headers"; 5 | import { ThemeRegistry } from "@/components/theme/ThemeRegistry"; 6 | import type { Metadata } from "next"; 7 | import { clientConfig } from "@/config/client-config"; 8 | import AppLayout from "@/components/layout/AppLayout"; 9 | 10 | export const metadata: Metadata = { 11 | title: clientConfig.appTitle, 12 | description: "", 13 | }; 14 | 15 | export default async function RootLayout({ 16 | children, 17 | }: { 18 | children: ReactNode; 19 | }) { 20 | const headersList = headers(); 21 | const nonce = headersList.get("x-nonce"); 22 | return ( 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import "./globals.css"; 3 | import type { ReactNode } from "react"; 4 | import { headers } from "next/headers"; 5 | import { ThemeRegistry } from "@/components/theme/ThemeRegistry"; 6 | import type { Metadata } from "next"; 7 | import { clientConfig } from "@/config/client-config"; 8 | import AppLayout from "@/components/layout/AppLayout"; 9 | 10 | export const metadata: Metadata = { 11 | title: clientConfig.appTitle, 12 | description: "", 13 | }; 14 | 15 | export default async function RootLayout({ 16 | children, 17 | }: { 18 | children: ReactNode; 19 | }) { 20 | const headersList = headers(); 21 | const nonce = headersList.get("x-nonce"); 22 | return ( 23 | 24 | 25 | 26 | 27 | {children} 28 | 29 | 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/entrypoints/api/routers/widget.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createWidgetSchema, 3 | getWidgetSchema, 4 | listWidgetsSchema, 5 | removeWidgetSchema, 6 | updateWidgetSchema, 7 | } from "#schemas/widget-schemas"; 8 | import { widgetService } from "#services/widget-service"; 9 | import { t } from "../trpc.js"; 10 | 11 | export const widgetRouter = t.router({ 12 | create: t.procedure 13 | .input(createWidgetSchema) 14 | .mutation((req) => widgetService.create(req.input)), 15 | get: t.procedure 16 | .input(getWidgetSchema) 17 | .query((req) => widgetService.get(req.input)), 18 | list: t.procedure 19 | .input(listWidgetsSchema) 20 | .query((req) => widgetService.list(req.input)), 21 | remove: t.procedure 22 | .input(removeWidgetSchema) 23 | .mutation((req) => widgetService.remove(req.input)), 24 | update: t.procedure 25 | .input(updateWidgetSchema) 26 | .mutation((req) => widgetService.update(req.input)), 27 | }); 28 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/services/widget-service.ts: -------------------------------------------------------------------------------- 1 | import { widgetsDbRepo } from "#adapters/widgets-db-repo"; 2 | import type { 3 | CreateWidgetSchema, 4 | GetWidgetSchema, 5 | ListWidgetsSchema, 6 | RemoveWidgetSchema, 7 | UpdateWidgetSchema, 8 | } from "#schemas/widget-schemas"; 9 | 10 | function create(params: CreateWidgetSchema) { 11 | return widgetsDbRepo.create(params); 12 | } 13 | 14 | function get({ id }: GetWidgetSchema) { 15 | return widgetsDbRepo.get(id); 16 | } 17 | 18 | function list(params: ListWidgetsSchema) { 19 | return widgetsDbRepo.list({ 20 | cursor: params.cursor, 21 | limit: params.pageSize, 22 | }); 23 | } 24 | 25 | function remove({ id }: RemoveWidgetSchema) { 26 | return widgetsDbRepo.remove(id); 27 | } 28 | 29 | function update(params: UpdateWidgetSchema) { 30 | return widgetsDbRepo.update(params); 31 | } 32 | 33 | export const widgetService = { 34 | create, 35 | get, 36 | list, 37 | remove, 38 | update, 39 | }; 40 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/favicon-32x32.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR D���gAMA�� �a cHRMz&�����u0�`:�p��Q<)PLTE�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�K�J�J�QkɌ2�`�I�K!�R������T�z �R�������ӡ%�V������D�m�I���q̑�O�P�������ߺ>�i��Ѩ߻6�b���_ł������V�{�J������B�k �QB�l����ҟ%�U�H������S�y��әڰ.�\�ݵiȊ�M�M&�VZ�N<)tRNS$���ֈ&M���Q��S�)������ܐ'��+R�W�(������bKGD6G���tIME�5�L�+IDAT8˅�{7A�߷vEZ[6�� �0�!� ֵȝ��!��J;;;��z��=�sf�@ �x���p$<!�hʘ���(�Ml!'�(��-�D�)�� �� 4 | ���[XY]3��֋��MB���Jl{g������ʴ��ã�ʉ��H`�ӳsju*X�y���%�on���� 5 | F<�ڏ]0��|�<��^x����}�_u����r'|}[x^��� �����{�raB�TH�윬�Ofb��B��M=�������z�bb�����T�^������[%tEXtdate:create2022-01-19T18:53:20+00:00��Ȑ%tEXtdate:modify2022-01-19T18:53:20+00:00��p,WzTXtRaw profile type iptcx��� qV((�O��I�R# .c #K� D�4�d#�T ��������ˀH�J.�t�B5�IEND�B`� -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-base/post-confirmation.ts: -------------------------------------------------------------------------------- 1 | import type { PostConfirmationTriggerHandler } from "aws-lambda"; 2 | import { 3 | AdminAddUserToGroupCommand, 4 | CognitoIdentityProviderClient, 5 | } from "@aws-sdk/client-cognito-identity-provider"; 6 | 7 | const cognitoClient = new CognitoIdentityProviderClient({}); 8 | 9 | export const handler: PostConfirmationTriggerHandler = async (event) => { 10 | console.log({ event }); 11 | const defaultGroupName = getDefaultGroupName(); 12 | await cognitoClient.send( 13 | new AdminAddUserToGroupCommand({ 14 | UserPoolId: event.userPoolId, 15 | GroupName: defaultGroupName, 16 | Username: event.userName, 17 | }), 18 | ); 19 | return event; 20 | }; 21 | 22 | function getDefaultGroupName(): string { 23 | const defaultGroupName = process.env["DEFAULT_GROUP_NAME"]; 24 | if (!defaultGroupName) { 25 | throw new Error("DEFAULT_GROUP_NAME env var is missing"); 26 | } 27 | return defaultGroupName; 28 | } 29 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/deleteUsers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CognitoIdentityProviderClient, 3 | AdminDeleteUserCommand, 4 | } from "@aws-sdk/client-cognito-identity-provider"; 5 | import type { AppSyncResolverEvent } from "aws-lambda"; 6 | import { type UsernameArgs, validate } from "./validateUsernames.js"; 7 | 8 | interface DeleteUsersParams { 9 | cognitoClient: CognitoIdentityProviderClient; 10 | event: AppSyncResolverEvent; 11 | userPoolId: string; 12 | } 13 | 14 | export async function deleteUsers(params: DeleteUsersParams): Promise { 15 | const { cognitoClient, event, userPoolId } = params; 16 | validate(event.arguments); 17 | const { usernames } = event.arguments; 18 | await Promise.all( 19 | usernames.map((u) => 20 | cognitoClient.send( 21 | new AdminDeleteUserCommand({ 22 | UserPoolId: userPoolId, 23 | Username: u, 24 | }), 25 | ), 26 | ), 27 | ); 28 | return usernames; 29 | } 30 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-custom-resource-provider.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkCustomResourceProvider(construct: IConstruct) { 5 | if ( 6 | construct.node.path.endsWith( 7 | "Provider/framework-onEvent/ServiceRole/DefaultPolicy/Resource", 8 | ) 9 | ) { 10 | NagSuppressions.addResourceSuppressions(construct, [ 11 | { 12 | id: "AwsSolutions-IAM5", 13 | reason: 14 | "AWS CDK Custom Resource Provider Framework can access all versions of specified function", 15 | }, 16 | ]); 17 | } else if ( 18 | construct.node.path.endsWith("Provider/framework-onEvent/Resource") 19 | ) { 20 | NagSuppressions.addResourceSuppressions(construct, [ 21 | { 22 | id: "AwsSolutions-L1", 23 | reason: "AWS CDK team will update runtime if security issue arises", 24 | }, 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/enableUsers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CognitoIdentityProviderClient, 3 | AdminEnableUserCommand, 4 | } from "@aws-sdk/client-cognito-identity-provider"; 5 | import type { AppSyncResolverEvent } from "aws-lambda"; 6 | import { type UsernameArgs, validate } from "./validateUsernames.js"; 7 | 8 | // params 9 | interface EnableUsersParams { 10 | cognitoClient: CognitoIdentityProviderClient; 11 | event: AppSyncResolverEvent; 12 | userPoolId: string; 13 | } 14 | 15 | export async function enableUsers(params: EnableUsersParams): Promise { 16 | const { cognitoClient, event, userPoolId } = params; 17 | validate(event.arguments); 18 | const { usernames } = event.arguments; 19 | await Promise.all( 20 | usernames.map((u) => 21 | cognitoClient.send( 22 | new AdminEnableUserCommand({ 23 | UserPoolId: userPoolId, 24 | Username: u, 25 | }), 26 | ), 27 | ), 28 | ); 29 | return usernames; 30 | } 31 | -------------------------------------------------------------------------------- /packages/gboost/src/create/operations/rename-files.ts: -------------------------------------------------------------------------------- 1 | import { renameSync } from "node:fs"; 2 | import { type BaseOperation, listFilePaths, OperationType } from "./common.js"; 3 | 4 | export interface RenameFilesOperation extends BaseOperation { 5 | /** 6 | * Parent directory to search below recursively 7 | */ 8 | directoryPath: string; 9 | /** 10 | * RegExp pattern to match files within `directoryPath` against. Matched files 11 | * will have `updateFile` called 12 | */ 13 | filePattern: RegExp; 14 | type: OperationType.RenameFiles; 15 | /** 16 | * Callback function with original file path. Returned string will rename file 17 | */ 18 | update: (oldFilePath: string) => string; 19 | } 20 | 21 | export function renameFiles(params: RenameFilesOperation) { 22 | const filePaths = listFilePaths(params.directoryPath); 23 | for (const filePath of filePaths) { 24 | if (params.filePattern.test(filePath)) { 25 | renameSync(filePath, params.update(filePath)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/disableUsers.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CognitoIdentityProviderClient, 3 | AdminDisableUserCommand, 4 | } from "@aws-sdk/client-cognito-identity-provider"; 5 | import type { AppSyncResolverEvent } from "aws-lambda"; 6 | import { type UsernameArgs, validate } from "./validateUsernames.js"; 7 | 8 | interface DisableUsersParams { 9 | cognitoClient: CognitoIdentityProviderClient; 10 | event: AppSyncResolverEvent; 11 | userPoolId: string; 12 | } 13 | 14 | export async function disableUsers( 15 | params: DisableUsersParams, 16 | ): Promise { 17 | const { cognitoClient, event, userPoolId } = params; 18 | validate(event.arguments); 19 | const { usernames } = event.arguments; 20 | await Promise.all( 21 | usernames.map((u) => 22 | cognitoClient.send( 23 | new AdminDisableUserCommand({ 24 | UserPoolId: userPoolId, 25 | Username: u, 26 | }), 27 | ), 28 | ), 29 | ); 30 | return usernames; 31 | } 32 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/mstile-144x144.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR��И�gAMA�� �a cHRMz&�����u0�`:�p��Q<�PLTE�K"�S�դ�۱�P���]ā�ե������C�l�������ݷ����ҟ�L���D�n���0�]������`Ń���2�_��#�S���jɋ���M�u���'�W����M���P�w���9�d����O���t͓���:�f���)�X���}Й�L���Y�~���.�\������(�X�ܴ�.�bKGD�ޕztIME�5:B��IDATx���N�@|����{���?�@!�v�]t$μ_i%o�t�B!�B!��g�OF �vǬ#�%�*LNG��t�/�ff�lB��BI!`q�(����!�BXY5 4 | k�%���M���� 5 | ag�(���Auh��qI!���(���yI!\\����B�ͭQw�%���d�_J 6 | ��۠^s�mHѸ !g�B��Վ�|qs E���7�P0n�{$S� componentService.create(req.input)), 15 | get: t.procedure 16 | .input(getComponentSchema) 17 | .query((req) => componentService.get(req.input)), 18 | list: t.procedure 19 | .input(listComponentsSchema) 20 | .query((req) => componentService.list(req.input)), 21 | remove: t.procedure 22 | .input(removeComponentSchema) 23 | .mutation((req) => componentService.remove(req.input)), 24 | update: t.procedure 25 | .input(updateComponentSchema) 26 | .mutation((req) => componentService.update(req.input)), 27 | }); 28 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/core/src/services/component-service.ts: -------------------------------------------------------------------------------- 1 | import { componentsDbRepo } from "#adapters/components-db-repo"; 2 | import type { 3 | CreateComponentSchema, 4 | GetComponentSchema, 5 | ListComponentsSchema, 6 | RemoveComponentSchema, 7 | UpdateComponentSchema, 8 | } from "#schemas/component-schemas"; 9 | 10 | function create(params: CreateComponentSchema) { 11 | return componentsDbRepo.create(params); 12 | } 13 | 14 | function get({ id }: GetComponentSchema) { 15 | return componentsDbRepo.get(id); 16 | } 17 | 18 | function list(params: ListComponentsSchema) { 19 | return componentsDbRepo.list({ 20 | cursor: params.cursor, 21 | limit: params.pageSize, 22 | }); 23 | } 24 | 25 | function remove({ id }: RemoveComponentSchema) { 26 | return componentsDbRepo.remove(id); 27 | } 28 | 29 | function update(params: UpdateComponentSchema) { 30 | return componentsDbRepo.update(params); 31 | } 32 | 33 | export const componentService = { 34 | create, 35 | get, 36 | list, 37 | remove, 38 | update, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/aspects/suppress-nags/suppress-cdk-monitoring-constructs.ts: -------------------------------------------------------------------------------- 1 | import { NagSuppressions } from "cdk-nag"; 2 | import type { IConstruct } from "constructs"; 3 | 4 | export function suppressCdkMonitoringConstructs(construct: IConstruct) { 5 | if ( 6 | construct.node.path.endsWith( 7 | "/SecretsManagerMetricsPublisher/Lambda/ServiceRole/DefaultPolicy/Resource", 8 | ) 9 | ) { 10 | NagSuppressions.addResourceSuppressions(construct, [ 11 | { 12 | id: "AwsSolutions-IAM5", 13 | reason: 14 | "CloudWatch Dashboard can monitor all secrets in SecretsManager", 15 | }, 16 | ]); 17 | } 18 | if ( 19 | construct.node.path.endsWith( 20 | "/SecretsManagerMetricsPublisher/Lambda/Resource", 21 | ) 22 | ) { 23 | NagSuppressions.addResourceSuppressions(construct, [ 24 | { 25 | id: "AwsSolutions-L1", 26 | reason: 27 | "cdk-monitoring-constructs maintainer will update Lambda version when required", 28 | }, 29 | ]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/gboost-infra/src/user-management/function/resetPasswords.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CognitoIdentityProviderClient, 3 | AdminResetUserPasswordCommand, 4 | } from "@aws-sdk/client-cognito-identity-provider"; 5 | import type { AppSyncResolverEvent } from "aws-lambda"; 6 | import { type UsernameArgs, validate } from "./validateUsernames.js"; 7 | 8 | // params 9 | interface ResetPasswordsParams { 10 | cognitoClient: CognitoIdentityProviderClient; 11 | event: AppSyncResolverEvent; 12 | userPoolId: string; 13 | } 14 | 15 | export async function resetPasswords( 16 | params: ResetPasswordsParams, 17 | ): Promise { 18 | const { cognitoClient, event, userPoolId } = params; 19 | validate(event.arguments); 20 | const { usernames } = event.arguments; 21 | await Promise.all( 22 | usernames.map((u) => 23 | cognitoClient.send( 24 | new AdminResetUserPasswordCommand({ 25 | UserPoolId: userPoolId, 26 | Username: u, 27 | }), 28 | ), 29 | ), 30 | ); 31 | return usernames; 32 | } 33 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/infra/src/app/stateless/monitor.ts: -------------------------------------------------------------------------------- 1 | import { Stack, StackProps } from "aws-cdk-lib"; 2 | import type { Construct } from "constructs"; 3 | import { 4 | DefaultDashboardFactory, 5 | MonitoringFacade, 6 | } from "cdk-monitoring-constructs"; 7 | import type { StageConfig } from "../../config/stage-config.js"; 8 | 9 | interface UiProps extends StackProps { 10 | stacks: Stack[]; 11 | config: StageConfig; 12 | } 13 | 14 | export class Monitor extends Stack { 15 | constructor(scope: Construct, id: string, props: UiProps) { 16 | super(scope, id, props); 17 | const monitoring = new MonitoringFacade(this, "Facade", { 18 | dashboardFactory: new DefaultDashboardFactory(this, "DashboardFactory", { 19 | dashboardNamePrefix: `${props.config.stageId}-dashboard`, 20 | }), 21 | }); 22 | for (const stack of props.stacks) { 23 | monitoring.monitorScope(stack, { 24 | billing: { enabled: false }, 25 | ec2: { enabled: false }, 26 | elasticCache: { enabled: false }, 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/utils/generate-csp.ts: -------------------------------------------------------------------------------- 1 | interface AddCspProps { 2 | nonce: string; 3 | } 4 | 5 | /** 6 | * @see https://github.com/vercel/next.js/issues/43743#issuecomment-1542684019 7 | */ 8 | export function generateCsp({ nonce }: AddCspProps) { 9 | let styleSrcElemCsp = `style-src-elem 'self'`; 10 | let scriptSrcCsp = `script-src 'self' 'nonce-${nonce}'`; 11 | if (process.env.NODE_ENV !== "production") { 12 | scriptSrcCsp += " 'unsafe-eval'; "; // required for webpack sourcemap code 13 | styleSrcElemCsp += " 'unsafe-inline'; "; // needed for Next.js error screens 14 | } else { 15 | scriptSrcCsp += "; "; 16 | styleSrcElemCsp += ` 'nonce-${nonce}'; `; 17 | } 18 | return ( 19 | "default-src 'self' https://*.amazonaws.com; " + 20 | styleSrcElemCsp + 21 | // mui uses emotion which dynamically adds inline styles so must use 22 | // 'unsafe-inline' for now. can remove once https://github.com/mui/material-ui/issues/34826 is resolved 23 | "style-src-attr 'unsafe-inline'; " + 24 | scriptSrcCsp 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/mstile-70x70.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR������gAMA�� �a cHRMz&�����u0�`:�p��Q<�PLTE�K0�^W�|jɋ�P<�g����ݷ!�R������C�m������D�n����ҟ�L����ե������1�_���iȊ��#�S���&�V���N�u���O�v����ث�O������9�d���:�f���t͓���}Й���)�X���*�Y���Z�\Ā�۱���|ϙ��������.�\[�:�eP�w$�T���3�`eLJ5c tRNS@��fbKGD ��a�tIME�5�KҽIDATx����NA`@m���<�DT���(M��GOφ�6!��=Y���5=�� � H"E���J3�/�+����ܼ�E��h 4 | Z��Z^Y5�ں����F�@��%��m �zY5vLD�{2��l�<�tt|b 5 | ��n|�}j 6 | :;�tѱ�� ��1�������ۓT}��(Y.uB,���d9�b9h����rP�ʁ��A )�9�)-��|O9�F�����+'�`�A0, ���.�K2���Я�鑤 e � eE���/`W 7 | =��K`t_-���j�x�&J���x/g*�����ߊ���{��.� 8 | �Ċ��+Jn@jIKEC@zMMBkj>�����E=��=UD�*r<�5@�ǚ���EjF�� �G���J��V� ���Q@��<�%>}8q�k⣟d{���}�2�NM��}���}nB�z�� � ��-?w��[%tEXtdate:create2022-01-19T18:53:21+00:00��$%tEXtdate:modify2022-01-19T18:53:21+00:00g�{�WzTXtRaw profile type iptcx��� qV((�O��I�R# .c #K� D�4�d#�T ��������ˀH�J.�t�B5�IEND�B`� -------------------------------------------------------------------------------- /examples/widgets-dynamo/packages/eslint-config-node/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | root: true, 7 | env: { 8 | es2022: true, 9 | node: true, 10 | }, 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from @typescript-eslint/eslint-plugin 14 | "plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 15 | ], 16 | ignorePatterns: ["node_modules", "lib"], 17 | parser: "@typescript-eslint/parser", // This allows ESLint to understand TypeScript syntax 18 | parserOptions: { 19 | ecmaVersion: "latest", 20 | sourceType: "module", 21 | }, 22 | rules: { 23 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /packages/gboost/src/create/get-template-operations/get-basic-ui-operations.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "node:path"; 2 | import { type Operation, OperationType } from "../operations/operations.js"; 3 | import type { BaseOperationParams } from "./base-operation-params.js"; 4 | 5 | export function getBasicUiOperations(params: BaseOperationParams): Operation[] { 6 | const { destinationPath, templatesDirPath } = params; 7 | return [ 8 | { 9 | type: OperationType.Copy, 10 | name: "CopyMinimalTemplate", 11 | sourcePath: resolve(templatesDirPath, "minimal"), 12 | destinationPath, 13 | }, 14 | { 15 | type: OperationType.Copy, 16 | name: "CopyBasicUiTemplate", 17 | sourcePath: resolve(templatesDirPath, "basic-ui"), 18 | destinationPath, 19 | }, 20 | { 21 | name: "UpdateInfraDependencies", 22 | type: OperationType.UpdatePackageJson, 23 | sourcePaths: [resolve(destinationPath, "infra/package.json.t")], 24 | devDependencies: { 25 | [`cdk-nextjs-standalone`]: "^4.0.0-beta.9", 26 | }, 27 | }, 28 | ]; 29 | } 30 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/layout/DrawerList.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import List from "@mui/material/List"; 3 | import { type ReactElement } from "react"; 4 | import { DrawerListItem } from "./DrawerListItem"; 5 | import WavingHandIcon from "@mui/icons-material/WavingHand"; 6 | import { useTheme } from "@mui/material/styles"; 7 | 8 | interface DrawerListProps { 9 | open: boolean; 10 | } 11 | 12 | export function DrawerList(props: DrawerListProps): ReactElement { 13 | const theme = useTheme(); 14 | const { open } = props; 15 | // TODO: highlight based on path 16 | return ( 17 | 18 | } 21 | open={open} 22 | text="Hello" 23 | /> 24 | } 27 | open={open} 28 | text="Goodbye" 29 | /> 30 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/db/utils/get-db-password.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { 3 | GetSecretValueCommand, 4 | SecretsManagerClient, 5 | } from "@aws-sdk/client-secrets-manager"; 6 | 7 | const smClient = new SecretsManagerClient({}); 8 | 9 | /** 10 | * Gets DB plaintext password via DB_SECRET_ARN environment variable. Assumes 11 | * AWS Secrets Manager SecretValue is JSON with "password" property which is 12 | * setup for you when using `Credentials.fromGeneratedSecret` method 13 | */ 14 | export async function getDbPassword(): Promise { 15 | const secretId = process.env["DB_SECRET_ARN"]; 16 | if (!secretId) throw new Error(`Missing process.env["DB_SECRET_ARN"]`); 17 | const response = await smClient.send( 18 | new GetSecretValueCommand({ SecretId: secretId }), 19 | ); 20 | const { SecretString } = response; 21 | if (SecretString) { 22 | const parsedSecretString = JSON.parse(SecretString) as { password: string }; 23 | return parsedSecretString.password; 24 | } else { 25 | throw new Error("GetSecretValueCommand returned empty SecretString"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/src/pages/overview/contributors/fix-audit-issues.mdx: -------------------------------------------------------------------------------- 1 | # Fix Audit Issues 2 | On each PR, `pnpm audit` is used to detect [CVEs](https://www.redhat.com/en/topics/security/what-is-cve). The GitHub Actions Workflow will fail if any CVEs >= moderate severity are found by running the command `pnpm audit --audit-level moderate`. If the dependency is a direct dependency of your project, you should try to [update dependencies](#update-dependencies). You can learn why a dependency is in your project (dependency hierarchy) with `pnpm why `. If the dependency is a transitive dependency (dependency of dependency), you'll need to use PNPM's [pnpm.overrides feature](https://pnpm.io/package_json#pnpmoverrides) by adding to the `package.json#pnpm.overrides` object a key/value pair like: `"@": ""`. Then run `pnpm i` to update your dependencies. 3 | 4 | Periodically, `pnpm.overrides` should be cleaned up as libraries overtime will update to patched version of packages. 5 | 6 | If there is no patched version of the library and you can safely ignore the CVE, you can add it to `pnpm.auditConfig.ignoreCves`. -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/rhf-inputs/RhfTextField.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { TextField, type TextFieldProps } from "@mui/material"; 3 | import { 4 | Controller, 5 | type Control, 6 | type FieldValues, 7 | type FieldPath, 8 | } from "react-hook-form"; 9 | 10 | type RhfTextFieldProps = TextFieldProps & { 11 | control: Control; 12 | name: FieldPath; 13 | label: string; 14 | }; 15 | 16 | /** 17 | * MUI `TextField` with react-hook-form bindings 18 | */ 19 | export function RhfTextField( 20 | props: RhfTextFieldProps, 21 | ) { 22 | const { control, name, label, ...rest } = props; 23 | return ( 24 | ( 28 | 36 | )} 37 | /> 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/layout/Drawer.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { type ReactElement } from "react"; 3 | import Box from "@mui/material/Box"; 4 | import { useTheme } from "@mui/material/styles"; 5 | import { DrawerList } from "./DrawerList"; 6 | 7 | interface DrawerProps { 8 | open: boolean; 9 | } 10 | 11 | export function Drawer(props: DrawerProps): ReactElement { 12 | const { open } = props; 13 | const theme = useTheme(); 14 | const transition = open 15 | ? theme.transitions.create("width", { 16 | easing: theme.transitions.easing.sharp, 17 | duration: theme.transitions.duration.enteringScreen, 18 | }) 19 | : theme.transitions.create("width", { 20 | easing: theme.transitions.easing.sharp, 21 | duration: theme.transitions.duration.leavingScreen, 22 | }); 23 | return ( 24 | t.palette["background"].paper} 26 | display="flex" 27 | height="100%" 28 | width={open ? "var(--drawer-width)" : "60px"} 29 | style={{ transition }} 30 | > 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/layout/Drawer.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { type ReactElement } from "react"; 3 | import { Box } from "@mui/material"; 4 | import { useTheme } from "@mui/material/styles"; 5 | import { DrawerList } from "./DrawerList"; 6 | 7 | interface DrawerProps { 8 | open: boolean; 9 | } 10 | 11 | export function Drawer(props: DrawerProps): ReactElement { 12 | const { open } = props; 13 | const theme = useTheme(); 14 | const transition = open 15 | ? theme.transitions.create("width", { 16 | easing: theme.transitions.easing.sharp, 17 | duration: theme.transitions.duration.enteringScreen, 18 | }) 19 | : theme.transitions.create("width", { 20 | easing: theme.transitions.easing.sharp, 21 | duration: theme.transitions.duration.leavingScreen, 22 | }); 23 | return ( 24 | t.palette["background"].paper} 26 | display="flex" 27 | height="100%" 28 | width={open ? "var(--drawer-width)" : "60px"} 29 | style={{ transition }} 30 | > 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/gboost-common/src/merge-deep.ts: -------------------------------------------------------------------------------- 1 | const isObject = (obj: unknown) => obj && typeof obj === "object"; 2 | 3 | /** 4 | * Performs a deep merge of objects and returns new object. Does not modify 5 | * objects (immutable) and merges arrays via concatenation. 6 | * 7 | * @link https://stackoverflow.com/a/48218209/9658768 8 | */ 9 | export function mergeDeep( 10 | ...objects: (object | undefined)[] 11 | ): T { 12 | const result: Record = {}; 13 | for (const obj of objects) { 14 | if (obj) { 15 | for (const key of Object.keys(obj)) { 16 | const pVal = result[key]; 17 | const oVal = obj[key as keyof typeof obj]; 18 | 19 | if (Array.isArray(pVal) && Array.isArray(oVal)) { 20 | result[key] = pVal.concat(...(oVal as unknown[])); 21 | } else if (isObject(pVal) && isObject(oVal)) { 22 | result[key] = mergeDeep( 23 | pVal as Record, 24 | oVal as Record, 25 | ); 26 | } else { 27 | result[key] = oVal; 28 | } 29 | } 30 | } 31 | } 32 | return result as T; 33 | } 34 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@myapp/monorepo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "pnpm --recursive --parallel build", 8 | "lint": "pnpm --recursive --parallel lint", 9 | "test": "pnpm --recursive --parallel test", 10 | "typecheck": "pnpm --recursive --parallel typecheck", 11 | "prepare": "husky install", 12 | "preinstall": "npx only-allow pnpm" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^18.15.2", 16 | "eslint": "^8.36.0", 17 | "husky": "^8.0.3", 18 | "lint-staged": "^13.2.0", 19 | "typescript": "^4.9.5" 20 | }, 21 | "dependencies": { 22 | "esbuild": "^0.17.11" 23 | }, 24 | "pnpm": { 25 | "overrides": { 26 | "xstate": "^4.33.6" 27 | }, 28 | "peerDependencyRules": { 29 | "allowedVersions": { 30 | "react": "18" 31 | }, 32 | "ignoreMissing": [ 33 | "@aws-amplify/geo", 34 | "@aws-amplify/core", 35 | "@babel/plugin-syntax-flow", 36 | "@babel/plugin-transform-react-jsx", 37 | "react-native" 38 | ] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/gboost-ui/src/utils/use-trace-update.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | /** 4 | * Utility debugging function to trace which props are updating in component. 5 | * Changed props will be printed to console. Accepts props or any variable. 6 | * Also useful for debugging useEffect dependency arrays 7 | * @link https://stackoverflow.com/a/51082563/9658768 8 | * @example 9 | * ```ts 10 | * function MyComponent(props) { 11 | * useTraceUpdate(props); 12 | * return
{props.children}
; 13 | * } 14 | * ``` 15 | */ 16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 17 | export function useTraceUpdate(props: any) { 18 | const prev = useRef(props); 19 | useEffect(() => { 20 | const changedProps = Object.entries(props).reduce( 21 | (ps, [k, v]) => { 22 | if (prev.current[k] !== v) { 23 | ps[k] = [prev.current[k], v]; 24 | } 25 | return ps; 26 | }, 27 | {} as Record, 28 | ); 29 | if (Object.keys(changedProps).length > 0) { 30 | console.log("Changed props:", changedProps); 31 | } 32 | prev.current = props; 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- 1 | �PNG 2 |  3 | IHDR��e�5gAMA�� �a cHRMz&�����u0�`:�p��Q<fPLTE�K���(�XxΖ_ł������R�x�ե��"�S����۱�O���F�o���A�k����M���}Й�L���8�d���0�]���mʍ����&�VwΕ]ā�bbKGD�o�� pHYs  ��~�tIME�5����IDATx���V1EQ)�x� x�����3�N�*����P�^ g��9;��G��#���)`O/�#�=��Q���x��W�w0�z����ݎ���&)`���f�>g���=�[J�#�Qb�]�d��(%�;JR�r� �� 5 | ���Ǩ�?2�*�^������������?����pW��� �WL�/��_����V?j ~�C�����'�#g��ď]�|?z�~�[�����+(◀ԯa�_����~T�:���h�+��_ 6 | ��� �o�#~�����[�yC��A��%tEXtdate:create2022-01-19T18:53:24+00:00D��%tEXtdate:modify2022-01-19T18:53:24+00:005�T?tEXtSoftwarewww.inkscape.org��<WzTXtRaw profile type iptcx��� qV((�O��I�R# .c #K� D�4�d#�T ��������ˀH�J.�t�B5�IEND�B`� -------------------------------------------------------------------------------- /packages/gboost-node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gboost-node", 3 | "version": "0.4.0", 4 | "description": "Green Boost UI Library of Node.js Functions", 5 | "type": "module", 6 | "exports": "./src/index.ts", 7 | "publishConfig": { 8 | "exports": "./lib/index.js" 9 | }, 10 | "files": [ 11 | "lib/**/*" 12 | ], 13 | "scripts": { 14 | "build": "tsx scripts/build.ts", 15 | "format": "prettier --write .", 16 | "lint": "eslint \"src/**/*.{ts,tsx}\"", 17 | "test": "vitest run", 18 | "typecheck": "tsc --noEmit" 19 | }, 20 | "dependencies": { 21 | "gboost-common": "workspace:^" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/awslabs/green-boost.git", 26 | "directory": "packages/gboost-node" 27 | }, 28 | "keywords": [ 29 | "aws", 30 | "node", 31 | "nodejs", 32 | "lambda", 33 | "green", 34 | "boost", 35 | "gboost" 36 | ], 37 | "author": "AWS Professional Services", 38 | "license": "Apache-2.0", 39 | "bugs": { 40 | "url": "https://github.com/awslabs/green-boost/issues" 41 | }, 42 | "homepage": "https://awslabs.github.io/green-boost" 43 | } 44 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/core/src/config/shared-config.ts: -------------------------------------------------------------------------------- 1 | import { StageName } from "./stage-name"; 2 | 3 | /** 4 | * Config shared between client and server 5 | */ 6 | export class SharedConfig { 7 | static appId = "{{GB_APP_ID}}"; 8 | constructor(stageName?: string) { 9 | this.stageName = stageName || StageName.Local; 10 | } 11 | /** 12 | * `enumStageName` is always of type `StageName` whereas `stageName` is a 13 | * string allowing for alternative stage names when developing locally. This 14 | * is helpful so you don't have developers deploying CloudFormation Stacks on 15 | * top of each other. 16 | */ 17 | get enumStageName(): StageName { 18 | return Object.values(StageName).includes(this.stageName as StageName) 19 | ? (this.stageName as StageName) 20 | : StageName.Local; 21 | } 22 | get isLocal() { 23 | return this.enumStageName === StageName.Local; 24 | } 25 | get isProd() { 26 | return this.enumStageName === StageName.Prod; 27 | } 28 | /** 29 | * `StageName` or string if developing locally 30 | */ 31 | stageName: string; 32 | } 33 | 34 | export const sharedConfig = new SharedConfig(process.env["STAGE_NAME"]); 35 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/core/src/config/shared-config.ts: -------------------------------------------------------------------------------- 1 | import { StageName } from "./stage-name"; 2 | 3 | /** 4 | * Config shared between client and server 5 | */ 6 | export class SharedConfig { 7 | static appId = "{{GB_APP_ID}}"; 8 | constructor(stageName?: string) { 9 | this.stageName = stageName || StageName.Local; 10 | } 11 | /** 12 | * `enumStageName` is always of type `StageName` whereas `stageName` is a 13 | * string allowing for alternative stage names when developing locally. This 14 | * is helpful so you don't have developers deploying CloudFormation Stacks on 15 | * top of each other. 16 | */ 17 | get enumStageName(): StageName { 18 | return Object.values(StageName).includes(this.stageName as StageName) 19 | ? (this.stageName as StageName) 20 | : StageName.Local; 21 | } 22 | get isLocal() { 23 | return this.enumStageName === StageName.Local; 24 | } 25 | get isProd() { 26 | return this.enumStageName === StageName.Prod; 27 | } 28 | /** 29 | * `StageName` or string if developing locally 30 | */ 31 | stageName: string; 32 | } 33 | 34 | export const sharedConfig = new SharedConfig(process.env["STAGE_NAME"]); 35 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Widgets 15 | 16 | 17 |
18 | <% if (!PROD) { %> 19 | 25 | <% } %> 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/gboost-common/src/get-error-message.ts: -------------------------------------------------------------------------------- 1 | interface ErrorWithMessage { 2 | message: string; 3 | } 4 | 5 | function isErrorWithMessage(error: unknown): error is ErrorWithMessage { 6 | return ( 7 | typeof error === "object" && 8 | error !== null && 9 | "message" in error && 10 | typeof (error as Record)["message"] === "string" 11 | ); 12 | } 13 | 14 | function toErrorWithMessage(maybeError: unknown): ErrorWithMessage { 15 | if (isErrorWithMessage(maybeError)) return maybeError; 16 | 17 | try { 18 | return new Error(JSON.stringify(maybeError)); 19 | } catch { 20 | // fallback in case there's an error stringifying the maybeError 21 | // like with circular references for example. 22 | return new Error(String(maybeError)); 23 | } 24 | } 25 | 26 | /** 27 | * You can throw anything you want in JS, so TS types errors in catch blocks 28 | * with unknown. This utility functions enables you to safely access the error 29 | * message if there is one 30 | * @link https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript 31 | */ 32 | export function getErrorMessage(error: unknown) { 33 | return toErrorWithMessage(error).message; 34 | } 35 | -------------------------------------------------------------------------------- /examples/widgets-dynamo/ui/src/components/NavAside.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from "react"; 2 | import { NavAside as GbNavAside, NavAsideItem } from "gboost-ui"; 3 | import { MdSettings, MdTipsAndUpdates } from "react-icons/md"; 4 | import { Icon, Text } from "@aws-amplify/ui-react"; 5 | import { NavAsideLink } from "./NavAsideLink.js"; 6 | 7 | interface NavAsideProps { 8 | open: boolean; 9 | } 10 | 11 | export function NavAside(props: NavAsideProps): ReactElement { 12 | const { open } = props; 13 | return ( 14 | 15 | 16 | 23 | } 24 | label={Widgets} 25 | /> 26 | 27 | 28 | 31 | } 32 | label={Components} 33 | /> 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "build": "next build", 8 | "dev": "next dev", 9 | "lint": "next lint", 10 | "start": "next start", 11 | "test": "vitest run", 12 | "typecheck": "tsc --noEmit" 13 | }, 14 | "dependencies": { 15 | "@emotion/cache": "^11.11.0", 16 | "@emotion/react": "^11.11.1", 17 | "@emotion/styled": "^11.11.0", 18 | "@hookform/resolvers": "^3.3.2", 19 | "@mui/icons-material": "^5.14.18", 20 | "@mui/lab": "5.0.0-alpha.153", 21 | "@mui/material": "^5.14.18", 22 | "@mui/x-data-grid": "^6.18.1", 23 | "gboost-ui": "^0.36.0", 24 | "next": "14.0.3", 25 | "open-next": "^2.3.2", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "react-hook-form": "^7.48.2" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^18.2.37", 32 | "@types/react-dom": "^18.2.15", 33 | "esbuild": "0.19.5", 34 | "eslint": "^8.53.0", 35 | "eslint-define-config": "^2.0.0", 36 | "typescript": "^5.2.2", 37 | "vitest": "^0.34.6", 38 | "webpack": "^5.89.0" 39 | } 40 | } -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/infra/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/infra", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "deploy:local": "cdk deploy \"**\" --require-approval never --no-rollback", 8 | "deploy:pipeline": "cdk --app \"./node_modules/.bin/tsx src/pipeline/pipeline-app.ts\" deploy \"*\" --no-rollback", 9 | "destroy:local": "cdk destroy --force \"**\"", 10 | "destroy:pipeline": "cdk --app \"./node_modules/.bin/tsx src/pipeline/pipeline-app.ts\" destroy --force \"*\"", 11 | "lint": "eslint \"src/**/*.ts\"", 12 | "test": "vitest run", 13 | "watch": "cdk --watch", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "dependencies": { 17 | "aws-cdk": "^2.110.0", 18 | "aws-cdk-lib": "^2.110.0", 19 | "cdk-nag": "^2.27.193", 20 | "cdk-nextjs-standalone": "4.0.0-beta.12", 21 | "constructs": "^10.3.0", 22 | "gboost-common": "^0.13.0", 23 | "gboost-infra": "^0.17.0" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^20.9.1", 27 | "eslint": "^8.53.0", 28 | "eslint-define-config": "^2.0.0", 29 | "tsx": "^4.1.3", 30 | "typescript": "^5.2.2", 31 | "vitest": "^0.34.6" 32 | } 33 | } -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/packages/eslint-config-node/.eslintrc.cjs.t: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // eslint-disable-next-line @typescript-eslint/no-var-requires 3 | const { defineConfig } = require("eslint-define-config"); 4 | 5 | module.exports = defineConfig({ 6 | root: true, 7 | env: { 8 | es2022: true, 9 | node: true, 10 | }, 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:security/recommended", 14 | "plugin:@typescript-eslint/recommended", // Uses the recommended rules from @typescript-eslint/eslint-plugin 15 | "plugin:prettier/recommended", // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 16 | ], 17 | ignorePatterns: ["node_modules", "lib"], 18 | parser: "@typescript-eslint/parser", // This allows ESLint to understand TypeScript syntax 19 | parserOptions: { 20 | ecmaVersion: "latest", 21 | sourceType: "module", 22 | }, 23 | rules: { 24 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 25 | "security/detect-object-injection": "off", // too many false positives 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /packages/gboost/src/create/operations/rename-files.test.ts: -------------------------------------------------------------------------------- 1 | import { afterEach, beforeEach, describe, expect, test } from "vitest"; 2 | import { mkdirSync, rmSync, statSync, writeFileSync } from "node:fs"; 3 | import { OperationType, renameFiles } from "./operations.js"; 4 | import { dirname, resolve } from "node:path"; 5 | import { fileURLToPath } from "node:url"; 6 | 7 | const thisFilePath = fileURLToPath(import.meta.url); 8 | 9 | describe("rename-files", () => { 10 | const testDirPath = resolve(thisFilePath, "../test-update-file-name"); 11 | beforeEach(() => { 12 | mkdirSync(testDirPath); 13 | }); 14 | afterEach(() => { 15 | rmSync(testDirPath, { recursive: true }); 16 | }); 17 | 18 | test("rename file", () => { 19 | const testFilePath = resolve(testDirPath, "test.ts.t"); 20 | writeFileSync(testFilePath, ""); 21 | renameFiles({ 22 | directoryPath: resolve(testFilePath, ".."), 23 | name: "TestRenameFile", 24 | filePattern: /.+\.t$/, 25 | type: OperationType.RenameFiles, 26 | update: (oldFilePath) => oldFilePath.slice(0, -2), 27 | }); 28 | const file = statSync(resolve(dirname(testFilePath), "test.ts")); 29 | expect(file.isFile()).toBe(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /docs/public/fast-forward-gradient.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/config/server-config.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { SharedConfig } from "./shared-config"; 3 | import { StageName } from "./stage-name"; 4 | 5 | /** 6 | * Config used only on server 7 | */ 8 | export class ServerConfig extends SharedConfig { 9 | static get dbAdminUsername() { 10 | return `${ServerConfig.sqlCompatAppId}_admin`; 11 | } 12 | static get dbIamUsername() { 13 | return `${ServerConfig.sqlCompatAppId}_iam_user`; 14 | } 15 | static get dbName() { 16 | return `${ServerConfig.sqlCompatAppId}_db`; 17 | } 18 | /** 19 | * Environment variable names. No [magic strings](https://deviq.com/antipatterns/magic-strings)! 20 | */ 21 | static envVarNames = { 22 | STAGE_NAME: "STAGE_NAME", 23 | NEXT_PUBLIC_STAGE_NAME: "NEXT_PUBLIC_STAGE_NAME", 24 | }; 25 | /** 26 | * Replace dashes with underscores to comply with SQL Naming conventions. 27 | */ 28 | static get sqlCompatAppId() { 29 | return SharedConfig.appId.replaceAll("-", "_"); 30 | } 31 | 32 | constructor(stageName?: string) { 33 | super(stageName || StageName.Local); 34 | } 35 | } 36 | 37 | export const serverConfig = new ServerConfig( 38 | process.env[ServerConfig.envVarNames.STAGE_NAME], 39 | ); 40 | -------------------------------------------------------------------------------- /packages/gboost/templates/minimal/ui/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | async headers() { 4 | return [ 5 | { 6 | source: "/(.*?)", 7 | headers: [ 8 | { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, 9 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security 10 | { key: "Strict-Transport-Security", value: "max-age=31536000" }, 11 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options 12 | { key: "X-Content-Type-Options", value: "nosniff" }, 13 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options 14 | { key: "X-Frame-Options", value: "SAMEORIGIN" }, 15 | ], 16 | }, 17 | ]; 18 | }, 19 | modularizeImports: { 20 | // https://mui.com/material-ui/guides/minimizing-bundle-size/#development-environment 21 | // @mui/icons-material is automatically modularized by Next.js 22 | "@mui/material": { 23 | transform: "@mui/material/{{member}}", 24 | }, 25 | }, 26 | reactStrictMode: true, 27 | transpilePackages: ["@{{GB_APP_ID}}/core"], 28 | }; 29 | 30 | export default nextConfig; 31 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/ui/src/components/theme/theme.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { grey } from "@mui/material/colors"; 3 | import { experimental_extendTheme as extendTheme } from "@mui/material/styles"; 4 | import { Inter } from "next/font/google"; 5 | 6 | /** 7 | * Brand Colors 8 | */ 9 | export const colors = { 10 | carrot: "#f56600", 11 | gulf: "#9ddde9", 12 | lagoon: "#36c2b4", 13 | shortbread: "#ffedcc", 14 | skate: "#00678c", 15 | summer: "#ffc400", 16 | }; 17 | 18 | const GoogleInterFont = Inter({ subsets: ["latin"] }); 19 | 20 | export const theme = extendTheme({ 21 | components: { 22 | MuiListItem: { 23 | styleOverrides: {}, 24 | }, // TODO: make nav list on focus not same gray as main bg 25 | MuiLink: { 26 | defaultProps: { 27 | underline: "hover", 28 | }, 29 | }, 30 | }, 31 | colorSchemes: { 32 | light: { 33 | palette: { 34 | background: { 35 | default: grey[50], 36 | }, 37 | primary: { 38 | main: colors.lagoon, 39 | }, 40 | secondary: { 41 | main: colors.summer, 42 | }, 43 | }, 44 | }, 45 | }, 46 | typography: { 47 | fontFamily: GoogleInterFont.style.fontFamily, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/components/theme/theme.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { grey } from "@mui/material/colors"; 3 | import { experimental_extendTheme as extendTheme } from "@mui/material/styles"; 4 | import { Inter } from "next/font/google"; 5 | 6 | /** 7 | * Brand Colors 8 | */ 9 | export const colors = { 10 | carrot: "#f56600", 11 | gulf: "#9ddde9", 12 | lagoon: "#36c2b4", 13 | shortbread: "#ffedcc", 14 | skate: "#00678c", 15 | summer: "#ffc400", 16 | }; 17 | 18 | const GoogleInterFont = Inter({ subsets: ["latin"] }); 19 | 20 | export const theme = extendTheme({ 21 | components: { 22 | MuiListItem: { 23 | styleOverrides: {}, 24 | }, // TODO: make nav list on focus not same gray as main bg 25 | MuiLink: { 26 | defaultProps: { 27 | underline: "hover", 28 | }, 29 | }, 30 | }, 31 | colorSchemes: { 32 | light: { 33 | palette: { 34 | background: { 35 | default: grey[50], 36 | }, 37 | primary: { 38 | main: colors.lagoon, 39 | }, 40 | secondary: { 41 | main: colors.summer, 42 | }, 43 | }, 44 | }, 45 | }, 46 | typography: { 47 | fontFamily: GoogleInterFont.style.fontFamily, 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /packages/gboost/src/invoke/inject-fn-config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetFunctionConfigurationCommand, 3 | LambdaClient, 4 | } from "@aws-sdk/client-lambda"; 5 | 6 | const lambdaClient = new LambdaClient({}); 7 | 8 | interface InjectFnConfigParams { 9 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 10 | dummyContext: Record; 11 | functionArn: string; 12 | } 13 | 14 | export async function injectFnConfig(params: InjectFnConfigParams) { 15 | const { dummyContext, functionArn } = params; 16 | const response = await lambdaClient.send( 17 | new GetFunctionConfigurationCommand({ FunctionName: functionArn }), 18 | ); 19 | process.env = { 20 | ...response.Environment?.Variables, 21 | ...process.env, // prioritize user defined env vars over lambda's 22 | }; 23 | if (response.FunctionName) { 24 | dummyContext["functionName"] = response.FunctionName; 25 | } 26 | if (response.FunctionArn) { 27 | dummyContext["invokedFunctionArn"] = response.FunctionArn; 28 | } 29 | if (response.MemorySize) { 30 | dummyContext["memoryLimitInMB"] = response.MemorySize.toString(); 31 | } 32 | if (response.Timeout) { 33 | dummyContext["getRemainingTimeInMillis"] = () => 34 | (response.Timeout as number) * 1000; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-core/ui/src/middleware.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { NextRequest, NextResponse } from "next/server"; 3 | import { generateCsp } from "./utils/generate-csp"; 4 | 5 | export default function middleware(request: NextRequest) { 6 | // clone request headers to add to response 7 | const requestHeaders = new Headers(request.headers); 8 | // generate nonce 9 | const nonce = crypto.randomUUID(); 10 | // set nonce so we can access in app/layout.tsx to use in styles 11 | requestHeaders.set("x-nonce", nonce); 12 | const csp = generateCsp({ nonce }); 13 | // Set the CSP header so that Next.js can read it and generate tags with the nonce 14 | requestHeaders.set("content-security-policy", csp); 15 | const response = NextResponse.next({ 16 | request: { headers: requestHeaders }, 17 | }); 18 | // Also set the CSP header in the response so that it is outputted to the browser 19 | response.headers.set("content-security-policy", generateCsp({ nonce })); 20 | return response; 21 | } 22 | 23 | export const config = { 24 | /** 25 | * Match all request paths except for the ones starting with: 26 | * - _next/static 27 | * - _next/image 28 | * - favicon.ico 29 | */ 30 | matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], 31 | }; 32 | -------------------------------------------------------------------------------- /packages/gboost/templates/basic-ui/infra/src/app-stage.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Aspects, Stage, Tags } from "aws-cdk-lib"; 3 | import { AwsSolutionsChecks } from "cdk-nag"; 4 | import type { Construct } from "constructs"; 5 | import { 6 | setConstructDefaultProps, 7 | SuppressNags, 8 | Suppression, 9 | } from "gboost-infra"; 10 | import { UiStack } from "./app/stateless/ui-stack"; 11 | import { Config } from "./config/config"; 12 | import { WafStack } from "./app/stateless/waf-stack"; 13 | 14 | export class AppStage extends Stage { 15 | constructor(scope: Construct, id: string, config: Config) { 16 | super(scope, id); 17 | setConstructDefaultProps(config.constructDefaultProps); 18 | 19 | const wafStack = new WafStack(this, "waf", { 20 | // us-east-1 required for use with CloudFront 21 | env: { account: config.account, region: "us-east-1" }, 22 | crossRegionReferences: config.region !== "us-east-1", 23 | config, 24 | }); 25 | new UiStack(this, "ui", { 26 | config, 27 | env: config.env, 28 | webAclArn: wafStack.webAclArn, 29 | }); 30 | 31 | Tags.of(this).add("appId", Config.appId); 32 | Aspects.of(this).add(new SuppressNags(Object.values(Suppression))); 33 | Aspects.of(this).add(new AwsSolutionsChecks()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-postgres/core/src/utils/transform-null-to-undefined.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Transform records with potential `null` values to `undefined`. This is 3 | * helpful for converting DB row types to types compliant with zod's 4 | * `.optional()`. Use the 2nd argument to specify any keys to be recursively 5 | * transformed (i.e. nested objects from relations) 6 | * 7 | * Why not use null? See: [here](https://github.com/sindresorhus/meta/discussions/7) 8 | */ 9 | export function transformNullToUndefined>( 10 | record: T, 11 | recursiveKeys = [] as string[], 12 | ): ReplaceNullWithUndefined { 13 | return Object.entries(record).reduce((prev, [k, v]) => { 14 | if (recursiveKeys.includes(k)) { 15 | return { 16 | ...prev, 17 | [k]: transformNullToUndefined(v as Record), 18 | }; 19 | } else if (v === null) { 20 | return { ...prev, [k]: undefined }; 21 | } else { 22 | return { ...prev, [k]: v }; 23 | } 24 | }, {} as ReplaceNullWithUndefined); 25 | } 26 | 27 | /** 28 | * Replace `Record` type with potential `null` values to `undefined`. 29 | */ 30 | export type ReplaceNullWithUndefined = { 31 | [K in keyof T]: null extends T[K] ? NonNullable | undefined : T[K]; 32 | }; 33 | -------------------------------------------------------------------------------- /docs/src/pages/learn/resources.mdx: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | There are tons of great articles/blogs/tutorials available on the web to help you build web apps with Green Boost's tech stack: TypeScript, AWS CDK, React, Node.js. Here is a consolidated list: 4 | 5 | ## AWS 6 | ### AWS CDK 7 | ## Clean Code 8 | - [Serverless Clean Code Architecture with Domain Driven Design](https://blog.serverlessadvocate.com/serverless-clean-architecture-code-with-domain-driven-design-852796846d28) 9 | - [An Introduction To Domain Driven Design (DDD)](https://khalilstemmler.com/articles/domain-driven-design-intro/) 10 | - [AWS Prescriptive Guidance: Building hexagonal architectures on AWS](https://docs.aws.amazon.com/pdfs/prescriptive-guidance/latest/hexagonal-architectures/hexagonal-architectures.pdf) 11 | - [Domain Driven Hexagon](https://github.com/Sairyss/domain-driven-hexagon) 12 | - [A Beginner’s Guide to Domain-centric Architectures (clean, hexagonal, …)](https://medium.com/codex/clean-architecture-for-dummies-df6561d42c94) 13 | - [Vertical Slice Architecture](https://www.jimmybogard.com/vertical-slice-architecture/) 14 | ## React 15 | ### Next.js 16 | - [Full Stack Airbnb Clone with Next.js 13 App Router: React, Tailwind, Prisma, MongoDB, NextAuth 2023](https://www.youtube.com/watch?v=c_-b_isI4vg) 17 | ## Node.js 18 | ## TypeScript 19 | -------------------------------------------------------------------------------- /packages/gboost/templates/crud-dynamo/core/package.json.t: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@{{GB_APP_ID}}/core", 3 | "version": "0.1.0", 4 | "private": true, 5 | "type": "module", 6 | "exports": { 7 | ".": "./src/index.ts", 8 | "./models": "./src/domain/models/index.ts", 9 | "./schemas": "./src/domain/schemas/index.ts", 10 | "./router": "./src/entrypoints/api/router.ts", 11 | "./utils": "./src/utils/index.ts" 12 | }, 13 | "imports": { 14 | "#adapters/*": "./src/adapters/*.ts", 15 | "#models/*": "./src/domain/models/*.ts", 16 | "#schemas/*": "./src/domain/schemas/*.ts", 17 | "#services/*": "./src/services/*.ts" 18 | }, 19 | "scripts": { 20 | "lint": "eslint \"src/**/*.ts\"", 21 | "test": "vitest run --passWithNoTests", 22 | "watch": "tsc -w", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@aws-lambda-powertools/logger": "^1.16.0", 27 | "@aws-sdk/client-dynamodb": "^3.451.0", 28 | "@aws-sdk/lib-dynamodb": "^3.451.0", 29 | "@aws-sdk/smithy-client": "^3.370.0", 30 | "@trpc/server": "^10.43.4", 31 | "zod": "^3.22.4" 32 | }, 33 | "devDependencies": { 34 | "@aws-sdk/types": "^3.451.0", 35 | "eslint": "^8.53.0", 36 | "eslint-define-config": "^2.0.0", 37 | "typescript": "^5.2.2", 38 | "vitest": "^0.34.6" 39 | } 40 | } --------------------------------------------------------------------------------