├── src ├── react-app │ ├── public │ │ ├── constructs │ │ │ └── .keep │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── icons │ │ │ ├── 192x192.png │ │ │ ├── 512x512.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── maskable_icon_x128.png │ │ │ ├── maskable_icon_x192.png │ │ │ ├── maskable_icon_x384.png │ │ │ ├── maskable_icon_x48.png │ │ │ ├── maskable_icon_x512.png │ │ │ ├── maskable_icon_x72.png │ │ │ └── maskable_icon_x96.png │ │ ├── service-worker.js │ │ └── manifest.json │ ├── src │ │ ├── vite-env.d.ts │ │ ├── targets │ │ │ ├── typescript │ │ │ │ ├── typescript-cdk-files │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── create-npm-ignore.ts │ │ │ │ │ ├── create-git-ignore.ts │ │ │ │ │ ├── create-jest-config.ts │ │ │ │ │ ├── create-readme.ts │ │ │ │ │ ├── create-test.ts │ │ │ │ │ ├── create-ts-config.ts │ │ │ │ │ ├── create-cdk-json.ts │ │ │ │ │ └── create-package-json.ts │ │ │ │ └── typescript-cdk-builder.ts │ │ │ ├── export │ │ │ │ ├── export-base.ts │ │ │ │ ├── file-builder-base.ts │ │ │ │ ├── export-archive.ts │ │ │ │ └── export-sync.ts │ │ │ ├── target-processor.ts │ │ │ └── generation │ │ │ │ └── code-generator.ts │ │ ├── project │ │ │ ├── types │ │ │ │ ├── versions.ts │ │ │ │ ├── index.ts │ │ │ │ ├── project-blueprint.ts │ │ │ │ ├── storage-v1.ts │ │ │ │ ├── project-action.ts │ │ │ │ ├── project-state.ts │ │ │ │ └── values.ts │ │ │ ├── actions │ │ │ │ ├── index.ts │ │ │ │ ├── set-view.ts │ │ │ │ ├── show-modal.ts │ │ │ │ ├── set-construct-id.ts │ │ │ │ ├── move-item.ts │ │ │ │ ├── set-settings.ts │ │ │ │ ├── set-data.ts │ │ │ │ ├── update-data.ts │ │ │ │ ├── toggle-select.ts │ │ │ │ ├── add-item.ts │ │ │ │ ├── remove-item.ts │ │ │ │ ├── add-call-item.ts │ │ │ │ └── set-value.ts │ │ │ ├── blueprint-service.ts │ │ │ ├── extended.ts │ │ │ ├── database.ts │ │ │ ├── project-context.ts │ │ │ ├── validator.ts │ │ │ └── helpers │ │ │ │ └── items-helper.ts │ │ ├── assets │ │ │ └── 192x192.png │ │ ├── styles │ │ │ ├── index.scss │ │ │ └── prism.scss │ │ ├── pages │ │ │ ├── home.tsx │ │ │ ├── not-found.tsx │ │ │ └── project.tsx │ │ ├── components │ │ │ ├── home │ │ │ │ ├── header.tsx │ │ │ │ ├── foother.tsx │ │ │ │ ├── projects-empty.tsx │ │ │ │ └── blueprints.tsx │ │ │ ├── documentation-link.tsx │ │ │ ├── object-designer │ │ │ │ ├── properties │ │ │ │ │ ├── unknown-property.tsx │ │ │ │ │ ├── target-property-single.tsx │ │ │ │ │ ├── string-property.tsx │ │ │ │ │ ├── number-property.tsx │ │ │ │ │ ├── boolean-property.tsx │ │ │ │ │ ├── enum-property.tsx │ │ │ │ │ └── target-property-value.tsx │ │ │ │ ├── property.tsx │ │ │ │ ├── object-designer.tsx │ │ │ │ ├── construct-id.tsx │ │ │ │ ├── property-label.tsx │ │ │ │ └── property-list.tsx │ │ │ ├── workbench │ │ │ │ ├── details-empty.tsx │ │ │ │ ├── items-empty.tsx │ │ │ │ ├── workbench.tsx │ │ │ │ ├── details.tsx │ │ │ │ ├── items.tsx │ │ │ │ ├── constructs-header.tsx │ │ │ │ ├── search-empty.tsx │ │ │ │ ├── details-header.tsx │ │ │ │ └── item.tsx │ │ │ ├── diagram │ │ │ │ └── diagram.tsx │ │ │ ├── error-boundary.tsx │ │ │ ├── blur-input.tsx │ │ │ ├── breadcrumbs.tsx │ │ │ ├── code │ │ │ │ └── code.tsx │ │ │ └── navbar.tsx │ │ ├── app.tsx │ │ ├── packages │ │ │ ├── package-parser.ts │ │ │ ├── packages-service.ts │ │ │ ├── tar-file-reader.ts │ │ │ ├── reconcile-manager.ts │ │ │ └── package-manager.ts │ │ ├── blueprints │ │ │ ├── cdk8s-blank.ts │ │ │ ├── cdktf-blank.ts │ │ │ ├── cdk-blank.ts │ │ │ └── generative-ai.ts │ │ ├── main.tsx │ │ ├── types.ts │ │ └── utils.ts │ ├── postcss.config.cjs │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── index.html │ └── package.json └── cdk-builder-stack.ts ├── .prettierignore ├── .npmignore ├── assets ├── architecture.png ├── aws-cdk-builder.png ├── aws-cdk-builder-code.png └── aws-cdk-builder-diagram.png ├── prettier.config.js ├── bin └── cdk-builder.ts ├── CODE_OF_CONDUCT.md ├── .eslintrc.cjs ├── package.json ├── tsconfig.json ├── LICENSE ├── cdk.json └── CONTRIBUTING.md /src/react-app/public/constructs/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | cdk.out 2 | build 3 | public -------------------------------------------------------------------------------- /src/react-app/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/common.ts: -------------------------------------------------------------------------------- 1 | export const TS_JSON_IDENT = 2; 2 | -------------------------------------------------------------------------------- /src/react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /assets/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/assets/architecture.png -------------------------------------------------------------------------------- /assets/aws-cdk-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/assets/aws-cdk-builder.png -------------------------------------------------------------------------------- /assets/aws-cdk-builder-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/assets/aws-cdk-builder-code.png -------------------------------------------------------------------------------- /src/react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/favicon.ico -------------------------------------------------------------------------------- /src/react-app/src/project/types/versions.ts: -------------------------------------------------------------------------------- 1 | export const PROJECT_FORMAT_VERSION = 2; 2 | export const BLUEPRINT_FORMAT_VERSION = 1; 3 | -------------------------------------------------------------------------------- /assets/aws-cdk-builder-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/assets/aws-cdk-builder-diagram.png -------------------------------------------------------------------------------- /src/react-app/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /src/react-app/src/assets/192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/src/assets/192x192.png -------------------------------------------------------------------------------- /src/react-app/public/icons/192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/192x192.png -------------------------------------------------------------------------------- /src/react-app/public/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/512x512.png -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: "es5", 4 | singleQuote: false, 5 | tabWidth: 2, 6 | useTabs: false, 7 | }; 8 | -------------------------------------------------------------------------------- /src/react-app/public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/react-app/public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/react-app/public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x128.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x192.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x384.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x48.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x512.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x72.png -------------------------------------------------------------------------------- /src/react-app/public/icons/maskable_icon_x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/aws-cdk-stack-builder-tool/HEAD/src/react-app/public/icons/maskable_icon_x96.png -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-npm-ignore.ts: -------------------------------------------------------------------------------- 1 | export function createNpmIgnore() { 2 | return `*.ts 3 | !*.d.ts 4 | 5 | # CDK asset staging directory 6 | .cdk.staging 7 | cdk.out 8 | `; 9 | } 10 | -------------------------------------------------------------------------------- /src/react-app/src/project/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./project-action"; 2 | export * from "./project-state"; 3 | export * from "./project-blueprint"; 4 | export * from "./storage-v1"; 5 | export * from "./values"; 6 | export * from "./versions"; 7 | -------------------------------------------------------------------------------- /src/react-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | //base: "/aws-cdk-stack-builder-tool", 8 | }); 9 | -------------------------------------------------------------------------------- /bin/cdk-builder.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { CDKBuilderStack } from "../src/cdk-builder-stack"; 5 | 6 | const app = new cdk.App(); 7 | new CDKBuilderStack(app, "cdk-builder"); 8 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-git-ignore.ts: -------------------------------------------------------------------------------- 1 | export function createGitIgnore() { 2 | return `*.js 3 | !jest.config.js 4 | *.d.ts 5 | node_modules 6 | 7 | # CDK asset staging directory 8 | .cdk.staging 9 | cdk.out 10 | `; 11 | } 12 | -------------------------------------------------------------------------------- /src/react-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/react-app/public/service-worker.js: -------------------------------------------------------------------------------- 1 | self.addEventListener("install", (event) => { 2 | self.skipWaiting(); 3 | }); 4 | 5 | self.addEventListener("activate", (event) => {}); 6 | 7 | self.addEventListener("fetch", (event) => { 8 | event.respondWith(fetch(event.request)); 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. -------------------------------------------------------------------------------- /src/react-app/src/targets/export/export-base.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../../project/types"; 2 | 3 | export abstract class ExportBase { 4 | constructor(protected projectState: ProjectState) {} 5 | 6 | abstract folder(name: string): Promise; 7 | abstract file(name: string, content: string): Promise; 8 | } 9 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./set-data"; 2 | export * from "./update-data"; 3 | export * from "./add-item"; 4 | export * from "./add-call-item"; 5 | export * from "./move-item"; 6 | export * from "./remove-item"; 7 | export * from "./set-construct-id"; 8 | export * from "./set-value"; 9 | export * from "./toggle-select"; 10 | export * from "./show-modal"; 11 | export * from "./set-settings"; 12 | -------------------------------------------------------------------------------- /src/react-app/src/targets/export/file-builder-base.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../../project/types"; 2 | import { CodeGenerator } from "../generation/code-generator"; 3 | import { ExportBase } from "./export-base"; 4 | 5 | export abstract class FileBuilderBase { 6 | constructor( 7 | protected projectState: ProjectState, 8 | protected generator: CodeGenerator, 9 | protected exportBase: ExportBase 10 | ) {} 11 | } 12 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-jest-config.ts: -------------------------------------------------------------------------------- 1 | import { TS_JSON_IDENT } from "./common"; 2 | 3 | export function createJestConfig() { 4 | const config = { 5 | testEnvironment: "node", 6 | roots: ["/test"], 7 | testMatch: ["**/*.test.ts"], 8 | transform: { 9 | "^.+\\.tsx?$": "ts-jest", 10 | }, 11 | }; 12 | 13 | return "module.exports = " + JSON.stringify(config, null, TS_JSON_IDENT); 14 | } 15 | -------------------------------------------------------------------------------- /src/react-app/src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | overflow-y: hidden; 7 | } 8 | 9 | .tooltip-container { 10 | min-width: 24rem; 11 | max-width: 40%; 12 | word-wrap: break-word; 13 | } 14 | 15 | input[type="search"]::-webkit-search-decoration:hover, 16 | input[type="search"]::-webkit-search-cancel-button:hover { 17 | cursor: pointer; 18 | } 19 | 20 | .code-editor a:hover { 21 | text-decoration: underline; 22 | } 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | ], 9 | ignorePatterns: ["dist", ".eslintrc.cjs"], 10 | parser: "@typescript-eslint/parser", 11 | plugins: ["react-refresh"], 12 | rules: { 13 | "react-refresh/only-export-components": [ 14 | "warn", 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /src/react-app/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require("tailwindcss/defaultTheme"); 2 | 3 | module.exports = { 4 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 5 | theme: { 6 | screens: { 7 | sm: "480px", 8 | md: "768px", 9 | lg: "976px", 10 | xl: "1440px", 11 | }, 12 | extend: { 13 | fontFamily: { 14 | sans: ["Inter var", ...defaultTheme.fontFamily.sans], 15 | }, 16 | colors: { 17 | accentColor: "black", 18 | }, 19 | }, 20 | }, 21 | plugins: [require("@tailwindcss/forms")], 22 | }; 23 | -------------------------------------------------------------------------------- /src/react-app/src/pages/home.tsx: -------------------------------------------------------------------------------- 1 | import { Create } from "../components/home/create"; 2 | import { Header } from "../components/home/header"; 3 | import { Projects } from "../components/home/projects"; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 |
9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-builder", 3 | "version": "1.0.0", 4 | "bin": { 5 | "cdk-builder": "bin/cdk-builder.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "npx cdk", 12 | "prettier": "prettier ./ --write" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "20.8.8", 16 | "prettier": "3.3.3", 17 | "typescript": "5.5.4" 18 | }, 19 | "dependencies": { 20 | "aws-cdk": "^2.150.0", 21 | "aws-cdk-lib": "^2.150.0", 22 | "constructs": "^10.3.0", 23 | "source-map-support": "^0.5.21" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app/src/components/home/header.tsx: -------------------------------------------------------------------------------- 1 | import logo from "../../assets/192x192.png"; 2 | 3 | export function Header() { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 |
11 |
12 |

AWS CDK Builder

13 |

14 | CDK Project Bootstrapping 15 |

16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/react-app/src/components/documentation-link.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { ProjectContext } from "../project/project-context"; 3 | 4 | const baseUrl = "https://docs.aws.amazon.com/cdk/api/v2/docs/"; 5 | 6 | export function DocumentationLink(props: { fqn: string }) { 7 | const { state } = useContext(ProjectContext); 8 | if (state.blueprintComputed.platform !== "cdk") { 9 | return null; 10 | } 11 | 12 | return ( 13 | 19 | documentation 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/set-view.ts: -------------------------------------------------------------------------------- 1 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 2 | 3 | export interface SetViewAction { 4 | kind: ProjectActionKind.SET_VIEW; 5 | payload: { 6 | view: string; 7 | }; 8 | } 9 | 10 | export function isSetViewAction( 11 | action: ProjectAction 12 | ): action is SetViewAction { 13 | return action.kind === ProjectActionKind.SET_VIEW; 14 | } 15 | 16 | export function setView( 17 | state: ProjectState, 18 | action: SetViewAction 19 | ): ProjectState { 20 | const { view } = action.payload; 21 | 22 | const retValue: ProjectState = { 23 | ...state, 24 | view, 25 | }; 26 | 27 | return retValue; 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "commonjs", 5 | "lib": ["ES2021"], 6 | "declaration": true, 7 | "strict": true, 8 | "noImplicitAny": true, 9 | "strictNullChecks": true, 10 | "noImplicitThis": true, 11 | "alwaysStrict": true, 12 | "noUnusedLocals": false, 13 | "noUnusedParameters": false, 14 | "noImplicitReturns": true, 15 | "noFallthroughCasesInSwitch": false, 16 | "inlineSourceMap": true, 17 | "inlineSources": true, 18 | "experimentalDecorators": true, 19 | "strictPropertyInitialization": false, 20 | "typeRoots": ["./node_modules/@types"] 21 | }, 22 | "exclude": ["node_modules", "cdk.out"] 23 | } 24 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/show-modal.ts: -------------------------------------------------------------------------------- 1 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 2 | 3 | export interface ShowModalAction { 4 | kind: ProjectActionKind.SHOW_MODAL; 5 | payload: { 6 | show: boolean; 7 | }; 8 | } 9 | 10 | export function isShowModalAction( 11 | action: ProjectAction 12 | ): action is ShowModalAction { 13 | return action.kind === ProjectActionKind.SHOW_MODAL; 14 | } 15 | 16 | export function showModal( 17 | state: ProjectState, 18 | action: ShowModalAction 19 | ): ProjectState { 20 | let { show } = action.payload; 21 | 22 | let retValue: ProjectState = { 23 | ...state, 24 | modal: show, 25 | }; 26 | 27 | return retValue; 28 | } 29 | -------------------------------------------------------------------------------- /src/react-app/src/components/home/foother.tsx: -------------------------------------------------------------------------------- 1 | export function Foother() { 2 | return ( 3 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/react-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app/src/project/types/project-blueprint.ts: -------------------------------------------------------------------------------- 1 | import { InstanceValue } from "./values"; 2 | import { CdkPlatform, ProjectRules } from "./project-state"; 3 | 4 | export interface BlueprintExtend { 5 | platform: CdkPlatform; 6 | libs: { [key: string]: string }; 7 | containers: string[]; 8 | favorites: string[]; 9 | rules: ProjectRules; 10 | } 11 | 12 | //https://base64.guru/converter/encode/image/svg 13 | export interface ProjectBlueprint { 14 | kind: "cdk/project-blueprint"; 15 | format: number; 16 | extend: string; 17 | id: string; 18 | name: string; 19 | description: string; 20 | icon?: string; 21 | background: string; 22 | libs?: { [key: string]: string }; 23 | containers?: string[]; 24 | favorites?: string[]; 25 | rules?: ProjectRules; 26 | root: InstanceValue; 27 | } 28 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/properties/unknown-property.tsx: -------------------------------------------------------------------------------- 1 | import { Utils } from "../../../utils"; 2 | import { ValueError } from "../../../project/validator"; 3 | 4 | export function UnknownProperty(props: { 5 | keyName?: string; 6 | text: string; 7 | errors: ValueError[]; 8 | }) { 9 | const { keyName, text, errors } = props; 10 | const hasErrors = errors.length > 0; 11 | 12 | return ( 13 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app/src/components/home/projects-empty.tsx: -------------------------------------------------------------------------------- 1 | export function ProjectsEmpty() { 2 | return ( 3 |
4 | 19 |

No projects

20 |

21 | Get started by creating a new project. 22 |

23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | AWS CDK Builder 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/details-empty.tsx: -------------------------------------------------------------------------------- 1 | export function DetailsEmpty() { 2 | return ( 3 |
4 | 19 |

No properties

20 |

21 | Select a resource to view properties 22 |

23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/react-app/src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense, lazy } from "react"; 2 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 3 | 4 | const Home = lazy(() => import("./pages/home")); 5 | const Project = lazy(() => import("./pages/project")); 6 | const NotFound = lazy(() => import("./pages/not-found")); 7 | 8 | export default function App() { 9 | return ( 10 | 11 | 14 |
Loading resources...
15 | 16 | } 17 | > 18 | 19 | } /> 20 | } /> 21 | } /> 22 | 23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/items-empty.tsx: -------------------------------------------------------------------------------- 1 | export function ItemsEmpty(props: { name: string }) { 2 | return ( 3 |
4 | 19 |

20 | {props.name} is empty 21 |

22 |

23 | Start by selecting constructs from the left panel 24 |

25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-readme.ts: -------------------------------------------------------------------------------- 1 | export function createReadme() { 2 | return `# Welcome to your CDK TypeScript project 3 | 4 | The \`cdk.json\` file tells the CDK Toolkit how to execute your app. 5 | 6 | ## How to deploy your project 7 | 8 | ### Install AWS CDK 9 | The AWS CDK Toolkit is installed with the Node Package Manager. In most cases, we recommend installing it globally. 10 | 11 | \`npm install -g aws-cdk\` 12 | 13 | ### Install NPM packages 14 | 15 | \`npm install\` 16 | 17 | ## Useful commands 18 | 19 | * \`npm run build\` compile typescript to js 20 | * \`npm run watch\` watch for changes and compile 21 | * \`npm run test\` perform the jest unit tests 22 | * \`cdk deploy\` deploy this stack to your default AWS account/region 23 | * \`cdk diff\` compare deployed stack with current state 24 | * \`cdk synth\` emits the synthesized CloudFormation template 25 | `; 26 | } 27 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-test.ts: -------------------------------------------------------------------------------- 1 | import { Utils } from "../../../utils"; 2 | import { ContainerState } from "../../generation/code-generator"; 3 | 4 | export function createTest(container: ContainerState) { 5 | const valueName = Utils.firstLetterToUpperCase(container.valueName); 6 | 7 | return `import * as cdk from "aws-cdk-lib"; 8 | import { Template } from "aws-cdk-lib/assertions"; 9 | import { ${container.valueName} } from "../lib/${container.fileName}"; 10 | 11 | // Example test 12 | test("Some resource created", () => { 13 | // const app = new cdk.App(); 14 | // // WHEN 15 | // const stack = new ${valueName}(app, "${valueName}"); 16 | // // THEN 17 | // const template = Template.fromStack(stack); 18 | 19 | // template.hasResourceProperties("AWS::SQS::Queue", { 20 | // VisibilityTimeout: 300 21 | // }); 22 | // 23 | // template.resourceCountIs("AWS::SNS::Topic", 1); 24 | }); 25 | `; 26 | } 27 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-ts-config.ts: -------------------------------------------------------------------------------- 1 | import { TS_JSON_IDENT } from "./common"; 2 | 3 | export function createTsConfig() { 4 | const config = { 5 | compilerOptions: { 6 | target: "ES2018", 7 | module: "commonjs", 8 | lib: ["es2018"], 9 | declaration: true, 10 | strict: true, 11 | noImplicitAny: true, 12 | strictNullChecks: true, 13 | noImplicitThis: true, 14 | alwaysStrict: true, 15 | noUnusedLocals: false, 16 | noUnusedParameters: false, 17 | noImplicitReturns: true, 18 | noFallthroughCasesInSwitch: false, 19 | inlineSourceMap: true, 20 | inlineSources: true, 21 | experimentalDecorators: true, 22 | strictPropertyInitialization: false, 23 | typeRoots: ["./node_modules/@types"], 24 | }, 25 | exclude: ["node_modules", "cdk.out"], 26 | }; 27 | 28 | return JSON.stringify(config, null, TS_JSON_IDENT); 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT No Attribution 2 | 3 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/set-construct-id.ts: -------------------------------------------------------------------------------- 1 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 2 | import { postProcess } from "../post-process"; 3 | 4 | export interface SetConstructIdAction { 5 | kind: ProjectActionKind.SET_CONSTRUCT_ID; 6 | payload: { 7 | valueId: string; 8 | valueName: string; 9 | }; 10 | } 11 | 12 | export function isSetConstructIdAction( 13 | action: ProjectAction 14 | ): action is SetConstructIdAction { 15 | return action.kind === ProjectActionKind.SET_CONSTRUCT_ID; 16 | } 17 | 18 | export function setConstructId( 19 | state: ProjectState, 20 | action: SetConstructIdAction 21 | ): ProjectState { 22 | const { valueId, valueName } = action.payload; 23 | const item = state.computed.values[valueId]; 24 | 25 | if (!item) return state; 26 | item.value.valueName = valueName.trim(); 27 | 28 | const retValue: ProjectState = { 29 | ...state, 30 | }; 31 | 32 | postProcess(retValue); 33 | 34 | return retValue; 35 | } 36 | -------------------------------------------------------------------------------- /src/react-app/src/project/types/storage-v1.ts: -------------------------------------------------------------------------------- 1 | import { InstanceValue } from "./values"; 2 | 3 | export interface StorageV1Data { 4 | kind: "cdk/project"; 5 | metadata: StorageV1Metadata; 6 | settings: StorageV1Settings; 7 | timestamp: number; 8 | blueprint: StorageV1Blueprint; 9 | root: InstanceValue; 10 | libs: { [key: string]: string }; 11 | } 12 | 13 | export interface StorageV1Metadata { 14 | format: number; 15 | projectName: string; 16 | projectId: string; 17 | } 18 | 19 | export interface StorageV1Settings { 20 | language: "typescript"; 21 | directoryHandle: FileSystemDirectoryHandle | null; 22 | } 23 | 24 | export interface StorageV1Blueprint { 25 | extend: string; 26 | id: string; 27 | name: string; 28 | libs?: { [key: string]: string }; 29 | containers?: string[]; 30 | favorites?: string[]; 31 | rules?: StorageV1Rules; 32 | } 33 | 34 | export interface StorageV1Rules { 35 | [fqn: string]: { 36 | allow?: string[]; 37 | deny?: string[]; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/cdk-builder.ts", 3 | "watch": { 4 | "include": ["**"], 5 | "exclude": [ 6 | "README.md", 7 | "cdk*.json", 8 | "**/*.d.ts", 9 | "**/*.js", 10 | "tsconfig.json", 11 | "package*.json", 12 | "yarn.lock", 13 | "node_modules", 14 | "test" 15 | ] 16 | }, 17 | "context": { 18 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 19 | "@aws-cdk/core:stackRelativeExports": true, 20 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 21 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 22 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 23 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 24 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 25 | "@aws-cdk/core:checkSecretUsage": true, 26 | "@aws-cdk/aws-iam:minimizePolicies": true, 27 | "@aws-cdk/core:target-partitions": ["aws", "aws-cn"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/react-app/src/project/blueprint-service.ts: -------------------------------------------------------------------------------- 1 | import { Database } from "./database"; 2 | import { projectBlueprints } from "./extended"; 3 | import { ProjectBlueprint } from "./types/project-blueprint"; 4 | 5 | const storeName = "blueprints"; 6 | 7 | export class BlueprintService { 8 | async save(blueprint: ProjectBlueprint) { 9 | if (blueprint.kind !== "cdk/project-blueprint") { 10 | throw new Error("Invalid blueprint"); 11 | } 12 | 13 | if (!blueprint.id) { 14 | throw new Error("Invalid blueprint id"); 15 | } 16 | 17 | await Database.set(storeName, blueprint.id, blueprint); 18 | } 19 | 20 | async load(): Promise { 21 | const result = await Database.values(storeName); 22 | 23 | const blueprints: ProjectBlueprint[] = []; 24 | for (const value of result) { 25 | try { 26 | blueprints.push(value); 27 | } catch (error) { 28 | console.error(error); 29 | } 30 | } 31 | 32 | return [...projectBlueprints, ...blueprints]; 33 | } 34 | 35 | async delete(blueprintId: string) { 36 | await Database.del(storeName, blueprintId); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/workbench.tsx: -------------------------------------------------------------------------------- 1 | import { Constructs } from "./constructs"; 2 | import { Items } from "./items"; 3 | import { Navbar } from "../navbar"; 4 | import { Details } from "./details"; 5 | import { DesignerModal } from "../designer-modal"; 6 | import { Breadcrumbs } from "../breadcrumbs"; 7 | import { useContext } from "react"; 8 | import { ProjectContext } from "../../project/project-context"; 9 | 10 | export function Workbench() { 11 | const { state } = useContext(ProjectContext); 12 | 13 | return ( 14 | <> 15 | 16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 |
28 |
29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/react-app/src/targets/export/export-archive.ts: -------------------------------------------------------------------------------- 1 | import JSZip from "jszip"; 2 | import { ProjectState } from "../../project/types"; 3 | import { ExportBase } from "./export-base"; 4 | 5 | export class ExportArchive extends ExportBase { 6 | private zip: JSZip; 7 | 8 | constructor(protected projectState: ProjectState) { 9 | super(projectState); 10 | 11 | this.zip = new JSZip(); 12 | } 13 | 14 | async folder(name: string) { 15 | this.zip.folder(name); 16 | } 17 | 18 | async file(name: string, content: string) { 19 | this.zip.file(name, content); 20 | } 21 | 22 | async download() { 23 | const blob = await this.zip.generateAsync({ type: "blob" }); 24 | 25 | const name = this.projectState.metadata.projectName; 26 | this.saveData(blob, `${name}.zip`); 27 | } 28 | 29 | saveData(blob: Blob, fileName: string) { 30 | const a = document.createElement("a"); 31 | document.body.appendChild(a); 32 | a.style.display = "none"; 33 | const url = window.URL.createObjectURL(blob); 34 | a.href = url; 35 | a.download = fileName; 36 | a.click(); 37 | 38 | window.URL.revokeObjectURL(url); 39 | document.body.removeChild(a); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/react-app/src/packages/package-parser.ts: -------------------------------------------------------------------------------- 1 | import packo from "pako"; 2 | import { TarFileItem, TarFileReader } from "./tar-file-reader"; 3 | 4 | export class PackageParser { 5 | parse(files: TarFileItem[], reader: TarFileReader) { 6 | const file = files.find((file) => file.name.endsWith("/.jsii")); 7 | 8 | if (file) { 9 | const content = reader.fileToString(file) || "{}"; 10 | let data = JSON.parse(content); 11 | 12 | if (data.schema === "jsii/file-redirect" && data.compression === "gzip") { 13 | const redirectFile = files.find( 14 | (file) => file.name === `package/${data.filename}` 15 | ); 16 | 17 | if (!redirectFile) { 18 | throw new Error(`Could not find file ${data.filename}`); 19 | } 20 | 21 | const arrayContent = reader.fileToArray(redirectFile); 22 | if (!arrayContent) { 23 | throw new Error(`Could not read file ${data.filename}`); 24 | } 25 | 26 | const array = packo.inflate(arrayContent); 27 | const textDecoder = new TextDecoder(); 28 | const content = textDecoder.decode(array); 29 | data = JSON.parse(content); 30 | } 31 | 32 | return data; 33 | } 34 | 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/react-app/src/components/diagram/diagram.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect, useState } from "react"; 2 | import ReactFlow, { Controls, Background, Node, Edge } from "reactflow"; 3 | import { DiagramGenerator } from "../../targets/generation/diagram-generator"; 4 | import { ProjectContext } from "../../project/project-context"; 5 | import { Navbar } from "../navbar"; 6 | 7 | export function Diagram() { 8 | const { state } = useContext(ProjectContext); 9 | const [nodes, setNodes] = useState[]>(); 10 | const [edges, setEdges] = useState[]>(); 11 | 12 | useEffect(() => { 13 | (async () => { 14 | const generator = new DiagramGenerator(state); 15 | const { nodes, edges } = await generator.create(); 16 | 17 | setNodes(nodes); 18 | setEdges(edges); 19 | })(); 20 | }, [state]); 21 | 22 | return ( 23 | <> 24 | 25 |
26 | 33 | 34 | 35 | 36 |
37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /src/react-app/src/targets/export/export-sync.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../../project/types"; 2 | import { ExportBase } from "./export-base"; 3 | 4 | export class ExportSync extends ExportBase { 5 | constructor( 6 | protected projectState: ProjectState, 7 | protected handle: FileSystemDirectoryHandle 8 | ) { 9 | super(projectState); 10 | } 11 | 12 | async folder(name: string): Promise { 13 | await this.handle.getDirectoryHandle(name, { 14 | create: true, 15 | }); 16 | } 17 | 18 | async file(name: string, content: string): Promise { 19 | const idx = name.lastIndexOf("/"); 20 | const path = idx >= 0 ? name.substring(0, idx).split("/") : []; 21 | const fileName = idx >= 0 ? name.substring(idx + 1) : name; 22 | 23 | let currentHandle = this.handle; 24 | for (const folder of path) { 25 | currentHandle = await currentHandle.getDirectoryHandle(folder, { 26 | create: true, 27 | }); 28 | } 29 | 30 | const fileHandle = await currentHandle.getFileHandle(fileName, { 31 | create: true, 32 | }); 33 | 34 | const writable = await fileHandle.createWritable({ 35 | keepExistingData: false, 36 | }); 37 | 38 | await writable.write(content); 39 | await writable.close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/move-item.ts: -------------------------------------------------------------------------------- 1 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 2 | import { postProcess } from "../post-process"; 3 | import { Utils } from "../../utils"; 4 | 5 | export interface MoveItemAction { 6 | kind: ProjectActionKind.MOVE_ITEM; 7 | payload: { 8 | valueId: string; 9 | index: number; 10 | }; 11 | } 12 | 13 | export function isMoveItemAction( 14 | action: ProjectAction 15 | ): action is MoveItemAction { 16 | return action.kind === ProjectActionKind.MOVE_ITEM; 17 | } 18 | 19 | export function moveItem( 20 | state: ProjectState, 21 | action: MoveItemAction 22 | ): ProjectState { 23 | const { valueId, index } = action.payload; 24 | const { selectedContainer } = state.computed; 25 | const value = selectedContainer.children.find((c) => c.valueId === valueId); 26 | if (!value) return state; 27 | 28 | const currentIndex = selectedContainer.children.indexOf(value); 29 | const delta = index - currentIndex; 30 | const items = Utils.moveArrayElement( 31 | [...selectedContainer.children], 32 | value, 33 | delta 34 | ); 35 | selectedContainer.children = items; 36 | 37 | const retValue: ProjectState = { 38 | ...state, 39 | }; 40 | 41 | postProcess(retValue); 42 | 43 | return retValue; 44 | } 45 | -------------------------------------------------------------------------------- /src/react-app/src/project/types/project-action.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AddItemAction, 3 | MoveItemAction, 4 | RemoveItemUpAction, 5 | SetDataAction, 6 | SetConstructIdAction, 7 | ToggleSelectAction, 8 | SetValueAction, 9 | ShowModalAction, 10 | } from "../actions"; 11 | import { AddCallItemAction } from "../actions/add-call-item"; 12 | import { SetSettingsAction } from "../actions/set-settings"; 13 | import { SetViewAction } from "../actions/set-view"; 14 | import { UpdateDataAction } from "../actions/update-data"; 15 | 16 | export enum ProjectActionKind { 17 | SET_DATA = "SET_DATA", 18 | UPDATE_DATA = "UPDATE_DATA", 19 | SET_VIEW = "SET_VIEW", 20 | ADD_ITEM = "ADD_ITEM", 21 | ADD_CALL_ITEM = "ADD_CALL_ITEM", 22 | REMOVE_ITEM = "REMOVE_ITEM", 23 | MOVE_ITEM = "MOVE_ITEM", 24 | TOGGLE_SELECT = "TOGGLE_SELECT", 25 | SET_STACK_NAME = "SET_STACK_NAME", 26 | SET_CONSTRUCT_ID = "SET_CONSTRUCT_ID", 27 | SET_VALUE = "SET_VALUE", 28 | SHOW_MODAL = "SHOW_MODAL", 29 | SET_SETTINGS = "SET_SETTINGS", 30 | } 31 | 32 | export type ProjectAction = 33 | | SetDataAction 34 | | UpdateDataAction 35 | | SetViewAction 36 | | AddItemAction 37 | | AddCallItemAction 38 | | RemoveItemUpAction 39 | | MoveItemAction 40 | | ToggleSelectAction 41 | | SetConstructIdAction 42 | | SetValueAction 43 | | ShowModalAction 44 | | SetSettingsAction; 45 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-cdk-json.ts: -------------------------------------------------------------------------------- 1 | import { TS_JSON_IDENT } from "./common"; 2 | 3 | export function createCdkJson(rootFileName: string) { 4 | /* prettier-ignore */ 5 | const config = { 6 | app: `npx ts-node --prefer-ts-exts bin/${rootFileName}.ts`, 7 | watch: { 8 | include: ["**"], 9 | exclude: [ 10 | "README.md", 11 | "cdk*.json", 12 | "**/*.d.ts", 13 | "**/*.js", 14 | "tsconfig.json", 15 | "package*.json", 16 | "yarn.lock", 17 | "node_modules", 18 | "test", 19 | ], 20 | }, 21 | context: { 22 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 23 | "@aws-cdk/core:stackRelativeExports": true, 24 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 25 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 26 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 27 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 28 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 29 | "@aws-cdk/core:checkSecretUsage": true, 30 | "@aws-cdk/aws-iam:minimizePolicies": true, 31 | "@aws-cdk/core:target-partitions": ["aws", "aws-cn"], 32 | }, 33 | }; 34 | 35 | return JSON.stringify(config, null, TS_JSON_IDENT); 36 | } 37 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/set-settings.ts: -------------------------------------------------------------------------------- 1 | import { postProcess } from "../post-process"; 2 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 3 | 4 | type ProjectSettings = 5 | | { 6 | kind: "language"; 7 | language: "typescript"; 8 | } 9 | | { 10 | kind: "directoryHandle"; 11 | directoryHandle: FileSystemDirectoryHandle | null; 12 | }; 13 | 14 | export interface SetSettingsAction { 15 | kind: ProjectActionKind.SET_SETTINGS; 16 | payload: ProjectSettings[]; 17 | } 18 | 19 | export function isSetSettingsAction( 20 | action: ProjectAction 21 | ): action is SetSettingsAction { 22 | return action.kind === ProjectActionKind.SET_SETTINGS; 23 | } 24 | 25 | export function setSettings( 26 | state: ProjectState, 27 | action: SetSettingsAction 28 | ): ProjectState { 29 | const { payload } = action; 30 | const settings = { ...state.settings }; 31 | 32 | for (const item of payload) { 33 | switch (item.kind) { 34 | case "language": { 35 | settings.language = item.language; 36 | break; 37 | } 38 | case "directoryHandle": { 39 | settings.directoryHandle = item.directoryHandle; 40 | break; 41 | } 42 | } 43 | } 44 | 45 | let retValue: ProjectState = { 46 | ...state, 47 | settings, 48 | }; 49 | 50 | postProcess(retValue); 51 | 52 | return retValue; 53 | } 54 | -------------------------------------------------------------------------------- /src/react-app/src/blueprints/cdk8s-blank.ts: -------------------------------------------------------------------------------- 1 | import { ProjectBlueprint } from "../project/types/project-blueprint"; 2 | import { Utils } from "../utils"; 3 | import { ValueKind } from "../project/types/values"; 4 | 5 | export const k8sBlankBlueprint: ProjectBlueprint = { 6 | format: 1, 7 | kind: "cdk/project-blueprint", 8 | extend: "cdk8s-base", 9 | id: "e4f64bec-18f1-4657-a9c1-706d13363998", 10 | name: "Kubernetes blank (**Work in progress**)", 11 | description: "Blank CDK for Kubernetes blueprint", 12 | icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IndoaXRlIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNyAyMWgxMGEyIDIgMCAwMDItMlY5LjQxNGExIDEgMCAwMC0uMjkzLS43MDdsLTUuNDE0LTUuNDE0QTEgMSAwIDAwMTIuNTg2IDNIN2EyIDIgMCAwMC0yIDJ2MTRhMiAyIDAgMDAyIDJ6Ij48L3BhdGg+PC9zdmc+", 13 | background: "#ec489a", 14 | libs: { "cdk8s-plus-22": "2.x" }, 15 | root: { 16 | kind: ValueKind.Instance, 17 | valueId: Utils.generateId(), 18 | valueName: "App", 19 | valueType: { 20 | fqn: "cdk8s.App", 21 | }, 22 | parameters: {}, 23 | children: [ 24 | { 25 | kind: ValueKind.Instance, 26 | valueId: Utils.generateId(), 27 | valueName: "MyChart", 28 | valueType: { 29 | fqn: "cdk8s.Chart", 30 | }, 31 | children: [], 32 | parameters: {}, 33 | }, 34 | ], 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/react-app/src/pages/not-found.tsx: -------------------------------------------------------------------------------- 1 | export default function NotFound() { 2 | return ( 3 | <> 4 |
5 |
6 |
7 |

8 | 404 9 |

10 |
11 |
12 |

13 | Page not found 14 |

15 |

16 | Please check the URL in the address bar and try again. 17 |

18 |
19 | 27 |
28 |
29 |
30 |
31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/react-app/src/blueprints/cdktf-blank.ts: -------------------------------------------------------------------------------- 1 | import { ProjectBlueprint } from "../project/types/project-blueprint"; 2 | import { Utils } from "../utils"; 3 | import { ValueKind } from "../project/types/values"; 4 | 5 | export const terraformBlankBlueprint: ProjectBlueprint = { 6 | format: 1, 7 | kind: "cdk/project-blueprint", 8 | extend: "cdktf-base", 9 | id: "ef1af905-c137-4007-b6be-40587880df58", 10 | name: "Terraform blank (**Work in progress**)", 11 | description: "Blank CDK for Terraform blueprint", 12 | icon: "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCAyNCAyNCIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2U9IndoaXRlIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PHBhdGggc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNNyAyMWgxMGEyIDIgMCAwMDItMlY5LjQxNGExIDEgMCAwMC0uMjkzLS43MDdsLTUuNDE0LTUuNDE0QTEgMSAwIDAwMTIuNTg2IDNIN2EyIDIgMCAwMC0yIDJ2MTRhMiAyIDAgMDAyIDJ6Ij48L3BhdGg+PC9zdmc+", 13 | background: "#ec489a", 14 | libs: { "@cdktf/provider-aws": "latest" }, 15 | root: { 16 | kind: ValueKind.Instance, 17 | valueId: Utils.generateId(), 18 | valueName: "App", 19 | valueType: { 20 | fqn: "cdktf.App", 21 | }, 22 | parameters: {}, 23 | children: [ 24 | { 25 | kind: ValueKind.Instance, 26 | valueId: Utils.generateId(), 27 | valueName: "MyStack", 28 | valueType: { 29 | fqn: "cdktf.TerraformStack", 30 | }, 31 | children: [], 32 | parameters: {}, 33 | }, 34 | ], 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/set-data.ts: -------------------------------------------------------------------------------- 1 | import { postProcess } from "../post-process"; 2 | import { AssemblyMetadata, TypeMetadata } from "../../types"; 3 | import { 4 | ProjectAction, 5 | ProjectActionKind, 6 | BlueprintComputed, 7 | ProjectState, 8 | StorageV1Data, 9 | } from "../types"; 10 | 11 | export interface SetDataAction { 12 | kind: ProjectActionKind.SET_DATA; 13 | payload: { 14 | version: string; 15 | assemblies: AssemblyMetadata[]; 16 | typeMetadata: { 17 | [fqn: string]: TypeMetadata; 18 | }; 19 | modules: { 20 | [name: string]: TypeMetadata[]; 21 | }; 22 | constructFqns: Set; 23 | project: StorageV1Data; 24 | blueprintComputed: BlueprintComputed; 25 | }; 26 | } 27 | 28 | export function isSetDataAction( 29 | action: ProjectAction 30 | ): action is SetDataAction { 31 | return action.kind === ProjectActionKind.SET_DATA; 32 | } 33 | 34 | export function setData( 35 | state: ProjectState, 36 | action: SetDataAction 37 | ): ProjectState { 38 | const { project, blueprintComputed } = action.payload; 39 | 40 | const { 41 | typeMetadata, 42 | modules, 43 | constructFqns, 44 | assemblies = [], 45 | version, 46 | } = action.payload; 47 | 48 | let retValue: ProjectState = { 49 | ...state, 50 | version, 51 | assemblies, 52 | types: typeMetadata, 53 | modules, 54 | constructFqns, 55 | ...project, 56 | blueprintComputed, 57 | }; 58 | 59 | postProcess(retValue); 60 | retValue.loaded = true; 61 | 62 | return retValue; 63 | } 64 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/update-data.ts: -------------------------------------------------------------------------------- 1 | import { postProcess } from "../post-process"; 2 | import { AssemblyMetadata, TypeMetadata } from "../../types"; 3 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 4 | 5 | export interface UpdateDataAction { 6 | kind: ProjectActionKind.UPDATE_DATA; 7 | payload: { 8 | version: string; 9 | assemblies: AssemblyMetadata[]; 10 | typeMetadata: { 11 | [fqn: string]: TypeMetadata; 12 | }; 13 | modules: { 14 | [name: string]: TypeMetadata[]; 15 | }; 16 | constructFqns: Set; 17 | libs: { [key: string]: string }; 18 | }; 19 | } 20 | 21 | export function isUpdateDataAction( 22 | action: ProjectAction 23 | ): action is UpdateDataAction { 24 | return action.kind === ProjectActionKind.UPDATE_DATA; 25 | } 26 | 27 | export function updateData( 28 | state: ProjectState, 29 | action: UpdateDataAction 30 | ): ProjectState { 31 | const { 32 | typeMetadata, 33 | modules, 34 | constructFqns, 35 | assemblies = [], 36 | version, 37 | } = action.payload; 38 | 39 | let retValue: ProjectState = { 40 | ...state, 41 | version, 42 | assemblies, 43 | types: typeMetadata, 44 | modules, 45 | constructFqns, 46 | blueprintComputed: { 47 | ...state.blueprintComputed, 48 | libs: { ...state.blueprintComputed.libs, ...action.payload.libs }, 49 | }, 50 | libs: { ...state.libs, ...action.payload.libs }, 51 | }; 52 | 53 | postProcess(retValue); 54 | retValue.loaded = true; 55 | 56 | return retValue; 57 | } 58 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/details.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useState } from "react"; 2 | import { ProjectContext } from "../../project/project-context"; 3 | import { DetailsEmpty } from "./details-empty"; 4 | import { DetailsHeader } from "./details-header"; 5 | import { ObjectDesigner } from "../object-designer/object-designer"; 6 | import { MethodsDesigner } from "../object-designer/methods-designer"; 7 | import { TypeTest } from "../../types"; 8 | 9 | export function Details() { 10 | const { state } = useContext(ProjectContext); 11 | const [filter, setFilter] = useState(""); 12 | const [tab, setTab] = useState("properties"); 13 | const item = state.computed.selectedRootValue; 14 | 15 | if (!item || !TypeTest.isFqn(item.value.valueType)) { 16 | return ( 17 |
18 | 19 |
20 | ); 21 | } 22 | 23 | return ( 24 |
25 |
26 | 33 |
34 |
35 | {tab === "methods" && } 36 | {tab === "properties" && ( 37 | 38 | )} 39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /src/react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdk-builder", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@headlessui/react": "^1.7.17", 14 | "@heroicons/react": "^2.0.18", 15 | "@types/prettier": "2.7.3", 16 | "axios": "^1.5.1", 17 | "elkjs": "^0.8.2", 18 | "idb": "^7.1.1", 19 | "jszip": "^3.10.1", 20 | "prettier": "2.7.1", 21 | "prismjs": "^1.29.0", 22 | "react": "^18.2.0", 23 | "react-dom": "^18.2.0", 24 | "react-popper-tooltip": "^4.4.2", 25 | "react-router-dom": "^6.17.0", 26 | "react-spring": "^9.7.3", 27 | "reactflow": "^11.3.2", 28 | "sass": "^1.69.4", 29 | "semver": "^7.5.4", 30 | "web-worker": "^1.2.0" 31 | }, 32 | "devDependencies": { 33 | "@tailwindcss/forms": "^0.5.6", 34 | "@types/node": "^20.8.8", 35 | "@types/pako": "^2.0.2", 36 | "@types/prismjs": "^1.26.2", 37 | "@types/react": "^18.2.15", 38 | "@types/react-dom": "^18.2.7", 39 | "@types/wicg-file-system-access": "^2023.10.2", 40 | "@typescript-eslint/eslint-plugin": "^6.0.0", 41 | "@typescript-eslint/parser": "^6.0.0", 42 | "@vitejs/plugin-react": "^4.0.3", 43 | "autoprefixer": "^10.4.16", 44 | "eslint": "^8.45.0", 45 | "eslint-plugin-react-hooks": "^4.6.0", 46 | "eslint-plugin-react-refresh": "^0.4.3", 47 | "postcss": "^8.4.31", 48 | "tailwindcss": "^3.3.4", 49 | "typescript": "^5.0.2", 50 | "vite": "^4.4.5" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/items.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from "react"; 2 | import { useTransition, animated } from "react-spring"; 3 | import { ProjectContext } from "../../project/project-context"; 4 | import { ItemsEmpty } from "./items-empty"; 5 | import { Item } from "./item"; 6 | import { CoreValue } from "../../project/types/values"; 7 | 8 | const height = 110; 9 | 10 | export function Items() { 11 | const { state } = useContext(ProjectContext); 12 | const transitions = useTransition( 13 | state.computed.selectedContainer.children.map((value, i) => ({ 14 | ...value, 15 | y: height * i, 16 | })), 17 | { 18 | key: (value: CoreValue) => value.valueId, 19 | from: { position: "absolute", height: 0, opacity: 0 }, 20 | leave: { height: 0, opacity: 0 }, 21 | enter: ({ y }) => ({ y, height, opacity: 1 }), 22 | update: ({ y }) => ({ y, height }), 23 | config: () => ({ 24 | duration: 200, 25 | }), 26 | } 27 | ); 28 | 29 | const total = state.computed.selectedContainer.children.length; 30 | if (total === 0) { 31 | return ( 32 |
33 | 34 |
35 | ); 36 | } 37 | 38 | return ( 39 |
43 | {transitions((itemStyle, value, _t, index) => ( 44 | 45 | 46 | 47 | ))} 48 |
49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/constructs-header.tsx: -------------------------------------------------------------------------------- 1 | import { PlusIcon } from "@heroicons/react/24/outline"; 2 | import { useEffect, useState } from "react"; 3 | import { PackagesModal } from "./packages-modal"; 4 | 5 | export function ConstructsHeader(props: { 6 | filter: string; 7 | setFilter: (filter: string) => void; 8 | }) { 9 | const [filter, setFilter] = useState(props.filter); 10 | const [openPackagesModal, setOpenPackagesModal] = useState(false); 11 | 12 | useEffect(() => { 13 | const timeout = setTimeout(async () => { 14 | props.setFilter(filter); 15 | }, 500); 16 | 17 | return () => clearTimeout(timeout); 18 | }, [filter, props]); 19 | 20 | return ( 21 | <> 22 | 23 |
24 | setFilter(e.target.value || "")} 31 | /> 32 |
33 | 34 | 41 | 42 |
43 |
44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/react-app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./app"; 4 | import { ErrorBoundary } from "./components/error-boundary"; 5 | import "prismjs"; 6 | import "./styles/index.scss"; 7 | import "./styles/prism.scss"; 8 | import "react-popper-tooltip/dist/styles.css"; 9 | import "reactflow/dist/style.css"; 10 | import "prismjs/components/prism-typescript"; 11 | import "prismjs/components/prism-markdown"; 12 | 13 | registerServiceWorker(); 14 | let basename = import.meta.env.BASE_URL; 15 | if (!basename.endsWith("/")) { 16 | basename = basename + "/"; 17 | } 18 | 19 | const root = ReactDOM.createRoot( 20 | document.getElementById("root") as HTMLElement 21 | ); 22 | 23 | root.render( 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | function registerServiceWorker() { 32 | if ("serviceWorker" in navigator) { 33 | window.addEventListener("load", () => { 34 | const url = `${basename}service-worker.js`; 35 | 36 | navigator.serviceWorker 37 | .register(url) 38 | .then((registration) => { 39 | registration.onupdatefound = () => { 40 | const installingWorker = registration.installing; 41 | 42 | if (installingWorker) { 43 | installingWorker.onstatechange = () => { 44 | if (installingWorker.state === "installed") { 45 | if (navigator.serviceWorker.controller) { 46 | console.log("New version is available"); 47 | } 48 | } 49 | }; 50 | } 51 | }; 52 | }) 53 | .catch((error) => { 54 | console.error(error); 55 | }); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/react-app/src/components/workbench/search-empty.tsx: -------------------------------------------------------------------------------- 1 | import { BackspaceIcon } from "@heroicons/react/24/solid"; 2 | import { useCallback } from "react"; 3 | 4 | export function SearchEmpty(props: { setFilter?: (filter: string) => void }) { 5 | const onButtonClick = useCallback(() => { 6 | if (props.setFilter) { 7 | props.setFilter(""); 8 | } 9 | }, [props]); 10 | 11 | return ( 12 |
13 |
14 | 29 |

Not Found

30 |

31 | Clear search to view all items 32 |

33 |
34 | 42 |
43 |
44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /src/react-app/src/targets/target-processor.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../project/types"; 2 | import { ExportArchive } from "./export/export-archive"; 3 | import { ExportSync } from "./export/export-sync"; 4 | import { TypeScriptCdkBuilder } from "./typescript/typescript-cdk-builder"; 5 | import { TypeScriptGenerator } from "./typescript/typescript-generator"; 6 | 7 | export class TargetProcessor { 8 | constructor( 9 | protected state: ProjectState, 10 | protected language: "typescript" 11 | ) {} 12 | 13 | async downloadArchive() { 14 | if (this.language === "typescript") { 15 | const generator = new TypeScriptGenerator(this.state); 16 | const archive = new ExportArchive(this.state); 17 | const fileBuilder = new TypeScriptCdkBuilder( 18 | this.state, 19 | generator, 20 | archive 21 | ); 22 | await fileBuilder.build(); 23 | await archive.download(); 24 | } else { 25 | throw new Error("Unknown language"); 26 | } 27 | } 28 | 29 | async syncFileSystem(dirHandle: FileSystemDirectoryHandle) { 30 | if (this.language === "typescript") { 31 | const generator = new TypeScriptGenerator(this.state); 32 | const sync = new ExportSync(this.state, dirHandle); 33 | const fileBuilder = new TypeScriptCdkBuilder(this.state, generator, sync); 34 | await fileBuilder.build(); 35 | } else { 36 | throw new Error("Unknown language"); 37 | } 38 | } 39 | 40 | generateContainerCode() { 41 | if (this.language === "typescript") { 42 | const generator = new TypeScriptGenerator(this.state); 43 | const generationState = generator.generate(); 44 | const selectedValueId = this.state.computed.selectedContainer.valueId; 45 | const containerCode = generationState.containers[selectedValueId].code; 46 | 47 | return { containerCode, generationState }; 48 | } else { 49 | throw new Error("Unknown language"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-files/create-package-json.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../../../project/types"; 2 | import { Utils } from "../../../utils"; 3 | import { TS_JSON_IDENT } from "./common"; 4 | 5 | export function createPackageJson( 6 | state: ProjectState, 7 | fqns: string[], 8 | rootFileName: string 9 | ) { 10 | const version = 11 | state.assemblies.find((c) => c.name === "aws-cdk-lib")?.version || ""; 12 | const constructsVersion = 13 | state.assemblies.find((c) => c.name === "constructs")?.version || ""; 14 | 15 | /* prettier-ignore */ 16 | const devDependencies: { [name: string]: string } = { 17 | "@types/jest": "^28.1.1", 18 | "@types/node": "18.7.15", 19 | "prettier": "^2.7.1", 20 | "jest": "^28.1.3", 21 | "ts-jest": "^28.0.8", 22 | "ts-node": "^10.9.1", 23 | "typescript": "^4.9.3", 24 | "aws-cdk": `^${version}`, 25 | }; 26 | 27 | /* prettier-ignore */ 28 | const dependencies: { [name: string]: string } = { 29 | "source-map-support": "^0.5.21", 30 | "constructs": `^${constructsVersion}`, 31 | "aws-cdk-lib": `^${version}`, 32 | }; 33 | 34 | const assemblyNames = Utils.unique( 35 | fqns.map((fqn) => state.types[fqn].assembly) 36 | ).sort(); 37 | 38 | for (const assemblyName of assemblyNames) { 39 | const assembly = state.assemblies.find((c) => c.name === assemblyName); 40 | if (!assembly) continue; 41 | 42 | dependencies[assembly.name] = `^${assembly.version}`; 43 | } 44 | 45 | /* prettier-ignore */ 46 | const config = { 47 | name: state.metadata.projectName.toLowerCase(), 48 | version: "0.1.0", 49 | bin: { 50 | [rootFileName]: `bin/${rootFileName}.ts`, 51 | }, 52 | scripts: { 53 | build: "tsc", 54 | watch: "tsc -w", 55 | test: "jest", 56 | cdk: "cdk", 57 | }, 58 | dependencies, 59 | devDependencies, 60 | }; 61 | 62 | return JSON.stringify(config, null, TS_JSON_IDENT); 63 | } 64 | -------------------------------------------------------------------------------- /src/react-app/src/project/types/project-state.ts: -------------------------------------------------------------------------------- 1 | import { AssemblyMetadata, TypeMetadata } from "../../types"; 2 | import { InstanceValue, ValueMetadata } from "./values"; 3 | import { ValueError } from "../validator"; 4 | 5 | export type CdkPlatform = "cdk" | "cdktf" | "cdk8s"; 6 | 7 | export interface ProjectState { 8 | version: string; 9 | loaded: boolean; 10 | metadata: ProjectMetadata; 11 | settings: ProjectSettings; 12 | blueprint: BlueprintMetadata; 13 | blueprintComputed: BlueprintComputed; 14 | root: InstanceValue; 15 | assemblies: AssemblyMetadata[]; 16 | libs: { [key: string]: string }; 17 | types: { [fqn: string]: TypeMetadata }; 18 | modules: { 19 | [name: string]: TypeMetadata[]; 20 | }; 21 | constructFqns: Set; 22 | view: string; 23 | modal: boolean; 24 | selectionIds: string[]; 25 | computed: Computed; 26 | } 27 | 28 | export interface ProjectMetadata { 29 | format: number; 30 | projectName: string; 31 | projectId: string; 32 | } 33 | 34 | export interface ProjectSettings { 35 | language: "typescript"; 36 | directoryHandle: FileSystemDirectoryHandle | null; 37 | } 38 | 39 | export interface BlueprintMetadata { 40 | extend: string; 41 | id: string; 42 | name: string; 43 | libs?: { [key: string]: string }; 44 | containers?: string[]; 45 | favorites?: string[]; 46 | rules?: ProjectRules; 47 | } 48 | 49 | export interface BlueprintComputed { 50 | platform: CdkPlatform; 51 | libs: { [key: string]: string }; 52 | containers: string[]; 53 | favorites: string[]; 54 | rules: ProjectRules; 55 | } 56 | 57 | export interface ProjectRules { 58 | [fqn: string]: { 59 | allow?: string[]; 60 | deny?: string[]; 61 | }; 62 | } 63 | 64 | export interface Computed { 65 | errors: { [valueId: string]: ValueError[] }; 66 | values: { [valueId: string]: ValueMetadata }; 67 | selectedContainer: InstanceValue; 68 | selectedRootValue: ValueMetadata; 69 | selectedValue: ValueMetadata | null; 70 | selection: ValueMetadata[]; 71 | containerPath: ValueMetadata[]; 72 | valuePath: ValueMetadata[]; 73 | } 74 | -------------------------------------------------------------------------------- /src/react-app/src/targets/generation/code-generator.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState } from "../../project/types"; 2 | import { Utils } from "../../utils"; 3 | import { GeneratorBase } from "./generator-base"; 4 | 5 | export interface GenerationState { 6 | fqns: string[]; 7 | containers: { 8 | [valueId: string]: ContainerState; 9 | }; 10 | } 11 | 12 | export interface ContainerState { 13 | isRoot: boolean; 14 | fileName: string; 15 | valueName: string; 16 | variableName: string; 17 | fqns: string[]; 18 | containerIds: string[]; 19 | code: string; 20 | } 21 | 22 | export abstract class CodeGenerator extends GeneratorBase { 23 | protected state: GenerationState = { 24 | fqns: [], 25 | containers: {}, 26 | }; 27 | 28 | constructor(protected projectState: ProjectState) { 29 | super(projectState); 30 | } 31 | 32 | abstract buildContainer(valueId: string, isRoot: boolean): void; 33 | abstract moduleCodeAliasArray(modules: string[]): string[]; 34 | abstract containerFileName(valueName: string): string; 35 | abstract valueName(valueName: string | undefined): string; 36 | 37 | generate(): GenerationState { 38 | const containerIds = Object.keys(this.metadata.containers); 39 | const rootId = this.projectState.root.valueId; 40 | for (const containerId of containerIds) { 41 | const container = this.projectState.computed.values[containerId]; 42 | const fileName = this.containerFileName(container.valueName || "null"); 43 | const variableName = this.metadata.variableNames[container.valueId]; 44 | const valueName = this.valueName(container.valueName); 45 | 46 | this.state.containers[containerId] = { 47 | isRoot: containerId === rootId, 48 | fqns: [], 49 | containerIds: [], 50 | code: "", 51 | valueName, 52 | fileName, 53 | variableName, 54 | }; 55 | } 56 | 57 | this.buildContainer(rootId, true); 58 | const allFqns = Object.values(this.state.containers).flatMap((v) => v.fqns); 59 | this.state.fqns = Utils.unique(allFqns); 60 | 61 | return this.state; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cdk-builder", 3 | "short_name": "CDK Builder", 4 | "name": "AWS CDK Builder", 5 | "description": "A tool to bootstrap AWS CDK projects", 6 | "start_url": ".", 7 | "display": "standalone", 8 | "theme_color": "#ffffff", 9 | "background_color": "#ffffff", 10 | "icons": [ 11 | { 12 | "src": "/icons/maskable_icon_x48.png", 13 | "sizes": "48x48", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | }, 17 | { 18 | "src": "/icons/maskable_icon_x96.png", 19 | "sizes": "96x96", 20 | "type": "image/png", 21 | "purpose": "maskable" 22 | }, 23 | { 24 | "src": "/icons/maskable_icon_x128.png", 25 | "sizes": "128x128", 26 | "type": "image/png", 27 | "purpose": "maskable" 28 | }, 29 | { 30 | "src": "/icons/maskable_icon_x192.png", 31 | "sizes": "192x192", 32 | "type": "image/png", 33 | "purpose": "maskable" 34 | }, 35 | { 36 | "src": "/icons/maskable_icon_x384.png", 37 | "sizes": "384x384", 38 | "type": "image/png", 39 | "purpose": "maskable" 40 | }, 41 | { 42 | "src": "/icons/maskable_icon_x512.png", 43 | "sizes": "512x512", 44 | "type": "image/png", 45 | "purpose": "maskable" 46 | }, 47 | { 48 | "src": "/icons/192x192.png", 49 | "sizes": "192x192", 50 | "type": "image/png", 51 | "purpose": "any" 52 | }, 53 | { 54 | "src": "/icons/512x512.png", 55 | "sizes": "512x512", 56 | "type": "image/png", 57 | "purpose": "any" 58 | } 59 | ], 60 | "file_handlers": [ 61 | { 62 | "action": "/", 63 | "accept": { 64 | "application/json": [".cdkproj"] 65 | }, 66 | "icons": [ 67 | { 68 | "src": "/icons/192x192.png", 69 | "sizes": "192x192", 70 | "type": "image/png" 71 | }, 72 | { 73 | "src": "/icons/512x512.png", 74 | "sizes": "512x512", 75 | "type": "image/png" 76 | } 77 | ], 78 | "launch_type": "single-client" 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/toggle-select.ts: -------------------------------------------------------------------------------- 1 | import { computeSelection } from "../post-process"; 2 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 3 | 4 | export interface ToggleSelectAction { 5 | kind: ProjectActionKind.TOGGLE_SELECT; 6 | payload: { 7 | valueId?: string; 8 | clearModal?: boolean; 9 | }; 10 | } 11 | 12 | export function isToggleSelectAction( 13 | action: ProjectAction 14 | ): action is ToggleSelectAction { 15 | return action.kind === ProjectActionKind.TOGGLE_SELECT; 16 | } 17 | 18 | export function toggleSelect( 19 | state: ProjectState, 20 | action: ToggleSelectAction 21 | ): ProjectState { 22 | const { valueId, clearModal } = action.payload; 23 | const { selection, selectedContainer, selectedRootValue } = state.computed; 24 | let selectionIds = [...state.selectionIds]; 25 | let modal = false; 26 | 27 | if (valueId) { 28 | const idx = selection.findIndex((c) => c.valueId === valueId); 29 | if (idx >= 0) { 30 | selectionIds.splice(idx + 1); 31 | } else { 32 | if (selectedContainer.children.find((c) => c.valueId === valueId)) { 33 | const idx = selection.findIndex( 34 | (c) => c.valueId === selectedContainer.valueId 35 | ); 36 | selectionIds.splice(idx + 1); 37 | } 38 | 39 | selectionIds.push(valueId); 40 | } 41 | } else if (clearModal) { 42 | const idx = selection.findIndex( 43 | (c) => c.valueId === selectedRootValue.valueId 44 | ); 45 | 46 | if (idx >= 0) { 47 | selectionIds.splice(idx + 1); 48 | } else { 49 | selectionIds = []; 50 | } 51 | } else { 52 | selectionIds = []; 53 | } 54 | 55 | const computed = computeSelection( 56 | state.root, 57 | selectionIds, 58 | state.computed.values, 59 | state.types, 60 | state.blueprintComputed 61 | ); 62 | 63 | if ( 64 | computed.selectedValue && 65 | computed.selectedValue.valueId !== computed.selectedRootValue.valueId 66 | ) { 67 | modal = true; 68 | } 69 | 70 | const retValue: ProjectState = { 71 | ...state, 72 | selectionIds, 73 | modal, 74 | computed: { 75 | ...state.computed, 76 | ...computed, 77 | }, 78 | }; 79 | 80 | return retValue; 81 | } 82 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/property.tsx: -------------------------------------------------------------------------------- 1 | import { ParameterMetadata, PropertyMetadata } from "../../types"; 2 | import { PropertyLabel } from "./property-label"; 3 | import { PropertyValue } from "./property-value"; 4 | import { SetValueAction } from "../../project/actions"; 5 | import { useContext } from "react"; 6 | import { ProjectContext } from "../../project/project-context"; 7 | import { CoreValue } from "../../project/types/values"; 8 | import { ValuesHelper } from "../../project/helpers/values-helper"; 9 | 10 | const valuesHelper = new ValuesHelper(); 11 | 12 | export function Property(props: { 13 | setValue: (action: SetValueAction) => void; 14 | modal: boolean; 15 | item: PropertyMetadata | ParameterMetadata; 16 | parent: CoreValue; 17 | path: string[]; 18 | }) { 19 | const { state } = useContext(ProjectContext); 20 | const { currentValue } = valuesHelper.getPropertyValue( 21 | props.parent, 22 | props.item.name, 23 | props.path 24 | ); 25 | 26 | const defaultValue = valuesHelper.getPropertyDefaultValue( 27 | props.item.docs, 28 | props.modal ? 75 : 45 29 | ); 30 | 31 | const errors = state.computed.errors[props.parent.valueId].filter( 32 | (c) => c.key === props.item.name 33 | ); 34 | 35 | if (props.modal) { 36 | return ( 37 | <> 38 |
39 | 40 |
41 |
42 | 51 |
52 | 53 | ); 54 | } else { 55 | return ( 56 |
57 | 58 | 67 |
68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/react-app/src/project/actions/add-item.ts: -------------------------------------------------------------------------------- 1 | import { ProjectAction, ProjectActionKind, ProjectState } from "../types"; 2 | import { InstanceValue, ValueKind, Values } from "../types/values"; 3 | import { postProcess } from "../post-process"; 4 | import { Utils } from "../../utils"; 5 | import { ValuesHelper } from "../helpers/values-helper"; 6 | 7 | const valuesHelper = new ValuesHelper(); 8 | 9 | export interface AddItemAction { 10 | kind: ProjectActionKind.ADD_ITEM; 11 | payload: { 12 | fqn: string; 13 | }; 14 | } 15 | 16 | export function isAddItemAction( 17 | action: ProjectAction 18 | ): action is AddItemAction { 19 | return action.kind === ProjectActionKind.ADD_ITEM; 20 | } 21 | 22 | export function addItem( 23 | state: ProjectState, 24 | action: AddItemAction 25 | ): ProjectState { 26 | const { fqn } = action.payload; 27 | const { values, selectedContainer } = state.computed; 28 | const typeMetadata = state.types[fqn]; 29 | const valueName = valuesHelper.generateValueName( 30 | selectedContainer.children, 31 | typeMetadata.name 32 | ); 33 | 34 | const value: InstanceValue = { 35 | kind: ValueKind.Instance, 36 | valueId: Utils.generateId(), 37 | valueName, 38 | valueType: { 39 | fqn, 40 | }, 41 | children: [], 42 | parameters: {}, 43 | }; 44 | 45 | let retValue: ProjectState | null = null; 46 | if (state.root.valueId === selectedContainer.valueId) { 47 | retValue = { 48 | ...state, 49 | root: { 50 | ...state.root, 51 | children: [...state.root.children, value], 52 | }, 53 | }; 54 | } else { 55 | retValue = { 56 | ...state, 57 | }; 58 | 59 | const valueMetadata = values[selectedContainer.valueId]; 60 | const parent = values[valueMetadata.parentId]; 61 | if (Values.isInstance(parent.value)) { 62 | const parentContainer = parent.value; 63 | parentContainer.children = parentContainer.children.map((item) => { 64 | if (item.valueId !== selectedContainer.valueId) { 65 | return item; 66 | } 67 | 68 | return { 69 | ...item, 70 | children: [...selectedContainer.children, value], 71 | }; 72 | }); 73 | } 74 | } 75 | 76 | postProcess(retValue); 77 | 78 | return retValue; 79 | } 80 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/object-designer.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useMemo } from "react"; 2 | import { ProjectContext } from "../../project/project-context"; 3 | import { TypeTest } from "../../types"; 4 | import { ArrayDesigner } from "./designers/array-designer"; 5 | import { MapDesigner } from "./designers/map-designer"; 6 | import { Values } from "../../project/types/values"; 7 | import { ItemsHelper } from "../../project/helpers/items-helper"; 8 | import { PropertyList } from "./property-list"; 9 | 10 | const itemsHelper = new ItemsHelper(); 11 | 12 | export function ObjectDesigner(props: { 13 | modal: boolean; 14 | filter?: string; 15 | setFilter?: (filter: string) => void; 16 | }) { 17 | const { state, dispatch } = useContext(ProjectContext); 18 | const selected = props.modal 19 | ? state.computed.selectedValue 20 | : state.computed.selectedRootValue; 21 | 22 | const items = useMemo(() => { 23 | if (!selected) return null; 24 | return itemsHelper.getItems(state.types, selected.value, props.filter); 25 | }, [props.filter, selected, state]); 26 | 27 | if (!selected) return null; 28 | 29 | if (TypeTest.isCollection(selected.value.valueType)) { 30 | const elementType = selected.value.valueType.collection.elementtype; 31 | if (selected.value.valueType.collection.kind === "array") { 32 | return ( 33 | 38 | ); 39 | } else if (selected.value.valueType.collection.kind === "map") { 40 | return ( 41 | 46 | ); 47 | } 48 | } 49 | 50 | if ( 51 | Values.isInstance(selected.value) || 52 | Values.isObject(selected.value) || 53 | Values.isCall(selected.value) 54 | ) { 55 | if (!items) { 56 | throw new Error(`Could not find items for ${selected.value.valueType}`); 57 | } 58 | 59 | return ( 60 | 68 | ); 69 | } 70 | 71 | return <>Unknown; 72 | } 73 | -------------------------------------------------------------------------------- /src/react-app/src/targets/typescript/typescript-cdk-builder.ts: -------------------------------------------------------------------------------- 1 | import { ProjectService } from "../../project/project-service"; 2 | import { TS_JSON_IDENT } from "./typescript-cdk-files/common"; 3 | import { createCdkJson } from "./typescript-cdk-files/create-cdk-json"; 4 | import { createGitIgnore } from "./typescript-cdk-files/create-git-ignore"; 5 | import { createJestConfig } from "./typescript-cdk-files/create-jest-config"; 6 | import { createNpmIgnore } from "./typescript-cdk-files/create-npm-ignore"; 7 | import { createPackageJson } from "./typescript-cdk-files/create-package-json"; 8 | import { createReadme } from "./typescript-cdk-files/create-readme"; 9 | import { createTest } from "./typescript-cdk-files/create-test"; 10 | import { createTsConfig } from "./typescript-cdk-files/create-ts-config"; 11 | import { FileBuilderBase } from "../export/file-builder-base"; 12 | 13 | export class TypeScriptCdkBuilder extends FileBuilderBase { 14 | async build() { 15 | const folder = this.exportBase.folder.bind(this.exportBase); 16 | const file = this.exportBase.file.bind(this.exportBase); 17 | 18 | const projectService = new ProjectService(); 19 | const projectData = await projectService.save(this.projectState, true); 20 | const result = this.generator.generate(); 21 | 22 | await folder("bin"); 23 | await folder("lib"); 24 | await folder("test"); 25 | 26 | for (const container of Object.values(result.containers)) { 27 | const folder = container.isRoot ? "bin" : "lib"; 28 | await file(`${folder}/${container.fileName}.ts`, container.code); 29 | await file(`test/${container.fileName}.test.ts`, createTest(container)); 30 | } 31 | 32 | const rootContainer = Object.values(result.containers).find( 33 | (c) => c.isRoot 34 | ); 35 | 36 | if (!rootContainer) { 37 | throw new Error("Root container not found"); 38 | } 39 | 40 | await file("README.md", createReadme()); 41 | await file(".npmignore", createNpmIgnore()); 42 | await file(".gitignore", createGitIgnore()); 43 | await file( 44 | "package.json", 45 | createPackageJson(this.projectState, result.fqns, rootContainer.fileName) 46 | ); 47 | await file("cdk.json", createCdkJson(rootContainer.fileName)); 48 | await file("tsconfig.json", createTsConfig()); 49 | await file("jest.config.js", createJestConfig()); 50 | await file( 51 | "project.cdkproj", 52 | JSON.stringify(projectData, null, TS_JSON_IDENT) 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/react-app/src/components/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ErrorInfo, ReactNode, useCallback } from "react"; 2 | 3 | interface Props { 4 | children: ReactNode; 5 | } 6 | 7 | interface State { 8 | hasError: boolean; 9 | } 10 | 11 | export class ErrorBoundary extends Component { 12 | public state: State = { 13 | hasError: false, 14 | }; 15 | 16 | public static getDerivedStateFromError(_: Error): State { 17 | // Update state so the next render will show the fallback UI. 18 | return { hasError: true }; 19 | } 20 | 21 | public componentDidCatch(error: Error, errorInfo: ErrorInfo) { 22 | console.error("Uncaught error:", error, errorInfo); 23 | } 24 | 25 | public render() { 26 | if (this.state.hasError) { 27 | return ; 28 | } 29 | 30 | return this.props.children; 31 | } 32 | } 33 | 34 | export default function ApplicationError() { 35 | const onButtonClick = useCallback(() => { 36 | window.location.reload(); 37 | }, []); 38 | 39 | return ( 40 | <> 41 |
42 |
43 |
44 |

45 | :( 46 |

47 |
48 |
49 |

50 | Error 51 |

52 |

53 | There was an error. If error occurs again try to clear site 54 | data. 55 |

56 |
57 |
58 | 65 |
66 |
67 |
68 |
69 |
70 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/construct-id.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useContext } from "react"; 2 | import { CoreValue } from "../../project/types/values"; 3 | import { ProjectContext } from "../../project/project-context"; 4 | import { ProjectActionKind } from "../../project/types"; 5 | import { TypeTest } from "../../types"; 6 | import { BlurInput } from "../blur-input"; 7 | import { DocumentationLink } from "../documentation-link"; 8 | 9 | export function ConstrcutId(props: { modal: boolean; value: CoreValue }) { 10 | const { state, dispatch } = useContext(ProjectContext); 11 | 12 | const typeMetadata = TypeTest.isFqn(props.value.valueType) 13 | ? state.types[props.value.valueType.fqn] 14 | : null; 15 | 16 | const onChange = useCallback( 17 | (value: string) => { 18 | dispatch({ 19 | kind: ProjectActionKind.SET_CONSTRUCT_ID, 20 | payload: { 21 | valueId: props.value.valueId, 22 | valueName: value, 23 | }, 24 | }); 25 | }, 26 | [dispatch, props.value.valueId] 27 | ); 28 | 29 | if (!props.modal) { 30 | return ( 31 |
32 |
33 | 34 | {typeMetadata && } 35 |
36 | 37 | 46 |
47 | ); 48 | } else { 49 | return ( 50 | <> 51 |
52 | 53 |
54 |
55 | 64 |
65 | 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/properties/target-property-single.tsx: -------------------------------------------------------------------------------- 1 | import { PencilSquareIcon } from "@heroicons/react/24/solid"; 2 | import { useCallback, useContext } from "react"; 3 | import { Utils } from "../../../utils"; 4 | import { ValueError } from "../../../project/validator"; 5 | import { SetValueAction } from "../../../project/actions"; 6 | import { ProjectContext } from "../../../project/project-context"; 7 | import { createSetValueAction } from "./target-property-utils"; 8 | 9 | export function TargetPropertySingle(props: { 10 | setValue: (action: SetValueAction) => void; 11 | valueString: string; 12 | titleString: string; 13 | keyName?: string; 14 | path: string[]; 15 | errors: ValueError[]; 16 | }) { 17 | const { state } = useContext(ProjectContext); 18 | const hasErrors = props.errors.length > 0; 19 | 20 | const onCreate = useCallback(() => { 21 | props.setValue( 22 | createSetValueAction( 23 | state, 24 | props.valueString, 25 | props.path, 26 | props.keyName, 27 | undefined, 28 | false 29 | ) 30 | ); 31 | }, [props, state]); 32 | 33 | return ( 34 |
35 |
36 | 47 |
48 | 49 | 62 | 63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /src/react-app/src/components/object-designer/properties/string-property.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback } from "react"; 2 | import { ProjectActionKind } from "../../../project/types"; 3 | import { BlurInput } from "../../blur-input"; 4 | import { SetValueAction } from "../../../project/actions"; 5 | import { ValueError } from "../../../project/validator"; 6 | import { CoreValue, ValueKind, Values } from "../../../project/types/values"; 7 | import { Utils } from "../../../utils"; 8 | 9 | export function StringProperty(props: { 10 | keyName?: string; 11 | path: string[]; 12 | currentValue?: CoreValue; 13 | defaultValue: string; 14 | errors: ValueError[]; 15 | indirect?: boolean; 16 | setValue: (action: SetValueAction) => void; 17 | onReturn?: () => void; 18 | }) { 19 | const hasErrors = props.errors.length > 0; 20 | 21 | const onChange = useCallback( 22 | (value: string) => { 23 | props.setValue({ 24 | kind: ProjectActionKind.SET_VALUE, 25 | payload: { 26 | key: props.keyName, 27 | path: props.path, 28 | oldValue: props.currentValue, 29 | value: 30 | value && value.length > 0 31 | ? { 32 | kind: ValueKind.Primitive, 33 | valueId: props.currentValue 34 | ? props.currentValue.valueId 35 | : Utils.generateId(), 36 | valueType: { 37 | primitive: "string", 38 | }, 39 | value, 40 | } 41 | : undefined, 42 | }, 43 | }); 44 | }, 45 | [props] 46 | ); 47 | 48 | let value = ""; 49 | if ( 50 | props.currentValue && 51 | Values.isPrimitive(props.currentValue) && 52 | typeof props.currentValue.value === "string" 53 | ) { 54 | value = props.currentValue.value; 55 | } 56 | 57 | return ( 58 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /src/react-app/src/components/blur-input.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from "react"; 2 | 3 | export function BlurInput(props: { 4 | name?: string; 5 | className?: string; 6 | type?: string; 7 | value: string; 8 | placeholder?: string; 9 | restoreIfEmpty?: boolean; 10 | trim?: boolean; 11 | multiline?: boolean; 12 | emitKeyStrokes?: boolean; 13 | onChange: (value: string) => void; 14 | onReturn?: () => void; 15 | }) { 16 | const [currentValue, setCurrentValue] = useState(props.value); 17 | 18 | useEffect(() => { 19 | setCurrentValue(props.value); 20 | }, [props.value]); 21 | 22 | const onBlurCallback = useCallback( 23 | (event: React.ChangeEvent) => { 24 | const value = event.target.value; 25 | const newValue = props.trim ? value.trim() : value; 26 | 27 | if (newValue.length === 0 && props.restoreIfEmpty === true) { 28 | setCurrentValue(props.value); 29 | } else { 30 | setCurrentValue(newValue); 31 | props.onChange(newValue); 32 | } 33 | }, 34 | [props] 35 | ); 36 | 37 | const onChange = useCallback( 38 | (event: React.ChangeEvent) => { 39 | const value = event.target.value; 40 | setCurrentValue(props.trim ? value.trim() : value); 41 | 42 | if (props.emitKeyStrokes) { 43 | onBlurCallback(event); 44 | } 45 | }, 46 | [onBlurCallback, props.emitKeyStrokes, props.trim] 47 | ); 48 | 49 | const onKeyDown = useCallback( 50 | (event: React.KeyboardEvent) => { 51 | if (!props.multiline && event.key === "Enter") { 52 | event.currentTarget.blur(); 53 | if (props.onReturn) { 54 | props.onReturn(); 55 | } 56 | } 57 | }, 58 | [props] 59 | ); 60 | 61 | if (props.multiline) { 62 | return ( 63 |