Skip to content

Commit

Permalink
feat: remove gitignore from baseproject
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielmoreira committed Oct 22, 2024
1 parent da9884d commit 843e460
Show file tree
Hide file tree
Showing 16 changed files with 1,610 additions and 1,161 deletions.
36 changes: 11 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
# QDK - Quick Development Kit

**⚠️ Important: This project is in an experimental phase and is under active development. Expect frequent changes and updates as we continue to evolve the toolkit. Breaking changes will occur as features are improved and refined. Use at your own discretion.**
**⚠️ Important: This project is experimental and actively evolving. Expect frequent updates and breaking changes. Use cautiously.**

QDK (Quick Development Kit) is a toolkit designed to simplify the generation of configuration files through TypeScript. It's inspired by tools like Projen but provides more flexibility for creating and managing any kind of configuration file in your projects.
QDK simplifies configuration file generation via TypeScript, offering flexibility like Projen but with more control over project configurations.

## Why QDK?

QDK was created to offer flexibility and full control over project configuration, avoiding the pitfalls of overly complex base classes with too many responsibilities, like those found in tools such as Projen.
QDK was designed to address the complexity seen in tools like Projen, which incorporates many elements, responsibilities, and features for code generation across various programming languages. This design choice can lead to cluttered projects, as base classes like `NodeProject` automatically pull in numerous dependencies (e.g., Gitpod, DevContainer, AWS services). As a result, users often need to disable unwanted features, complicating customization.

Some tools suffer from **"kitchen sink"** or **"tight coupling"** antipatterns, where core classes like `NodeProject` drag in unrelated dependencies (Gitpod, DevContainer, AWS) by default. This forces users to manually disable unwanted features, which complicates inheritance and makes it confusing to decide whether to rely on built-in properties (e.g., from `NodeProject`) or instantiate separate components.
In contrast, QDK specifically targets Node.js and TypeScript projects, focusing on:

### QDK is built around the following principles
- **Generate only what’s requested**: Files are only created when explicitly requested. For instance, if you need a `package.json`, instantiate the `PackageJson` component; if you want a `.gitignore`, instantiate the `Gitignore`, and so on.
- **Minimal coupling**: Components are designed to be as independent as possible. However, logical dependencies do exist; for example, multiple components may need to communicate with `package.json` to add dependencies or utilize the package manager for command execution.
- **Composable design**: QDK favors small, focused components over large, complex objects, allowing projects to define their own component defaults without inheriting from a bloated base.
- **Customizable defaults**: Users can set options for individual components while establishing global default settings, enhancing overall flexibility.
- **No runtime impact**: QDK does not add any code, libraries, or dependencies to your project's runtime. It solely serves to generate code based on a central configuration. Additionally, QDK can be easily removed from your workspace; ejecting it is as simple as deleting the `.qdk` directories and the `qdk.config.ts` file.

- **Only generate what’s explicitly requested.**
Avoid unnecessary features or configurations unless they are specifically requested by the user.
You may wonder, "But won't my project need to instantiate several components instead of having nice defaults decided by the project?" In this case, I don't see it as a drawback, but rather as a feature. The JavaScript community evolves rapidly, and different techniques, frameworks, and libraries require distinct configurations. It's challenging to decide on defaults and behaviors that broadly meet everyone's needs. Thus, QDK's approach is to help manage the files that need to be generated while providing small components that allow you to create your own defaults.

- **Avoid creating components that manage unrelated components.**
Maintain clear boundaries between components to prevent unintended dependencies and keep project configurations clean.

- **Let users compose components and promote composability.**
Allow full control over how components are combined and enable users to mix and match them effortlessly, ensuring flexibility in project setups.

- **Ensure easy extensibility with flexible defaults.**
Provide sensible defaults that are easily adjustable without requiring complex overrides or patches.

We believe that companies with multiple teams often have specific setups tailored to their unique workflows, including configurations for pipelines, tooling, folder structures, etc. These setups are essential for maintaining efficiency and consistency across projects and teams, allowing for precise control over what is being generated and ensuring that users can easily understand and verify the outputs. Predictability is a core necessity of the tool, as it enables effective project management and reduces uncertainties in the development process.

