Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

📝 Documentation: Long-term project vision #1181

Open
2 tasks done
JoshuaKGoldberg opened this issue Jan 3, 2024 · 6 comments
Open
2 tasks done

📝 Documentation: Long-term project vision #1181

JoshuaKGoldberg opened this issue Jan 3, 2024 · 6 comments
Assignees
Labels
area: documentation Improvements or additions to docs status: accepting prs Please, send a pull request to resolve this! type: feature New enhancement or request

Comments

@JoshuaKGoldberg
Copy link
Owner

JoshuaKGoldberg commented Jan 3, 2024

Bug Report Checklist

Overview

create-typescript-app has come a long way in the last two (!) years! It started as a small template for me to unify my disparate repository config files. Now it's a full project with >500 >1000 stars, repeat community contributors, and offshoot projects to help it work smoothly. I love this. 💖

I plan on continuing to prioritize create-typescript-app over at least the next year. I see its progress as evolving through at least three distinct stages:

  1. (~2022) Personal template: used just in my own personal repos
  2. (~2023) Shared template: Adding in more comprehensive docs, options, and generally more flexibility so folks other than me can use it on their repos too
  3. (~2024) Shared engine: Splitting out a new package so that other templates like this one can be made

My hope is that in the next year or so, folks will be able to make their own shared templates that mix-and-match pieces of tooling. For example, a GitHub Actions flavor of create-typescript-app might use all the same pieces as this one except it'd swap out the builder from tsup to web-ext. See also #1175.

I don't know exactly how this would look - but I am excited to find out 😄.

Filing this issue to track placing a slightly more solidified form of this explanation in the docs.

Additional Info

No response

@JoshuaKGoldberg
Copy link
Owner Author

OK, I'm pretty sure I've figured out how this should roughly work. I think there are five layers that'll need to be made:

  1. 🏷️ Inputs: read in data from the creation context
  2. 🧱 Blocks: each individual dev tooling piece, optionally with data from 🏷️ inputs
    b. 🪪 Metadata: signals output from the block that can be used in other blocks
    b. 🧹 Migrations: descriptions of how to clean up from previous versions
  3. 🧰 Addons: extensions that provide additional input to a block, optionally with data from 🏷️ inputs
  4. 🎁 Presets: configurable groups of 🧱 blocks and 🧰 addons
  5. 💝 create: the end-user runtime that receives all that info and creates or updates a repository

I'm thinking the 💝 create package will be what manages those tooling layers. Pieces of each will be published as packages that you can then pull into your project. Any customizations done in a package will need to be applied as options to a project pulls in. This will solve the issue of applying changes after migration (#1184).

On top of all that will be end-user templates such as ➕ create-typescript-app. They'd include the actual 🧱 blocks, 🧰 addons, and 🎁 presets end-users will tell 💝 create to build their repositories with.

🏷️ Inputs

🏷️ Inputs will be small metadata-driven functions that provide any data needed to inform 🧱 blocks and 🧰 addons later.
These may include:

  • Files on disk, such as raw text or parsed .json
  • Sending network requests
  • The user's npm whoami

💝 create will manage providing 🏷️ inputs with a runtime context containing a file system, network fetcher, and shell runner.

For example, an 🏷️ input that retrieves the current running time:

import { createInput } from "@create/input";

export const inputJSONFile = createInput({
  produce: () => performance.now(),
});

Note that 🧱 blocks and 🧰 addons won't be required to use 🏷️ inputs to source data. Doing so just makes that data easier to mock out in tests later on.

🏷️ Input 📥 Options

🏷️ Inputs will need to be reusable and able to take in 📥 options. They'll describe those options as the properties of a Zod object schema. That will let them validate provided values and infer types from an options property in their context.

For example, an 🏷️ input that retrieves JSON data from a file on disk using the provided virtual file system:

import { createInput } from "@create/input";
import { z } from "zod";

