constructor()

in packages/type-safe-api/src/project/type-safe-api-project.ts [208:638]


  constructor(options: TypeSafeApiProjectOptions) {
    super(options);

    const nxWorkspace = this.getNxWorkspace(options);

    const isNxWorkspace =
      this.parent &&
      (ProjectUtils.isNamedInstanceOf(this.parent, MonorepoTsProject) ||
        ProjectUtils.isNamedInstanceOf(this.parent, MonorepoJavaProject) ||
        ProjectUtils.isNamedInstanceOf(this.parent, MonorepoPythonProject));

    const handlerLanguages = [...new Set(options.handlers?.languages ?? [])];

    // Try to infer monorepo default release branch, otherwise default to mainline unless overridden
    const defaultReleaseBranch =
      nxWorkspace?.affected.defaultBase ?? "mainline";
    const packageManager =
      this.parent && ProjectUtils.isNamedInstanceOf(this.parent, NodeProject)
        ? this.parent.package.packageManager
        : NodePackageManager.PNPM;

    // API Definition project containing the model
    const modelDir = "model";
    const parsedSpecFile = ".api.json";

    this.model = generateModelProject({
      parent: nxWorkspace ? this.parent : this,
      outdir: nxWorkspace ? path.join(options.outdir!, modelDir) : modelDir,
      name: `${options.name}-model`,
      modelLanguage: options.model.language,
      modelOptions: options.model.options,
      handlerLanguages,
      defaultReleaseBranch: nxWorkspace?.affected?.defaultBase ?? "mainline",
      packageManager,
      parsedSpecFile,
    });
    const modelProject = [
      this.model.openapi,
      this.model.smithy,
      this.model.typeSpec,
    ].filter((m) => m)[0] as Project;

    // Ensure we always generate a runtime project for the infrastructure language, regardless of what was specified by
    // the user. Likewise we generate a runtime project for any handler languages specified
    const runtimeLanguages = [
      ...new Set([
        ...(options.runtime?.languages ?? []),
        options.infrastructure.language,
        ...(options.handlers?.languages ?? []),
      ]),
    ];

    const generatedDir = "generated";
    const runtimeDir = path.join(generatedDir, "runtime");
    const runtimeDirRelativeToParent = nxWorkspace
      ? path.join(options.outdir!, runtimeDir)
      : runtimeDir;

    // Path from a generated package directory (eg api/generated/runtime/typescript) to the model dir (ie api/model)
    const relativePathToModelDirFromGeneratedPackageDir = path.relative(
      path.join(this.outdir, runtimeDir, "language"),
      path.join(this.outdir, modelDir)
    );
    const parsedSpecRelativeToGeneratedPackageDir = path.join(
      relativePathToModelDirFromGeneratedPackageDir,
      this.model.parsedSpecFile
    );

    // Declare the generated runtime projects
    const generatedRuntimeProjects = generateRuntimeProjects(runtimeLanguages, {
      parent: nxWorkspace ? this.parent! : this,
      parentPackageName: this.name,
      generatedCodeDir: runtimeDirRelativeToParent,
      isWithinMonorepo: isNxWorkspace,
      // Spec path relative to each generated runtime dir
      parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir,
      typescriptOptions: {
        defaultReleaseBranch,
        packageManager,
        commitGeneratedCode:
          options.runtime?.options?.typescript?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.runtime?.options?.typescript,
      },
      pythonOptions: {
        authorName: "APJ Cope",
        authorEmail: "apj-cope@amazon.com",
        version: "0.0.0",
        commitGeneratedCode:
          options.runtime?.options?.python?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.runtime?.options?.python,
      },
      javaOptions: {
        version: "0.0.0",
        commitGeneratedCode:
          options.runtime?.options?.java?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.runtime?.options?.java,
      },
    });

    const documentationFormats = [
      ...new Set(options.documentation?.formats ?? []),
    ];

    const docsDir = path.join(generatedDir, "documentation");
    const docsDirRelativeToParent = nxWorkspace
      ? path.join(options.outdir!, docsDir)
      : docsDir;

    const generatedDocs = generateDocsProjects(documentationFormats, {
      parent: nxWorkspace ? this.parent! : this,
      parentPackageName: this.name,
      generatedDocsDir: docsDirRelativeToParent,
      // Spec path relative to each generated doc format dir
      parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir,
      documentationOptions: options.documentation?.options,
    });

    this.documentation = {
      htmlRedoc: generatedDocs[DocumentationFormat.HTML_REDOC],
      markdown: generatedDocs[DocumentationFormat.MARKDOWN],
      plantuml: generatedDocs[DocumentationFormat.PLANTUML],
    };

    const libraries = [...new Set(options.library?.libraries ?? [])];

    const libraryDir = path.join(generatedDir, "libraries");
    const libraryDirRelativeToParent = nxWorkspace
      ? path.join(options.outdir!, libraryDir)
      : libraryDir;

    // Declare the generated runtime projects
    const generatedLibraryProjects = generateLibraryProjects(libraries, {
      parent: nxWorkspace ? this.parent! : this,
      parentPackageName: this.name,
      generatedCodeDir: libraryDirRelativeToParent,
      isWithinMonorepo: isNxWorkspace,
      // Spec path relative to each generated library dir
      parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir,
      typescriptReactQueryHooksOptions: {
        defaultReleaseBranch,
        packageManager,
        commitGeneratedCode:
          options.library?.options?.typescriptReactQueryHooks
            ?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.library?.options?.typescriptReactQueryHooks,
      },
    });

    // Ensure the generated runtime, libraries and docs projects have a dependency on the model project
    if (this.parent) {
      [
        ...Object.values(generatedRuntimeProjects),
        ...Object.values(generatedDocs),
        ...Object.values(generatedLibraryProjects),
      ].forEach((project) => {
        NxProject.ensure(project).addImplicitDependency(modelProject);
      });
    }

    this.runtime = {
      typescript: generatedRuntimeProjects[Language.TYPESCRIPT]
        ? (generatedRuntimeProjects[Language.TYPESCRIPT] as TypeScriptProject)
        : undefined,
      java: generatedRuntimeProjects[Language.JAVA]
        ? (generatedRuntimeProjects[Language.JAVA] as JavaProject)
        : undefined,
      python: generatedRuntimeProjects[Language.PYTHON]
        ? (generatedRuntimeProjects[Language.PYTHON] as PythonProject)
        : undefined,
    };

    this.library = {
      typescriptReactQueryHooks: generatedLibraryProjects[
        Library.TYPESCRIPT_REACT_QUERY_HOOKS
      ]
        ? (generatedLibraryProjects[
            Library.TYPESCRIPT_REACT_QUERY_HOOKS
          ] as TypeScriptProject)
        : undefined,
    };

    const handlersDir = "handlers";
    const handlersDirRelativeToParent = nxWorkspace
      ? path.join(options.outdir!, handlersDir)
      : handlersDir;

    const relativePathToModelDirFromHandlersDir = path.relative(
      path.join(this.outdir, handlersDir, "language"),
      path.join(this.outdir, modelDir)
    );
    const parsedSpecRelativeToHandlersDir = path.join(
      relativePathToModelDirFromHandlersDir,
      this.model.parsedSpecFile
    );

    // Declare the generated handlers projects
    const generatedHandlersProjects = generateHandlersProjects(
      handlerLanguages,
      {
        parent: nxWorkspace ? this.parent! : this,
        parentPackageName: this.name,
        generatedCodeDir: handlersDirRelativeToParent,
        isWithinMonorepo: isNxWorkspace,
        // Spec path relative to each generated handlers package dir
        parsedSpecPath: parsedSpecRelativeToHandlersDir,
        typescriptOptions: {
          // Try to infer monorepo default release branch, otherwise default to mainline unless overridden
          defaultReleaseBranch: nxWorkspace?.affected.defaultBase ?? "mainline",
          packageManager:
            this.parent &&
            ProjectUtils.isNamedInstanceOf(this.parent, NodeProject)
              ? this.parent.package.packageManager
              : NodePackageManager.PNPM,
          commitGeneratedCode:
            options.handlers?.options?.typescript?.commitGeneratedCode ??
            options.commitGeneratedCode ??
            false,
          ...options.handlers?.options?.typescript,
        },
        pythonOptions: {
          authorName: "APJ Cope",
          authorEmail: "apj-cope@amazon.com",
          version: "0.0.0",
          commitGeneratedCode:
            options.handlers?.options?.python?.commitGeneratedCode ??
            options.commitGeneratedCode ??
            false,
          ...options.handlers?.options?.python,
        },
        javaOptions: {
          version: "0.0.0",
          commitGeneratedCode:
            options.handlers?.options?.java?.commitGeneratedCode ??
            options.commitGeneratedCode ??
            false,
          ...options.handlers?.options?.java,
        },
        generatedRuntimes: {
          typescript: this.runtime.typescript as
            | GeneratedTypescriptRuntimeProject
            | undefined,
          python: this.runtime.python as
            | GeneratedPythonRuntimeProject
            | undefined,
          java: this.runtime.java as GeneratedJavaRuntimeProject | undefined,
        },
      }
    );

    this.handlers = {
      typescript: generatedHandlersProjects[Language.TYPESCRIPT]
        ? (generatedHandlersProjects[Language.TYPESCRIPT] as TypeScriptProject)
        : undefined,
      java: generatedHandlersProjects[Language.JAVA]
        ? (generatedHandlersProjects[Language.JAVA] as JavaProject)
        : undefined,
      python: generatedHandlersProjects[Language.PYTHON]
        ? (generatedHandlersProjects[Language.PYTHON] as PythonProject)
        : undefined,
    };

    // Ensure the handlers project depends on the appropriate runtime projects
    if (this.handlers.typescript) {
      NxProject.ensure(this.handlers.typescript).addImplicitDependency(
        this.runtime.typescript!
      );
    }
    if (this.handlers.java) {
      NxProject.ensure(this.handlers.java).addImplicitDependency(
        this.runtime.java!
      );
    }
    if (this.handlers.python) {
      NxProject.ensure(this.handlers.python).addImplicitDependency(
        this.runtime.python!
      );
    }

    const infraDir = path.join(generatedDir, "infrastructure");
    const infraDirRelativeToParent = nxWorkspace
      ? path.join(options.outdir!, infraDir)
      : infraDir;

    // Infrastructure project
    const infraProject = generateInfraProject(options.infrastructure.language, {
      parent: nxWorkspace ? this.parent! : this,
      parentPackageName: this.name,
      generatedCodeDir: infraDirRelativeToParent,
      isWithinMonorepo: isNxWorkspace,
      // Spec path relative to each generated infra package dir
      parsedSpecPath: parsedSpecRelativeToGeneratedPackageDir,
      typescriptOptions: {
        defaultReleaseBranch,
        packageManager,
        commitGeneratedCode:
          options.infrastructure.options?.typescript?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.infrastructure.options?.typescript,
      },
      pythonOptions: {
        authorName: "APJ Cope",
        authorEmail: "apj-cope@amazon.com",
        version: "0.0.0",
        commitGeneratedCode:
          options.infrastructure.options?.python?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.infrastructure.options?.python,
      },
      javaOptions: {
        version: "0.0.0",
        commitGeneratedCode:
          options.infrastructure.options?.java?.commitGeneratedCode ??
          options.commitGeneratedCode ??
          false,
        ...options.infrastructure.options?.java,
      },
      generatedRuntimes: {
        typescript: this.runtime.typescript as
          | GeneratedTypescriptRuntimeProject
          | undefined,
        python: this.runtime.python as
          | GeneratedPythonRuntimeProject
          | undefined,
        java: this.runtime.java as GeneratedJavaRuntimeProject | undefined,
      },
      generatedHandlers: {
        typescript: this.handlers.typescript as
          | GeneratedTypescriptHandlersProject
          | undefined,
        python: this.handlers.python as
          | GeneratedPythonHandlersProject
          | undefined,
        java: this.handlers.java as GeneratedJavaHandlersProject | undefined,
      },
    });

    const infraProjects: {
      -readonly [K in keyof GeneratedCodeProjects]: GeneratedCodeProjects[K];
    } = {};

    // Add implicit dependencies and assign the appropriate infrastructure project member
    switch (options.infrastructure.language) {
      case Language.JAVA:
        NxProject.ensure(infraProject).addImplicitDependency(
          this.runtime.java!
        );
        infraProjects.java = infraProject as JavaProject;
        break;
      case Language.PYTHON:
        NxProject.ensure(infraProject).addImplicitDependency(
          this.runtime.python!
        );
        infraProjects.python = infraProject as PythonProject;
        break;
      case Language.TYPESCRIPT:
        NxProject.ensure(infraProject).addImplicitDependency(
          this.runtime.typescript!
        );
        infraProjects.typescript = infraProject as TypeScriptProject;
        break;
      default:
        throw new Error(
          `Unknown infrastructure language ${options.infrastructure.language}`
        );
    }
    this.infrastructure = infraProjects;

    NxProject.ensure(infraProject).addImplicitDependency(modelProject);

    // Expose collections of projects
    const allRuntimes = Object.values(generatedRuntimeProjects);
    const allInfrastructure = [infraProject];
    const allLibraries = Object.values(generatedLibraryProjects);
    const allDocumentation = Object.values(generatedDocs);
    const allHandlers = Object.values(generatedHandlersProjects);

    this.all = {
      model: [modelProject],
      runtimes: allRuntimes,
      infrastructure: allInfrastructure,
      libraries: allLibraries,
      documentation: allDocumentation,
      handlers: allHandlers,
      projects: [
        modelProject,
        ...allRuntimes,
        ...allInfrastructure,
        ...allLibraries,
        ...allDocumentation,
        ...allHandlers,
      ],
    };

    if (!nxWorkspace) {
      // Add a task for the non-monorepo case to build the projects in the right order
      [
        modelProject,
        ...Object.values(generatedRuntimeProjects),
        infraProject,
        ...Object.values(generatedLibraryProjects),
        ...Object.values(generatedDocs),
      ].forEach((project) => {
        this.compileTask.exec("npx projen build", {
          cwd: path.relative(this.outdir, project.outdir),
        });
      });
    }

    // Add the README as a sample file which the user may edit
    new SampleFile(this, "README.md", {
      sourcePath: path.resolve(
        __dirname,
        "..",
        "..",
        "samples",
        "type-safe-api",
        "readme",
        "TYPE_SAFE_API.md"
      ),
    });
  }