diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f9b6bc6 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,23 @@ +name: Publish Package to npmjs +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20.x" + registry-url: "https://registry.npmjs.org" + cache: yarn + - run: yarn install --frozen-lockfile + - run: npm publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..c27ae53 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,33 @@ +name: Node.js Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: "yarn" + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run tests + run: yarn test diff --git a/.gitignore b/.gitignore index d4ca257..9a8b391 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules drafts +output/* \ No newline at end of file diff --git a/cli.test.js b/cli.test.js index 186f6be..af37483 100644 --- a/cli.test.js +++ b/cli.test.js @@ -1,19 +1,70 @@ -import test from "node:test"; -import assert from "node:assert"; -import { exec } from "node:child_process"; +// Assertion +import { beforeEach, afterEach, describe, it } from "node:test"; +import assert from "node:assert/strict"; +// Built-in modules +import { readFile, rm } from "node:fs/promises"; +import { resolve } from "node:path"; import { promisify } from "node:util"; -import { resolve } from "path"; +import { exec } from "node:child_process"; +// Local modules import { ERR_MISSING_TEMPLATE } from "./src/errors.js"; const execAsync = promisify(exec); - const CLI_PATH = resolve("./src/cli.js"); -test("Exit if missing template name", async () => { - const command = `node ${CLI_PATH}`; +function clearOutputDir() { + const outputDir = resolve("output"); + // Clean up the output directory before each test + rm(outputDir, { recursive: true, force: true }).catch(() => {}); +} + +describe("YNDAP CLI", () => { + beforeEach(clearOutputDir); + afterEach(clearOutputDir); + + // ============ + // Successful test case + // ============ + it("Create a file passing correct params, creating .js by default", async () => { + const templateName = "sum"; + const outputDir = resolve("output/sum"); + const targetFile = resolve(outputDir, "sum.js"); + await execAsync(`node ${CLI_PATH} -t ${templateName} -o ${outputDir}`); + const content = await readFile(targetFile, "utf8"); + + assert.ok(content.includes("export default function sum")); + }); + + it("Create a file passing correct params, creating .ts by change extension", async () => { + const templateName = "sum"; + const outputDir = resolve("output/sum"); + const targetFile = resolve(outputDir, "sum.ts"); + await execAsync( + `node ${CLI_PATH} -t ${templateName} -o ${outputDir} -e ts`, + ); + const content = await readFile(targetFile, "utf8"); + + assert.ok(content.includes("export default function sum")); + }); + + it("Create a file passing an another github user", async () => { + const targetFile = resolve("output", "is-even.js"); + await execAsync( + `node ${CLI_PATH} -t even -o ${targetFile} -r alexcastrodev/ydnap-example`, + ); + const content = await readFile(targetFile, "utf8"); + assert.ok(content.includes("export default function isEven")); + }); + + // ============ + // Fail test case + // ============ + it("Exit if missing template name", async () => { + const command = `node ${CLI_PATH}`; - await assert.rejects(execAsync(command), { - code: 1, - stderr: new RegExp(ERR_MISSING_TEMPLATE), + await assert.rejects(execAsync(command), { + code: 1, + stderr: new RegExp(ERR_MISSING_TEMPLATE), + }); }); }); diff --git a/package.json b/package.json index dafb6cb..04c9f75 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "ydnap", - "version": "1.0.0", + "name": "@guildadev/ydnap", + "version": "0.1.0", "description": "A CLI that offers you templates for things you don't need a package for.", "bin": { - "ydnap": "./src/cli.js" + "ydnap": "src/cli.js" }, "scripts": { - "test": "node --test './test/**'", + "test": "node --test", "format": "prettier --write ." }, "type": "module", diff --git a/readme.md b/readme.md index 7c77760..9c0254a 100644 --- a/readme.md +++ b/readme.md @@ -7,16 +7,59 @@ It is designed to be lightweight and easy to use, easy to collaborate, without t Sick of installing packages just to archive a simple task? YDNAP is here to help! -In the end of the day, you maybe don't need ramda, lodash, date-fns, or any other package to do simple tasks. +In the end of the day, you maybe don't need Ramda, Lodash, date-fns, or any other package to do simple tasks. + +### CLI Options + +| Option | Type | Short | Default | Choices | Description | +| ------------- | ------- | ----- | ------- | ---------- | ------------------------------------------------------------------------------- | +| `--verbose` | boolean | `-v` | `false` | N/A | Enables verbose mode for detailed logging. | +| `--extension` | string | `-e` | `js` | `js`, `ts` | Specifies the file extension to use (`js` for JavaScript, `ts` for TypeScript). | +| `--template` | string | `-t` | N/A | N/A | Specifies the template folder to use. | +| `--repo` | string | `-r` | N/A | N/A | (Optional) Specifies the repository URL to fetch templates from. | +| `--output` | string | `-o` | N/A | N/A | (Optional) Specifies the output directory for generated files. | + +## Installation + +You can install YDNAP globally using npm: + +```bash +npm install -g ydnap +``` + +Or you can use it without installing it by using npx: + +```bash +npx ydnap +``` ## Usage -### Using with npx +You can use YDNAP and create files using our [templates](https://github.com/GuildaDev/ydnap-templates), or you can create your own templates (or share them with your friiiiends). + +To use our template, you can run: + +```bash +ydnap -t sum # or npx ydnap -t sum +``` + +by default, we will always find the javascript file. + +you can also specify the typescript file: ```bash -npx ydnap -t sum +ydnap -t sum -l ts # or npx ydnap -t sum -l ts ``` +Using you repository (eg https://github.com/alexcastrodev/ydnap-example/tree/main/src/even) + +```bash +ydnap -u alexcastrodev/ydnap-example -t even +``` + +> **Note** +> It's mandatory that the `-t` (template) argument points to a folder, and the file inside the folder should be named `index.ts` or `index.js`. + ## Drawbacks YDNAP is designed to solve small tasks, like navigating through objects with JavaScript or TypeScript (without needing the full weight of libraries like Ramda or Lodash), or creating a useDebounceCallback for React without installing an entire hooks library. diff --git a/src/cli.js b/src/cli.js index db02818..5d6dc98 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,8 +1,21 @@ #!/usr/bin/env node import { parseArgs } from "node:util"; -import { ERR_MISSING_TEMPLATE } from "./errors.js"; +import { ERR_MISSING_TEMPLATE, ERR_FETCH_TEMPLATE } from "./errors.js"; +import { resolve, extname, dirname } from "node:path"; +import { mkdirSync } from "node:fs"; +import { writeFile } from "fs/promises"; +import { Writable } from "node:stream"; + +// =========== +// Initialization +// =========== const options = { + verbose: { + type: "boolean", + short: "v", + default: false, + }, extension: { type: "string", short: "e", @@ -13,18 +26,141 @@ const options = { type: "string", short: "t", }, + repo: { + type: "string", + short: "r", + }, + output: { + type: "string", + short: "o", + }, + help: { + type: "boolean", + short: "h", + }, }; const { values } = parseArgs({ options, tokens: true }); +// =========== +// Helpers +// =========== + +const logStream = new Writable({ + write(chunk, encoding, callback) { + process.stdout.write(chunk, encoding, callback); + }, +}); + +function log(...args) { + if (values.verbose) { + logStream.write(`YNDAP: ${args.join(" ")}\n`); + } +} + +// =========== +// Help +// =========== + +if (values.help) { + console.log(` +Usage: ydnap [options] + +Options: + -t, --template