export const inputJSONFile = createInput({
  options: {
    fileName: z.string(),
  },
  async produce({ fs, options }) {
    try {
      return JSON.parse((await fs.readFile(options.fileName)).toString());
    } catch {
      return undefined;
    }
  },
});

Later on, 🧱 blocks and 🧰 addons that use the input will be able to provide those options.

🏷️ Input 🧪 Testing

The create ecosystem will include testing utilities that provide mock data to an 🏷️ input under test.

For example, testing the previous inputJSONFile:

import { createMockInputContext } from "@create/input-tester";
import { inputJSONFile } from "./inputJSONFile.ts";

describe("inputJSONFile", () => {
  it("returns package data when the file on disk contains valid JSON", () => {
    const expected = { name: "mock-package" };
    const context = createMockInputContext({
      files: {
        "package.json": JSON.stringify(expected),
      },
      options: {
        fileName: "package.json",
      },
    });

    const actual = await inputJSONFile(context);

    expect(actual).toEqual(expected);
  });
});

🏷️ Input Composition

🏷️ Inputs should be composable: meaning each can take data from other inputs. 💝 create will include a take function in contexts that calls another 🏷️ input with the current context.

For example, an 🏷️ input that determines the npm username based on either npm whoami or package.json 🏷️ inputs:

import { createInput } from "@create/input";
import { inputJSONFile } from "@example/input-json-data";
import { inputNpmWhoami } from "@example/input-npm-whoami";

export const inputNpmUsername = createInput({
  async produce({ fs, take }) {
    return (
      (await take(inputNpmWhoami)) ??
      (await take(inputJSONFile, { fileName: "package.json" })).author
    );
  },
});

🧱 Blocks

The main logic for template contents will be stored in 🧱 blocks. Each will define its shape of 🏷️ inputs, user-provided options, and resultant outputs.

Resultant outputs will be passed to create to be merged with other 🧱 blocks' outputs and applied. Outputs may include:

  • Cleanup scripts to run after setup
  • Files to create or modify on disk
  • Network requests to the GitHub API
  • Packages to install

For example, a 🧱 block that adds a .nvmrc file:

import { createBlock } from "@create/block";

export const blockNvmrc = createBlock({
  async produce() {
    return {
      files: {
        ".nvmrc": "20.12.2",
      },
    };
  },
});

The create ecosystem will include testing utilities that provide mock data to a 🧱 block under test:

import { createMockBlockContext } from "@create/block-tester";
import { blockNvmrc } from "./blockNvmrc.ts";

describe("blockNvmrc", () => {
  it("returns an .nvmrc", () => {
    const context = createMockInputContext();

    const actual = await blockNvmrc(context);

    expect(actual).toEqual({ ".nvmrc": "20.12.2" });
  });
});

🧱 Blocks and 🏷️ Inputs

Blocks can take in data from 🏷️ inputs. 💝 create will handle lazily evaluating 🏷️ inputs and retrieving user-provided inputs. They'll receive the same take function in their context that executes an 🏷️ input.

For example, a 🧱 block that adds all-contributors recognition using a JSON file 🏷️ input:

import { BlockContext, BlockOutput } from "@create/block";
import { formatYml } from "format-yml"; // todo: make package

export const blockAllContributors = createBlock({
  async produce({ take }) {
    const existing = await take(inputJSONFile, {
      fileName: "package.json",
    });

    return {
      files: {
        ".all-contributorsrc": JSON.parse({
          // ...
          contributors: existing?.contributors ?? [],
          // ...
        }),
        ".github": {
          workflows: {
            "contributors.yml": formatYml({
              // ...
              name: "Contributors",
              // ...
            }),
          },
        },
      },
    };
  },
});

🧱 Block 📥 Options

🧱 Blocks may be configurable with user options similar to 🏷️ inputs. They will define them as the properties for a Zod object schema and then receive them in their context.

For example, a 🧱 block that adds Prettier formatting with optional Prettier options:

import { createBlock } from "@create/block";
import prettier from "prettier";
import { prettierSchema } from "zod-prettier-schema"; // todo: make package
import { z } from "zod";

