19 | );
20 | });
21 |
--------------------------------------------------------------------------------
/src/stubs/qwik/usestore-component.tsx:
--------------------------------------------------------------------------------
1 | import { component$, useStore } from "@builder.io/qwik";
2 |
3 | export default component$(() => {
4 | // useStore is a hook that creates a reactive state object in Qwik
5 | // Here, a store with a single property 'count' initialized to 0 is created
6 | // The store is reactive, meaning any changes to its properties will
7 | // trigger a re-render of the component that uses it
8 | const store = useStore({ count: 0 });
9 |
10 | return (
11 |
12 |
Count: {store.count}
13 |
14 |
15 |
16 |
17 | );
18 | });
19 |
--------------------------------------------------------------------------------
/biome.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
3 | "organizeImports": {
4 | "enabled": true
5 | },
6 | "files": {
7 | "ignore": ["src/stubs/*", "*.js", "*.cjs", "*.mjs", "src/components"]
8 | },
9 | "linter": {
10 | "enabled": true,
11 | "rules": {
12 | "recommended": true
13 | }
14 | },
15 | "javascript": {
16 | "formatter": {
17 | "enabled": true,
18 | "lineWidth": 120,
19 | "arrowParentheses": "asNeeded",
20 | "jsxQuoteStyle": "double",
21 | "semicolons": "always",
22 | "trailingComma": "es5",
23 | "quoteProperties": "asNeeded",
24 | "bracketSameLine": true
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/utils/frameworks/svelte/svelte.mts:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 |
3 | const framework = "svelte";
4 | export default function (componentName: string, folder: string) {
5 | return inquirer
6 | .prompt([
7 | {
8 | type: "confirm",
9 | name: "typescript",
10 | message: "Do you want to use Typescript?",
11 | default: true,
12 | },
13 | ])
14 | .then((answers: { typescript: boolean }) => {
15 | return {
16 | componentName: componentName,
17 | framework: framework.toLowerCase(),
18 | template: answers.typescript ? "component-ts.svelte" : "component-js.svelte",
19 | folder: folder,
20 | };
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/frameworks/svelte/svelte.mjs:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | const framework = "svelte";
3 | export default function (componentName, folder) {
4 | return inquirer
5 | .prompt([
6 | {
7 | type: "confirm",
8 | name: "typescript",
9 | message: "Do you want to use Typescript?",
10 | default: true,
11 | },
12 | ])
13 | .then((answers) => {
14 | return {
15 | componentName: componentName,
16 | framework: framework.toLowerCase(),
17 | template: answers.typescript
18 | ? "component-ts.svelte"
19 | : "component-js.svelte",
20 | folder: folder,
21 | };
22 | });
23 | }
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yaml:
--------------------------------------------------------------------------------
1 | name: Bug report
2 | description: Create a report to help us improve
3 | body:
4 | - type: markdown
5 | attributes:
6 | value: Thanks for taking the time to fill out this bug! If you need real-time help, join us on Discord.
7 | - type: textarea
8 | id: repro
9 | attributes:
10 | label: Reproduction steps
11 | description: "How do you trigger this bug? Please walk us through it step by step."
12 | value:
13 | render: bash
14 | validations:
15 | required: true
16 | - type: dropdown
17 | id: assignee
18 | attributes:
19 | label: Do you want to work on this issue?
20 | multiple: false
21 | options:
22 | - "No"
23 | - "Yes"
24 | default: 0
25 |
--------------------------------------------------------------------------------
/src/utils/frameworks/qwik/qwik.mts:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 |
3 | const framework = "qwik";
4 | export default function (componentName: string, folder: string) {
5 | return inquirer
6 | .prompt([
7 | {
8 | type: "list",
9 | name: "type",
10 | message: "Choose wich type of component to create",
11 | choices: ["Hello World", "useStore", "useStyles"],
12 | default: "Hello World",
13 | },
14 | ])
15 | .then((answers: { type: string }) => {
16 | return {
17 | componentName: componentName,
18 | framework: framework.toLowerCase(),
19 | template:
20 | answers.type === "Hello World"
21 | ? "hello-world-component.tsx"
22 | : answers.type === "useStore"
23 | ? "usestore-component.tsx"
24 | : answers.type === "useStyles"
25 | ? "usestyles-component.tsx"
26 | : "hello-world-component.tsx",
27 | folder: folder,
28 | };
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3 |
4 | name: Node.js Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions/setup-node@v3
16 | with:
17 | node-version: 18
18 | - run: npm ci
19 | - run: npx tsc
20 |
21 | publish-npm:
22 | needs: build
23 | runs-on: ubuntu-latest
24 | steps:
25 | - uses: actions/checkout@v3
26 | - uses: actions/setup-node@v3
27 | with:
28 | node-version: 18
29 | registry-url: https://registry.npmjs.org/
30 | - run: npm ci
31 | - run: npx tsc
32 | - run: npm publish
33 | env:
34 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
35 |
--------------------------------------------------------------------------------
/cmd/make-js-component.mts:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 |
3 | import createComponent, { createAnotherComponent } from "../src/utils/utils.mjs";
4 | import wizard, { Answers } from "../src/utils/wizard.mjs";
5 |
6 | enum vueApi {
7 | Composition = "composition",
8 | Option = "option",
9 | }
10 |
11 | wizard()
12 | .then((answers: Answers) => {
13 | const { componentName, framework, template, folder, anotherComponent, advancedOpts, advanced } = answers;
14 | const api = template.indexOf("composition") !== -1 ? vueApi.Composition : vueApi.Option;
15 | const t = advanced ? "advanced-component.vue" : template;
16 | if (anotherComponent) {
17 | createComponent(componentName, framework, t, folder, api, advancedOpts).then(() => {
18 | console.log("✅ Component created");
19 | createAnotherComponent();
20 | });
21 | } else
22 | createComponent(componentName, framework, t, folder, api, advancedOpts).then(() =>
23 | console.log("✅ Component created")
24 | );
25 | })
26 | .catch((e: Error) => {
27 | console.error(e.message);
28 | });
29 |
--------------------------------------------------------------------------------
/src/utils/frameworks/qwik/qwik.mjs:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | const framework = "qwik";
3 | export default function (componentName, folder) {
4 | return inquirer
5 | .prompt([
6 | {
7 | type: "list",
8 | name: "type",
9 | message: "Choose wich type of component to create",
10 | choices: ["Hello World", "useStore", "useStyles"],
11 | default: "Hello World",
12 | },
13 | ])
14 | .then((answers) => {
15 | return {
16 | componentName: componentName,
17 | framework: framework.toLowerCase(),
18 | template: answers.type === "Hello World"
19 | ? "hello-world-component.tsx"
20 | : answers.type === "useStore"
21 | ? "usestore-component.tsx"
22 | : answers.type === "useStyles"
23 | ? "usestyles-component.tsx"
24 | : "hello-world-component.tsx",
25 | folder: folder,
26 | };
27 | });
28 | }
29 |
--------------------------------------------------------------------------------
/src/stubs/angular/component.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from "@angular/core/testing";
2 | import { ComponentName } from "./component.component";
3 |
4 | describe("ComponentName", () => {
5 | beforeEach(async () => {
6 | await TestBed.configureTestingModule({
7 | imports: [ComponentName],
8 | }).compileComponents();
9 | });
10 |
11 | it("should create the app", () => {
12 | const fixture = TestBed.createComponent(ComponentName);
13 | const app = fixture.componentInstance;
14 | expect(app).toBeTruthy();
15 | });
16 |
17 | it(`should have the 'ComponentName' title`, () => {
18 | const fixture = TestBed.createComponent(ComponentName);
19 | const app = fixture.componentInstance;
20 | expect(app.title).toEqual("ComponentName");
21 | });
22 |
23 | it("should render title", () => {
24 | const fixture = TestBed.createComponent(ComponentName);
25 | fixture.detectChanges();
26 | const compiled = fixture.nativeElement as HTMLElement;
27 | expect(compiled.querySelector("h2")?.textContent).toContain("Hello, ComponentName");
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/cmd/make-js-component.mjs:
--------------------------------------------------------------------------------
1 | #! /usr/bin/env node
2 | import createComponent, { createAnotherComponent } from "../src/utils/utils.mjs";
3 | import wizard from "../src/utils/wizard.mjs";
4 | var vueApi;
5 | (function (vueApi) {
6 | vueApi["Composition"] = "composition";
7 | vueApi["Option"] = "option";
8 | })(vueApi || (vueApi = {}));
9 | wizard()
10 | .then((answers) => {
11 | const { componentName, framework, template, folder, anotherComponent, advancedOpts, advanced } = answers;
12 | const api = template.indexOf("composition") !== -1 ? vueApi.Composition : vueApi.Option;
13 | const t = advanced ? "advanced-component.vue" : template;
14 | if (anotherComponent) {
15 | createComponent(componentName, framework, t, folder, api, advancedOpts).then(() => {
16 | console.log("✅ Component created");
17 | createAnotherComponent();
18 | });
19 | }
20 | else
21 | createComponent(componentName, framework, t, folder, api, advancedOpts).then(() => console.log("✅ Component created"));
22 | })
23 | .catch((e) => {
24 | console.error(e.message);
25 | });
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Ghostylab
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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "make-js-component",
3 | "version": "0.4.0",
4 | "description": "Easily create your js framework component in one command",
5 | "repository": "https://github.com/Giuliano1993/make-js-component",
6 | "bin": "./cmd/make-js-component.mjs",
7 | "scripts": {
8 | "dev": "npx tsc -w",
9 | "test": "jest",
10 | "biome-ci": "npx @biomejs/biome ci .",
11 | "biome-check": "npx @biomejs/biome check . --apply",
12 | "biome-lint": "npx @biomejs/biome lint . --apply",
13 | "biome-format": "npx @biomejs/biome format . --write"
14 |
15 | },
16 | "keywords": [
17 | "node",
18 | "js",
19 | "framework",
20 | "vue",
21 | "svelte",
22 | "react",
23 | "qwik",
24 | "astro",
25 | "command",
26 | "npx"
27 | ],
28 | "author": "Giuliano Gostinfini (Ghostylab)",
29 | "license": "ISC",
30 | "devDependencies": {
31 | "@biomejs/biome": "1.4.1",
32 | "@types/inquirer": "^9.0.7",
33 | "@types/node": "^20.9.0",
34 | "cli-testing-library": "^2.0.2",
35 | "jest": "^29.7.0",
36 | "jest-extended": "^4.0.2",
37 | "typescript": "^5.3.2"
38 | },
39 | "dependencies": {
40 | "commander": "^11.1.0",
41 | "inquirer": "^9.2.12"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/utils/frameworks/vue/vue.mts:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | import { prepareAdvanced } from "../../utils.mjs";
3 |
4 | const framework = "vue";
5 | export default function (componentName: string, folder: string) {
6 | return inquirer
7 | .prompt([
8 | {
9 | type: "list",
10 | name: "nuxt",
11 | message: "Do you use Nuxt? The destination folder will be (./components)",
12 | choices: ["yes", "No"],
13 | default: "No",
14 | },
15 | {
16 | type: "list",
17 | name: "api",
18 | message: "Choose wich api to use",
19 | choices: ["Composition", "Options"],
20 | default: "Composition",
21 | },
22 | ...prepareAdvanced(["props", "refs", "data", "mounted", "emits", "components"]),
23 | ])
24 | .then(
25 | (answers: {
26 | nuxt: string;
27 | api: string;
28 | advanced: boolean;
29 | advancedOpts?: string[];
30 | }) => {
31 | return {
32 | componentName: componentName,
33 | framework: framework,
34 | template: answers.api === "Composition" ? "component-composition.vue" : "component-options.vue",
35 | folder: answers.nuxt === "yes" ? (folder === "" ? "../../components" : `../../components/${folder}`) : folder,
36 | advanced: answers.advanced,
37 | api: answers.api.toLocaleLowerCase(),
38 | advancedOpts: answers.advancedOpts || [],
39 | };
40 | }
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/frameworks/react/react.mts:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 |
3 | const framework = "react";
4 |
5 | export default function (componentName: string, folder: string) {
6 | return inquirer
7 | .prompt([
8 | {
9 | type: "confirm",
10 | name: "typescript",
11 | message: "Do you want to use Typescript?",
12 | default: true,
13 | },
14 | {
15 | type: "list",
16 | name: "css",
17 | message: "Do you want to use any CSS framework?",
18 | choices: ["Tailwind", "Styled Components", "CSS Module", "No"],
19 | default: "No",
20 | },
21 | ])
22 | .then((answers: { typescript: boolean; css: string }) => {
23 | const { typescript } = answers;
24 | const { css } = answers;
25 | const extension = typescript ? "tsx" : "jsx";
26 | let templateBase = "function-component";
27 |
28 | switch (css) {
29 | case "Tailwind":
30 | templateBase += "-tailwind";
31 | break;
32 | case "Styled Components":
33 | templateBase += "-styled-components";
34 | break;
35 | case "CSS Module":
36 | templateBase += "-css-module";
37 | break;
38 | default:
39 | break;
40 | }
41 | const template = `${templateBase}.${extension}`;
42 |
43 | return {
44 | componentName: componentName,
45 | framework: framework.toLowerCase(),
46 | template: template,
47 | folder: folder,
48 | };
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/frameworks/react/react.mjs:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | const framework = "react";
3 | export default function (componentName, folder) {
4 | return inquirer
5 | .prompt([
6 | {
7 | type: "confirm",
8 | name: "typescript",
9 | message: "Do you want to use Typescript?",
10 | default: true,
11 | },
12 | {
13 | type: "list",
14 | name: "css",
15 | message: "Do you want to use any CSS framework?",
16 | choices: ["Tailwind", "Styled Components", "CSS Module", "No"],
17 | default: "No",
18 | },
19 | ])
20 | .then((answers) => {
21 | const { typescript } = answers;
22 | const { css } = answers;
23 | const extension = typescript ? "tsx" : "jsx";
24 | let templateBase = "function-component";
25 |
26 | switch (css) {
27 | case "Tailwind":
28 | templateBase += "-tailwind";
29 | break;
30 | case "Styled Components":
31 | templateBase += "-styled-components";
32 | break;
33 | case "CSS Module":
34 | templateBase += "-css-module";
35 | break;
36 | default:
37 | break;
38 | }
39 | const template = `${templateBase}.${extension}`;
40 |
41 | return {
42 | componentName: componentName,
43 | framework: framework.toLowerCase(),
44 | template: template,
45 | folder: folder,
46 | };
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/__tests__/menu.test.cjs:
--------------------------------------------------------------------------------
1 | const {render} = require('cli-testing-library');
2 | const { default: exp } = require('constants');
3 | const {resolve} = require('path')
4 |
5 |
6 |
7 | test('Open tool', async () => {
8 | const path = resolve(__dirname, "../cmd/make-js-component.mjs")
9 | const {clear, findByText, queryByText, userEvent, stdoutArr, debug} = await render('node', [
10 | path
11 | ])
12 | //console.log(path)
13 | const instance = await findByText('component')
14 | expect(instance).toBeInTheConsole()
15 | })
16 |
17 | test('Component a name', async () => {
18 | const path = resolve(__dirname, "../cmd/make-js-component.mjs")
19 | const {clear, findByText, queryByText, userEvent, stdoutArr, debug} = await render('node', [
20 | path
21 | ])
22 | //console.log(path)
23 | userEvent.keyboard('componentName[Enter]');
24 | expect(await findByText('src/components')).toBeInTheConsole()
25 | })
26 |
27 | test('Default folder and pick React', async () => {
28 | const path = resolve(__dirname, "../cmd/make-js-component.mjs")
29 | const {clear, findByText, queryByText, userEvent, stdoutArr, debug} = await render('node', [
30 | path
31 | ])
32 | //console.log(path)
33 | userEvent.keyboard('componentName[Enter]');
34 | expect(await findByText('src/components')).toBeInTheConsole()
35 | userEvent.keyboard('[Enter]');
36 | userEvent.keyboard('[ArrowDown]')
37 | userEvent.keyboard('[ArrowDown]')
38 | expect(await findByText('❯ React')).toBeInTheConsole()
39 |
40 | })
41 |
--------------------------------------------------------------------------------
/src/utils/frameworks/vue/vue.mjs:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | import { prepareAdvanced } from "../../utils.mjs";
3 | const framework = "vue";
4 | export default function (componentName, folder) {
5 | return inquirer
6 | .prompt([
7 | {
8 | type: "list",
9 | name: "nuxt",
10 | message:
11 | "Do you use Nuxt? The destination folder will be (./components)",
12 | choices: ["yes", "No"],
13 | default: "No",
14 | },
15 | {
16 | type: "list",
17 | name: "api",
18 | message: "Choose wich api to use",
19 | choices: ["Composition", "Options"],
20 | default: "Composition",
21 | },
22 | ...prepareAdvanced([
23 | "props",
24 | "refs",
25 | "data",
26 | "mounted",
27 | "emits",
28 | "components",
29 | ]),
30 | ])
31 | .then((answers) => {
32 | return {
33 | componentName: componentName,
34 | framework: framework,
35 | template:
36 | answers.api === "Composition"
37 | ? "component-composition.vue"
38 | : "component-options.vue",
39 | folder:
40 | answers.nuxt === "yes"
41 | ? folder === ""
42 | ? "../../components"
43 | : `../../components/${folder}`
44 | : folder,
45 | advanced: answers.advanced,
46 | api: answers.api.toLocaleLowerCase(),
47 | advancedOpts: answers.advancedOpts || [],
48 | };
49 | });
50 | }
51 |
--------------------------------------------------------------------------------
/src/utils/configs.cjs:
--------------------------------------------------------------------------------
1 | "use strict";
2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 | if (k2 === undefined) k2 = k;
4 | var desc = Object.getOwnPropertyDescriptor(m, k);
5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 | desc = { enumerable: true, get: function() { return m[k]; } };
7 | }
8 | Object.defineProperty(o, k2, desc);
9 | }) : (function(o, m, k, k2) {
10 | if (k2 === undefined) k2 = k;
11 | o[k2] = m[k];
12 | }));
13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 | Object.defineProperty(o, "default", { enumerable: true, value: v });
15 | }) : function(o, v) {
16 | o["default"] = v;
17 | });
18 | var __importStar = (this && this.__importStar) || function (mod) {
19 | if (mod && mod.__esModule) return mod;
20 | var result = {};
21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 | __setModuleDefault(result, mod);
23 | return result;
24 | };
25 | Object.defineProperty(exports, "__esModule", { value: true });
26 | exports.configs = void 0;
27 | const path = __importStar(require("node:path"));
28 | const mainFilename = path.dirname(module?.filename || "");
29 | const dir = path.join(mainFilename, "../..");
30 | exports.configs = {
31 | INIT_PATH: dir,
32 | BASE_DIR: "./src",
33 | STUBS_DIR: "stubs",
34 | COMPONENT_FOLDER: "/components",
35 | };
36 |
--------------------------------------------------------------------------------
/src/utils/frameworks/angular/make-angular-component.mjs:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import path from "path";
3 | import { configs } from "../../configs.cjs";
4 | import { checkFileExists } from "../../utils.mjs";
5 | export function makeAngularComponent(filePathDestination, component, componentName) {
6 | let componentContent = component.replace(/selector: 'SelectorName'/, `selector: 'app-${convertFromCamelCase(componentName)}'`);
7 | componentContent = replaceComponentName(componentContent, componentName);
8 | checkFileExists(filePathDestination, componentContent);
9 | makeAngularComponentTest(componentName);
10 | }
11 | function makeAngularComponentTest(componentName) {
12 | const templateFileTestPath = path.join(configs.INIT_PATH, "src", configs.STUBS_DIR, "angular", "component.component.spec.ts");
13 | fs.readFile(templateFileTestPath, "utf8", (err, component) => {
14 | const componentContent = replaceComponentName(component, componentName);
15 | const filePathDestination = path.join(configs.BASE_DIR, configs.COMPONENT_FOLDER, `${componentName}.component.spec.ts`);
16 | checkFileExists(filePathDestination, componentContent);
17 | });
18 | }
19 | function convertToCamelCase(string) {
20 | return string
21 | .replace(/-([a-z])/g, (s) => {
22 | return s.toUpperCase();
23 | })
24 | .replace(/^[a-z]/, s => {
25 | return s.toUpperCase();
26 | });
27 | }
28 | function convertFromCamelCase(string) {
29 | return string.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
30 | }
31 | function replaceComponentName(data, componentName) {
32 | return data.replace(/ComponentName/g, `${convertToCamelCase(componentName)}Component`);
33 | }
34 |
--------------------------------------------------------------------------------
/src/utils/frameworks/angular/make-angular-component.mts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import path from "path";
3 | import { configs } from "../../configs.cjs";
4 | import { ErrnoException, checkFileExists } from "../../utils.mjs";
5 |
6 | export function makeAngularComponent(filePathDestination: string, component: string, componentName: string): void {
7 | let componentContent = component.replace(
8 | /selector: 'SelectorName'/,
9 | `selector: 'app-${convertFromCamelCase(componentName)}'`
10 | );
11 | componentContent = replaceComponentName(componentContent, componentName);
12 |
13 | checkFileExists(filePathDestination, componentContent);
14 | makeAngularComponentTest(componentName);
15 | }
16 |
17 | function makeAngularComponentTest(componentName: string): void {
18 | const templateFileTestPath: string = path.join(
19 | configs.INIT_PATH,
20 | "src",
21 | configs.STUBS_DIR,
22 | "angular",
23 | "component.component.spec.ts"
24 | );
25 | fs.readFile(templateFileTestPath, "utf8", (err: ErrnoException | null, component: string) => {
26 | const componentContent = replaceComponentName(component, componentName);
27 | const filePathDestination: string = path.join(
28 | configs.BASE_DIR,
29 | configs.COMPONENT_FOLDER,
30 | `${componentName}.component.spec.ts`
31 | );
32 | checkFileExists(filePathDestination, componentContent);
33 | });
34 | }
35 |
36 | function convertToCamelCase(string: string): string {
37 | return string
38 | .replace(/-([a-z])/g, (s: string) => {
39 | return s.toUpperCase();
40 | })
41 | .replace(/^[a-z]/, s => {
42 | return s.toUpperCase();
43 | });
44 | }
45 |
46 | function convertFromCamelCase(string: string): string {
47 | return string.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
48 | }
49 |
50 | function replaceComponentName(data: string, componentName: string): string {
51 | return data.replace(/ComponentName/g, `${convertToCamelCase(componentName)}Component`);
52 | }
53 |
--------------------------------------------------------------------------------
/src/utils/frameworks/vue/helper.mts:
--------------------------------------------------------------------------------
1 | export enum vueApi {
2 | Composition = "composition",
3 | Option = "option",
4 | }
5 |
6 | export default function advancedVueBuilder(
7 | data: string,
8 | componentType: vueApi,
9 | advancedOpts: string[] | undefined
10 | ): string {
11 | if (typeof advancedOpts === "undefined") return "";
12 | let output = data;
13 | if (componentType === vueApi.Composition) {
14 | const replacable = {
15 | props: "const props = defineProps(['foo'])",
16 | emits: "const emit = defineEmits(['inFocus', 'submit'])",
17 | refs: "const element = ref(null)",
18 | mounted: `onMounted(() => {
19 | console.log("the component is now mounted.")
20 | })`,
21 | data: "",
22 | components: "",
23 | };
24 | const importsFunctions: string[] = [];
25 | for (const key in replacable) {
26 | const codeInject = advancedOpts.indexOf(key) !== -1 ? replacable[key as keyof typeof replacable] : "";
27 | const replacePattern = `__${key}__`;
28 | output = output.replaceAll(replacePattern, codeInject);
29 | if (key === "refs" && advancedOpts.indexOf(key) !== -1) {
30 | importsFunctions.push("ref");
31 | } else if (key === "mounted" && advancedOpts.indexOf(key) !== -1) {
32 | importsFunctions.push("onMounted");
33 | }
34 | }
35 |
36 | let imports = "";
37 | if (importsFunctions.length > 0) {
38 | imports = `import { ${importsFunctions.join(", ")} } from 'vue'`;
39 | }
40 | output = output.replace("__refimport__", imports);
41 | } else if (componentType === vueApi.Option) {
42 | const replacable = {
43 | props: "props: ['foo'],",
44 | emits: "emits: ['inFocus', 'submit'],",
45 | data: "data:{},",
46 | mounted: "mounted(){},",
47 | refs: "",
48 | components: "components: {},",
49 | };
50 | for (const key in replacable) {
51 | const codeInject = advancedOpts.indexOf(key) !== -1 ? replacable[key as keyof typeof replacable] : "";
52 | const replacePattern = `__${key}__`;
53 | output = output.replaceAll(replacePattern, codeInject);
54 | }
55 | }
56 | output = cleanVueData(output, componentType);
57 |
58 | return output;
59 | }
60 |
61 | function cleanVueData(data: string, api: vueApi): string {
62 | const apiStart = api === vueApi.Composition ? "__compositionstart__" : "__optionsstart__";
63 | const apiEnd = api === vueApi.Composition ? "__compositionend__" : "__optionsend__";
64 | const deleteStart = api === vueApi.Composition ? "__optionsstart__" : "__compositionstart__";
65 | const deleteEnd = api === vueApi.Composition ? "__optionsend__" : "__compositionend__";
66 |
67 | const output = data.replace(apiStart, "").replace(apiEnd, "");
68 |
69 | const start = output.indexOf(deleteStart);
70 | const end = output.indexOf(deleteEnd);
71 | return output.slice(0, start) + output.slice(end + deleteEnd.length);
72 | }
73 |
--------------------------------------------------------------------------------
/src/utils/frameworks/vue/helper.mjs:
--------------------------------------------------------------------------------
1 | export var vueApi;
2 | (function (vueApi) {
3 | vueApi["Composition"] = "composition";
4 | vueApi["Option"] = "option";
5 | })(vueApi || (vueApi = {}));
6 | export default function advancedVueBuilder(data, componentType, advancedOpts) {
7 | if (typeof advancedOpts === "undefined")
8 | return "";
9 | let output = data;
10 | if (componentType === vueApi.Composition) {
11 | const replacable = {
12 | props: "const props = defineProps(['foo'])",
13 | emits: "const emit = defineEmits(['inFocus', 'submit'])",
14 | refs: "const element = ref(null)",
15 | mounted: `onMounted(() => {
16 | console.log("the component is now mounted.")
17 | })`,
18 | data: "",
19 | components: "",
20 | };
21 | const importsFunctions = [];
22 | for (const key in replacable) {
23 | const codeInject = advancedOpts.indexOf(key) !== -1 ? replacable[key] : "";
24 | const replacePattern = `__${key}__`;
25 | output = output.replaceAll(replacePattern, codeInject);
26 | if (key === "refs" && advancedOpts.indexOf(key) !== -1) {
27 | importsFunctions.push("ref");
28 | }
29 | else if (key === "mounted" && advancedOpts.indexOf(key) !== -1) {
30 | importsFunctions.push("onMounted");
31 | }
32 | }
33 | let imports = "";
34 | if (importsFunctions.length > 0) {
35 | imports = `import { ${importsFunctions.join(", ")} } from 'vue'`;
36 | }
37 | output = output.replace("__refimport__", imports);
38 | }
39 | else if (componentType === vueApi.Option) {
40 | const replacable = {
41 | props: "props: ['foo'],",
42 | emits: "emits: ['inFocus', 'submit'],",
43 | data: "data:{},",
44 | mounted: "mounted(){},",
45 | refs: "",
46 | components: "components: {},",
47 | };
48 | for (const key in replacable) {
49 | const codeInject = advancedOpts.indexOf(key) !== -1 ? replacable[key] : "";
50 | const replacePattern = `__${key}__`;
51 | output = output.replaceAll(replacePattern, codeInject);
52 | }
53 | }
54 | output = cleanVueData(output, componentType);
55 | return output;
56 | }
57 | function cleanVueData(data, api) {
58 | const apiStart = api === vueApi.Composition ? "__compositionstart__" : "__optionsstart__";
59 | const apiEnd = api === vueApi.Composition ? "__compositionend__" : "__optionsend__";
60 | const deleteStart = api === vueApi.Composition ? "__optionsstart__" : "__compositionstart__";
61 | const deleteEnd = api === vueApi.Composition ? "__optionsend__" : "__compositionend__";
62 | const output = data.replace(apiStart, "").replace(apiEnd, "");
63 | const start = output.indexOf(deleteStart);
64 | const end = output.indexOf(deleteEnd);
65 | return output.slice(0, start) + output.slice(end + deleteEnd.length);
66 | }
67 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ## Issues & Pull Requests
2 |
3 | - [Issues \& Pull Requests](#issues--pull-requests)
4 | - [Getting started locally](#getting-started-locally)
5 | - [Creating an Issue](#creating-an-issue)
6 | - [Working on an Issue](#working-on-an-issue)
7 | - [Pull requests](#pull-requests)
8 | - [Adding a New Framework](#adding-a-new-framework)
9 |
10 | ### Getting started locally
11 |
12 | 1. Clone the repo
13 | 2. In the repo folder run `npm run dev`; this will start watching ts files and transpile them to js
14 | 3. Now you can run locally the command: just type `npx .` in your terminal to execute it
15 |
16 | ### Creating an Issue
17 |
18 | Before **creating** an Issue please follow these steps:
19 |
20 | 1. search existing Issues before creating a new issue (has someone raised this already)
21 | 2. if it doesn't exist create a new issue giving as much context as possible
22 | 3. if you wish to work on the Issue please check the relative checkbox
23 |
24 | ### Working on an Issue
25 |
26 | Before working on an existing Issue please follow these steps:
27 |
28 | 1. comment asking for the issue to be assigned to you
29 | 2. after the Issue is assigned to you, you can start working on it
30 | 3. **only** start working on this Issue (and open a Pull Request) when it has been assigned to you.
31 | 4. when forking the issue, create a branch for your edits
32 | 5. before pushing run `npm run biome-ci` to be sure that code formatting is correct and it will pass the PR workflow.
33 | 1. If some errors are highlighted, you can fix them by running the following commands:
34 | 1. `npm run biome-check`
35 | 2. `npm run biome-lint` ( in this case, some errors may remain, so you may need to address them individually)
36 | 3. `npm run biome-format`
37 | 6. reference the Issue in your Pull Request (for example `closes #123`)
38 | 7. please do **not** force push to your PR branch, this makes it very difficult to re-review - commits will be squashed when merged
39 |
40 | ### Pull requests
41 |
42 | Remember, before opening a PR, to have an issue assigned to work on! If you have an idea but you don't find any issue for it, first open an issue and ask to have it assigned! This way you don't risk to work on something which is already being worked on or that isn't needed right now!
43 | When the issue is assigned to you, you're welcome to start working on it, I'll be glad to merge it!
44 |
45 | ## Adding a New Framework
46 |
47 | Missing your favorite js framework? You can add it!
48 |
49 | 1. **Modify the Wizard:**
50 |
51 | - Create a file `.mts` for the framework inside the `src/utils/frameworks/{frameworkname}` folder.
52 | It should import `inquirer` and export a function that take `componentName` and `folder` as parameters.
53 | Here you can add some eventual extra questions specifics to this framework. Check the existing framework files as an example
54 | - Open the `wizard.mts`.
55 | - Import the file you previously created
56 | - Add the framework name to the `frameworks` array
57 | - Add the framework to the `switch-case`
58 |
59 | 2. **Create Stubs:**
60 |
61 | - Create a folder with the framework name insiede the `src/stubs` folder
62 | - Inside the folder add add as many files as the options made available by the wizard
63 | - Add templates for the new framework. These will serve as the initial structure for a component of that framework.
64 |
65 | 3. **Test the Command:**
66 |
67 | - Run the command with the new framework to ensure that it works as expected.
68 |
69 | 4. **Update Documentation:**
70 | - Add a new section in the README.md file under "Available Frameworks" to provide information about the newly added framework.
71 | - Include any specific instructions or choices related to the new framework or open an issue for this purpouse.
72 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://repo-rater.eddiehub.io/rate?owner=Giuliano1993&name=make-js-component)
2 |
3 |
4 |
5 |
6 |
7 | # Make Js Component
8 |
9 | Make Js Component is an NPX command born with the purpose of streamline the process for developers of creating components with the many FE frameworks around here.
10 |
11 | Since some frameworks have standard commands, some had them, some really don't, the quickest thing is usually copy pasting compononent after component and then edit it.
12 |
13 | MJC allows you to just call a command and have your JS component ready to use, and edit, with also a bunch of options available in order to start with the perfect boilerplate.
14 |
15 | Can't find the framework or the options you need? Checkout the [Contributing guide](./CONTRIBUTING.md) and open an issue to let us know and, if you wish, you can open a PR to have the feature inclued in the command!
16 |
17 | - [Make Js Component](#make-js-component)
18 | - [Basic Usage](#basic-usage)
19 | - [Options](#options)
20 | - [--name](#--name)
21 | - [--folder](#--folder)
22 | - [--framework](#--framework)
23 | - [--\[framework\]](#--framework-1)
24 | - [--multiple](#--multiple)
25 | - [Available Frameworks](#available-frameworks)
26 | - [Vue](#vue)
27 | - [React](#react)
28 | - [Angular](#angular)
29 | - [Qwik](#qwik)
30 | - [Svelte](#svelte)
31 | - [Astro](#astro)
32 | - [Contributing](#contributing)
33 | - [Setup locally](#setup-locally)
34 |
35 | ## Basic Usage
36 |
37 | ```bash
38 | npx make-js-component
39 | ```
40 | This command will start a short wizard that will create your component in a few steps.
41 |
42 | ### Options
43 |
44 | #### --name
45 |
46 | Specify the component name
47 |
48 | ```bash
49 | npx make-js-component --name
50 | ```
51 |
52 | #### --folder
53 |
54 | Set the /components subfolder in which to create the new component/s.
55 |
56 | ```bash
57 | npx make-js-component --folder
58 | ```
59 |
60 | #### --framework
61 |
62 | Set which framework your component is for.
63 |
64 | ```bash
65 | npx make-js-component --framework [vue|angular|react|svelte|qwik|astro]
66 | ```
67 |
68 | #### --[framework]
69 |
70 | You can specify the desired framework directly by adding a flag. The available flags are the same as the options for --framework flag.
71 |
72 | ```bash
73 | #this will create a vue component
74 | npx make-js-component --vue
75 | ```
76 |
77 | #### --multiple
78 |
79 | Using this flag allows you to create multiple components in succession. If you type “exit” while in the naming your component phase, it will exit the prompt.
80 |
81 | ```bash
82 | npx make-js-component --multiple
83 | ```
84 |
85 | ## Available Frameworks
86 |
87 | ### Vue
88 | > Want to help with vue components? Check out [Vue related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3AVue)
89 |
90 | When choosing Vue, the wizard will ask you whether you prefer to use the **Options API** or the **Composition API**, and you can make your selection using the arrow keys.
91 |
92 | ### React
93 | > Want to help with React components? Check out [React related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3AReact)
94 |
95 | When choosing React, the wizard will ask you if you want to use **TypeScript** or not, and you can make your selection using the arrow keys.
96 |
97 | ### Angular
98 | > Want to help with Angular components? Check out [Angular related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3AAngular)
99 |
100 | ### Qwik
101 | > Want to help with Qwik components? Check out [Qwik related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3AQwik)
102 |
103 | ### Svelte
104 | > Want to help with Svelte components? Check out [Svelte related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3ASvelte)
105 |
106 | ### Astro
107 | > Want to help with Astro components? Check out [Astro related issues](https://github.com/Giuliano1993/make-js-component/issues?q=is%3Aissue+is%3Aopen+label%3AAstro)
108 |
109 | ## Contributing
110 |
111 | Read the [Contributing guide](./CONTRIBUTING.md) for the contribution process
112 |
113 | ## Setup locally
114 |
115 | If you're cloning the repo, both for contributing or just to start taking confidence with the code just follow these steps:
116 |
117 | 1. clone the repo with `git clone https://github.com/Giuliano1993/make-js-component`
118 | 2. inside the folder run `npm install`
119 | 3. then to transpile ts files into js and watch them, run `npm run dev`
120 |
121 | To run your local version of the package and test it, run
122 |
123 | ```bash
124 | npx .
125 | ```
126 |
--------------------------------------------------------------------------------
/src/utils/wizard.mjs:
--------------------------------------------------------------------------------
1 | import { Command } from "commander";
2 | import inquirer from "inquirer";
3 | import angularWizard from "./frameworks/angular/angular.mjs";
4 | import astroWizard from "./frameworks/astro/astro.mjs";
5 | import qwikWizard from "./frameworks/qwik/qwik.mjs";
6 | import reactWizard from "./frameworks/react/react.mjs";
7 | import svelteWizard from "./frameworks/svelte/svelte.mjs";
8 | import vueWizard from "./frameworks/vue/vue.mjs";
9 | import { capitalizeFirstLetter } from "./utils.mjs";
10 | const program = new Command();
11 | const wizard = async () => {
12 | // Parse command line arguments using commander
13 | const frameworks = ["Vue", "Angular", "React", "Svelte", "Qwik", "Astro"];
14 | program
15 | .option("--name ", "Specify a name")
16 | .option(
17 | "-f, --framework ",
18 | `Specify framework [${frameworks.join("|")}]`
19 | )
20 | .option("--vue", "Create a Vue component")
21 | .option("--angular", "Create an Angular component")
22 | .option("--react", "Create a React component")
23 | .option("--svelte", "Create a Svelte component")
24 | .option("--qwik", "Create a Qwik component")
25 | .option("--astro", "Create an Astro component")
26 | .option("--folder ", "Specify the subfolder")
27 | .option("--multiple", "Creating multiple components at once")
28 | .parse(process.argv);
29 | const options = program.opts();
30 | const componentNameFromFlag = options.name || "";
31 | const frameworkFromFlag =
32 | options.framework || options.vue
33 | ? "vue"
34 | : null || options.angular
35 | ? "angular"
36 | : null || options.react
37 | ? "react"
38 | : null || options.svelte
39 | ? "svelte"
40 | : null || options.qwik
41 | ? "qwik"
42 | : null || options.astro
43 | ? "astro"
44 | : null || "";
45 | const folderFromFlag = options.folder || "";
46 | const multipleFromFlag = options.multiple || false;
47 |
48 | const prompts = [];
49 | // Only ask for componentName if --name argument is not provided
50 | if (!componentNameFromFlag) {
51 | prompts.push({
52 | type: "input",
53 | name: "componentName",
54 | message: "Give a name to your component",
55 | validate: (input) => {
56 | const trimmedInput = input.trim();
57 | if (trimmedInput === "") {
58 | return "Component name cannot be empty";
59 | }
60 | if (multipleFromFlag && trimmedInput === "exit") {
61 | process.exit();
62 | }
63 | // Use a regular expression to check for only alphanumeric characters
64 | const isValid = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/.test(trimmedInput);
65 | return (
66 | isValid || "Component name can only contain alphanumeric characters"
67 | );
68 | },
69 | });
70 | }
71 | if (!folderFromFlag) {
72 | prompts.push({
73 | type: "input",
74 | name: "folder",
75 | message: "Custom path for the component (default: src/components)",
76 | default: "",
77 | });
78 | }
79 | if (!frameworkFromFlag) {
80 | prompts.push({
81 | type: "list",
82 | name: "framework",
83 | message: "Pick a framework to create the component for",
84 | choices: frameworks,
85 | });
86 | }
87 |
88 | return inquirer
89 | .prompt(prompts)
90 | .then((answers) => {
91 | const folder = answers.folder || folderFromFlag;
92 | const framework =
93 | answers.framework || capitalizeFirstLetter(frameworkFromFlag);
94 | const componentName = answers.componentName || componentNameFromFlag;
95 | switch (framework) {
96 | case "Vue":
97 | return vueWizard(componentName, folder);
98 | case "Angular":
99 | return angularWizard(componentName, folder);
100 | case "React":
101 | return reactWizard(componentName, folder);
102 | case "Svelte":
103 | return svelteWizard(componentName, folder);
104 | case "Qwik":
105 | return qwikWizard(componentName, folder);
106 | case "Astro":
107 | return astroWizard(componentName, folder);
108 | default:
109 | throw new Error("A valid framework must be selected");
110 | }
111 | })
112 | .then((values) => {
113 | if (!multipleFromFlag) {
114 | return inquirer
115 | .prompt([
116 | {
117 | type: "confirm",
118 | name: "anotherComponent",
119 | message: "Do you want to create another component?",
120 | default: false,
121 | },
122 | ])
123 | .then((answers) => {
124 | const { anotherComponent } = answers;
125 | const completeValues = {
126 | ...values,
127 | anotherComponent: anotherComponent,
128 | };
129 | return completeValues;
130 | });
131 | }
132 | return { ...values, anotherComponent: true };
133 | })
134 | .catch((e) => {
135 | throw new Error(e.message);
136 | });
137 | };
138 | export default wizard;
139 |
--------------------------------------------------------------------------------
/src/utils/utils.mts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "node:path";
3 | import { configs } from "./configs.cjs";
4 | import { makeAngularComponent } from "./frameworks/angular/make-angular-component.mjs";
5 |
6 | import inquirer from "inquirer";
7 | import advancedVueBuilder, { vueApi } from "./frameworks/vue/helper.mjs";
8 | import wizard, { Answers } from "./wizard.mjs";
9 |
10 | export interface ErrnoException extends Error {
11 | errno?: number | undefined;
12 | code?: string | undefined;
13 | path?: string | undefined;
14 | syscall?: string | undefined;
15 | }
16 |
17 | export default async function createComponent(
18 | componentName: string,
19 | framework: string,
20 | template: string,
21 | customFolder: string,
22 | api: vueApi,
23 | advancedOpts: string[] | undefined
24 | ) {
25 | const destinationFolder: string = `${configs.BASE_DIR}${configs.COMPONENT_FOLDER}`;
26 | if (!fs.existsSync(destinationFolder)) {
27 | fs.mkdirSync(destinationFolder);
28 | }
29 |
30 | const templateFilePath: string = path.join(configs.INIT_PATH, "src", configs.STUBS_DIR, framework, template);
31 | fs.readFile(templateFilePath, "utf8", async (err: ErrnoException | null, data: string) => {
32 | const customDestinationFolder: string = path.join(configs.BASE_DIR, configs.COMPONENT_FOLDER, customFolder);
33 | const extension = template.substring(template.indexOf("."));
34 | const compFileName = `${componentName}${extension}`;
35 |
36 | if (!fs.existsSync(customDestinationFolder)) {
37 | fs.mkdirSync(customDestinationFolder, { recursive: true });
38 | }
39 |
40 | const filePathDestination: string = path.join(
41 | configs.BASE_DIR,
42 | configs.COMPONENT_FOLDER,
43 | customFolder,
44 | compFileName
45 | );
46 | let output = data;
47 | if (framework === "angular") {
48 | makeAngularComponent(filePathDestination, output, componentName);
49 | } else {
50 | if (template.indexOf("advanced") !== -1) {
51 | switch (framework) {
52 | case "vue":
53 | output = advancedVueBuilder(output, api, advancedOpts);
54 | break;
55 | default:
56 | break;
57 | }
58 | }
59 | output = output.replaceAll("ComponentName", capitalizeFirstLetter(componentName));
60 | await checkFileExists(filePathDestination, output);
61 | return filePathDestination;
62 | }
63 | if (path.parse(template).name === "function-component-css-module") {
64 | const styleFileName: string = `${componentName}.module.css`;
65 | const styleFilePathDestination: string = path.join(
66 | configs.BASE_DIR,
67 | configs.COMPONENT_FOLDER,
68 | customFolder,
69 | styleFileName
70 | );
71 | await checkFileExists(
72 | styleFilePathDestination,
73 | `.${componentName} {\n\tfont-size: 1.125rem; /* 18px */\n\tline-height: 1.75rem; /* 28px */\n\tfont-weight: bold;\n}\n`
74 | );
75 | return filePathDestination;
76 | }
77 | });
78 | }
79 |
80 | export async function checkFileExists(filePathDestination: string, data: string) {
81 | if (fs.existsSync(filePathDestination)) {
82 | console.log(`⚠️ A component with this name and extension already exists in ${filePathDestination}`);
83 | inquirer
84 | .prompt([
85 | {
86 | type: "confirm",
87 | name: "duplicateFile",
88 | message: "Do you want to continue with component creation? NOTE: this action will override the existing file",
89 | default: false,
90 | },
91 | ])
92 | .then((answer: { duplicateFile: boolean }) => {
93 | if (answer.duplicateFile) {
94 | (async () => {
95 | await writeFile(filePathDestination, data);
96 | })();
97 | } else {
98 | return console.log("❌ File not created");
99 | }
100 | });
101 | } else {
102 | await writeFile(filePathDestination, data);
103 | }
104 | }
105 |
106 | async function writeFile(filePathDestination: string, data: string) {
107 | fs.writeFile(filePathDestination, data, (err: ErrnoException | null) => {
108 | if (err) {
109 | console.error(err);
110 | }
111 | });
112 | }
113 |
114 | export function createAnotherComponent() {
115 | enum vueApi {
116 | Composition = "composition",
117 | Option = "option",
118 | }
119 |
120 | wizard()
121 | .then((answers: Answers) => {
122 | const { componentName, framework, template, folder, anotherComponent, advancedOpts, advanced } = answers;
123 | const api = template.indexOf("composition") !== -1 ? vueApi.Composition : vueApi.Option;
124 | const t = advanced ? "advanced-component.vue" : template;
125 | createComponent(componentName, framework, t, folder, api, advancedOpts);
126 | if (anotherComponent) {
127 | createAnotherComponent();
128 | }
129 | })
130 | .catch((e: Error) => {
131 | console.error(e.message);
132 | });
133 | return;
134 | }
135 |
136 | export function capitalizeFirstLetter(string: string): string {
137 | return string.charAt(0).toUpperCase() + string.slice(1);
138 | }
139 |
140 | export function prepareAdvanced(options: string[]) {
141 | const arr = [
142 | {
143 | type: "confirm",
144 | name: "advanced",
145 | message: "Do you want to check for advanced options?",
146 | default: false,
147 | },
148 | {
149 | type: "checkbox",
150 | name: "advancedOpts",
151 | message: "Pick the parts you want in your component?",
152 | choices: options,
153 | when: (answers: { nuxt: string; api: string; advanced: boolean }) => {
154 | return answers.advanced;
155 | },
156 | default: false,
157 | },
158 | ];
159 |
160 | return [...arr];
161 | }
162 |
--------------------------------------------------------------------------------
/src/utils/utils.mjs:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import * as path from "node:path";
3 | import { configs } from "./configs.cjs";
4 | import { makeAngularComponent } from "./frameworks/angular/make-angular-component.mjs";
5 | import inquirer from "inquirer";
6 | import advancedVueBuilder from "./frameworks/vue/helper.mjs";
7 | import wizard from "./wizard.mjs";
8 | export default async function createComponent(
9 | componentName,
10 | framework,
11 | template,
12 | customFolder,
13 | api,
14 | advancedOpts
15 | ) {
16 | const destinationFolder = `${configs.BASE_DIR}${configs.COMPONENT_FOLDER}`;
17 | if (!fs.existsSync(destinationFolder)) {
18 | fs.mkdirSync(destinationFolder);
19 | }
20 | const templateFilePath = path.join(
21 | configs.INIT_PATH,
22 | "src",
23 | configs.STUBS_DIR,
24 | framework,
25 | template
26 | );
27 | fs.readFile(templateFilePath, "utf8", async (err, data) => {
28 | const customDestinationFolder = path.join(
29 | configs.BASE_DIR,
30 | configs.COMPONENT_FOLDER,
31 | customFolder
32 | );
33 | const extension = template.substring(template.indexOf("."));
34 | const compFileName = `${componentName}${extension}`;
35 | if (!fs.existsSync(customDestinationFolder)) {
36 | fs.mkdirSync(customDestinationFolder, { recursive: true });
37 | }
38 | const filePathDestination = path.join(
39 | configs.BASE_DIR,
40 | configs.COMPONENT_FOLDER,
41 | customFolder,
42 | compFileName
43 | );
44 | let output = data;
45 | if (framework === "angular") {
46 | makeAngularComponent(filePathDestination, output, componentName);
47 | } else {
48 | if (template.indexOf("advanced") !== -1) {
49 | switch (framework) {
50 | case "vue":
51 | output = advancedVueBuilder(output, api, advancedOpts);
52 | break;
53 | default:
54 | break;
55 | }
56 | }
57 | output = output.replaceAll(
58 | "ComponentName",
59 | capitalizeFirstLetter(componentName)
60 | );
61 | await checkFileExists(filePathDestination, output);
62 | return filePathDestination;
63 | }
64 | if (path.parse(template).name === "function-component-css-module") {
65 | const styleFileName = `${componentName}.module.css`;
66 | const styleFilePathDestination = path.join(
67 | configs.BASE_DIR,
68 | configs.COMPONENT_FOLDER,
69 | customFolder,
70 | styleFileName
71 | );
72 | await checkFileExists(
73 | styleFilePathDestination,
74 | `.${componentName} {\n\tfont-size: 1.125rem; /* 18px */\n\tline-height: 1.75rem; /* 28px */\n\tfont-weight: bold;\n}\n`
75 | );
76 | return filePathDestination;
77 | }
78 | });
79 | }
80 | export async function checkFileExists(filePathDestination, data) {
81 | if (fs.existsSync(filePathDestination)) {
82 | console.log(
83 | `⚠️ A component with this name and extension already exists in ${filePathDestination}`
84 | );
85 | inquirer
86 | .prompt([
87 | {
88 | type: "confirm",
89 | name: "duplicateFile",
90 | message:
91 | "Do you want to continue with component creation? NOTE: this action will override the existing file",
92 | default: false,
93 | },
94 | ])
95 | .then((answer) => {
96 | if (answer.duplicateFile) {
97 | (async () => {
98 | await writeFile(filePathDestination, data);
99 | })();
100 | } else {
101 | return console.log("❌ File not created");
102 | }
103 | });
104 | } else {
105 | await writeFile(filePathDestination, data);
106 | }
107 | }
108 | async function writeFile(filePathDestination, data) {
109 | fs.writeFile(filePathDestination, data, (err) => {
110 | if (err) {
111 | console.error(err);
112 | }
113 | });
114 | }
115 | export function createAnotherComponent() {
116 | let vueApi;
117 | (function (vueApi) {
118 | vueApi["Composition"] = "composition";
119 | vueApi["Option"] = "option";
120 | })(vueApi || (vueApi = {}));
121 | wizard()
122 | .then((answers) => {
123 | const {
124 | componentName,
125 | framework,
126 | template,
127 | folder,
128 | anotherComponent,
129 | advancedOpts,
130 | advanced,
131 | } = answers;
132 | const api =
133 | template.indexOf("composition") !== -1
134 | ? vueApi.Composition
135 | : vueApi.Option;
136 | const t = advanced ? "advanced-component.vue" : template;
137 | createComponent(componentName, framework, t, folder, api, advancedOpts);
138 | if (anotherComponent) {
139 | createAnotherComponent();
140 | }
141 | })
142 | .catch((e) => {
143 | console.error(e.message);
144 | });
145 | return;
146 | }
147 | export function capitalizeFirstLetter(string) {
148 | return string.charAt(0).toUpperCase() + string.slice(1);
149 | }
150 | export function prepareAdvanced(options) {
151 | const arr = [
152 | {
153 | type: "confirm",
154 | name: "advanced",
155 | message: "Do you want to check for advanced options?",
156 | default: false,
157 | },
158 | {
159 | type: "checkbox",
160 | name: "advancedOpts",
161 | message: "Pick the parts you want in your component?",
162 | choices: options,
163 | when: (answers) => {
164 | return answers.advanced;
165 | },
166 | default: false,
167 | },
168 | ];
169 | return [...arr];
170 | }
171 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | ghostylab@gmail.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/src/utils/wizard.mts:
--------------------------------------------------------------------------------
1 | import { Command, OptionValues } from "commander";
2 | import inquirer from "inquirer";
3 | import angularWizard from "./frameworks/angular/angular.mjs";
4 | import astroWizard from "./frameworks/astro/astro.mjs";
5 | import qwikWizard from "./frameworks/qwik/qwik.mjs";
6 | import reactWizard from "./frameworks/react/react.mjs";
7 | import svelteWizard from "./frameworks/svelte/svelte.mjs";
8 | import vueWizard from "./frameworks/vue/vue.mjs";
9 | import { capitalizeFirstLetter } from "./utils.mjs";
10 |
11 | const program = new Command();
12 |
13 | export type Answers = {
14 | componentName: string;
15 | framework: string;
16 | template: string;
17 | folder: string;
18 | anotherComponent?: boolean;
19 | advanced?: boolean;
20 | advancedOpts?: string[];
21 | api?: string;
22 | };
23 |
24 | type FrameworkFromFlagType = "vue" | "angular" | "react" | "svelte" | "qwik" | "astro" | "";
25 |
26 | type FrameworksType = "Vue" | "Angular" | "React" | "Svelte" | "Qwik" | "Astro";
27 |
28 | interface PromptProps {
29 | readonly type: string;
30 | readonly name: string;
31 | readonly message: string;
32 | readonly validate?: (input: string) => boolean | string;
33 | readonly default?: string | boolean;
34 | readonly choices?: FrameworksType[];
35 | }
36 |
37 | const wizard: () => Promise = async () => {
38 | // Parse command line arguments using commander
39 | const frameworks: FrameworksType[] = ["Vue", "Angular", "React", "Svelte", "Qwik", "Astro"];
40 |
41 | program
42 | .option("--name ", "Specify a name")
43 | .option("-f, --framework ", `Specify framework [${frameworks.join("|")}]`)
44 | .option("--vue", "Create a Vue component")
45 | .option("--angular", "Create an Angular component")
46 | .option("--react", "Create a React component")
47 | .option("--svelte", "Create a Svelte component")
48 | .option("--qwik", "Create a Qwik component")
49 | .option("--astro", "Create an Astro component")
50 | .option("--folder ", "Specify the subfolder")
51 | .option("--multiple", "Creating multiple components at once")
52 | .parse(process.argv);
53 |
54 | const options: OptionValues = program.opts();
55 | const componentNameFromFlag: string = options.name || "";
56 |
57 | const frameworkFromFlag: FrameworkFromFlagType =
58 | options.framework || options.vue
59 | ? "vue"
60 | : null || options.angular
61 | ? "angular"
62 | : null || options.react
63 | ? "react"
64 | : null || options.svelte
65 | ? "svelte"
66 | : null || options.qwik
67 | ? "qwik"
68 | : null || options.astro
69 | ? "astro"
70 | : null || "";
71 |
72 | const folderFromFlag: string = options.folder || "";
73 | const multipleFromFlag: boolean = options.multiple || false;
74 |
75 | const prompts: PromptProps[] = [];
76 |
77 | // Only ask for componentName if --name argument is not provided
78 | if (!componentNameFromFlag) {
79 | prompts.push({
80 | type: "input",
81 | name: "componentName",
82 | message: "Give a name to your component",
83 | validate: (input: string) => {
84 | const trimmedInput: string = input.trim();
85 | if (trimmedInput === "") {
86 | return "Component name cannot be empty";
87 | }
88 | if (multipleFromFlag && trimmedInput === "exit") {
89 | process.exit();
90 | }
91 | // Use a regular expression to check for only alphanumeric characters
92 | const isValid: boolean = /^[A-Za-z0-9]+(-[A-Za-z0-9]+)*$/.test(trimmedInput);
93 | return isValid || "Component name can only contain alphanumeric characters";
94 | },
95 | });
96 | }
97 |
98 | if (!folderFromFlag) {
99 | prompts.push({
100 | type: "input",
101 | name: "folder",
102 | message: "Custom path for the component (default: src/components)",
103 | default: "",
104 | });
105 | }
106 |
107 | if (!frameworkFromFlag) {
108 | prompts.push({
109 | type: "list",
110 | name: "framework",
111 | message: "Pick a framework to create the component for",
112 | choices: frameworks,
113 | });
114 | }
115 |
116 | return inquirer
117 | .prompt(prompts)
118 | .then(
119 | (answers: {
120 | componentName: string;
121 | folder: string;
122 | framework: string;
123 | }) => {
124 | const folder: string = answers.folder || folderFromFlag;
125 | const framework: string = answers.framework || capitalizeFirstLetter(frameworkFromFlag);
126 | const componentName: string = answers.componentName || componentNameFromFlag;
127 |
128 | switch (framework) {
129 | case "Vue":
130 | return vueWizard(componentName, folder);
131 | case "Angular":
132 | return angularWizard(componentName, folder);
133 | case "React":
134 | return reactWizard(componentName, folder);
135 | case "Svelte":
136 | return svelteWizard(componentName, folder);
137 | case "Qwik":
138 | return qwikWizard(componentName, folder);
139 | case "Astro":
140 | return astroWizard(componentName, folder);
141 | default:
142 | throw new Error("A valid framework must be selected");
143 | }
144 | }
145 | )
146 | .then((values: Answers) => {
147 | if (!multipleFromFlag) {
148 | return inquirer
149 | .prompt([
150 | {
151 | type: "confirm",
152 | name: "anotherComponent",
153 | message: "Do you want to create another component?",
154 | default: false,
155 | },
156 | ])
157 | .then((answers: { values: Answers; anotherComponent: boolean }) => {
158 | const { anotherComponent } = answers;
159 | const completeValues = {
160 | ...values,
161 | anotherComponent: anotherComponent,
162 | };
163 | return completeValues;
164 | });
165 | }
166 | return { ...values, anotherComponent: true };
167 | })
168 | .catch((e: Error) => {
169 | throw new Error(e.message);
170 | });
171 | };
172 | export default wizard;
173 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * For a detailed explanation regarding each configuration property, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | /** @type {import('jest').Config} */
7 | const config = {
8 | // All imported modules in your tests should be mocked automatically
9 | // automock: false,
10 |
11 | // Stop running tests after `n` failures
12 | // bail: 0,
13 |
14 | // The directory where Jest should store its cached dependency information
15 | // cacheDirectory: "C:\\Users\\giuli\\AppData\\Local\\Temp\\jest",
16 |
17 | // Automatically clear mock calls, instances, contexts and results before every test
18 | // clearMocks: false,
19 |
20 | // Indicates whether the coverage information should be collected while executing the test
21 | // collectCoverage: false,
22 |
23 | // An array of glob patterns indicating a set of files for which coverage information should be collected
24 | // collectCoverageFrom: undefined,
25 |
26 | // The directory where Jest should output its coverage files
27 | // coverageDirectory: undefined,
28 |
29 | // An array of regexp pattern strings used to skip coverage collection
30 | // coveragePathIgnorePatterns: [
31 | // "\\\\node_modules\\\\"
32 | // ],
33 |
34 | // Indicates which provider should be used to instrument code for coverage
35 | coverageProvider: "v8",
36 |
37 | // A list of reporter names that Jest uses when writing coverage reports
38 | // coverageReporters: [
39 | // "json",
40 | // "text",
41 | // "lcov",
42 | // "clover"
43 | // ],
44 |
45 | // An object that configures minimum threshold enforcement for coverage results
46 | // coverageThreshold: undefined,
47 |
48 | // A path to a custom dependency extractor
49 | // dependencyExtractor: undefined,
50 |
51 | // Make calling deprecated APIs throw helpful error messages
52 | // errorOnDeprecated: false,
53 |
54 | // The default configuration for fake timers
55 | // fakeTimers: {
56 | // "enableGlobally": false
57 | // },
58 |
59 | // Force coverage collection from ignored files using an array of glob patterns
60 | // forceCoverageMatch: [],
61 |
62 | // A path to a module which exports an async function that is triggered once before all test suites
63 | // globalSetup: undefined,
64 |
65 | // A path to a module which exports an async function that is triggered once after all test suites
66 | // globalTeardown: undefined,
67 |
68 | // A set of global variables that need to be available in all test environments
69 | // globals: {},
70 |
71 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
72 | // maxWorkers: "50%",
73 |
74 | // An array of directory names to be searched recursively up from the requiring module's location
75 | // moduleDirectories: [
76 | // "node_modules"
77 | // ],
78 |
79 | // An array of file extensions your modules use
80 | // moduleFileExtensions: [
81 | // "js",
82 | // "mjs",
83 | // "cjs",
84 | // "jsx",
85 | // "ts",
86 | // "tsx",
87 | // "json",
88 | // "node"
89 | // ],
90 |
91 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
92 | // moduleNameMapper: {},
93 |
94 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
95 | // modulePathIgnorePatterns: [],
96 |
97 | // Activates notifications for test results
98 | // notify: false,
99 |
100 | // An enum that specifies notification mode. Requires { notify: true }
101 | // notifyMode: "failure-change",
102 |
103 | // A preset that is used as a base for Jest's configuration
104 | // preset: undefined,
105 |
106 | // Run tests from one or more projects
107 | // projects: undefined,
108 |
109 | // Use this configuration option to add custom reporters to Jest
110 | // reporters: undefined,
111 |
112 | // Automatically reset mock state before every test
113 | // resetMocks: false,
114 |
115 | // Reset the module registry before running each individual test
116 | // resetModules: false,
117 |
118 | // A path to a custom resolver
119 | // resolver: undefined,
120 |
121 | // Automatically restore mock state and implementation before every test
122 | // restoreMocks: false,
123 |
124 | // The root directory that Jest should scan for tests and modules within
125 | // rootDir: undefined,
126 |
127 | // A list of paths to directories that Jest should use to search for files in
128 | // roots: [
129 | // ""
130 | // ],
131 |
132 | // Allows you to use a custom runner instead of Jest's default test runner
133 | // runner: "jest-runner",
134 |
135 | // The paths to modules that run some code to configure or set up the testing environment before each test
136 | // setupFiles: [],
137 |
138 | // A list of paths to modules that run some code to configure or set up the testing framework before each test
139 | setupFilesAfterEnv: ["./setup.jest.js"],
140 |
141 | // The number of seconds after which a test is considered as slow and reported as such in the results.
142 | // slowTestThreshold: 5,
143 |
144 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing
145 | // snapshotSerializers: [],
146 |
147 | // The test environment that will be used for testing
148 | // testEnvironment: "jest-environment-node",
149 |
150 | // Options that will be passed to the testEnvironment
151 | // testEnvironmentOptions: {},
152 |
153 | // Adds a location field to test results
154 | // testLocationInResults: false,
155 |
156 | // The glob patterns Jest uses to detect test files
157 | testMatch: [
158 | "**/__tests__/**/*.(c)[jt]s?(x)",
159 | "**/?(*.)+(spec|test).(c)[tj]s?(x)"
160 | ],
161 |
162 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
163 | // testPathIgnorePatterns: [
164 | // "\\\\node_modules\\\\"
165 | // ],
166 |
167 | // The regexp pattern or array of patterns that Jest uses to detect test files
168 | // testRegex: [],
169 |
170 | // This option allows the use of a custom results processor
171 | // testResultsProcessor: undefined,
172 |
173 | // This option allows use of a custom test runner
174 | // testRunner: "jest-circus/runner",
175 |
176 | // A map from regular expressions to paths to transformers
177 | // transform: undefined,
178 |
179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
180 | // transformIgnorePatterns: [
181 | // "\\\\node_modules\\\\",
182 | // "\\.pnp\\.[^\\\\]+$"
183 | // ],
184 |
185 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
186 | // unmockedModulePathPatterns: undefined,
187 |
188 | // Indicates whether each individual test should be reported during the run
189 | // verbose: undefined,
190 |
191 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
192 | // watchPathIgnorePatterns: [],
193 |
194 | // Whether to use watchman for file crawling
195 | // watchman: true,
196 | };
197 |
198 | module.exports = config;
199 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": [
16 | "ES2022"
17 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
18 | // "jsx": "preserve", /* Specify what JSX code is generated. */
19 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
20 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
21 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
22 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
23 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
24 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
25 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
26 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
27 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
28 |
29 | /* Modules */
30 | "module": "NodeNext", /* Specify what module code is generated. */
31 | // "rootDir": "./", /* Specify the root folder within your source files. */
32 | "moduleResolution": "nodenext", /* Specify how TypeScript looks up a file from a given module specifier. */
33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
40 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
41 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
42 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
43 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
44 | // "resolveJsonModule": true, /* Enable importing .json files. */
45 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
46 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
47 |
48 | /* JavaScript Support */
49 | //"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
50 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
51 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
52 |
53 | /* Emit */
54 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
55 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
56 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
57 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
60 | // "outDir": "./", /* Specify an output folder for all emitted files. */
61 | // "removeComments": true, /* Disable emitting comments. */
62 | // "noEmit": true, /* Disable emitting files from a compilation. */
63 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
64 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
65 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
66 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
67 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
68 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
69 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
70 | // "newLine": "crlf", /* Set the newline character for emitting files. */
71 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
72 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
73 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
74 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
75 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
76 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
77 |
78 | /* Interop Constraints */
79 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
80 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
81 | "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
82 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
83 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
84 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
85 |
86 | /* Type Checking */
87 | "strict": true, /* Enable all strict type-checking options. */
88 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
89 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
90 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
91 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
92 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
93 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
94 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
95 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
96 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
97 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
98 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
99 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
100 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
101 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
102 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
103 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
104 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
105 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
106 |
107 | /* Completeness */
108 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
109 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
110 | },
111 | "exclude": [
112 | "./src/**/*.tsx",
113 | "./src/components/*",
114 | "./src/stubs/angular/*",
115 | ],
116 | }
117 |
--------------------------------------------------------------------------------