QDK adopts a modular, component-centric approach that prioritizes clarity, customizability, and composability.

## Features

- Generate configuration files using TypeScript
- Easily customize your `package.json` with scripts and dependencies
- Manage TypeScript configurations, ESLint setups, and more
To assist in deciding between different approaches, QDK will focus on creating project templates that you can use as a starting point for your project, allowing you to customize the organization in a way that is most effective for you.

## Quickstart

Expand Down
11 changes: 9 additions & 2 deletions src/components/Gitignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,27 @@ import {

export interface GitignoreOptionsType {
pattern?: string[];
mergeDefaults?: boolean;
}

export type GitignoreInitialOptionsType = Partial<GitignoreOptionsType>;

const GitignoreDefaults: Partial<GitignoreOptionsType> = {
pattern: gitignoreDefault,
mergeDefaults: true,
};

const optionsMerger: OptionsMerger<
GitignoreOptionsType,
GitignoreInitialOptionsType
GitignoreInitialOptionsType,
typeof GitignoreDefaults
> = (initialOptions, defaults) => ({
...defaults,
...initialOptions,
pattern:
(initialOptions.mergeDefaults ?? defaults.mergeDefaults)
? [...(defaults.pattern ?? []), ...(initialOptions.pattern ?? [])]
: [...(initialOptions.pattern ?? [])],
});

export const GitignoreOptions = createOptions(
Expand All @@ -34,7 +41,7 @@ export const GitignoreOptions = createOptions(
export class Gitignore extends Component<GitignoreOptionsType> {
private readonly ignored: string[] = [];
readonly file: TextFile;
constructor(scope: Scope, options: GitignoreInitialOptionsType) {
constructor(scope: Scope, options: GitignoreInitialOptionsType = {}) {
super(scope, GitignoreOptions.getOptions(options, { scope }));
this.file = new TextFile(this, { basename: '.gitignore' }, '');
if (this.options.pattern?.length) this.add(...this.options.pattern);
Expand Down
12 changes: 0 additions & 12 deletions src/projects/BaseProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import {
Component,
createOptions,
getErrorCode,
Gitignore,
gitignoreDefault,
HasOptions,
JsonFile,
OptionsMerger,
Expand Down Expand Up @@ -55,7 +53,6 @@ export type BaseProjectInitialOptionsType = Pick<
Partial<BaseProjectOptionsType>;

const BaseProjectDefaults = {
gitignore: gitignoreDefault,
buildDir: 'dist',
outdir: '.',
sourceSets: {
Expand Down Expand Up @@ -137,15 +134,6 @@ export abstract class BaseProject<
const options = BaseProjectOptions.getOptions(opts, { scope });
super(scope ?? undefined, options.name);
this.options = options as T;
const gitignore = this.options.gitignore ?? true;
if (gitignore) {
new Gitignore(this, {
pattern:
typeof gitignore === 'boolean'
? BaseProjectDefaults.gitignore
: gitignore,
});
}
this.metadataFile = new JsonFile<BaseProjectMetadata>(
this,
{ basename: '.qdk/meta.json', writeOnSynth: false },
Expand Down
15 changes: 15 additions & 0 deletions templates/blank/qdk.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,20 @@ export default class MyApp extends qdk.QdkApp {
description: 'Sample QDK Project',
version: '0.1.0',
});

// Add .gitignore to the project
// new qdk.Gitignore(project);

// Use npm package manager
// new qdk.NpmPackageManager(project);

// Use pnpm package manager
// new qdk.PnpmPackageManager(project);

// Customize package.json
// new qdk.PackageJson(project);

// Add typescript
// new qdk.Typescript(project);
}
}
4 changes: 4 additions & 0 deletions templates/monorepo/.qdk/components/MonorepoProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ export class MonorepoProject extends qdk.BaseProject<MonorepoProjectOptionsType>
options: MonorepoProjectInitialOptionsType,
) {
super(scope, MonorepoProjectOptions.getOptions(options, { scope }));
// -----------------
// Setup .gitignore
// -----------------
new qdk.Gitignore(this);

// --------------------------------------
// Setup Pnpm as the package manager with workspace support
Expand Down
5 changes: 1 addition & 4 deletions templates/monorepo/.qdk/components/NodeProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ export type NodeProjectInitialOptionsType = qdk.BaseProjectInitialOptionsType &

/**
* Default options for Node.js projects.
* The base directory is set to 'packages/' and gitignore is disabled by default.
* The base directory is set to 'packages/'.
*/
const NodeProjectDefaults = {
basedir: 'packages/', // Default directory name for a node project
gitignore: false, // Default: gitignore is not generated
} satisfies Partial<NodeProjectOptionsType>;

/**
Expand All @@ -54,7 +53,6 @@ const nodeProjectOptionsMerger: qdk.OptionsMerger<
initialOptions.basedir ?? defaults.basedir, // Use basedir or default 'packages/'
getNameWithoutScope(initialOptions.name), // Calculate directory from project name
),
gitignore: initialOptions.gitignore ?? defaults.gitignore,
},
context,
),
Expand Down Expand Up @@ -90,7 +88,6 @@ export class NodeProject<
*/
constructor(scope: qdk.Scope, options: I) {
super(scope, NodeProjectOptions.getOptions(options, { scope }) as T);

// --------------------------------------
// Setup pnpm as the package manager
// --------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions templates/readme/qdk.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export default class MyApp extends qdk.QdkApp {
version: '0.1.0',
});

// Add .gitignore to the project
new qdk.Gitignore(project);

// Attach a package manager to the project
new qdk.NpmPackageManager(project);
// If you prefer pnpm, use the line below:
Expand Down
27 changes: 15 additions & 12 deletions templates/simple/qdk.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,48 @@ export default class MyApp extends qdk.QdkApp {
super(options);
// Create a new empty project

const myProject = new qdk.Project(this, {
const project = new qdk.Project(this, {
name: 'qdk-sample',
description: 'Sample QDK Project',
version: '0.1.0',
// outdir: 'somewhere/else', // by default outdir is '.'
});

// Add .gitignore to the project
new qdk.Gitignore(project);

// Use npm package manager
new qdk.NpmPackageManager(myProject);
new qdk.NpmPackageManager(project);

// Customize package.json and add custom dependencies
new qdk.PackageJson(myProject, {
new qdk.PackageJson(project, {
license: 'MIT',
module: `${myProject.buildDir}/src/index.js`,
module: `${project.buildDir}/src/index.js`,
}) // by default the package type is 'module'
.addDevDeps('vitest')
.setScript('test', 'vitest');

// Typescript TSConfig
new qdk.Typescript(myProject, {
new qdk.Typescript(project, {
tsconfig: {
extends: [qdk.TsConfigBases.Node20],
include: [
'qdk.config.ts',
'eslint.config.mjs',
...(myProject.sourceSets.main?.pattern ?? []),
...(myProject.sourceSets.tests?.pattern ?? []),
...(myProject.sourceSets.qdk?.pattern ?? []),
...(project.sourceSets.main?.pattern ?? []),
...(project.sourceSets.tests?.pattern ?? []),
...(project.sourceSets.qdk?.pattern ?? []),
],
compilerOptions: {
outDir: myProject.buildDir,
outDir: project.buildDir,
strictNullChecks: true,
resolveJsonModule: true,
},
},
});

// Enable ESLint (+ prettier)
new qdk.EsLint(myProject, {
new qdk.EsLint(project, {
templateParams: {
rules: {
'@typescript-eslint/no-unsafe-call': 'off',
Expand All @@ -56,14 +59,14 @@ export default class MyApp extends qdk.QdkApp {
this.hook('synth:after', async () => {
await qdk.PackageManager
// Find the package manager configured for this project
.required(myProject)
.required(project)
// Run: npx eslint --fix to automatically correct linting issues
.exec('eslint --fix');
});

// You can extract your features into components
// to enable reuse across different projects.
new MySampleFiles(myProject);
new MySampleFiles(project);
}
}

Expand Down
Loading

0 comments on commit 843e460

Please sign in to comment.