export const blockPrettier = createBlock({
  options: {
    config: prettierSchema.optional(),
  },
  async produce({ options }) {
    return {
      files: {
        ".prettierrc.json":
          options.config &&
          JSON.stringify({
            $schema: "http://json.schemastore.org/prettierrc",
            ...config,
          }),
      },
      packages: {
        devDependencies: ["prettier"],
      },
      scripts: {
        format: "prettier .",
      },
    };
  },
});

🧱 Block 📥 options will then be testable with the same mock context utilities as before:

import { createMockBlockContext } from "@create/block-tester";
import { blockPrettier } from "./blockPrettier.ts";

describe("blockPrettier", () => {
  it("creates a .prettierrc.json when provided options", () => {
    const prettierConfig = {
      useTabs: true,
    };
    const context = createMockInputContext({
      options: {
        config: prettierConfig,
      },
    });

    const actual = await blockPrettier(context);

    expect(actual).toEqual({
      files: {
        ".prettierrc.json": JSON.stringify({
          $schema: "http://json.schemastore.org/prettierrc",
          ...prettierConfig,
        }),
      },
      packages: {
        devDependencies: ["prettier"],
      },
      scripts: {
        format: "prettier .",
      },
    });
  });
});

🧱 Block 🪪 Metadata

🧱 Blocks should be able to signal added metadata on the system that other blocks will need to handle. They can do so by returning properties in a metadata object.

Metadata may include:

  • documentation: A Record<string, string> of docs entries to add to .md file(s)
  • files: An array of objects containing glob: string and type: FileType of Config, Source, or Test
    • Over time, this may need to encompass more metadata, such as whether files are auto-generated

For example, this Vitest 🧱 block indicates that there can now be src/**/*.test.* test files, as documented in .github/DEVELOPMENT.md:

import { BlockOutput, FileType } from "@create/block";

export function blockVitest(): BlockOutput {
  return {
    files: {
      "vitest.config.ts": `import { defineConfig } from "vitest/config"; ...`,
    },
    metadata: {
      documentation: {
        ".github/DEVELOPMENT.md": `## Testing ...`,
      },
      files: [{ glob: "src/**/*.test.*", type: FileType.Test }],
    },
  };
}

In order to use 🪪 metadata provided by other blocks, block outputs can each be provided as a function.
That function will be called with an object containing all previously generated 🪪 metadata.

For example, this Tsup 🧱 block reacts to 🪪 metadata to exclude test files from its entry:

import { BlockContext, BlockOutput, FileType } from "@create/block";

export function blockTsup(): BlockOutput {
  return {
    fs: ({ metadata }: BlockContext) => {
      return {
        "tsup.config.ts": `import { defineConfig } from "tsup";
          // ...
          entry: [${JSON.stringify([
            "src/**/*.ts",
            ...metadata.files
              .filter(file.type === FileType.Test)
              .map((file) => file.glob),
          ])}],
          // ...
        `,
      };
    },
  };
}

In other words, 🧱 blocks will be executed in two phases:

  1. An initial, metadata-less phase that can produce outputs and metadata
  2. A second, metadata-provided phase that can produce more outputs

It would be nice to figure out a way to simplify them into one phase, while still allowing 🪪 metadata to be dependent on 📥 options. A future design iteration might figure that out.

🧱 Block 🧹 Migrations

🧱 Blocks should be able to describe how to bump from previous versions to the current. Those descriptions will be stored as 🧹 migrations detailing the actions to take to migrate from previous versions.

For example, a 🧱 block adding in Knip that switches from knip.jsonc to knip.json:

import { BlockContext, BlockOutput } from "@create/block";

export function blockKnip({ fs }: BlockKnip): BlockOutput {
  return {
    files: {
      "knip.json": JSON.stringify({
        $schema: "https://unpkg.com/knip@latest/schema.json",
      }),
    },
    migrations: [
      {
        name: "Rename knip.jsonc to knip.json",
        run: async () => {
          try {
            await fs.rename("knip.jsonc", "knip.json");
          } catch {
            // Ignore failures if knip.jsonc doesn't exist
          }
        },
      },
    ],
  };
}

Migrations will allow create to be run in an idempotent --migrate mode that can keep a repository up-to-date automatically.

🧰 Addons

There will often be times when sets of 🧱 block options would be useful to package together. For example, many packages consuming an ESLint 🧱 block might want to add on JSDoc linting rules.

Reusable generators for 📥 options will be available as 🧰 addons. Their produced 📥 options will then be merged together by 💝 create and then passed to 🧱 blocks at runtime.

For example, a JSDoc linting 🧰 addon for a rudimentary ESLint linting 🧱 block with options for adding plugins:

import { createAddon } from "@create/addon";
import { blockESLint, BlockESLintOptions } from "@example/block-eslint";

export const addonESLintJSDoc = createAddon({
  produce(): AddonOutput<BlockESLintOptions> {
    return {
      options: {
        configs: [`jsdoc.configs["flat/recommended-typescript-error"]`],
        imports: [`import jsdoc from "eslint-plugin-jsdoc"`],
        rules: {
          "jsdoc/informative-docs": "error",
          "jsdoc/lines-before-block": "off",
        },
      },
    };
  },
});

Options produced by 🧰 addons will be merged together by ... spreading, both for arrays and objects.

The create ecosystem will include testing utilities that provide mock data to an 🧰 addon under test:

import { createMockAddonContext } from "@create/addon-tester";
import { addonESLintJSDoc } from "./addonESLintJSDoc.ts";

describe("addonESLintJSDoc", () => {
  it("returns configs, imports, and rules", () => {
    const context = createMockAddonContext();

    const actual = await addonESLintJSDoc(context);

    expect(actual).toEqual({
      options: {
        configs: [`jsdoc.configs["flat/recommended-typescript-error"]`],
        imports: [`import jsdoc from "eslint-plugin-jsdoc"`],
        rules: {
          "jsdoc/informative-docs": "error",
          "jsdoc/lines-before-block": "off",
        },
      },
    });
  });
});

🧰 Addon 📥 Options

🧰 Addons may be configurable with user options similar to 🏷️ inputs and 🧱 blocks. They should be able to describe their options as the properties for a Zod object schema, then infer types for their context.

For example, a Perfectionist linting 🧰 addon for a rudimentary ESLint linting 🧱 block with options for partitioning objects:

import { createAddon } from "@create/addon";
import { blockESLint, BlockESLintOptions } from "@example/block-eslint";
import { z } from "zod";

export const addonESLintPerfectionist = createAddon({
  options: {
    partitionByComment: z.boolean(),
  },
  produce({ options }): AddonOutput<BlockESLintOptions> {
    return {
      options: {
        configs: [`perfectionist.configs["recommended-natural"]`],
        imports: `import perfectionist from "eslint-plugin-perfectionist"`,
        rules: options.partitionByComment && {
          "perfectionist/sort-objects": [
            "error",
            {
              order: "asc",
              partitionByComment: true,
              type: "natural",
            },
          ],
        },
      },
    };
  },
});

🧰 Addon 📥 options will then be testable with the same mock context utilities as before:

import { createMockAddonContext } from "@create/addon-tester";
import { addonESLintPerfectionist } from "./addonESLintPerfectionist.ts";

describe("addonESLintPerfectionist", () => {
  it("includes perfectionist/sort-objects configuration when options.partitionByComment is provided", () => {
    const context = createMockAddonContext({
      options: {
        partitionByComment: true,
      },
    });

    const actual = await addonESLintPerfectionist(context);

    expect(actual).toEqual({
      options: {
        configs: [`perfectionist.configs["recommended-natural"]`],
        imports: `import perfectionist from "eslint-plugin-perfectionist"`,
        rules: {
          "perfectionist/sort-objects": [
            "error",
            {
              order: "asc",
              partitionByComment: true,
              type: "natural",
            },
          ],
        },
      },
    });
  });
});

