From 0a437e19bf46bdc1acd1c4a368cec27d8856c5ab Mon Sep 17 00:00:00 2001 From: fraxken Date: Thu, 15 Aug 2024 13:48:37 +0200 Subject: [PATCH] refactor!: migrate to TypeScript and enhance API --- .eslintrc | 7 -- .github/workflows/codeql.yml | 10 +- .github/workflows/node.js.yml | 6 +- .github/workflows/scorecard.yml | 10 +- .gitignore | 1 + README.md | 41 ++++---- eslint.config.mjs | 3 + index.d.ts | 46 --------- index.js | 81 --------------- package.json | 23 +++-- src/functions/download.ts | 99 ++++++++++++++++++ src/functions/downloadAndExtract.ts | 56 ++++++++++ src/gitlab/types.ts | 142 ++++++++++++++++++++++++++ src/index.ts | 2 + src/{utils.js => utils.ts} | 4 +- test/test.spec.js | 67 ------------ test/test.spec.ts | 72 +++++++++++++ test/{utils.spec.js => utils.spec.ts} | 0 tsconfig.json | 15 +++ 19 files changed, 438 insertions(+), 247 deletions(-) delete mode 100644 .eslintrc create mode 100644 eslint.config.mjs delete mode 100644 index.d.ts delete mode 100644 index.js create mode 100644 src/functions/download.ts create mode 100644 src/functions/downloadAndExtract.ts create mode 100644 src/gitlab/types.ts create mode 100644 src/index.ts rename src/{utils.js => utils.ts} (83%) delete mode 100644 test/test.spec.js create mode 100644 test/test.spec.ts rename test/{utils.spec.js => utils.spec.ts} (100%) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index a9fc04e..0000000 --- a/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@nodesecure/eslint-config", - "parserOptions": { - "sourceType": "module", - "requireConfigFile": false - } -} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d9f4574..85c1242 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,16 +41,16 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: Checkout repository - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 + uses: github/codeql-action/init@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -63,7 +63,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 + uses: github/codeql-action/autobuild@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -76,6 +76,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 + uses: github/codeql-action/analyze@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: category: "/language:${{matrix.language}}" \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index c9cd5fa..ed0c22b 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -17,13 +17,13 @@ jobs: fail-fast: false steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e51842e..40e7537 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,17 +32,17 @@ jobs: steps: - name: Harden Runner - uses: step-security/harden-runner@f086349bfa2bd1361f7909c78558e816508cdc10 # v2.8.0 + uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1 with: egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs - name: "Checkout code" - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3 + uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 with: results_file: results.sarif results_format: sarif @@ -64,7 +64,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@834a144ee995460fba8ed112a2fc961b36a5ec5a # v4.3.6 with: name: SARIF file path: results.sarif @@ -72,6 +72,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f # v3.25.7 + uses: github/codeql-action/upload-sarif@429e1977040da7a23b6822b13c129cd1ba93dbb2 # v3.26.2 with: sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 61b20b0..51fa9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,4 @@ typings/ .env temp/ +dist/ diff --git a/README.md b/README.md index 78f8208..2c6938c 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/NodeSecure/gitlab/badge?style=for-the-badge)](https://api.securityscorecards.dev/projects/github.com/NodeSecure/gitlab) ![MIT](https://img.shields.io/github/license/NodeSecure/gitlab.svg?style=for-the-badge) -![size](https://img.shields.io/github/repo-size/NodeSecure/gitlab?style=for-the-badge) -![known vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/NodeSecure/gitlab?style=for-the-badge) ![build](https://img.shields.io/github/actions/workflow/status/NodeSecure/gitlab/node.js.yml?style=for-the-badge) Download and (optionaly) extract gitlab repository archive. @@ -58,16 +56,11 @@ export interface DownloadOptions { * @default process.env.GITLAB_TOKEN */ token?: string; -} - -export type ExtractOptions = DownloadOptions & { /** - * Remove the tar.gz archive after a succesfull extraction - * - * @default true + * @default https://gitlab.com/api/v4/projects/ */ - removeArchive?: boolean; -}; + gitlab?: string; +} export interface DownloadResult { /** Archive or repository location on disk */ @@ -80,20 +73,24 @@ export interface DownloadResult { branch: string; } -export function download(repo: string, options?: DownloadOptions): Promise; -export function downloadAndExtract(repo: string, options?: ExtractOptions): Promise; -export function setToken(gitlabToken: string): void; -export function setUrl(gitlabUrl: string | URL): void; -``` - -### Private repositories - -To work with private repositories you can either setup a `GITLAB_TOKEN` system variable or use `setToken` method: +export function download( + repo: string, + options?: DownloadOptions +): Promise; -```js -import * as gitlab from "@nodesecure/gitlab"; +export interface DownloadExtractOptions extends DownloadOptions { + /** + * Remove the tar.gz archive after a succesfull extraction + * + * @default true + */ + removeArchive?: boolean; +} -gitlab.setToken("..."); +export function downloadAndExtract( + repo: string, + options?: DownloadExtractOptions +): Promise; ``` ### Custom gitlab URL diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..8a1e9d0 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,3 @@ +import { typescriptConfig } from "@openally/config.eslint"; + +export default typescriptConfig(); diff --git a/index.d.ts b/index.d.ts deleted file mode 100644 index dc0117e..0000000 --- a/index.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -export interface DownloadOptions { - /** - * The destination (location) to extract the tar.gz - * - * @default process.cwd() - */ - dest?: string; - /** - * The default gitlab branch name (master, main ...). - * By default it fetch the "default" gitlab branch. - * - * @default null - */ - branch?: string | null; - /** - * Authentication token for private repositories - * - * @default process.env.GITLAB_TOKEN - */ - token?: string; -} - -export type ExtractOptions = DownloadOptions & { - /** - * Remove the tar.gz archive after a succesfull extraction - * - * @default true - */ - removeArchive?: boolean; -} - -export interface DownloadResult { - /** Archive or repository location on disk */ - location: string; - /** Gitlab repository name */ - repository: string; - /** Gitlab organization name */ - organization: string; - /** Gitlab branch name */ - branch: string; -} - -export function download(repo: string, options?: DownloadOptions): Promise; -export function downloadAndExtract(repo: string, options?: ExtractOptions): Promise; -export function setToken(gitlabToken: string): void; -export function setUrl(gitlabUrl: string | URL): void; diff --git a/index.js b/index.js deleted file mode 100644 index 611c05a..0000000 --- a/index.js +++ /dev/null @@ -1,81 +0,0 @@ -// Import Node.js Dependencies -import path from "node:path"; -import { createWriteStream, createReadStream, promises as fs } from "node:fs"; -import { createGunzip } from "node:zlib"; -import { pipeline } from "node:stream/promises"; - -// Import Third-party Dependencies -import tar from "tar-fs"; -import httpie from "@myunisoft/httpie"; - -// Import Internal Dependencies -import * as utils from "./src/utils.js"; - -// CONSTANTS -const kGitlabURL = new URL("https://gitlab.com/api/v4/projects/"); - -// VARS -let GITLAB_TOKEN = process.env.GITLAB_TOKEN ?? void 0; -let GITLAB_URL = process.env.GITLAB_URL ?? void 0; - -export async function download(repository, options = Object.create(null)) { - if (typeof repository !== "string") { - throw new TypeError("repository must be a string!"); - } - const { branch = null, dest = process.cwd(), token = GITLAB_TOKEN } = options; - const headers = { - authorization: typeof token === "string" ? `Bearer ${token}` : void 0, - "user-agent": "NodeSecure" - }; - - const { data: gitlabManifest } = await httpie.get(new URL(utils.getRepositoryPath(repository), GITLAB_URL ?? kGitlabURL), { - headers, maxRedirections: 1 - }); - - const wantedBranch = typeof branch === "string" ? branch : gitlabManifest.default_branch; - const location = path.join(dest, `${gitlabManifest.name}-${wantedBranch}.tar.gz`); - - // Download the archive with the repositoryId - const repositoryURL = new URL(`${gitlabManifest.id}/repository/archive.tar.gz?ref=${wantedBranch}`, GITLAB_URL ?? kGitlabURL); - const writableCallback = httpie.stream("GET", repositoryURL, { - headers: { ...headers, "Accept-Encoding": "gzip, deflate" }, - maxRedirections: 1 - }); - await writableCallback(() => createWriteStream(location)); - - return { - location, - branch: wantedBranch, - organization: gitlabManifest.path_with_namespace.split("/")[0], - repository: gitlabManifest.name - }; -} - -export async function downloadAndExtract(repository, options = Object.create(null)) { - const { removeArchive = true, ...downloadOptions } = options; - const { branch, dest = process.cwd(), token } = downloadOptions; - - const result = await download(repository, { branch, dest, token }); - - const newLocation = path.join(dest, `${result.repository}-${result.branch}`); - await pipeline( - createReadStream(result.location), - createGunzip(), - tar.extract(newLocation) - ); - if (removeArchive) { - await fs.unlink(result.location); - } - - result.location = newLocation; - - return result; -} - -export function setToken(gitlabToken) { - GITLAB_TOKEN = gitlabToken; -} - -export function setUrl(gitlabUrl) { - GITLAB_URL = gitlabUrl; -} diff --git a/package.json b/package.json index 93055e7..e72b6ec 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,14 @@ "name": "@nodesecure/gitlab", "version": "1.1.0", "description": "Download and extract gitlab repository", - "exports": "./index.js", + "exports": "./dist/index.js", + "types": "./dist/index.d.ts", "type": "module", "scripts": { - "prepublishOnly": "pkg-ok", - "test": "node -r dotenv --test", + "build": "tsc", + "prepublishOnly": "npm run build", + "lint": "eslint src", + "test": "glob -c \"tsx --test\" \"./test/**/*.spec.ts\"", "coverage": "c8 -r html npm test" }, "repository": { @@ -18,9 +21,7 @@ "gitlab" ], "files": [ - "index.js", - "index.d.ts", - "src" + "dist" ], "author": "GENTILHOMME Thomas ", "license": "MIT", @@ -33,12 +34,14 @@ "tar-fs": "^3.0.6" }, "devDependencies": { - "@nodesecure/eslint-config": "^1.9.0", + "@openally/config.eslint": "^1.0.0", + "@openally/config.typescript": "^1.0.3", "@slimio/is": "^2.0.0", + "@types/node": "^22.3.0", + "@types/tar-fs": "^2.0.4", "c8": "^10.1.2", - "dotenv": "^16.4.5", - "eslint": "^9.2.0", - "pkg-ok": "^3.0.0" + "tsx": "^4.17.0", + "typescript": "^5.5.4" }, "engines": { "node": ">=18" diff --git a/src/functions/download.ts b/src/functions/download.ts new file mode 100644 index 0000000..ab21165 --- /dev/null +++ b/src/functions/download.ts @@ -0,0 +1,99 @@ +// Import Node.js Dependencies +import path from "node:path"; +import { + createWriteStream +} from "node:fs"; + +// Import Third-party Dependencies +import httpie from "@myunisoft/httpie"; + +// Import Internal Dependencies +import * as utils from "../utils.js"; +import * as gitlab from "../gitlab/types.js"; + +// CONSTANTS +export const GITLAB_DEFAULT_URL = new URL("https://gitlab.com/api/v4/projects/"); + +export interface DownloadOptions { + /** + * The destination (location) to extract the tar.gz + * + * @default process.cwd() + */ + dest?: string; + /** + * The default gitlab branch name (master, main ...). + * By default it fetch the "default" gitlab branch. + * + * @default null + */ + branch?: string | null; + /** + * Authentication token for private repositories + * + * @default process.env.GITLAB_TOKEN + */ + token?: string; + /** + * @default https://gitlab.com/api/v4/projects/ + */ + gitlab?: string; +} + +export interface DownloadResult { + /** Archive or repository location on disk */ + location: string; + /** Gitlab repository name */ + repository: string; + /** Gitlab organization name */ + organization: string; + /** Gitlab branch name */ + branch: string; +} + +export async function download( + repository: string, + options: DownloadOptions = Object.create(null) +): Promise { + if (typeof repository !== "string") { + throw new TypeError("repository must be a string!"); + } + const { + branch = null, + dest = process.cwd(), + token = process.env.GITLAB_TOKEN, + gitlab = GITLAB_DEFAULT_URL + } = options; + + const headers = { + authorization: typeof token === "string" ? `Bearer ${token}` : void 0, + "user-agent": "NodeSecure" + }; + + const repositoryURL = new URL(utils.getRepositoryPath(repository), gitlab); + const { data: gitlabManifest } = await httpie.get(repositoryURL, { + headers, + maxRedirections: 1 + }); + + const wantedBranch = typeof branch === "string" ? branch : gitlabManifest.default_branch; + const location = path.join(dest, `${gitlabManifest.name}-${wantedBranch}.tar.gz`); + + // Download the archive with the repositoryId + const archiveURL = new URL( + `${gitlabManifest.id}/repository/archive.tar.gz?ref=${wantedBranch}`, + gitlab + ); + const writableCallback = httpie.stream("GET", archiveURL, { + headers: { ...headers, "Accept-Encoding": "gzip, deflate" }, + maxRedirections: 1 + }); + await writableCallback(() => createWriteStream(location)); + + return { + location, + branch: wantedBranch, + organization: gitlabManifest.path_with_namespace.split("/")[0], + repository: gitlabManifest.name + }; +} diff --git a/src/functions/downloadAndExtract.ts b/src/functions/downloadAndExtract.ts new file mode 100644 index 0000000..544b915 --- /dev/null +++ b/src/functions/downloadAndExtract.ts @@ -0,0 +1,56 @@ +// Import Node.js Dependencies +import path from "node:path"; +import { + createReadStream, + promises as fs +} from "node:fs"; +import { createGunzip } from "node:zlib"; +import { pipeline } from "node:stream/promises"; + +// Import Third-party Dependencies +import tar from "tar-fs"; + +// Import Internal Dependencies +import { + download, + type DownloadResult, + type DownloadOptions +} from "./download.js"; + +export interface DownloadExtractOptions extends DownloadOptions { + /** + * Remove the tar.gz archive after a succesfull extraction + * + * @default true + */ + removeArchive?: boolean; +} + +export async function downloadAndExtract( + repository: string, + options: DownloadExtractOptions = Object.create(null) +): Promise { + const { removeArchive = true, ...downloadOptions } = options; + + const result = await download( + repository, + downloadOptions + ); + + const newLocation = path.join( + downloadOptions.dest ?? process.cwd(), + `${result.repository}-${result.branch}` + ); + await pipeline( + createReadStream(result.location), + createGunzip(), + tar.extract(newLocation) + ); + if (removeArchive) { + await fs.unlink(result.location); + } + + result.location = newLocation; + + return result; +} diff --git a/src/gitlab/types.ts b/src/gitlab/types.ts new file mode 100644 index 0000000..2dd36fd --- /dev/null +++ b/src/gitlab/types.ts @@ -0,0 +1,142 @@ +export interface Namespace { + id: number; + name: string; + path: string; + kind: string; + full_path: string; + parent_id: number | null; + avatar_url: string; + web_url: string; +} + +export interface Links { + self: string; + issues: string; + merge_requests: string; + repo_branches: string; + labels: string; + events: string; + members: string; + cluster_agents: string; +} + +export interface Permissions { + project_access: any | null; + group_access: { + access_level: number; + notification_level: number; + }; +} + +export interface Project { + id: number; + description: string; + name: string; + name_with_namespace: string; + path: string; + path_with_namespace: string; + created_at: string; + default_branch: string; + tag_list: string[]; + topics: string[]; + ssh_url_to_repo: string; + http_url_to_repo: string; + web_url: string; + readme_url: string; + forks_count: number; + avatar_url: string; + star_count: number; + last_activity_at: string; + namespace: Namespace; + container_registry_image_prefix: string; + _links: Links; + packages_enabled: boolean | null; + empty_repo: boolean; + archived: boolean; + visibility: string; + resolve_outdated_diff_discussions: boolean | null; + repository_object_format: string; + issues_enabled: boolean; + merge_requests_enabled: boolean; + wiki_enabled: boolean; + jobs_enabled: boolean; + snippets_enabled: boolean; + container_registry_enabled: boolean; + service_desk_enabled: boolean | null; + service_desk_address: string; + can_create_merge_request_in: boolean; + issues_access_level: string; + repository_access_level: string; + merge_requests_access_level: string; + forking_access_level: string; + wiki_access_level: string; + builds_access_level: string; + snippets_access_level: string; + pages_access_level: string; + analytics_access_level: string; + container_registry_access_level: string; + security_and_compliance_access_level: string; + releases_access_level: string; + environments_access_level: string; + feature_flags_access_level: string; + infrastructure_access_level: string; + monitor_access_level: string; + model_experiments_access_level: string; + model_registry_access_level: string; + emails_disabled: boolean; + emails_enabled: boolean; + shared_runners_enabled: boolean; + lfs_enabled: boolean; + creator_id: number; + import_url: string | null; + import_type: string | null; + import_status: string; + import_error: string | null; + open_issues_count: number; + description_html: string; + updated_at: string; + ci_default_git_depth: number | null; + ci_forward_deployment_enabled: boolean | null; + ci_forward_deployment_rollback_allowed: boolean; + ci_job_token_scope_enabled: boolean; + ci_separated_caches: boolean; + ci_allow_fork_pipelines_to_run_in_parent_project: boolean; + ci_id_token_sub_claim_components: string[]; + build_git_strategy: string; + keep_latest_artifact: boolean; + restrict_user_defined_variables: boolean; + ci_pipeline_variables_minimum_override_role: string; + runners_token: string | null; + runner_token_expiration_interval: number | null; + group_runners_enabled: boolean; + auto_cancel_pending_pipelines: string; + build_timeout: number; + auto_devops_enabled: boolean; + auto_devops_deploy_strategy: string; + ci_push_repository_for_job_token_allowed: boolean; + ci_config_path: string | null; + public_jobs: boolean; + shared_with_groups: any[]; + only_allow_merge_if_pipeline_succeeds: boolean; + allow_merge_on_skipped_pipeline: boolean | null; + request_access_enabled: boolean; + only_allow_merge_if_all_discussions_are_resolved: boolean; + remove_source_branch_after_merge: boolean | null; + printing_merge_request_link_enabled: boolean; + merge_method: string; + squash_option: string; + enforce_auth_checks_on_uploads: boolean; + suggestion_commit_message: string | null; + merge_commit_template: string | null; + squash_commit_template: string | null; + issue_branch_template: string | null; + warn_about_potentially_unwanted_characters: boolean; + autoclose_referenced_issues: boolean; + external_authorization_classification_label: string; + requirements_enabled: boolean; + requirements_access_level: string; + security_and_compliance_enabled: boolean; + pre_receive_secret_detection_enabled: boolean; + compliance_frameworks: any[]; + permissions: Permissions; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..1a291b4 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from "./functions/download.js"; +export * from "./functions/downloadAndExtract.js"; diff --git a/src/utils.js b/src/utils.ts similarity index 83% rename from src/utils.js rename to src/utils.ts index b0b5e3a..29f185a 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -4,7 +4,9 @@ * getRepositoryPath("51886657"); // "51886657" * getRepositoryPath("myorg.foo.bar") // "myorg%2Ffoo%2Fbar" */ -export function getRepositoryPath(repository) { +export function getRepositoryPath( + repository: string +): string { const repoId = Number.parseInt(repository, 10); return Number.isNaN(repoId) ? repository.split(".").join("%2F") : String(repoId); diff --git a/test/test.spec.js b/test/test.spec.js deleted file mode 100644 index 6cb9341..0000000 --- a/test/test.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -// Import Node.js Dependencies -import { fileURLToPath } from "node:url"; -import path from "node:path"; -import fs from "node:fs/promises"; -import { test } from "node:test"; -import assert from "node:assert"; - -// Import Third-party Dependencies -import is from "@slimio/is"; - -// Import Internal Dependency -import * as gitlab from "../index.js"; - -// CONSTANTS -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const kDownloadDir = path.join(__dirname, "downloads"); - -await fs.mkdir(kDownloadDir); - -test("gitlab.download should be an asyncFunction", () => { - assert.equal(is.func(gitlab.download), true); - assert.equal(is.asyncFunction(gitlab.download), true); -}); - -test("gitlab.downloadAndExtract should be an asyncFunction", () => { - assert.equal(is.func(gitlab.downloadAndExtract), true); - assert.equal(is.asyncFunction(gitlab.downloadAndExtract), true); -}); - -test("download must throw: repository must be a string!", async() => { - await assert.rejects( - async() => await gitlab.download(10), - { - name: "TypeError", - message: "repository must be a string!" - } - ); -}); - -test("extract tar.gz at in the current working dir", async() => { - const { location } = await gitlab.download("polychromatic.plombier-chauffagiste"); - - await fs.access(location); - assert.strictEqual(path.extname(location), ".gz"); -}); - -test("download and extract a public gitlab repository", async() => { - const { location } = await gitlab.downloadAndExtract("polychromatic.plombier-chauffagiste", { - dest: kDownloadDir - }); - - await assert.doesNotReject( - async() => await fs.access(location) - ); -}); - -test("teardown", async() => { - await new Promise((resolve) => setImmediate(resolve)); - - await assert.doesNotReject( - async() => await fs.rm(kDownloadDir, { recursive: true, force: true }) - ); - - await assert.doesNotReject( - async() => await fs.unlink(path.join(process.cwd(), "plombier-chauffagiste-master.tar.gz")) - ); -}); diff --git a/test/test.spec.ts b/test/test.spec.ts new file mode 100644 index 0000000..bad6287 --- /dev/null +++ b/test/test.spec.ts @@ -0,0 +1,72 @@ +// Import Node.js Dependencies +import { fileURLToPath } from "node:url"; +import path from "node:path"; +import fs from "node:fs/promises"; +import { describe, test } from "node:test"; +import assert from "node:assert"; +import * as timers from "node:timers/promises"; + +// Import Third-party Dependencies +import is from "@slimio/is"; + +// Import Internal Dependency +import * as gitlab from "../src/index.js"; + +// CONSTANTS +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const kDownloadDir = path.join(__dirname, "downloads"); + +await fs.mkdir(kDownloadDir); + +describe("download", () => { + test("should be an asyncFunction", () => { + assert.equal(is.func(gitlab.download), true); + assert.equal(is.asyncFunction(gitlab.download), true); + }); + + test("must throw: repository must be a string!", async() => { + await assert.rejects( + async() => await gitlab.download(10 as any), + { + name: "TypeError", + message: "repository must be a string!" + } + ); + }); + + test("extract tar.gz at in the current working dir", async() => { + const { location } = await gitlab.download("polychromatic.plombier-chauffagiste"); + + await fs.access(location); + assert.strictEqual(path.extname(location), ".gz"); + }); +}); + +describe("downloadAndExtract", () => { + test("gitlab.downloadAndExtract should be an asyncFunction", () => { + assert.equal(is.func(gitlab.downloadAndExtract), true); + assert.equal(is.asyncFunction(gitlab.downloadAndExtract), true); + }); + + test("download and extract a public gitlab repository", async() => { + const { location } = await gitlab.downloadAndExtract("polychromatic.plombier-chauffagiste", { + dest: kDownloadDir + }); + + await assert.doesNotReject( + async() => await fs.access(location) + ); + }); +}); + +test("teardown", async() => { + await timers.setImmediate(); + + await assert.doesNotReject( + async() => await fs.rm(kDownloadDir, { recursive: true, force: true }) + ); + + await assert.doesNotReject( + async() => await fs.unlink(path.join(process.cwd(), "plombier-chauffagiste-master.tar.gz")) + ); +}); diff --git a/test/utils.spec.js b/test/utils.spec.ts similarity index 100% rename from test/utils.spec.js rename to test/utils.spec.ts diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2f42f9b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@openally/config.typescript", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +}