├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── ci.yml │ ├── playwright.yml │ └── update-snapshots.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── e2e ├── astro │ ├── astro.spec.ts │ └── astro.spec.ts-snapshots │ │ └── Astro-1-chromium-linux.png ├── nextjs │ ├── nextjs.spec.ts │ └── nextjs.spec.ts-snapshots │ │ ├── Next-js-1-chromium-win32.png │ │ └── nextjs-chromium-linux.png ├── nuxtjs │ ├── nuxtjs.spec.ts │ └── nuxtjs.spec.ts-snapshots │ │ └── Nuxt-js-1-chromium-linux.png ├── preact │ ├── preact.spec.ts │ └── preact.spec.ts-snapshots │ │ └── Preact-1-chromium-linux.png ├── react │ ├── react.spec.ts │ └── react.spec.ts-snapshots │ │ ├── React-1-chromium-linux.png │ │ └── React-1-chromium-win32.png ├── svelte │ ├── svelte.spec.ts │ └── svelte.spec.ts-snapshots │ │ └── Svelte-1-chromium-linux.png ├── test-configs │ ├── astro.json │ ├── nextjs.json │ ├── nuxtjs.json │ ├── preact.json │ ├── react.json │ ├── solid.json │ ├── svelte.json │ ├── vanilla.json │ └── vue.json ├── vanilla │ ├── vanilla.spec.ts │ └── vanilla.spec.ts-snapshots │ │ └── Vanilla-1-chromium-linux.png └── vue │ ├── vue.spec.ts │ └── vue.spec.ts-snapshots │ └── React-1-chromium-linux.png ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── src ├── cli │ ├── commands │ │ ├── createAstro.ts │ │ ├── createNext.ts │ │ ├── createNuxt.ts │ │ ├── createSolid.ts │ │ ├── createSvelte.ts │ │ └── createVite.ts │ ├── config.ts │ ├── output │ │ ├── addPluginsTransformer.ts │ │ ├── createProject.ts │ │ ├── installDependencies.ts │ │ └── installTailwind.ts │ └── readInput.ts ├── constants.ts ├── index.ts └── utils │ ├── create-svelte.d.ts │ ├── execAsync.ts │ ├── getPackageManager.ts │ ├── getVersion.ts │ ├── installPackages.ts │ ├── logger.ts │ ├── resolvePackageManager.ts │ └── validateAppName.ts ├── templates ├── common │ ├── .prettierignore │ ├── .prettierrc │ ├── directives.css │ ├── postcss.config.js │ └── yarn.lock ├── nextjs-ts │ ├── index.tsx │ ├── postcss.config.js │ └── tailwind.config.js ├── nextjs │ ├── index.jsx │ ├── postcss.config.js │ └── tailwind.config.js ├── nuxtjs-ts │ ├── app.vue │ ├── nuxt.config.ts │ ├── pages │ │ └── index.vue │ ├── postcss.config.js │ └── tailwind.config.js ├── nuxtjs │ ├── app.vue │ ├── nuxt.config.js │ ├── pages │ │ └── index.vue │ ├── postcss.config.js │ └── tailwind.config.js ├── preact-ts │ ├── index.js │ ├── postcss.config.js │ └── tailwind.config.js ├── preact │ ├── index.js │ ├── postcss.config.js │ └── tailwind.config.js ├── react-ts │ ├── App.tsx │ ├── postcss.config.js │ └── tailwind.config.js ├── react │ ├── App.jsx │ ├── postcss.config.js │ └── tailwind.config.js ├── solid-ts │ ├── App.tsx │ ├── postcss.config.js │ └── tailwind.config.js ├── solid │ ├── App.jsx │ ├── postcss.config.js │ └── tailwind.config.js ├── svelte-kit │ ├── +layout.svelte │ ├── +page.svelte │ ├── postcss.config.js │ ├── svelte.config.js │ └── tailwind.config.js ├── vanilla-ts │ ├── index.html │ ├── main.ts │ ├── postcss.config.js │ └── tailwind.config.js ├── vanilla │ ├── index.html │ ├── main.js │ ├── postcss.config.js │ └── tailwind.config.js ├── vue-ts │ ├── App.vue │ ├── postcss.config.js │ └── tailwind.config.js └── vue │ ├── App.vue │ ├── postcss.config.js │ └── tailwind.config.js ├── test └── cli.test.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | dist/ 4 | /templates/ 5 | vanilla/ 6 | vanilla-ts/ 7 | nextjs/ 8 | nextjs-ts/ 9 | react/ 10 | react-ts/ 11 | test 12 | playwright.config.ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "project": "./tsconfig.json" 7 | }, 8 | "plugins": ["@typescript-eslint", "import"], 9 | "extends": [ 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "plugin:@typescript-eslint/strict", 13 | "plugin:import/recommended", 14 | "plugin:import/typescript", 15 | "prettier" 16 | ], 17 | "env": { 18 | "node": true, 19 | "es6": true 20 | }, 21 | "rules": { 22 | "no-shadow": 1, 23 | "import/no-named-as-default": 0, 24 | "import/no-named-as-default-member": 0, 25 | "import/order": "off", 26 | 27 | // TODO: These shold be enabled, but for some reason it currently doesn't work as expected 28 | "@typescript-eslint/no-unsafe-assignment": "off", 29 | "@typescript-eslint/no-unsafe-call": "off", 30 | "@typescript-eslint/no-unsafe-member-access": "off", 31 | "@typescript-eslint/no-unsafe-return": "off", 32 | "@typescript-eslint/no-unsafe-argument": "off", 33 | "@typescript-eslint/restrict-template-expressions": "off" 34 | }, 35 | "settings": { 36 | "import/resolver": { 37 | "typescript": { 38 | "alwaysTryTypes": true, 39 | "project": "./tsconfig.json" 40 | } 41 | } 42 | }, 43 | "ignorePatterns": ["node_modules/", "build/", "dist/"] 44 | } 45 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [pull_request] 3 | jobs: 4 | lint: 5 | name: Lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout Repository 9 | uses: actions/checkout@v3 10 | - name: Setup PNPM 11 | uses: pnpm/action-setup@v2.2.1 12 | with: 13 | version: 6.0.2 14 | - name: Setup Node 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | - name: Install Dependencies 19 | run: pnpm install 20 | - name: Lint 21 | run: pnpm run lint 22 | - name: Prettier 23 | run: pnpm run prettier:check 24 | 25 | build: 26 | name: Build 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | - name: Setup PNPM 32 | uses: pnpm/action-setup@v2.2.1 33 | with: 34 | version: 6.0.2 35 | - name: Setup Node 36 | uses: actions/setup-node@v3 37 | with: 38 | node-version: 16 39 | - name: Install Dependencies 40 | run: pnpm install 41 | - name: Build 42 | run: pnpm run build 43 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Run e2e Tests 2 | on: 3 | pull_request: 4 | branches: [main] 5 | jobs: 6 | e2e: 7 | timeout-minutes: 60 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | include: 12 | - variant: nextjs 13 | port: 3000 14 | - variant: react 15 | port: 5173 16 | - variant: vue 17 | port: 5173 18 | - variant: preact 19 | port: 5173 20 | - variant: svelte 21 | port: 5173 22 | - variant: vanilla 23 | port: 5173 24 | - variant: nuxtjs 25 | port: 3000 26 | - variant: astro 27 | port: 3000 28 | 29 | steps: 30 | - uses: actions/checkout@v3 31 | - name: Setup PNPM 32 | uses: pnpm/action-setup@v2.2.1 33 | with: 34 | version: 7 35 | - uses: actions/setup-node@v3 36 | with: 37 | node-version: 16 38 | cache: "pnpm" 39 | 40 | # Install dependencies and build CLI app 41 | - name: Install dependencies 42 | run: pnpm install 43 | - name: Install Playwright Browsers 44 | run: npx playwright install --with-deps chromium 45 | - name: Build Project 46 | run: pnpm run build 47 | 48 | # Build and run Playwright tests 49 | - name: Build ${{ matrix.variant }} 50 | run: cd dist && node index.js --config ../e2e/test-configs/${{ matrix.variant }}.json 51 | - name: Test ${{ matrix.variant }} 52 | run: npx playwright test e2e/${{ matrix.variant }}/${{ matrix.variant}}.spec.ts 53 | env: 54 | COMMAND: "cd dist/${{ matrix.variant }} && yarn dev" 55 | PORT: ${{ matrix.port }} 56 | 57 | # Upload Test Results on Failure 58 | - uses: actions/upload-artifact@v3 59 | if: failure() 60 | with: 61 | name: playwright-report 62 | path: playwright-report/ 63 | retention-days: 30a 64 | -------------------------------------------------------------------------------- /.github/workflows/update-snapshots.yml: -------------------------------------------------------------------------------- 1 | name: Run e2e Tests 2 | on: [workflow_dispatch] 3 | jobs: 4 | e2e: 5 | timeout-minutes: 60 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | include: 10 | - variant: nextjs 11 | port: 3000 12 | - variant: react 13 | port: 5173 14 | - variant: vue 15 | port: 5173 16 | - variant: preact 17 | port: 5173 18 | - variant: svelte 19 | port: 5173 20 | - variant: vanilla 21 | port: 5173 22 | - variant: nuxtjs 23 | port: 3000 24 | - variant: astro 25 | port: 3000 26 | 27 | steps: 28 | - uses: actions/checkout@v3 29 | - name: Setup PNPM 30 | uses: pnpm/action-setup@v2.2.1 31 | with: 32 | version: 7 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: 16 36 | cache: "pnpm" 37 | 38 | # Install dependencies and build CLI app 39 | - name: Install dependencies 40 | run: pnpm install 41 | - name: Install Playwright Browsers 42 | run: npx playwright install --with-deps chromium 43 | - name: Build Project 44 | run: pnpm run build 45 | 46 | # Build and run Playwright tests 47 | - name: Build ${{ matrix.variant }} 48 | run: cd dist && node index.js --config ../e2e/test-configs/${{ matrix.variant }}.json 49 | - name: Test ${{ matrix.variant }} 50 | run: npx playwright test e2e/${{ matrix.variant }}/${{ matrix.variant}}.spec.ts --update-snapshots 51 | env: 52 | COMMAND: "cd dist/${{ matrix.variant }} && yarn dev" 53 | PORT: ${{ matrix.port }} 54 | 55 | # Upload Test Results on Failure 56 | - uses: actions/upload-artifact@v3 57 | if: failure() 58 | with: 59 | name: playwright-report 60 | path: playwright-report/ 61 | retention-days: 30a 62 | 63 | # Commit snapshot changes 64 | - uses: EndBug/add-and-commit@v9 65 | with: 66 | pull: "--rebase --autostash" 67 | message: "[CI] Update snapshots: ${{ matrix.variant }}" 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### STANDARD GIT IGNORE FILE ### 2 | 3 | # DEPENDENCIES 4 | node_modules/ 5 | /.pnp 6 | .pnp.js 7 | 8 | # TESTING 9 | /coverage 10 | *.lcov 11 | .nyc_output 12 | 13 | # BUILD 14 | build/ 15 | public/build/ 16 | dist/ 17 | generated/ 18 | 19 | # ENV FILES 20 | .env 21 | .env.local 22 | .env.development.local 23 | .env.test.local 24 | .env.production.local 25 | 26 | # LOGS 27 | logs 28 | *.log 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # MISC 34 | .idea 35 | .turbo/ 36 | .cache/ 37 | .next/ 38 | .nuxt/ 39 | tmp/ 40 | temp/ 41 | .eslintcache 42 | 43 | # MAC 44 | ._* 45 | .DS_Store 46 | Thumbs.db 47 | 48 | # TEMPLATE TESTS 49 | /next 50 | /next-ts 51 | /vanilla 52 | /vanilla-ts 53 | /tests 54 | /svelte 55 | /test-results/ 56 | /playwright-report/ 57 | /playwright/.cache/ 58 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | dist/ 4 | /vanilla 5 | /vanilla-ts 6 | /nextjs 7 | /tests 8 | pnpm-lock.yaml 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "arrowParens": "always", 4 | "printWidth": 80, 5 | "singleQuote": false, 6 | "jsxSingleQuote": false, 7 | "semi": true, 8 | "trailingComma": "all", 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### 0.0.4 (2022-07-25) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Andrej Jurkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | image 2 | 3 | # Create Tailwind (create-tw) 4 | 5 | ### The easiest way to get started with TailwindCSS. 6 | 7 | It uses popular scaffolding scripts like `create-next-app`, `create-vite`, `create-astro`, or `create-svelte` to scaffold projects and then configures TailwindCSS to work with your project out of the box. 8 | 9 | #### npx 10 | 11 | ```bash 12 | npx create-tw@latest 13 | 14 | # OR 15 | 16 | npx create-tw@latest --template 17 | ``` 18 | 19 | #### yarn 20 | 21 | ```bash 22 | yarn create tw 23 | 24 | # OR 25 | 26 | yearn create tw --template 27 | ``` 28 | 29 | #### pnpm 30 | 31 | ```bash 32 | pnpm create tw 33 | 34 | # OR 35 | 36 | pnpm create tw --template 37 | ``` 38 | 39 | 1. Pick project type 40 | - Nextjs (create-next-app) 41 | - Vanilla (create-vite) 42 | - React (create-vite) 43 | - Vue (create-vite) 44 | - Astro (create-astro) 45 | - Svelte (create-svelte) 46 | - Preact (create-vite) 47 | - Solid (degit) 48 | 2. Pick language 49 | - TypeScript 50 | - JavaScript 51 | 3. Pick dependencies 52 | - Prettier, clsx, etc. 53 | 4. Pick Plugins 54 | - Daisy UI, @tailwindcss/typography, etc 55 | 56 | It's ready! ✅ 57 | 58 | ### Currently in very early stage of development 59 | 60 | Ideas, suggestions and bug reports are much appreciated. 61 | In the following days I'm planning to add support for React, Vue & Svelte with Vite. 62 | 63 | ### Scaffolding tools 64 | 65 | | id | Template | Tool | 66 | | :--------- | :--------- | :-------------- | 67 | | nextjs | Next.js | create-next-app | 68 | | vanilla | Vanilla | create-vite | 69 | | react | React | create-vite | 70 | | vue | Vue | create-vite | 71 | | astro | Astro | create-astro | 72 | | svelte-kit | Svelte Kit | create-svelte | 73 | | preact | Preact | create-vite | 74 | | solid | Solid | degit | 75 | 76 | NOTE: Add the `-ts` postfix to the ID to install with TypeScript 77 | 78 | ### Include template id to skip input steps 79 | 80 | ```bash 81 | npx create-tw@latest --template 82 | # OR 83 | yarn create tw --template 84 | ``` 85 | -------------------------------------------------------------------------------- /e2e/astro/astro.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Astro", async ({ page }) => { 4 | await page.goto("http://localhost:3000"); 5 | 6 | await expect(page).toHaveScreenshot(); 7 | }); 8 | -------------------------------------------------------------------------------- /e2e/astro/astro.spec.ts-snapshots/Astro-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/astro/astro.spec.ts-snapshots/Astro-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/nextjs/nextjs.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Next.js", async ({ page }) => { 4 | await page.goto("/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot("nextjs.png"); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/nextjs/nextjs.spec.ts-snapshots/Next-js-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/nextjs/nextjs.spec.ts-snapshots/Next-js-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/nextjs/nextjs.spec.ts-snapshots/nextjs-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/nextjs/nextjs.spec.ts-snapshots/nextjs-chromium-linux.png -------------------------------------------------------------------------------- /e2e/nuxtjs/nuxtjs.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Nuxt.js", async ({ page }) => { 4 | await page.goto("http://localhost:3000"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/nuxtjs/nuxtjs.spec.ts-snapshots/Nuxt-js-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/nuxtjs/nuxtjs.spec.ts-snapshots/Nuxt-js-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/preact/preact.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Preact", async ({ page }) => { 4 | await page.goto("http://127.0.0.1:5173/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/preact/preact.spec.ts-snapshots/Preact-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/preact/preact.spec.ts-snapshots/Preact-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/react/react.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("React", async ({ page }) => { 4 | await page.goto("http://127.0.0.1:5173/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/react/react.spec.ts-snapshots/React-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/react/react.spec.ts-snapshots/React-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/react/react.spec.ts-snapshots/React-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/react/react.spec.ts-snapshots/React-1-chromium-win32.png -------------------------------------------------------------------------------- /e2e/svelte/svelte.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Svelte", async ({ page }) => { 4 | await page.goto("http://127.0.0.1:5173/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/svelte/svelte.spec.ts-snapshots/Svelte-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/svelte/svelte.spec.ts-snapshots/Svelte-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/test-configs/astro.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "astro", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "astro", 7 | "appConfig": "astro" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "nextjs", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "nextjs", 7 | "appConfig": "nextjs" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/nuxtjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "nuxtjs", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "nuxtjs", 7 | "appConfig": "nuxtjs" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/preact.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "preact", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "preact", 7 | "appConfig": "preact" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/react.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "react", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "react", 7 | "appConfig": "react" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/solid.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "solid", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "solid", 7 | "appConfig": "solid" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/svelte.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "svelte", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "svelte", 7 | "appConfig": "svelte-kit" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/vanilla.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "vanilla", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "vanilla", 7 | "appConfig": "vanilla" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/test-configs/vue.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "vue", 3 | "plugins": [], 4 | "dependencies": [], 5 | "packageManager": "yarn", 6 | "projectDir": "vue", 7 | "appConfig": "vue" 8 | } 9 | -------------------------------------------------------------------------------- /e2e/vanilla/vanilla.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("Vanilla", async ({ page }) => { 4 | await page.goto("http://127.0.0.1:5173/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/vanilla/vanilla.spec.ts-snapshots/Vanilla-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/vanilla/vanilla.spec.ts-snapshots/Vanilla-1-chromium-linux.png -------------------------------------------------------------------------------- /e2e/vue/vue.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "@playwright/test"; 2 | 3 | test("React", async ({ page }) => { 4 | await page.goto("http://127.0.0.1:5173/"); 5 | 6 | expect(await page.innerText("h1")).toBe("Create Tailwind"); 7 | 8 | await page.waitForTimeout(3000); 9 | 10 | await expect(page).toHaveScreenshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/vue/vue.spec.ts-snapshots/React-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/e2e/vue/vue.spec.ts-snapshots/React-1-chromium-linux.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-tw", 3 | "version": "1.1.2", 4 | "description": "CLI tool to create TailwindCSS projects using already existing scaffolding tools like create-next or create-vite", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/AndrejJurkin/create-tw" 9 | }, 10 | "type": "module", 11 | "exports": "./dist/index.js", 12 | "bin": { 13 | "create-tw": "./dist/index.js" 14 | }, 15 | "engines": { 16 | "node": ">=14.16" 17 | }, 18 | "scripts": { 19 | "build": "tsup src/index.ts --format esm --clean --sourcemap --minify --metafile", 20 | "dev": "tsup src/index.ts --format esm --watch --clean --onSuccess \"node dist/index.js\"", 21 | "start": "node dist/index.js", 22 | "test": "vitest", 23 | "release": "standard-version", 24 | "publish:beta": "npm run build && npm publish --tag beta", 25 | "publish": " npm run build && npm publish", 26 | "prettier:check": "prettier --check .", 27 | "prettier:fix": "prettier --write .", 28 | "lint": "eslint . --ext .ts,.js", 29 | "lint:fix": "eslint . --ext .ts,.js --fix" 30 | }, 31 | "keywords": [ 32 | "tailwindcss", 33 | "nextjs", 34 | "scaffold", 35 | "react", 36 | "html", 37 | "css" 38 | ], 39 | "author": "Andrej Jurkin", 40 | "license": "MIT", 41 | "dependencies": { 42 | "chalk": "5.0.1", 43 | "commander": "^9.5.0", 44 | "create-svelte": "^2.3.4", 45 | "figlet": "^1.5.2", 46 | "fs-extra": "^10.1.0", 47 | "gradient-string": "^2.0.2", 48 | "inquirer": "^9.1.4", 49 | "ora": "6.1.1", 50 | "recast": "^0.21.5" 51 | }, 52 | "devDependencies": { 53 | "@playwright/test": "^1.30.0", 54 | "@swc/core": "^1.3.35", 55 | "@types/figlet": "^1.5.5", 56 | "@types/fs-extra": "^9.0.13", 57 | "@types/gradient-string": "^1.1.2", 58 | "@types/inquirer": "^8.2.5", 59 | "@types/jest": "^29.4.0", 60 | "@types/node": "^18.13.0", 61 | "@typescript-eslint/eslint-plugin": "^5.51.0", 62 | "@typescript-eslint/parser": "^5.51.0", 63 | "eslint": "^8.34.0", 64 | "eslint-config-prettier": "^8.6.0", 65 | "eslint-import-resolver-typescript": "^3.5.3", 66 | "eslint-plugin-import": "^2.27.5", 67 | "execa": "^6.1.0", 68 | "jest": "^29.4.2", 69 | "prettier": "^2.8.4", 70 | "standard-version": "^9.5.0", 71 | "tsup": "^6.6.0", 72 | "typescript": "^4.9.5", 73 | "vitest": "^0.21.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import type { PlaywrightTestConfig } from "@playwright/test"; 2 | import { devices } from "@playwright/test"; 3 | 4 | /** 5 | * Read environment variables from file. 6 | * https://github.com/motdotla/dotenv 7 | */ 8 | // require('dotenv').config(); 9 | 10 | const COMMAND = process.env.COMMAND; 11 | const PORT = process.env.PORT || 3000; 12 | 13 | /** 14 | * See https://playwright.dev/docs/test-configuration. 15 | */ 16 | const config: PlaywrightTestConfig = { 17 | testDir: "./e2e", 18 | /* Maximum time one test can run for. */ 19 | timeout: 30 * 1000, 20 | expect: { 21 | /** 22 | * Maximum time expect() should wait for the condition to be met. 23 | * For example in `await expect(locator).toHaveText();` 24 | */ 25 | timeout: 5000, 26 | toHaveScreenshot: { 27 | maxDiffPixelRatio: 0.05, 28 | }, 29 | }, 30 | /* Run tests in files in parallel */ 31 | fullyParallel: true, 32 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 33 | forbidOnly: !!process.env.CI, 34 | /* Retry on CI only */ 35 | retries: process.env.CI ? 2 : 0, 36 | /* Opt out of parallel tests on CI. */ 37 | workers: process.env.CI ? 1 : undefined, 38 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 39 | reporter: "html", 40 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 41 | use: { 42 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ 43 | actionTimeout: 0, 44 | /* Base URL to use in actions like `await page.goto('/')`. */ 45 | // baseURL: 'http://localhost:3000', 46 | 47 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 48 | trace: "on-first-retry", 49 | }, 50 | 51 | /* Configure projects for major browsers */ 52 | projects: [ 53 | { 54 | name: "chromium", 55 | use: { 56 | ...devices["Desktop Chrome"], 57 | }, 58 | }, 59 | 60 | // { 61 | // name: 'firefox', 62 | // use: { 63 | // ...devices['Desktop Firefox'], 64 | // }, 65 | // }, 66 | 67 | // { 68 | // name: 'webkit', 69 | // use: { 70 | // ...devices['Desktop Safari'], 71 | // }, 72 | // }, 73 | 74 | /* Test against mobile viewports. */ 75 | // { 76 | // name: 'Mobile Chrome', 77 | // use: { 78 | // ...devices['Pixel 5'], 79 | // }, 80 | // }, 81 | // { 82 | // name: 'Mobile Safari', 83 | // use: { 84 | // ...devices['iPhone 12'], 85 | // }, 86 | // }, 87 | 88 | /* Test against branded browsers. */ 89 | // { 90 | // name: 'Microsoft Edge', 91 | // use: { 92 | // channel: 'msedge', 93 | // }, 94 | // }, 95 | // { 96 | // name: 'Google Chrome', 97 | // use: { 98 | // channel: 'chrome', 99 | // }, 100 | // }, 101 | ], 102 | 103 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */ 104 | // outputDir: 'test-results/', 105 | 106 | /* Run your local dev server before starting the tests */ 107 | webServer: { 108 | command: COMMAND, 109 | port: PORT, 110 | reuseExistingServer: !process.env.CI, 111 | }, 112 | }; 113 | 114 | export default config; 115 | -------------------------------------------------------------------------------- /src/cli/commands/createAstro.ts: -------------------------------------------------------------------------------- 1 | import { UserInput } from "./../config"; 2 | 3 | export function createAstroCommand(input: UserInput) { 4 | const { packageManager, projectName } = input; 5 | 6 | const parts: string[] = [ 7 | packageManager, 8 | packageManager === "npm" ? "init" : "create", 9 | "astro", 10 | projectName, 11 | "-- --template with-tailwindcss", 12 | ]; 13 | 14 | return parts.join(" "); 15 | } 16 | -------------------------------------------------------------------------------- /src/cli/commands/createNext.ts: -------------------------------------------------------------------------------- 1 | import { resolvePacakgeManager } from "../../utils/resolvePackageManager.js"; 2 | import { UserInput } from "../config.js"; 3 | 4 | export function createNextCommand(input: UserInput) { 5 | const { appConfig, packageManager } = input; 6 | 7 | // npx --yes is to automatically accept the prompt to install latest version 8 | const parts: string[] = [resolvePacakgeManager(packageManager)]; 9 | 10 | if (packageManager === "npm") { 11 | parts.push("create-next-app@latest"); 12 | } else { 13 | parts.push("create next-app"); 14 | } 15 | 16 | parts.push(input.projectName); 17 | 18 | if (appConfig.language === "ts") { 19 | if (packageManager === "npm" || packageManager === "pnpm") { 20 | parts.push("--ts"); 21 | } else { 22 | parts.push("--typescript"); 23 | } 24 | } 25 | 26 | return parts.join(" "); 27 | } 28 | -------------------------------------------------------------------------------- /src/cli/commands/createNuxt.ts: -------------------------------------------------------------------------------- 1 | import { UserInput } from "../config.js"; 2 | 3 | export function createNuxtCommand(input: UserInput) { 4 | const { appConfig } = input; 5 | 6 | // npx --yes is to automatically accept the prompt to install latest version 7 | const parts: string[] = ["npx --yes"]; 8 | 9 | parts.push("nuxi init"); 10 | parts.push(input.projectName); 11 | 12 | if (appConfig.language === "ts") { 13 | parts.push("--ts"); 14 | } 15 | 16 | return parts.join(" "); 17 | } 18 | -------------------------------------------------------------------------------- /src/cli/commands/createSolid.ts: -------------------------------------------------------------------------------- 1 | import { UserInput } from "./../config"; 2 | 3 | export default function createSolidCommand(input: UserInput) { 4 | const { projectName } = input; 5 | const language = input.appConfig.language; 6 | 7 | const parts = ["npx", `degit solidjs/templates/${language}`, projectName]; 8 | 9 | return parts.join(" "); 10 | } 11 | -------------------------------------------------------------------------------- /src/cli/commands/createSvelte.ts: -------------------------------------------------------------------------------- 1 | import { create } from "create-svelte"; 2 | import { UserInput } from "../config.js"; 3 | 4 | export default function createSvelteCommand(input: UserInput) { 5 | return create(input.projectName, { 6 | name: input.projectName, 7 | template: "skeleton", 8 | types: input.appConfig.language === "ts" ? "typescript" : null, 9 | prettier: false, 10 | eslint: true, 11 | playwright: false, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/cli/commands/createVite.ts: -------------------------------------------------------------------------------- 1 | import { resolvePacakgeManager } from "../../utils/resolvePackageManager.js"; 2 | import { UserInput } from "../config.js"; 3 | 4 | export function createViteCommand(input: UserInput) { 5 | const { packageManager, appConfig, projectName } = input; 6 | 7 | const parts: string[] = [resolvePacakgeManager(packageManager)]; 8 | 9 | if (packageManager === "npm") { 10 | parts.push("create-vite@latest"); 11 | } else { 12 | parts.push("create vite"); 13 | } 14 | 15 | parts.push(projectName); 16 | parts.push("--template", appConfig.templateId); 17 | return parts.join(" "); 18 | } 19 | -------------------------------------------------------------------------------- /src/cli/config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-empty-function */ 2 | import chalk from "chalk"; 3 | import { PKG_ROOT } from "./../constants"; 4 | import fs from "fs-extra"; 5 | import path from "path"; 6 | import { PackageManager } from "../utils/getPackageManager.js"; 7 | import { createViteCommand } from "./commands/createVite.js"; 8 | import { createNextCommand } from "./commands/createNext.js"; 9 | import { createAstroCommand } from "./commands/createAstro.js"; 10 | import { createNuxtCommand } from "./commands/createNuxt.js"; 11 | import createSvelteCommand from "./commands/createSvelte.js"; 12 | import createSolidCommand from "./commands/createSolid.js"; 13 | 14 | /** 15 | * Node dependency 16 | */ 17 | export interface Dependency { 18 | package: string; 19 | type: "dev" | "prod"; 20 | } 21 | 22 | /** 23 | * Tailwind CSS Plugin definition 24 | */ 25 | export interface Plugin { 26 | package: string; 27 | addConfigImport: boolean; 28 | } 29 | 30 | /** 31 | * The extra dependencies that we allow to select from when creating a new application. 32 | */ 33 | export const supportedDependencies: readonly Dependency[] = [ 34 | { 35 | package: "prettier", 36 | type: "dev", 37 | }, 38 | { 39 | package: "clsx", 40 | type: "dev", 41 | }, 42 | { 43 | package: "tailwind-merge", 44 | type: "prod", 45 | }, 46 | ]; 47 | 48 | /** 49 | * The TailwindCSS plugins that we allow to select from when creating a new application. 50 | */ 51 | export const supportedPlugins: readonly Plugin[] = [ 52 | { 53 | package: "@tailwindcss/typography", 54 | addConfigImport: true, 55 | }, 56 | { 57 | package: "@tailwindcss/forms", 58 | addConfigImport: true, 59 | }, 60 | { 61 | package: "@tailwindcss/aspect-ratio", 62 | addConfigImport: true, 63 | }, 64 | { 65 | package: "@tailwindcss/line-clamp", 66 | addConfigImport: true, 67 | }, 68 | { 69 | package: "daisyui", 70 | addConfigImport: true, 71 | }, 72 | { 73 | package: "prettier-plugin-tailwindcss", 74 | addConfigImport: true, 75 | }, 76 | ]; 77 | 78 | /** 79 | * The app ids that we currently support. 80 | */ 81 | export const supportedTemplateIds = [ 82 | "nextjs", 83 | "nuxtjs-ts", 84 | "react", 85 | "react-ts", 86 | "vue", 87 | "vue-ts", 88 | "astro", 89 | "astro-ts", 90 | "svelte-kit", 91 | "svelte-kit-ts", 92 | "preact", 93 | "preact-ts", 94 | "solid", 95 | "solid-ts", 96 | "nextjs-ts", 97 | "nuxtjs", 98 | "vanilla", 99 | "vanilla-ts", 100 | ] as const; 101 | 102 | export type Dependencies = (typeof supportedDependencies)[number]; 103 | export type Plugins = (typeof supportedPlugins)[number]; 104 | export type TemplateId = (typeof supportedTemplateIds)[number]; 105 | export type Language = "ts" | "js"; 106 | 107 | /** 108 | * The user input that is passed to the CLI. 109 | */ 110 | export interface UserInput { 111 | // The name of the project specified by the user, either from arguments or read from stdin. 112 | projectName: string; 113 | 114 | // Additional dependencies to install specified by the user. 115 | dependencies: Dependencies[]; 116 | 117 | // TailwindCSS plugins to install specified by the user. 118 | plugins: Plugins[]; 119 | 120 | // The package manager to use, it is the one the user used to run the CLI 121 | packageManager: PackageManager; 122 | 123 | // The directory to create the application in. Calculated based on the app name. 124 | projectDir: string; 125 | 126 | // The app config by app id. 127 | appConfig: AppConfig; 128 | } 129 | 130 | export interface AppConfig { 131 | templateId: TemplateId; 132 | displayName: string; 133 | dependencies?: Dependencies[]; 134 | plugins?: Plugins[]; 135 | language: Language; 136 | templateDir: string; 137 | scaffoldingTool: string; 138 | twConfigExtension: string; 139 | twDependencies?: readonly Dependency[]; 140 | skipTailwindInstall?: boolean; 141 | copyTemplate: (userInput: UserInput) => Promise; 142 | deleteFiles?: (userInput: UserInput) => Promise; 143 | getCssOutputPath: (userInput: UserInput) => string; 144 | createInstallCommand: (userInput: UserInput) => string | Promise; 145 | } 146 | 147 | export const NEXTJS_CONFIG: AppConfig = { 148 | templateId: "nextjs", 149 | displayName: `Next.js ${chalk.dim("(create-next-app)")}`, 150 | language: "js", 151 | templateDir: path.join(PKG_ROOT, "templates/nextjs"), 152 | scaffoldingTool: "create-next-app", 153 | twConfigExtension: ".js", 154 | copyTemplate: async ({ projectDir }) => { 155 | await fs.copy( 156 | path.join(NEXTJS_CONFIG.templateDir, "index.jsx"), 157 | path.join(projectDir, "pages", "index.js"), 158 | ); 159 | }, 160 | deleteFiles: async ({ projectDir }) => { 161 | await fs.remove(path.join(projectDir, "styles/Home.module.css")); 162 | }, 163 | getCssOutputPath: ({ projectDir }) => { 164 | return path.join(projectDir, "styles", "globals.css"); 165 | }, 166 | createInstallCommand: createNextCommand, 167 | }; 168 | 169 | export const NEXTJS_TS_CONFIG: AppConfig = { 170 | templateId: "nextjs-ts", 171 | displayName: `${chalk.bold("Next.js TS")} ${chalk.dim("(create-next-app)")}`, 172 | language: "ts", 173 | templateDir: path.join(PKG_ROOT, "templates/nextjs-ts"), 174 | scaffoldingTool: "create-next-app", 175 | twConfigExtension: ".js", 176 | copyTemplate: async ({ projectDir }) => { 177 | await fs.copy( 178 | path.join(NEXTJS_TS_CONFIG.templateDir, "index.tsx"), 179 | path.join(projectDir, "pages", "index.tsx"), 180 | ); 181 | }, 182 | deleteFiles: async ({ projectDir }) => { 183 | await fs.remove(path.join(projectDir, "styles/Home.module.css")); 184 | }, 185 | getCssOutputPath: ({ projectDir }) => { 186 | return path.join(projectDir, "styles", "globals.css"); 187 | }, 188 | createInstallCommand: createNextCommand, 189 | }; 190 | 191 | export const NUXTJS_CONFIG: AppConfig = { 192 | templateId: "nuxtjs", 193 | displayName: `Nuxt ${chalk.dim("(nuxi init)")}`, 194 | language: "js", 195 | templateDir: path.join(PKG_ROOT, "templates/nuxtjs"), 196 | scaffoldingTool: "nuxi init", 197 | twConfigExtension: ".js", 198 | dependencies: [ 199 | { 200 | package: "@nuxtjs/tailwindcss", 201 | type: "dev", 202 | }, 203 | ], 204 | copyTemplate: async ({ projectDir }) => { 205 | await fs.copy( 206 | path.join(NUXTJS_CONFIG.templateDir, "app.vue"), 207 | path.join(projectDir, "app.vue"), 208 | ); 209 | 210 | await fs.copy( 211 | path.join(NUXTJS_CONFIG.templateDir, "pages", "index.vue"), 212 | path.join(projectDir, "pages", "index.vue"), 213 | ); 214 | 215 | await fs.copy( 216 | path.join(NUXTJS_CONFIG.templateDir, "nuxt.config.js"), 217 | path.join(projectDir, "nuxt.config.js"), 218 | ); 219 | }, 220 | deleteFiles: async ({ projectDir }) => { 221 | await fs.remove(path.join(projectDir, "nuxt.config.ts")); 222 | }, 223 | getCssOutputPath: ({ projectDir }) => { 224 | return path.join(projectDir, "assets", "main.css"); 225 | }, 226 | createInstallCommand: createNuxtCommand, 227 | }; 228 | 229 | export const NUXTJS_TS_CONFIG: AppConfig = { 230 | templateId: "nuxtjs-ts", 231 | displayName: `${chalk.bold("Nuxt TS")} ${chalk.dim("(nuxi init)")}`, 232 | language: "ts", 233 | templateDir: path.join(PKG_ROOT, "templates/nuxtjs-ts"), 234 | scaffoldingTool: "nuxi init", 235 | twConfigExtension: ".js", 236 | dependencies: [ 237 | { 238 | package: "@nuxtjs/tailwindcss", 239 | type: "dev", 240 | }, 241 | ], 242 | copyTemplate: async ({ projectDir }) => { 243 | await fs.copy( 244 | path.join(NUXTJS_TS_CONFIG.templateDir, "app.vue"), 245 | path.join(projectDir, "app.vue"), 246 | ); 247 | 248 | await fs.copy( 249 | path.join(NUXTJS_TS_CONFIG.templateDir, "pages", "index.vue"), 250 | path.join(projectDir, "pages", "index.vue"), 251 | ); 252 | 253 | await fs.copy( 254 | path.join(NUXTJS_TS_CONFIG.templateDir, "nuxt.config.ts"), 255 | path.join(projectDir, "nuxt.config.ts"), 256 | ); 257 | }, 258 | getCssOutputPath: ({ projectDir }) => { 259 | return path.join(projectDir, "assets", "main.css"); 260 | }, 261 | createInstallCommand: createNuxtCommand, 262 | }; 263 | 264 | export const VANILLA_CONFIG: AppConfig = { 265 | templateId: "vanilla", 266 | displayName: `Vanilla ${chalk.dim("(create-vite)")}`, 267 | language: "js", 268 | templateDir: path.join(PKG_ROOT, "templates/vanilla"), 269 | scaffoldingTool: "create-vite", 270 | twConfigExtension: ".cjs", 271 | copyTemplate: async ({ projectDir }) => { 272 | await fs.copy( 273 | path.join(VANILLA_CONFIG.templateDir, "index.html"), 274 | path.join(projectDir, "index.html"), 275 | ); 276 | await fs.copy( 277 | path.join(VANILLA_CONFIG.templateDir, "main.js"), 278 | path.join(projectDir, "main.js"), 279 | ); 280 | }, 281 | getCssOutputPath: ({ projectDir }) => { 282 | return path.join(projectDir, "style.css"); 283 | }, 284 | createInstallCommand: createViteCommand, 285 | }; 286 | 287 | export const VANILLA_TS_CONFIG: AppConfig = { 288 | templateId: "vanilla-ts", 289 | displayName: `Vanilla ${chalk.dim("(TypeScirpt, create-vite)")}`, 290 | language: "ts", 291 | templateDir: path.join(PKG_ROOT, "templates/vanilla-ts"), 292 | scaffoldingTool: "create-vite", 293 | twConfigExtension: ".cjs", 294 | copyTemplate: async ({ projectDir }) => { 295 | await fs.copy( 296 | path.join(VANILLA_TS_CONFIG.templateDir, "index.html"), 297 | path.join(projectDir, "index.html"), 298 | ); 299 | await fs.copy( 300 | path.join(VANILLA_TS_CONFIG.templateDir, "main.ts"), 301 | path.join(projectDir, "src/main.ts"), 302 | ); 303 | }, 304 | deleteFiles: async ({ projectDir }) => { 305 | await fs.remove(path.join(projectDir, "src/counter.ts")); 306 | await fs.remove(path.join(projectDir, "src/typescript.svg")); 307 | }, 308 | getCssOutputPath: ({ projectDir }) => { 309 | return path.join(projectDir, "src", "style.css"); 310 | }, 311 | createInstallCommand: createViteCommand, 312 | }; 313 | 314 | export const REACT_CONFIG: AppConfig = { 315 | templateId: "react", 316 | displayName: `React ${chalk.dim("(create-vite)")}`, 317 | language: "js", 318 | templateDir: path.join(PKG_ROOT, "templates/react"), 319 | scaffoldingTool: "create-vite", 320 | twConfigExtension: ".cjs", 321 | copyTemplate: async ({ projectDir }) => { 322 | await fs.copy( 323 | path.join(REACT_CONFIG.templateDir, "App.jsx"), 324 | path.join(projectDir, "src/App.jsx"), 325 | ); 326 | }, 327 | getCssOutputPath: ({ projectDir }) => { 328 | return path.join(projectDir, "src/index.css"); 329 | }, 330 | deleteFiles: async ({ projectDir }) => { 331 | await fs.remove(path.join(projectDir, "src/app.css")); 332 | }, 333 | createInstallCommand: createViteCommand, 334 | }; 335 | 336 | export const REACT_TS_CONFIG: AppConfig = { 337 | templateId: "react-ts", 338 | displayName: `React ${chalk.dim("(TypeScript, create-vite)")}`, 339 | language: "js", 340 | templateDir: path.join(PKG_ROOT, "templates/react-ts"), 341 | scaffoldingTool: "create-vite", 342 | twConfigExtension: ".cjs", 343 | copyTemplate: async ({ projectDir }) => { 344 | await fs.copy( 345 | path.join(REACT_TS_CONFIG.templateDir, "App.tsx"), 346 | path.join(projectDir, "src/App.tsx"), 347 | ); 348 | }, 349 | getCssOutputPath: ({ projectDir }) => { 350 | return path.join(projectDir, "src/index.css"); 351 | }, 352 | deleteFiles: async ({ projectDir }) => { 353 | await fs.remove(path.join(projectDir, "src/app.css")); 354 | }, 355 | createInstallCommand: createViteCommand, 356 | }; 357 | 358 | export const VUE_CONFIG: AppConfig = { 359 | templateId: "vue", 360 | displayName: `Vue ${chalk.dim("(create-vite)")}`, 361 | language: "js", 362 | templateDir: path.join(PKG_ROOT, "templates/vue"), 363 | scaffoldingTool: "create-vite", 364 | twConfigExtension: ".cjs", 365 | copyTemplate: async ({ projectDir }) => { 366 | await fs.copy( 367 | path.join(VUE_CONFIG.templateDir, "App.vue"), 368 | path.join(projectDir, "src/App.vue"), 369 | ); 370 | }, 371 | getCssOutputPath: ({ projectDir }) => { 372 | return path.join(projectDir, "src/style.css"); 373 | }, 374 | deleteFiles: async ({ projectDir }) => { 375 | await fs.remove(path.join(projectDir, "src/components")); 376 | }, 377 | createInstallCommand: createViteCommand, 378 | }; 379 | 380 | export const VUE_TS_CONFIG: AppConfig = { 381 | templateId: "vue-ts", 382 | displayName: `Vue ${chalk.dim("(TypeScript, create-vite)")}`, 383 | language: "ts", 384 | templateDir: path.join(PKG_ROOT, "templates/vue-ts"), 385 | scaffoldingTool: "create-vite", 386 | twConfigExtension: ".cjs", 387 | copyTemplate: async ({ projectDir }) => { 388 | await fs.copy( 389 | path.join(VUE_CONFIG.templateDir, "App.vue"), 390 | path.join(projectDir, "src/App.vue"), 391 | ); 392 | }, 393 | getCssOutputPath: ({ projectDir }) => { 394 | return path.join(projectDir, "src/style.css"); 395 | }, 396 | deleteFiles: async ({ projectDir }) => { 397 | await fs.remove(path.join(projectDir, "src/components")); 398 | }, 399 | createInstallCommand: createViteCommand, 400 | }; 401 | 402 | export const ASTRO_CONFIG: AppConfig = { 403 | templateId: "astro", 404 | displayName: `Astro ${chalk.dim("(create-astro)")}`, 405 | language: "js", 406 | templateDir: path.join(PKG_ROOT, "templates/astro"), 407 | scaffoldingTool: "create-astro", 408 | twConfigExtension: ".cjs", 409 | skipTailwindInstall: true, 410 | copyTemplate: async () => {}, 411 | getCssOutputPath: () => "", 412 | createInstallCommand: createAstroCommand, 413 | deleteFiles: async () => {}, 414 | }; 415 | 416 | export const ASTRO_TS_CONFIG: AppConfig = { 417 | ...ASTRO_CONFIG, 418 | }; 419 | 420 | export const SVELTE_KIT_CONFIG: AppConfig = { 421 | templateId: "svelte-kit", 422 | displayName: `Svelte Kit ${chalk.dim("(create-svelte)")}`, 423 | language: "js", 424 | templateDir: path.join(PKG_ROOT, "templates/svelte-kit"), 425 | scaffoldingTool: "create-svelte", 426 | twConfigExtension: ".cjs", 427 | twDependencies: [ 428 | { 429 | package: "svelte-preprocess", 430 | type: "dev", 431 | }, 432 | ], 433 | copyTemplate: async ({ projectDir }) => { 434 | await fs.copy( 435 | path.join(SVELTE_KIT_CONFIG.templateDir, "+layout.svelte"), 436 | path.join(projectDir, "src/routes/+layout.svelte"), 437 | ); 438 | await fs.copy( 439 | path.join(SVELTE_KIT_CONFIG.templateDir, "+page.svelte"), 440 | path.join(projectDir, "src/routes/+page.svelte"), 441 | ); 442 | await fs.copy( 443 | path.join(SVELTE_KIT_CONFIG.templateDir, "svelte.config.js"), 444 | path.join(projectDir, "svelte.config.js"), 445 | ); 446 | }, 447 | getCssOutputPath: ({ projectDir }) => { 448 | return path.join(projectDir, "src/style.css"); 449 | }, 450 | createInstallCommand: createSvelteCommand, 451 | deleteFiles: async () => {}, 452 | }; 453 | 454 | export const SVELTE_KIT_TS_CONFIG: AppConfig = { 455 | ...SVELTE_KIT_CONFIG, 456 | }; 457 | 458 | export const PREACT_CONFIG: AppConfig = { 459 | templateId: "preact", 460 | displayName: `Preact ${chalk.dim("(create-vite)")}`, 461 | language: "js", 462 | templateDir: path.join(PKG_ROOT, "templates/preact"), 463 | scaffoldingTool: "create-vite", 464 | twConfigExtension: ".cjs", 465 | copyTemplate: async ({ projectDir }) => { 466 | await fs.copy( 467 | path.join(PREACT_CONFIG.templateDir, "index.js"), 468 | path.join(projectDir, "src/app.jsx"), 469 | ); 470 | }, 471 | getCssOutputPath: ({ projectDir }) => { 472 | return path.join(projectDir, "src/index.css"); 473 | }, 474 | createInstallCommand: createViteCommand, 475 | deleteFiles: async ({ projectDir }) => { 476 | await fs.remove(path.join(projectDir, "src/app.css")); 477 | }, 478 | }; 479 | 480 | export const PREACT_TS_CONFIG: AppConfig = { 481 | templateId: "preact-ts", 482 | displayName: `Preact ${chalk.dim("(TypeScirpt, create-vite)")}`, 483 | language: "ts", 484 | templateDir: path.join(PKG_ROOT, "templates/preact-ts"), 485 | scaffoldingTool: "create-vite", 486 | twConfigExtension: ".cjs", 487 | copyTemplate: async ({ projectDir }) => { 488 | await fs.copy( 489 | path.join(PREACT_TS_CONFIG.templateDir, "index.js"), 490 | path.join(projectDir, "src/app.tsx"), 491 | ); 492 | }, 493 | deleteFiles: async ({ projectDir }) => { 494 | await fs.remove(path.join(projectDir, "src/app.css")); 495 | }, 496 | getCssOutputPath: ({ projectDir }) => { 497 | return path.join(projectDir, "src", "index.css"); 498 | }, 499 | createInstallCommand: createViteCommand, 500 | }; 501 | 502 | export const SOLID_CONFIG: AppConfig = { 503 | templateId: "solid", 504 | displayName: `Solid ${chalk.dim("(degit solidjs/templates/js)")}`, 505 | language: "js", 506 | templateDir: path.join(PKG_ROOT, "templates/solid"), 507 | scaffoldingTool: "degit", 508 | twConfigExtension: ".cjs", 509 | deleteFiles: async ({ projectDir }) => { 510 | await fs.remove(path.join(projectDir, "pnpm-lock.yaml")); 511 | await fs.remove(path.join(projectDir, "src/assets")); 512 | await fs.remove(path.join(projectDir, "src/App.module.css")); 513 | await fs.remove(path.join(projectDir, "src/logo.svg")); 514 | }, 515 | copyTemplate: async ({ projectDir }) => { 516 | await fs.copy( 517 | path.join(SOLID_CONFIG.templateDir, "App.jsx"), 518 | path.join(projectDir, "src/App.jsx"), 519 | ); 520 | }, 521 | getCssOutputPath: ({ projectDir }) => { 522 | return path.join(projectDir, "src", "index.css"); 523 | }, 524 | createInstallCommand: createSolidCommand, 525 | }; 526 | 527 | export const SOLID_TS_CONFIG: AppConfig = { 528 | ...SOLID_CONFIG, 529 | templateId: "solid-ts", 530 | displayName: `Solid ${chalk.dim("(degit solidjs/templates/ts)")}`, 531 | language: "ts", 532 | templateDir: path.join(PKG_ROOT, "templates/solid-ts"), 533 | copyTemplate: async ({ projectDir }) => { 534 | await fs.copy( 535 | path.join(SOLID_TS_CONFIG.templateDir, "App.tsx"), 536 | path.join(projectDir, "src/App.tsx"), 537 | ); 538 | }, 539 | }; 540 | 541 | export const CONFIG_BY_ID: Record = { 542 | nextjs: NEXTJS_CONFIG, 543 | "nextjs-ts": NEXTJS_TS_CONFIG, 544 | nuxtjs: NUXTJS_CONFIG, 545 | "nuxtjs-ts": NUXTJS_TS_CONFIG, 546 | vanilla: VANILLA_CONFIG, 547 | "vanilla-ts": VANILLA_TS_CONFIG, 548 | react: REACT_CONFIG, 549 | "react-ts": REACT_TS_CONFIG, 550 | vue: VUE_CONFIG, 551 | "vue-ts": VUE_TS_CONFIG, 552 | astro: ASTRO_CONFIG, 553 | "astro-ts": ASTRO_TS_CONFIG, 554 | "svelte-kit": SVELTE_KIT_CONFIG, 555 | "svelte-kit-ts": SVELTE_KIT_TS_CONFIG, 556 | preact: PREACT_CONFIG, 557 | "preact-ts": PREACT_TS_CONFIG, 558 | solid: SOLID_CONFIG, 559 | "solid-ts": SOLID_TS_CONFIG, 560 | }; 561 | 562 | export const getConfig = (configId: string) => CONFIG_BY_ID[configId]; 563 | -------------------------------------------------------------------------------- /src/cli/output/addPluginsTransformer.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import * as recast from "recast"; 3 | import { Plugin } from "../config.js"; 4 | 5 | /** 6 | * Create recast transformer that adds tailwind plugins to tailwind.config.js 7 | * 8 | * @param plugins tailwind plugins 9 | * @returns transformer function that adds tailwind plugins to tailwind.config.js 10 | */ 11 | const addPluginsTransformer = (plugins: Plugin[]) => { 12 | return (code: any) => { 13 | const ast = recast.parse(code); 14 | 15 | recast.visit(ast, { 16 | visitProperty(path: any) { 17 | if (path.node.key.name === "plugins") { 18 | path.node.value.elements = [ 19 | ...path.node.value.elements, 20 | ...plugins 21 | .filter((p) => p.addConfigImport) 22 | .map((plugin) => recast.parse(`require("${plugin.package}")`)), 23 | ]; 24 | } 25 | this.traverse(path); 26 | }, 27 | }); 28 | 29 | return recast.print(ast).code; 30 | }; 31 | }; 32 | 33 | export default addPluginsTransformer; 34 | -------------------------------------------------------------------------------- /src/cli/output/createProject.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "./../../utils/logger"; 2 | import { spawn } from "child_process"; 3 | import chalk from "chalk"; 4 | import { UserInput } from "../config.js"; 5 | 6 | /** 7 | * Create and execute the command to install the project. 8 | * 9 | * @param input CLI input 10 | */ 11 | export default async function createProject(input: UserInput) { 12 | const { appConfig } = input; 13 | const command = appConfig.createInstallCommand(input); 14 | 15 | logger.info( 16 | `\nInstalling project using ${chalk.green( 17 | input.appConfig.scaffoldingTool, 18 | )}\n`, 19 | ); 20 | 21 | if (typeof command === "string") { 22 | const child = spawn(command, { 23 | stdio: "inherit", 24 | shell: true, 25 | }); 26 | 27 | await new Promise((resolve, reject) => { 28 | child.on("error", reject); 29 | child.on("close", (code) => { 30 | resolve(code); 31 | }); 32 | }); 33 | } 34 | 35 | if (typeof command === "function") { 36 | await command(); 37 | } 38 | 39 | logger.log( 40 | `${chalk.bold.green("✔")} Project created using ${chalk.green.bold( 41 | appConfig.scaffoldingTool, 42 | )}`, 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /src/cli/output/installDependencies.ts: -------------------------------------------------------------------------------- 1 | import { Dependency, UserInput } from "./../config"; 2 | import fs from "fs-extra"; 3 | import ora from "ora"; 4 | import path from "path"; 5 | import { COMMON_TEMPLATES_ROOT } from "../../constants.js"; 6 | import getPackageManager from "../../utils/getPackageManager.js"; 7 | import installPackages from "../../utils/installPackages.js"; 8 | 9 | /** 10 | * Install dependencies for the project. 11 | * 12 | * @param input CLI input 13 | * @param projectDir Path to the project directory 14 | */ 15 | export default async function installDependencies(input: UserInput) { 16 | const { plugins, projectDir, appConfig } = input; 17 | 18 | const devDependencies = input.dependencies 19 | .filter(filterDevDependency) 20 | .map(mapPackage); 21 | 22 | const appConfigDevDependencies = 23 | appConfig.dependencies?.filter(filterDevDependency).map(mapPackage) ?? []; 24 | 25 | // TODO: Add support for appConfig.dependencies 26 | const dependencies = input.dependencies 27 | .filter(filterProdDependency) 28 | .map(mapPackage); 29 | 30 | const twDependencies = 31 | input.appConfig.twDependencies?.map((d) => d.package) ?? []; 32 | 33 | const twPlugins = plugins.map((p) => p.package); 34 | 35 | const devPackages = [ 36 | "tailwindcss", 37 | "postcss", 38 | "autoprefixer", 39 | ...devDependencies, 40 | ...twDependencies, 41 | ...twPlugins, 42 | ...appConfigDevDependencies, 43 | ]; 44 | 45 | const spinner = ora(`Installing dependencies`).start(); 46 | 47 | await installPackages({ 48 | dev: true, 49 | projectDir, 50 | packageManager: getPackageManager(), 51 | packages: devPackages, 52 | }); 53 | 54 | await installPackages({ 55 | dev: false, 56 | projectDir, 57 | packageManager: getPackageManager(), 58 | packages: dependencies, 59 | }); 60 | 61 | spinner.succeed(`Dependencies installed`); 62 | 63 | // If prettier is in dependencies create prettier config and prettier ignore files 64 | if (devDependencies.includes("prettier")) { 65 | const rc = path.join(COMMON_TEMPLATES_ROOT, ".prettierrc"); 66 | const ignore = path.join(COMMON_TEMPLATES_ROOT, ".prettierignore"); 67 | 68 | spinner.start(`Creating .prettierrc and .prettierignore`).start(); 69 | await fs.copy(rc, path.join(projectDir, ".prettierrc")); 70 | await fs.copy(ignore, path.join(projectDir, ".prettierignore")); 71 | spinner.succeed(`.prettierrc and .prettierignore created`); 72 | } 73 | } 74 | 75 | const filterDevDependency = (d: Dependency) => d.type === "dev"; 76 | 77 | const filterProdDependency = (d: Dependency) => d.type === "prod"; 78 | 79 | const mapPackage = (d: Dependency) => d.package; 80 | -------------------------------------------------------------------------------- /src/cli/output/installTailwind.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs-extra"; 2 | import ora from "ora"; 3 | import path from "path"; 4 | import { COMMON_TEMPLATES_ROOT } from "../../constants.js"; 5 | import addPluginsTransformer from "./addPluginsTransformer.js"; 6 | import { UserInput } from "../config.js"; 7 | 8 | /** 9 | * Install all Tailwind dependencies, create config files and copy templates. 10 | * 11 | * @param input CLI input 12 | */ 13 | export default async function installTailwind(input: UserInput) { 14 | if (input.appConfig.skipTailwindInstall) { 15 | return; 16 | } 17 | 18 | await createTailwindConfig(input); 19 | await createPostCssConfig(input); 20 | await copyTailwindDirectives(input); 21 | await copyTailwindTemplate(input); 22 | await deleteFiles(input); 23 | } 24 | 25 | async function createTailwindConfig(input: UserInput) { 26 | const { appConfig, projectDir } = input; 27 | const { templateDir } = appConfig; 28 | 29 | const fileName = getConfigFileName("tailwind.config", input); 30 | const tailwindConfig = path.join(templateDir, "tailwind.config.js"); 31 | const spinner = ora(`Creating ${fileName}`).start(); 32 | 33 | await fs.copy(tailwindConfig, path.join(projectDir, fileName)); 34 | 35 | // Parse tailwind.config.js 36 | const tailwindConfigSource = await fs.readFile( 37 | path.join(projectDir, fileName), 38 | ); 39 | const transformer = addPluginsTransformer(input.plugins); 40 | const transformed = transformer(tailwindConfigSource); 41 | 42 | await fs.writeFile(path.join(projectDir, fileName), transformed); 43 | 44 | spinner.succeed(`${fileName} created`); 45 | } 46 | 47 | async function createPostCssConfig(input: UserInput) { 48 | const { appConfig, projectDir } = input; 49 | const { templateDir } = appConfig; 50 | const fileName = getConfigFileName("postcss.config", input); 51 | const postCssConfig = path.join(templateDir, "postcss.config.js"); 52 | const spinner = ora(`Creating ${fileName}`).start(); 53 | 54 | await fs.copy(postCssConfig, path.join(projectDir, fileName)); 55 | spinner.succeed(`${fileName} created`); 56 | } 57 | 58 | async function copyTailwindDirectives(input: UserInput) { 59 | const { appConfig } = input; 60 | const directives = path.join(COMMON_TEMPLATES_ROOT, "directives.css"); 61 | const spinner = ora(`Copying Tailwind directives`).start(); 62 | 63 | await fs.copy(directives, appConfig.getCssOutputPath(input)); 64 | spinner.succeed(`Added Tailwind directives`); 65 | } 66 | 67 | async function copyTailwindTemplate(input: UserInput) { 68 | const { appConfig } = input; 69 | const spinner = ora(`Copying Tailwind template`).start(); 70 | 71 | await appConfig.copyTemplate(input); 72 | spinner.succeed(`Added Tailwind template`); 73 | } 74 | 75 | async function deleteFiles(input: UserInput) { 76 | const spinner = ora(`Deleting unneeded files`).start(); 77 | 78 | await input.appConfig.deleteFiles?.(input); 79 | spinner.succeed("Deleted unneeded files"); 80 | } 81 | 82 | function getConfigFileName(fileName: string, input: UserInput) { 83 | return `${fileName}${input.appConfig.twConfigExtension}`; 84 | } 85 | -------------------------------------------------------------------------------- /src/cli/readInput.ts: -------------------------------------------------------------------------------- 1 | import { logger } from "./../utils/logger"; 2 | import getPackageManager from "./../utils/getPackageManager"; 3 | import inquirer from "inquirer"; 4 | import { Command } from "commander"; 5 | import { getVersion } from "../utils/getVersion"; 6 | import validateProjectName from "../utils/validateAppName.js"; 7 | import chalk from "chalk"; 8 | import { APP_NAME } from "../constants.js"; 9 | import { 10 | TemplateId, 11 | supportedTemplateIds, 12 | UserInput, 13 | NEXTJS_CONFIG, 14 | getConfig, 15 | supportedDependencies, 16 | supportedPlugins, 17 | Language, 18 | } from "./config.js"; 19 | import path from "path"; 20 | import fs from "fs-extra"; 21 | 22 | const DEFAULTS: UserInput = { 23 | projectName: "tailwind-app", 24 | plugins: [], 25 | dependencies: [], 26 | packageManager: getPackageManager(), 27 | projectDir: "", 28 | appConfig: NEXTJS_CONFIG, 29 | }; 30 | 31 | export async function readInput() { 32 | const input = { ...DEFAULTS }; 33 | const program = new Command().name(APP_NAME); 34 | 35 | program 36 | .description( 37 | "A CLI for quickly creating applications based on Tailwind CSS", 38 | ) 39 | .argument("[app]", "The name of the application") 40 | .option("--template ", "The template to use") 41 | .option("--config ", "The path to the test config") 42 | .version(getVersion()) 43 | .parse(process.argv); 44 | 45 | const { template: templateId, config } = program.opts(); 46 | 47 | if (config) { 48 | const configPath = path.resolve(process.cwd(), config); 49 | const configJson = fs.readFileSync(configPath, "utf-8"); 50 | const values = JSON.parse(configJson); 51 | 52 | return { 53 | ...DEFAULTS, 54 | ...values, 55 | appConfig: getConfig(values.appConfig), 56 | }; 57 | } 58 | 59 | // Get project name from the first argument or prompt for it 60 | input.projectName = program.args[0] ?? (await readProjectName()); 61 | 62 | // If template id was provided in options, check if it is supported 63 | // If not, prompt for template id interactively 64 | if (await checkTemplateSupport(templateId)) { 65 | const tempateConfig = getConfig(templateId); 66 | 67 | if (!tempateConfig) { 68 | throw new Error(`Unknown template id: ${templateId}`); 69 | } 70 | 71 | input.appConfig = tempateConfig; 72 | } else { 73 | // We filter out the TS templates, since we select the language in the next step 74 | const tid = await readTemplateId( 75 | supportedTemplateIds.filter((id) => !id.includes("ts")), 76 | ); 77 | const language = await readLanguage(); 78 | const templateIdKey = `${tid}${language === "ts" ? "-ts" : ""}`; 79 | const templateConfig = getConfig(templateIdKey); 80 | 81 | if (!templateConfig) { 82 | throw new Error(`Unknown template id: ${templateIdKey}`); 83 | } 84 | 85 | input.appConfig = templateConfig; 86 | } 87 | 88 | input.dependencies = await readDependencies(); 89 | input.plugins = await readPlugins(); 90 | input.projectDir = path.resolve(process.cwd(), input.projectName); 91 | 92 | return input; 93 | } 94 | 95 | async function readProjectName() { 96 | const { projectName } = await inquirer.prompt>( 97 | { 98 | name: "projectName", 99 | type: "input", 100 | message: "Project name", 101 | default: DEFAULTS.projectName, 102 | validate: validateProjectName, 103 | transformer: (i: string) => { 104 | return i.trim(); 105 | }, 106 | }, 107 | ); 108 | 109 | return projectName; 110 | } 111 | 112 | async function readTemplateId(types: TemplateId[]) { 113 | const { templateId } = await inquirer.prompt<{ 114 | templateId: string; 115 | }>({ 116 | name: "templateId", 117 | type: "list", 118 | message: "App type", 119 | choices: types.map((t) => ({ 120 | name: getConfig(t)?.displayName, 121 | value: t, 122 | })), 123 | pageSize: types.length, 124 | default: "nextjs", 125 | }); 126 | 127 | return templateId; 128 | } 129 | 130 | async function readDependencies() { 131 | const { dependencies } = await inquirer.prompt< 132 | Pick 133 | >({ 134 | name: "dependencies", 135 | type: "checkbox", 136 | message: "Which dependencies would you like to include?", 137 | choices: supportedDependencies.map((dependency) => ({ 138 | name: dependency.package, 139 | checked: false, 140 | value: dependency, 141 | })), 142 | }); 143 | 144 | return dependencies; 145 | } 146 | 147 | async function readLanguage() { 148 | const { language } = await inquirer.prompt<{ language: Language }>({ 149 | name: "language", 150 | type: "list", 151 | message: "What language will your project be written in?", 152 | choices: [ 153 | { name: "TypeScript", value: "ts", short: "ts" }, 154 | { name: "JavaScript", value: "js", short: "js" }, 155 | ], 156 | default: "typescript", 157 | }); 158 | 159 | return language; 160 | } 161 | 162 | async function readPlugins() { 163 | const { plugins } = await inquirer.prompt>({ 164 | name: "plugins", 165 | type: "checkbox", 166 | message: "Which plugins would you like to include?", 167 | choices: supportedPlugins.map((dependency) => ({ 168 | name: dependency.package, 169 | checked: false, 170 | value: dependency, 171 | })), 172 | }); 173 | 174 | return plugins; 175 | } 176 | 177 | /** 178 | * Check if the template provided in options is supported 179 | * @param templateId the id of the template to check i.e. "nextjs", "vanilla-ts", etc. 180 | * @returns true if the template is supported, false otherwise 181 | */ 182 | async function checkTemplateSupport(templateId: string) { 183 | const templateSupported = supportedTemplateIds.includes( 184 | templateId as TemplateId, 185 | ); 186 | 187 | if (templateId && !templateSupported) { 188 | logger.error(`Unknown template: ${templateId}\n`); 189 | logger.info( 190 | `Currently supported templates:\n${chalk.green( 191 | supportedTemplateIds.join("\n"), 192 | )}`, 193 | ); 194 | logger.info( 195 | `You can skip passing the template and select it interactively.\n`, 196 | ); 197 | 198 | const answer = await inquirer.prompt({ 199 | name: "continue", 200 | type: "confirm", 201 | message: "Would you like to continue with interactive mode?", 202 | }); 203 | 204 | if (!answer.continue) { 205 | process.exit(1); 206 | } 207 | } 208 | 209 | return templateSupported; 210 | } 211 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { fileURLToPath } from "url"; 3 | 4 | export const APP_NAME = "create-tw"; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const distPath = path.dirname(__filename); 8 | export const PKG_ROOT = path.join(distPath, "../"); 9 | 10 | export const COMMON_TEMPLATES_ROOT = path.join(PKG_ROOT, "templates/common"); 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import inquirer from "inquirer"; 3 | import { readInput } from "./cli/readInput"; 4 | import getPackageManager from "./utils/getPackageManager"; 5 | import { logger } from "./utils/logger"; 6 | import chalk from "chalk"; 7 | import fs from "fs-extra"; 8 | import installTailwind from "./cli/output/installTailwind.js"; 9 | import installDependencies from "./cli/output/installDependencies.js"; 10 | import figlet from "figlet"; 11 | import createProject from "./cli/output/createProject.js"; 12 | import path from 'path' 13 | import { COMMON_TEMPLATES_ROOT } from "./constants"; 14 | 15 | process.once("SIGINT", () => { 16 | process.exit(1); 17 | }); 18 | 19 | async function main() { 20 | logger.info("\n"); 21 | logger.higlight( 22 | figlet.textSync("create tw ", { 23 | font: "Mini", 24 | }), 25 | ); 26 | 27 | logger.info(`\n${chalk.bold("Welcome to create-tw!")}`); 28 | logger.success("The easiest way to create a Tailwind project\n"); 29 | 30 | const input = await readInput(); 31 | const { projectDir, projectName } = input; 32 | const pkgManager = getPackageManager(); 33 | 34 | logger.info(`\nUsing: ${chalk.cyan.bold(pkgManager)}\n`); 35 | 36 | 37 | if (fs.existsSync(projectDir)) { 38 | // Ask to overwrite 39 | const answer = await inquirer.prompt({ 40 | name: "overwrite", 41 | type: "confirm", 42 | message: `${chalk.yellow.bold(`Directory already exists. Overwrite?`)}`, 43 | }); 44 | 45 | if (!answer.overwrite) { 46 | logger.error("Aborting..."); 47 | process.exit(1); 48 | } 49 | 50 | fs.removeSync(projectDir); 51 | } 52 | 53 | await createProject(input); 54 | 55 | // Add yarn.lock in project folder so the dependencies installation won't fail 56 | if (pkgManager === 'yarn') { 57 | await fs.copy( 58 | path.join(COMMON_TEMPLATES_ROOT, "yarn.lock"), 59 | path.join(projectDir, "yarn.lock"), 60 | ); 61 | } 62 | 63 | await installTailwind(input); 64 | await installDependencies(input); 65 | 66 | logger.info(`\nProject created in ${chalk.green.bold(projectDir)}\n`); 67 | logger.info(`${chalk.cyan.bold(`cd ${projectName}`)}`); 68 | logger.info( 69 | `${chalk.cyan.bold( 70 | `${getPackageManager()} ${ 71 | getPackageManager() === "npm" ? "run" : "" 72 | } dev`, 73 | )}\n`, 74 | ); 75 | logger.log("Happy coding!"); 76 | 77 | process.exit(0); 78 | } 79 | 80 | main().catch((e) => { 81 | logger.error(`\n${e}\n`); 82 | process.exit(1); 83 | }); 84 | -------------------------------------------------------------------------------- /src/utils/create-svelte.d.ts: -------------------------------------------------------------------------------- 1 | // Create Svelte is not typed unfortunately. 2 | // We need to maintain this manually for now. 3 | declare module "create-svelte" { 4 | interface Options { 5 | name: string; 6 | template: "default" | "skeleton"; 7 | types: "typescript" | "checkjs" | null; 8 | prettier: boolean; 9 | eslint: boolean; 10 | playwright: boolean; 11 | } 12 | 13 | export function create(cwd: string, option: Options): Promise; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/execAsync.ts: -------------------------------------------------------------------------------- 1 | import { exec } from "child_process"; 2 | import { promisify } from "util"; 3 | 4 | const execa = promisify(exec); 5 | 6 | export default execa; 7 | -------------------------------------------------------------------------------- /src/utils/getPackageManager.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | 3 | export type PackageManager = "npm" | "pnpm" | "yarn"; 4 | 5 | export default function getPackageManager(): PackageManager { 6 | try { 7 | const userAgent = process.env.npm_config_user_agent; 8 | if (userAgent) { 9 | if (userAgent.startsWith("yarn")) { 10 | return "yarn"; 11 | } else if (userAgent.startsWith("pnpm")) { 12 | return "pnpm"; 13 | } 14 | } 15 | try { 16 | execSync("yarn --version", { stdio: "ignore" }); 17 | return "yarn"; 18 | } catch { 19 | execSync("pnpm --version", { stdio: "ignore" }); 20 | return "pnpm"; 21 | } 22 | } catch { 23 | return "npm"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getVersion.ts: -------------------------------------------------------------------------------- 1 | import packageJson from "../../package.json"; 2 | 3 | export function getVersion() { 4 | return packageJson.version; 5 | } 6 | -------------------------------------------------------------------------------- /src/utils/installPackages.ts: -------------------------------------------------------------------------------- 1 | import execAsync from "./execAsync"; 2 | import { PackageManager } from "./getPackageManager"; 3 | 4 | interface Options { 5 | packageManager: PackageManager; 6 | dev: boolean; 7 | projectDir: string; 8 | packages: string[]; 9 | } 10 | 11 | export default async function installPackages(options: Options) { 12 | const { packageManager, dev, projectDir, packages } = options; 13 | 14 | if (!packages.length) { 15 | return; 16 | } 17 | 18 | const installCommand = packageManager === "npm" ? "install" : "add"; 19 | const flags = dev ? "-D" : ""; 20 | const cmd = `${ 21 | packageManager as string 22 | } ${installCommand} ${flags} ${packages.join(" ")}`; 23 | 24 | await execAsync(cmd, { cwd: projectDir }); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import chalk from "chalk"; 2 | 3 | export const logger = { 4 | log: (...args: unknown[]) => { 5 | console.log(...args); 6 | }, 7 | error(...args: unknown[]) { 8 | console.log(chalk.red(...args)); 9 | }, 10 | warn(...args: unknown[]) { 11 | console.log(chalk.yellow(...args)); 12 | }, 13 | info(...args: unknown[]) { 14 | console.log(chalk.cyan(...args)); 15 | }, 16 | success(...args: unknown[]) { 17 | console.log(chalk.green(...args)); 18 | }, 19 | higlight(...args: unknown[]) { 20 | console.log(chalk.bgBlack(...args)); 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/resolvePackageManager.ts: -------------------------------------------------------------------------------- 1 | import { PackageManager } from "./getPackageManager"; 2 | 3 | // The --yes is to automatically accept latest version prompt 4 | // We use npx to install the latest version of scaffolding tools 5 | export function resolvePacakgeManager(packageManager: PackageManager) { 6 | return packageManager === "npm" ? "npx --yes" : packageManager; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/validateAppName.ts: -------------------------------------------------------------------------------- 1 | export default function validateAppName(input: string) { 2 | if ( 3 | /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(input) 4 | ) { 5 | return true; 6 | } else { 7 | return "App name must be lowercase, alphanumeric, and only use -, _, and @"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /templates/common/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build/ 3 | dist/ -------------------------------------------------------------------------------- /templates/common/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "arrowParens": "always", 4 | "printWidth": 80, 5 | "singleQuote": false, 6 | "jsxSingleQuote": false, 7 | "semi": true, 8 | "trailingComma": "all", 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /templates/common/directives.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /templates/common/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/common/yarn.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/templates/common/yarn.lock -------------------------------------------------------------------------------- /templates/nextjs-ts/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import Script from "next/script"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 | 8 | Create Next App 9 | 10 | 11 | 12 |
13 |
14 |

15 | Create Tailwind 16 |

17 | 41 |
42 | 43 |
44 |
45 |

46 | Next.js Project Created using{" "} 47 | 52 | create-next-app 53 | 54 |

55 |

56 | Officially maintained by the creators of Next.js 57 |

58 |
59 |
60 | 61 | 68 |
69 | 18 | 70 | -------------------------------------------------------------------------------- /templates/nuxtjs-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/nuxtjs-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./components/**/*.{js,vue,ts}", 5 | "./layouts/**/*.vue", 6 | "./pages/**/*.vue", 7 | "./plugins/**/*.{js,ts}", 8 | "./nuxt.config.{js,ts}", 9 | ], 10 | theme: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | }; 15 | -------------------------------------------------------------------------------- /templates/nuxtjs/app.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /templates/nuxtjs/nuxt.config.js: -------------------------------------------------------------------------------- 1 | // https://v3.nuxtjs.org/api/configuration/nuxt.config 2 | export default defineNuxtConfig({ 3 | modules: ["@nuxtjs/tailwindcss"], 4 | css: ["@/assets/main.css"], 5 | }); 6 | -------------------------------------------------------------------------------- /templates/nuxtjs/pages/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 70 | -------------------------------------------------------------------------------- /templates/nuxtjs/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/nuxtjs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./components/**/*.{js,vue,ts}", 5 | "./layouts/**/*.vue", 6 | "./pages/**/*.vue", 7 | "./plugins/**/*.{js,ts}", 8 | "./nuxt.config.{js,ts}", 9 | ], 10 | theme: { 11 | extend: {}, 12 | }, 13 | plugins: [], 14 | }; 15 | -------------------------------------------------------------------------------- /templates/preact-ts/index.js: -------------------------------------------------------------------------------- 1 | export function App() { 2 | return ( 3 |
4 |

Create Tailwind

5 |

6 | If you like this project, consider giving it a star on GitHub! 7 |

8 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /templates/preact-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/preact-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/preact/index.js: -------------------------------------------------------------------------------- 1 | export function App() { 2 | return ( 3 |
4 |

Create Tailwind

5 |

6 | If you like this project, consider giving it a star on GitHub! 7 |

8 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /templates/preact/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/preact/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,ts,jsx,tsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/react-ts/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | function App() { 4 | useEffect(() => { 5 | const script = document.createElement("script"); 6 | 7 | script.src = "https://buttons.github.io/buttons.js"; 8 | script.async = true; 9 | 10 | document.body.appendChild(script); 11 | 12 | return () => { 13 | document.body.removeChild(script); 14 | }; 15 | }, []); 16 | 17 | return ( 18 |
19 |
20 |
21 |

22 | Create Tailwind 23 |

24 |

25 | Please support this project by starring the repository on GitHub. 26 |

27 | 50 |
51 | 52 |
53 |
54 |

55 | React.js Project Created using{" "} 56 | 61 | Vite 62 | 63 |

64 |
65 |
66 | 67 | 74 |
75 |
76 | ); 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /templates/react-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/react-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/react/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | 3 | function App() { 4 | useEffect(() => { 5 | const script = document.createElement("script"); 6 | 7 | script.src = "https://buttons.github.io/buttons.js"; 8 | script.async = true; 9 | 10 | document.body.appendChild(script); 11 | 12 | return () => { 13 | document.body.removeChild(script); 14 | }; 15 | }, []); 16 | 17 | return ( 18 |
19 |
20 |
21 |

22 | Create Tailwind 23 |

24 |

25 | If you like this project, consider giving it a star on GitHub! 26 |

27 | 50 |
51 | 52 |
53 |
54 |

55 | React.js Project Created using{" "} 56 | 61 | Vite 62 | 63 |

64 |
65 |
66 | 67 | 74 |
75 |
76 | ); 77 | } 78 | 79 | export default App; 80 | -------------------------------------------------------------------------------- /templates/react/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/react/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,jsx}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/solid-ts/App.tsx: -------------------------------------------------------------------------------- 1 | import type { Component } from "solid-js"; 2 | 3 | const App: Component = () => { 4 | return ( 5 |
6 |

Create Tailwind

7 |

8 | If you like this project, consider giving it a star on GitHub! 9 |

10 | 33 |
34 | ); 35 | }; 36 | 37 | export default App; 38 | -------------------------------------------------------------------------------- /templates/solid-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /templates/solid-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./index.html", 4 | "./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}", 5 | ], 6 | darkMode: "class", 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /templates/solid/App.jsx: -------------------------------------------------------------------------------- 1 | export default function App() { 2 | return ( 3 |
4 |

Create Tailwind

5 |

6 | If you like this project, consider giving it a star on GitHub! 7 |

8 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /templates/solid/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 3 | plugins: { 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /templates/solid/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | "./index.html", 4 | "./src/**/*.{js,ts,jsx,tsx,css,md,mdx,html,json,scss}", 5 | ], 6 | darkMode: "class", 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /templates/svelte-kit/+layout.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /templates/svelte-kit/+page.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
8 |
9 |

Create Tailwind

10 |

11 | If you like this project, consider giving it a star on GitHub! 12 |

13 |
14 | Star 23 | Discuss 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /templates/svelte-kit/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/svelte-kit/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from "@sveltejs/adapter-auto"; 2 | import preprocess from "svelte-preprocess"; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | kit: { 7 | adapter: adapter(), 8 | }, 9 | preprocess: [ 10 | preprocess({ 11 | postcss: true, 12 | }), 13 | ], 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /templates/svelte-kit/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{html,js,svelte,ts}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /templates/vanilla-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Create Tailwind 8 | 9 | 10 | 11 | 14 |
15 |

Create Tailwind

16 |
17 | Star 27 | Discuss 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | Vite 44 | 45 |
46 |

Created With Vite

47 |

The Next Generation Frontend Tooling

48 | 58 |
59 |
60 | 61 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /templates/vanilla-ts/main.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/templates/vanilla-ts/main.ts -------------------------------------------------------------------------------- /templates/vanilla-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/vanilla-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["index.html", "./src/**/*.{html,js,ts}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/vanilla/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Create Tailwind 8 | 9 | 10 | 11 | 14 |
15 |

Create Tailwind

16 |
17 | Star 27 | Discuss 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | Vite 44 | 45 |
46 |

Created With Vite

47 |

The Next Generation Frontend Tooling

48 | 58 |
59 |
60 | 61 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /templates/vanilla/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndrejJurkin/create-tw/84e7468acb197583e4b40fbf619f44fa6606f19b/templates/vanilla/main.js -------------------------------------------------------------------------------- /templates/vanilla/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/vanilla/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./*.{html,js,ts}"], 3 | theme: { 4 | extend: {}, 5 | }, 6 | plugins: [], 7 | }; 8 | -------------------------------------------------------------------------------- /templates/vue-ts/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 59 | -------------------------------------------------------------------------------- /templates/vue-ts/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/vue-ts/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /templates/vue/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 59 | -------------------------------------------------------------------------------- /templates/vue/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /templates/vue/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /test/cli.test.ts: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import type { SyncOptions } from "execa"; 3 | import { execaCommandSync } from "execa"; 4 | import { remove } from "fs-extra"; 5 | import { afterEach, beforeAll, expect, test } from "vitest"; 6 | import { fileURLToPath } from "url"; 7 | 8 | // run `yarn build` before running any test 9 | execaCommandSync("yarn build"); 10 | 11 | // replacement for default nodejs __dirname 12 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 13 | 14 | const CLI_PATH = path.join(__dirname, "..", "dist", "index.js"); 15 | 16 | const projectName = "test-app"; 17 | const genPath = path.join(__dirname, projectName); 18 | 19 | function run(args: string[], options: SyncOptions = {}) { 20 | return execaCommandSync(`node ${CLI_PATH} ${args.join(" ")}`); 21 | } 22 | 23 | beforeAll(() => remove(genPath)); 24 | afterEach(() => remove(genPath)); 25 | 26 | test("prompts for the project name if none supplied i.e. yarn crete tw", () => { 27 | const { stdout } = run([]); 28 | 29 | expect(stdout).toContain("Project name"); 30 | }); 31 | 32 | test("prompts for the framework if project name supplied i.e. yarn create tw ", () => { 33 | const { stdout } = run([projectName]); 34 | 35 | expect(stdout).not.toContain("Project name"); 36 | expect(stdout).toContain("App type"); 37 | }); 38 | 39 | test("show supported templates if --template is passed", () => { 40 | const { stdout } = run([projectName, "--template", "unknown"]); 41 | 42 | expect(stdout).toContain("Unknown template: unknown"); 43 | expect(stdout).toContain("Currently supported templates:"); 44 | }); 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "lib": ["DOM", "DOM.Iterable", "ES2020"], 6 | "types": ["cypress", "node"], 7 | "module": "Node16", 8 | "moduleResolution": "Node16", 9 | "resolveJsonModule": true, 10 | "outDir": "./dist", 11 | "noEmit": true, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "sourceMap": true, 15 | "removeComments": true, 16 | "checkJs": false, 17 | "strict": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "noImplicitOverride": true, 20 | "noImplicitReturns": true, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | "useUnknownInCatchVariables": true, 24 | "noUncheckedIndexedAccess": true, 25 | 26 | "allowSyntheticDefaultImports": true, 27 | "esModuleInterop": true, 28 | "emitDecoratorMetadata": true, 29 | "experimentalDecorators": true, 30 | "forceConsistentCasingInFileNames": true, 31 | "skipLibCheck": true, 32 | "useDefineForClassFields": true 33 | }, 34 | "include": ["src", "e2e"], 35 | "exclude": ["node_modules"] 36 | } 37 | --------------------------------------------------------------------------------