🎁 Presets

Users won't want to manually configure 🧱 blocks and 🧰 addons in all of their projects. 🎁 Presets that configure broadly used or organization-wide configurations will help share setups.

For example, a 🎁 preset that configures ESLint, README.md with logo, and Vitest 🧱 blocks with JSONC linting, JSDoc linting, and test linting 🧰 addons:

import { createPreset } from "@create/preset";
import { blockESLint } from "@example/block-eslint";
import { blockReadme } from "@example/block-readme";
import { blockVitest } from "@example/block-vitest";

export const myPreset = createPreset({
  produce() {
    return [
      blockESLint({
        addons: [addonESLintJSDoc(), addonESLintJSONC(), addonESLintVitest()],
      }),
      blockReadme({
        logo: "./docs/my-logo.png",
      }),
      blockVitest(),
    ];
  },
});

🎁 Preset 📥 Options

🎁 Presets will need to be able to take in options. As with previous layers, they'll describe their options as the properties for a Zod object schema.

For example, a 🎁 preset that takes in keywords and forwards them to a package.json 🧱 block:

import { createPreset } from "@create/preset";
import { blockPackageJson } from "@example/block-package-json";
import { z } from "zod";

export const myPreset = createPreset({
  options: {
    keywords: z.array(z.string()),
  },
  produce({ options }) {
    return [
      blockPackageJson({
        keywords: options.keywords,
      }),
    ];
  },
});

🎁 Preset 📄 Documentation

For example, the scaffolding of a 🧱 block that generates documentation for a preset from its entry point:

import { createBlock } from "@create/block";
import { z } from "zod";

createBlock({
  options: {
    entry: z.string().default("./src/index.ts"),
  },
  produce({ options }) {
    return {
      metadata: {
        documentation: {
          "README.md": `## Preset Options ...`,
        },
      },
    };
  },
});

Template Repositories

Users may opt to keep a GitHub template repository storing a canonical representation of their template. The template can reference that repository's locator. Projects created from the template can then be created from the template.

For example, a preset referencing a GitHub repository:

import { createPreset } from "@create/preset";

export const myTemplatePreset = createPreset({
  repository: "https://github.com/owner/repository",
  produce() {
    // ...
  },
});

This is necessary for including the "generated from" notice on repositories for a template. The repository containing a preset might be built with a different preset. For example, a repository containing presets for different native app builders might itself use a general TypeScript preset.

💝 create

Internally, create will:

  1. Initialize shared context: the built-in options, file system, network fetcher, and shell runner
  2. Run 🧱 blocks in order with their portions of the context
    • File, network, and shell operations are stored so they can be run later
    • Migrations are also stored to be run later
    • Any metadata are stored and merged internally
  3. Run any stored migrations
  4. Run delayed portions of 🧱 blocks in order that required metadata
  5. Run all stored file, network, and shell operations

There may need to be options provided for changing when pieces run. For example, there may be migrations that depend on being run before stored file operations.

💝 create CLIs

Initializing a new repository can be done by running create on the CLI. Zod arguments will be automatically converted to Node.js parseArgs args.

Using the earlier myPreset example as a package named my-create-preset:

npx create --preset my-create-preset --preset-option-keywords abc --preset-option-keywords def

The result of running the CLI will be a repository that's ready to be developed on immediately.

💝 create Configuration

Users will alternately be able to set up the 🧱 blocks and 🧰 addons in a file like create.config.ts. They will have the user default-export calling a createConfig function with an array of blocks.

For example, a small project that only configures one TypeScript 🧱 block to have a specific compiler option:

// create.config.ts
import { createConfig } from "@create/config";
import { blockTsc } from "@example/block-tsc";

export default createConfig([
  blockTsc({
    compilerOptions: {
      target: "ES2024",
    },
  }),
]);

