diff --git a/.changeset/cyan-gorillas-perform.md b/.changeset/cyan-gorillas-perform.md new file mode 100644 index 000000000..0d4f52e93 --- /dev/null +++ b/.changeset/cyan-gorillas-perform.md @@ -0,0 +1,5 @@ +--- +'@nordcom/nordstar': patch +--- + +Improve internal tooling. diff --git a/package.json b/package.json index 17c403b50..e49fc172d 100644 --- a/package.json +++ b/package.json @@ -37,8 +37,8 @@ "clean:packages": "turbo clean --filter=!@nordcom/nordstar-docs --filter=!@nordcom/nordstar-storybook", "clean:docs": "turbo clean --filter=@nordcom/nordstar-docs", "clean:storybook": "turbo clean --filter=@nordcom/nordstar-storybook", - "create:component": "plop component", - "create:package": "plop package", + "create": "plop --plopfile ./plop/plopfile.js", + "create:component": "plop --plopfile ./plop/plopfile.js component", "version": "changeset version", "postversion": "npm install", "version:unstable": "changeset version --snapshot unstable", @@ -98,6 +98,7 @@ "eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-unused-imports": "3.0.0", "husky": "8.0.3", + "inquirer-directory": "2.2.0", "jsdom": "23.0.1", "lint-staged": "15.2.0", "plop": "4.0.0", @@ -109,6 +110,7 @@ "react-dom": "18.2.0", "rimraf": "5.0.5", "sass": "1.69.5", + "strip-ansi": "7.1.0", "turbo": "1.11.1", "typescript": "5.3.3", "vite": "5.0.6", diff --git a/plop/component/src/{{componentName}}.test.tsx.hbs b/plop/component/src/{{componentName}}.test.tsx.hbs index e1d3143f2..c3db6676b 100644 --- a/plop/component/src/{{componentName}}.test.tsx.hbs +++ b/plop/component/src/{{componentName}}.test.tsx.hbs @@ -1,3 +1,5 @@ +import '@testing-library/jest-dom'; + import { describe, expect, it } from 'vitest'; import { {{capitalize componentName}} } from '../src'; diff --git a/plop/component/src/{{componentName}}.tsx.hbs b/plop/component/src/{{componentName}}.tsx.hbs index 8e643b4e3..ba5f53405 100644 --- a/plop/component/src/{{componentName}}.tsx.hbs +++ b/plop/component/src/{{componentName}}.tsx.hbs @@ -6,13 +6,11 @@ export type {{capitalize componentName}}Props = { as?: As; }; -const {{capitalize componentName}} = forwardRef<'div', {{capitalize componentName}}Props>( - ({ as: Tag = 'div', className, ...props }, ref) => { - const classes = `${styles.container}${className ? ` ${className}` : ''}`; +const {{capitalize componentName}} = forwardRef<'div', {{capitalize componentName}}Props>(({ as: Tag = 'div', className, ...props }, ref) => { + const classes = `${styles.container}${className ? ` ${className}` : ''}`; - return ; - } -); + return ; +}); {{capitalize componentName}}.displayName = 'Nordstar.{{capitalize componentName}}'; diff --git a/plop/component/tsconfig.json.hbs b/plop/component/tsconfig.json.hbs index bf03994dc..3d29a2259 100644 --- a/plop/component/tsconfig.json.hbs +++ b/plop/component/tsconfig.json.hbs @@ -5,7 +5,7 @@ "rootDir": "./src", "outDir": "dist" }, - "exclude": ["node_modules", "dist", "src/**/*.test.*", "src/**/*.stories.*"], - "extends": "../../../tsconfig.json", - "include": ["src", "index.ts", "../../../@types/declaration.d.ts"] + "exclude": ["node_modules", "dist", "**/*.test.*", "**/*.stories.*"], + "extends": "../../tsconfig.json", + "include": ["./src/**/*.ts", "./**/*.tsx", "../../../@types/declaration.d.ts"] } diff --git a/plop/package/README.md.hbs b/plop/package/README.md.hbs deleted file mode 100644 index 1b7417bbb..000000000 --- a/plop/package/README.md.hbs +++ /dev/null @@ -1,9 +0,0 @@ -# @nordcom/nordstar-{{packageName}} - -{{description}} - -## Installation - -```sh -npm install @nordcom/nordstar-{{packageName}} -``` diff --git a/plop/package/package.json.hbs b/plop/package/package.json.hbs deleted file mode 100644 index d6338eff6..000000000 --- a/plop/package/package.json.hbs +++ /dev/null @@ -1,74 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/package.json", - "name": "@nordcom/nordstar-{{packageName}}", - "type": "module", - "version": "0.0.0", - "description": "", - "main": "./src/index.ts", - "types": "./src/index.ts", - "module": "./src/index.ts", - "exports": { - ".": { - "import": "./src/index.ts", - "types": "./src/index.ts" - } - }, - "sideEffects": false, - "engines": { - "npm": ">=8", - "node": ">=18 <=21" - }, - "scripts": { - "build": "vite build", - "dev": "vite build --watch", - "clean": "rimraf dist .turbo", - "typecheck": "tsc --noEmit", - "prepack": "clean-package", - "postpack": "clean-package restore" - }, - "keywords": [ - "nordstar", - "nordcom", - "{{packageName}}" - ], - "author": { - "name": "Nordcom Group Inc.", - "email": "opensource@nordcom.io", - "url": "https://nordcom.io/" - }, - "contributors": [ - { - "name": "Filiph Siitam Sandström", - "email": "filiph@nordcom.io", - "url": "https://github.com/filiphsps/" - } - ], - "repository": { - "type": "git", - "url": "git+https://github.com/NordcomInc/nordstar.git", - "directory": "packages/{{outDir}}/{{packageName}}" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/NordcomInc/nordstar/issues" - }, - "homepage": "https://nordstar.nordcom.io/", - "files": [ - "dist" - ], - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@nordcom/nordstar-system": "0.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "devDependencies": { - "clean-package": "2.2.0", - "react": "18.2.0" - }, - "clean-package": "../../../clean-package.config.json" -} diff --git a/plop/package/src/index.ts.hbs b/plop/package/src/index.ts.hbs deleted file mode 100644 index e69de29bb..000000000 diff --git a/plop/package/tsconfig.json.hbs b/plop/package/tsconfig.json.hbs deleted file mode 100644 index bf03994dc..000000000 --- a/plop/package/tsconfig.json.hbs +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig.json", - "compilerOptions": { - "baseUrl": "./", - "rootDir": "./src", - "outDir": "dist" - }, - "exclude": ["node_modules", "dist", "src/**/*.test.*", "src/**/*.stories.*"], - "extends": "../../../tsconfig.json", - "include": ["src", "index.ts", "../../../@types/declaration.d.ts"] -} diff --git a/plop/package/vite.config.ts.hbs b/plop/package/vite.config.ts.hbs deleted file mode 100644 index a05fa2f90..000000000 --- a/plop/package/vite.config.ts.hbs +++ /dev/null @@ -1,43 +0,0 @@ -import react from '@vitejs/plugin-react'; -import { resolve } from 'node:path'; -import { defineConfig } from 'vite'; -import dts from 'vite-plugin-dts'; -import { libInjectCss } from 'vite-plugin-lib-inject-css'; -import tsConfigPaths from 'vite-tsconfig-paths'; - -export default defineConfig({ - root: resolve(__dirname), - build: { - target: 'esnext', - copyPublicDir: false, - emptyOutDir: true, - outDir: resolve(__dirname, 'dist'), - sourcemap: true, - lib: { - entry: ['src/index.ts', 'src/{{packageName}}.tsx'], - formats: ['es'] - }, - rollupOptions: { - external: ['react', 'react/jsx-runtime', 'react-dom', /^@nordcom\/nordstar-/], - output: { - globals: { - react: 'React', - 'react-dom': 'ReactDOM' - }, - sourcemapExcludeSources: true - } - } - }, - plugins: [ - react(), - libInjectCss(), - tsConfigPaths(), - dts({ - clearPureImport: false, - entryRoot: resolve(__dirname, 'src'), - rollupTypes: false, - insertTypesEntry: false, - tsconfigPath: resolve(__dirname, 'tsconfig.json') - }) - ] -}); diff --git a/plop/page/{{lowerCase name}}/page.tsx.hbs b/plop/page/{{lowerCase name}}/page.tsx.hbs new file mode 100644 index 000000000..617faef13 --- /dev/null +++ b/plop/page/{{lowerCase name}}/page.tsx.hbs @@ -0,0 +1,16 @@ +import { Heading, View } from '@nordcom/nordstar'; +import type { Metadata } from 'next'; + +export type {{name}}PageProps = {}; + +export const metadata: Metadata = { + title: '{{name}} Page' +}; + +export default async function {{name}}Page({}: {{name}}PageProps) { + return ( + + TODO + + ); +} diff --git a/plop/plopfile.js b/plop/plopfile.js new file mode 100644 index 000000000..31e4a9dd5 --- /dev/null +++ b/plop/plopfile.js @@ -0,0 +1,313 @@ +import chalk from 'chalk'; +import DirectoryPrompt from 'inquirer-directory'; +import InputPrompt from 'inquirer/lib/prompts/input.js'; +import { mkdir } from 'node:fs'; +import path from 'node:path'; +import { moveCursor } from 'node:readline'; +import stripAnsi from 'strip-ansi'; + +const themeColor = chalk.reset.hex('#ed1e79').bold; + +class NamingPrompt extends InputPrompt { + #prevRaw; + + _run(cb) { + this.#prevRaw = process.stdin.isRaw; + if (this.opt.transformer) { + process.stdin.setRawMode(true); + } + + super._run(cb); + } + + render(error) { + error = error ? chalk.red(error) : ''; + + const transformer = this.opt.transformer; + const question = this.getQuestion(); + + const final = !error && ['answered', 'done'].includes(this.status); + + if (!transformer) { + this.screen.render(`${question}${this.rl.line}`, error); + return; + } + + const transformed = stripAnsi(transformer(this.rl.line, this.answers, { isFinal: final })); + const suffix = transformed.replaceAll(this.rl.line, '').trim(); + const content = transformed.slice(0, -suffix.length); + + let body = `${!final ? content : this.answer}${chalk.dim(suffix)}`; + if (final) body = chalk.cyan(stripAnsi(body)); + this.screen.render(`${question}${body}`, error); + + if (!final) { + moveCursor(process.stdout, -suffix.length, 0); + return; + } + } + + onEnd(state) { + this.answer = state.value; + this.status = 'answered'; + + // Re-render prompt + this.render(); + + this.screen.done(); + this.done(state.value); + + // Restore raw mode. + if (this.opt.transformer) { + process.stdin.setRawMode(this.#prevRaw); + } + } + + onError(state) { + this.render(state.isValid); + } +} + +const capitalize = (str) => { + return str.charAt(0).toUpperCase() + str.slice(1); +}; + +const camelCase = (str) => { + return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()); +}; + +/** + * @param {import("plop").NodePlopAPI} plop + */ +export default function main(plop) { + plop.setWelcomeMessage(`${chalk.reset(themeColor('What are we generating today?'))}`); + + plop.setHelper('capitalize', (text) => { + return capitalize(camelCase(text)); + }); + plop.setHelper('camelCase', (text) => { + return camelCase(text); + }); + plop.setHelper('lowerCase', (text) => { + return text.toLowerCase(); + }); + + plop.setPrompt('directory', DirectoryPrompt); + plop.setPrompt('naming', NamingPrompt); + + ['component'].forEach((gen) => { + plop.setGenerator(gen, { + description: `Generate a new ${gen}.`, + prompts: [ + { + type: 'input', + name: `${gen}Name`, + message: `Enter ${gen} name:`, + + validate: (value) => { + if (!value) { + return `${gen} name is required.`; + } + + // check is case is correct + if (value !== value.toLowerCase()) { + return `${gen} name must be in lowercase.`; + } + + // cannot have spaces + if (value.includes(' ')) { + return `${gen} name cannot have spaces.`; + } + + return true; + } + }, + { + type: 'input', + name: 'description', + message: `The description of this ${gen}:`, + + validate: (value) => { + if (!value) { + return `${gen} description is required.`; + } + + if (!value.endsWith('.')) { + return `${gen} description must end with a period.`; + } + + return true; + } + }, + { + type: 'directory', + name: 'outDir', + message: `Where should this ${gen} live?`, + basePath: './packages', + default: './packages/components', + + validate: (value) => { + if (!value) { + return `outDir is required`; + } + + return true; + } + } + ], + actions(answers) { + const actions = []; + + if (!answers) return actions; + + const { description, outDir } = answers; + const generatorName = answers[`${gen}Name`] ?? ''; + + const data = { + [`${gen}Name`]: generatorName, + description, + outDir + }; + + actions.push({ + type: 'addMany', + templateFiles: `${gen}/**`, + destination: `../packages/{{outDir}}/{{dashCase ${gen}Name}}`, + base: `${gen}`, + data, + abortOnFail: true + }); + + return actions; + } + }); + }); + + plop.setGenerator('page', { + description: `(TODO) Create a page for the docs app.`, + prompts: async ({ prompt }) => { + return prompt([ + { + type: 'naming', + name: `name`, + message: `Enter the page name:`, + + transformer(value) { + if (value.length <= 0) return value; + + return `${value}Page`; + }, + + validate(value) { + if (!value) { + return `The name of the page is required.`; + } + + if (value.endsWith('Page')) { + return `The name should not end with "Page".`; + } + + // check is case is correct + if (value.at(0) !== value.at(0).toUpperCase()) { + return `The name of the page must be capitalized.`; + } + + // cannot have spaces + if (value.includes(' ')) { + return `The name of the page cannot have spaces.`; + } + + return true; + } + }, + { + type: 'list', + name: 'create_dir', + message: `Create a new subdirectory?`, + default: 'Use existing', + choices: ['Use existing', 'Create new'], + + validate(value) { + if (!value) { + return `create_dir is required`; + } + + return true; + } + }, + { + type: 'directory', + name: 'create_dir_root', + message: `Where should we create the subdirectory?`, + basePath: './docs/src/app', + async when(answers) { + return answers.create_dir === 'Create new'; + } + }, + { + type: 'input', + name: `create_dir_name`, + message: `Enter the name of the subdirectory:`, + async when(answers) { + return answers.create_dir === 'Create new'; + }, + + transformer(value) { + return value; + }, + + filter(value) { + return new Promise((resolve, reject) => { + try { + mkdir(path.join('./docs/src/app/', value), { recursive: true }, () => resolve(value)); + } catch (error) { + reject(error); + } + }); + }, + + validate(value) { + if (!value) { + return `The name of the page is required.`; + } + + return true; + } + }, + { + type: 'directory', + name: 'path', + message: `Where should we create the page`, + basePath: './docs/src/app', + + validate: (value) => { + if (!value) { + return `path is required`; + } + + return true; + } + } + ]); + }, + + actions(answers) { + if (!ansers) return []; + + const { name, path } = answers; + return [ + { + type: 'addMany', + templateFiles: `page/**`, + destination: `../docs/src/app/{{path}}`, + base: `page`, + data: { + name, + path + }, + abortOnFail: true + } + ]; + } + }); +} diff --git a/plopfile.cjs b/plopfile.cjs deleted file mode 100644 index b21085324..000000000 --- a/plopfile.cjs +++ /dev/null @@ -1,111 +0,0 @@ -const capitalize = (str) => { - return str.charAt(0).toUpperCase() + str.slice(1); -}; - -const camelCase = (str) => { - return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()); -}; - -const workspaces = ['components', 'core']; -const generators = ['component', 'package']; - -const defaultOutDirs = { - component: 'components', - package: 'core' -}; - -/** - * @param {import("plop").NodePlopAPI} plop - */ -module.exports = function main(plop) { - plop.setHelper('capitalize', (text) => { - return capitalize(camelCase(text)); - }); - plop.setHelper('camelCase', (text) => { - return camelCase(text); - }); - - generators.forEach((gen) => { - plop.setGenerator(gen, { - description: `Generates a ${gen}`, - prompts: [ - { - type: 'input', - name: `${gen}Name`, - message: `Enter ${gen} name:`, - - validate: (value) => { - if (!value) { - return `${gen} name is required`; - } - - // check is case is correct - if (value !== value.toLowerCase()) { - return `${gen} name must be in lowercase`; - } - - // cannot have spaces - if (value.includes(' ')) { - return `${gen} name cannot have spaces`; - } - - return true; - } - }, - { - type: 'input', - name: 'description', - message: `The description of this ${gen}:`, - - validate: (value) => { - if (!value) { - return `${gen} description is required`; - } - - return true; - } - }, - { - type: 'list', - name: 'outDir', - message: `Where should this ${gen} live?`, - default: defaultOutDirs[gen], - choices: workspaces, - - validate: (value) => { - if (!value) { - return `outDir is required`; - } - - return true; - } - } - ], - actions(answers) { - const actions = []; - - if (!answers) return actions; - - const { description, outDir } = answers; - const generatorName = answers[`${gen}Name`] ?? ''; - - const data = { - [`${gen}Name`]: generatorName, - description, - outDir - }; - - actions.push({ - type: 'addMany', - templateFiles: `plop/${gen}/**`, - destination: `./packages/{{outDir}}/{{dashCase ${gen}Name}}`, - base: `plop/${gen}`, - data, - abortOnFail: true - }); - - return actions; - } - }); - }); -}; diff --git a/tsconfig.test.json b/tsconfig.test.json index f770518b0..9b0066794 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -6,7 +6,7 @@ "types": ["vitest/globals", "@testing-library/jest-dom"] }, - "include": ["packages", "@types/declaration.d.ts", "**/*.test.*"], + "include": ["packages", "@types/declaration.d.ts", "**/*.test.*", "plop/**/*.js"], "exclude": [ "**/node_modules", "**/dist",