A more realistic example would be this equivalent to the create-typescript-app "common" base with a logo and bin using a dedicated 🎁 preset:

// create.config.ts
import { createConfig } from "@create/config";
import { presetTypeScriptPackageCommon } from "@example/preset-typescript-package-common";

export default createConfig(
  presetTypeScriptPackageCommon({
    bin: "./bin/index.js",
    readme: {
      logo: "./docs/my-logo.png",
    },
  })
);

Running a command like npx create will detect the create.config.ts and re-run 💝 create for the repository. Any 🧹 migrations will clean up out-of-date files.

💝 create CLI Prompts

It's common for template builders to include a CLI prompt for options. 💝 create will provide a dedicated CLI package that prompts users for options based on the Zod options schema for a 🎁 preset.

For example, given a 🎁 preset that describes its name and other documentation:

import { createPreset } from "@create/preset";

export const myPreset = createPreset({
  documentation: {
    name: "My Preset",
  },
  options: {
    access: z
      .union([z.literal("public"), z.literal("private")])
      .default("public"),
    description: z.string(),
  },
  produce() {
    /* ... */
  },
});

...a 💝 create CLI would be able to prompt a running user for each of those options:

npx create-my-preset

Let's ✨ create ✨ a repository for you based on My Preset!

> Enter a value for access.
  Allowed values: "public", "private" (default: "public")
  ...

> Enter a value for description:
  ...

> Would you like to make a create.config.ts file to pull in template updates later?
  y/n

Future versions of 💝 create could provide hooks to customize those CLIs, such as adding more documentation options in createPreset.

💝 create Monorepo Support

Adding explicit handling for monorepos is not something I plan for a v1 of 💝 create. I'll want to have experience maintaining a few more of my own monorepos before seriously investigating what that would look like.

This does not block end-users from writing monorepo-tailored 🧱 blocks or 🎁 presets. They can always write two versions of their logic for the ones that need it, such as:

  • @example/block-tsc
  • @example/block-tsc-references

Alternately, individual packages can always configure 💝 create tooling on their own.

create-typescript-app

💝 create will be a general engine.
It won't have any specific 🧱 blocks or 🎁 presets built-in.

Instead, external packages such as ➕ create-typescript-app will take on the responsibility of creating their own framework-/library-specific 🧱 blocks and 🎁 presets.

For example, a non-exhaustive list of ➕ create-typescript-app packages might look like:

  • 🧱 Blocks:
    • @create-typescript/block-all-contributors
    • @create-typescript/block-compliance
    • @create-typescript/block-contributing
    • @create-typescript/block-cspell
    • @create-typescript/block-eslint
    • @create-typescript/block-github-alt-text
    • @create-typescript/block-husky
    • @create-typescript/block-knip
    • @create-typescript/block-markdownlint
    • @create-typescript/block-package-json
    • @create-typescript/block-pnpm
    • @create-typescript/block-prettier
    • @create-typescript/block-license-mit
    • @create-typescript/block-readme
    • @create-typescript/block-release-it
    • @create-typescript/block-renovate
    • @create-typescript/block-tsc
    • @create-typescript/block-tsup
    • @create-typescript/block-vitest
  • 🧰 Addons:
    • @create-typescript/addon-all-contributors-auto-action
    • @create-typescript/addon-eslint-comments
    • @create-typescript/addon-eslint-jsdoc
    • @create-typescript/addon-eslint-jsonc
    • @create-typescript/addon-eslint-eslint
    • @create-typescript/addon-eslint-md
    • @create-typescript/addon-eslint-regexp
    • @create-typescript/addon-eslint-perfectionist
    • @create-typescript/addon-eslint-vitest
    • @create-typescript/addon-markdownlint-sentences-per-line
    • @create-typescript/addon-pnpm-dedupe
    • @create-typescript/addon-prettier-plugin-curly
    • @create-typescript/addon-prettier-plugin-sh
    • @create-typescript/addon-prettier-plugin-packagejson
    • @create-typescript/addon-tsup-bin
    • @create-typescript/addon-vitest-console-fail-test
    • @create-typescript/addon-vitest-coverage
  • 🎁 Presets:
    • @create-typescript/preset-minimal
    • @create-typescript/preset-common
    • @create-typescript/preset-everything

The 🎁 presets will be configurable with 📥 options to swap out pieces as needed for repositories. For example, some repositories will want to swap out the Tsup 🧱 block for a different builder.

Over time, @create-typescript will encompass all common TypeScript package types from repositories I (Josh) use. That will include browser extensions, GitHub actions, and web frameworks such as Astro.

@johnnyreilly
Copy link
Collaborator

I need time to digest this, but off the bat this looks beautiful and well thought out. Your emoji game is on point ❤️

@tobySolutions
Copy link

For example, a 🧱 block that adds a .nvmrc file:

This is amazing! Read through core parts to get an idea on things. DAMN!

@JohannesKonings
Copy link

cool, looks like a similar approach like https://github.com/projen/projen

@JoshuaKGoldberg
Copy link
Owner Author

I will respond to ☝️ soon - finishing up some drafts!

In the meantime, @DonIsaac pointed me to https://nx.dev/features/generate-code. I'll comment on the differences with that too!

@JoshuaKGoldberg
Copy link
Owner Author

OK! In order...

https://github.com/projen/projen

Projen is really interesting, thanks for pointing me at it @JohannesKonings! I think it's a step in the right direction from Yeomen, similar to my vision for create. There are a few significant points I think we differ on:

  • It has templates implemented using classes and a class per project type, which is not the architecture I'd choose for configuration.
  • It imposes a much tighter grip on repositories than I'd want create to by default:
    • package.json tasks are expected to be managed by projen, e.g. "build": "npx projen build". In general, projen builds in a core concept of "tasks" that I don't want to couple to repo generation.
    • I wouldn't want to bake in project-specific concepts such as "Bundling" to the core engine, even if they are common to many projects.
  • I don't see in the docs or vision several features that I think are core to what create needs: migration, inference of options from existing files, granular block-level testing (not just project-level)

Ultimately, even though Projen looks great and has an active team behind it, I don't think it's a match for what I'm looking to use in create-typescript-app. It seems like a much better fit for teams rather than small open source project Its focus seems to be more around integration and a comprehensive core engine. My leaning is towards having an extensible core engine focused on web projects. I could be very wrong here 😅 but, next point...

Another ding against using Projen is that we're just very different maintenance crews. Projen is still 0.x and it would take a lot of time for me to ramp up on it & get integrated with the team. Pragmatically speaking, it'd be easier for me to 'tunnel vision' on building my own thing that focuses on my use case. Then later on, if I discover why another project is better, the core building blocks of create & create-typescript-app built on it should be moveable.

https://nx.dev/features/generate-code

I took a dive and this isn't what I'm looking for here. From https://nx.dev/extending-nx/recipes/local-generators, generators "automate many tasks you regularly perform as part of your development workflow" - but that's generally scoped within a repository. They're not targeted to composable, reusable templates.


I put a very rough starting sketch of a create-based CTA here: https://github.com/JoshuaKGoldberg/create-typescript-app/tree/create. It's super early stage and I'll have to iterate on it a bunch to get its files output the same as the template as-is. But I'm excited that the core engine is starting to shape up!

What'll likely happen next is:

  1. I'll iterate on the core create engine and branch here until there's feature parity on file generation
  2. I'll add APIs to create for just the files portion, thereby allowing...
  3. create-typescript-app to use create's file APIs to generate files
  4. Repeat steps 2-3 for each of the remaining parts of create-typescript-app
  5. Eventually, create-typescript-app will be mostly a thin shell around create - at which point I'll be able to switch it over entirely

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: documentation Improvements or additions to docs status: accepting prs Please, send a pull request to resolve this! type: feature New enhancement or request
Projects
None yet
Development

No branches or pull requests

4 participants