diff --git a/examples/e2e/models-e2e/project.json b/examples/e2e/models-e2e/project.json index 276f31c8..55229681 100644 --- a/examples/e2e/models-e2e/project.json +++ b/examples/e2e/models-e2e/project.json @@ -7,6 +7,9 @@ "implicitDependencies": ["models"], "targets": { "lint": {}, + "teardown": { + "executor": "@push-based/nx-verdaccio:env-teardown" + }, "e2e": { "executor": "@nx/vite:test", "inputs": ["default", "^production"], diff --git a/projects/nx-verdaccio/src/executors/env-teardown/README.md b/projects/nx-verdaccio/src/executors/env-teardown/README.md new file mode 100644 index 00000000..f8dc5bb0 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/README.md @@ -0,0 +1,53 @@ +# Teardown Environment Executor + +This executor helps to cleanup a [environment](../../../../../README.md#-environment-folders-to-isolate-files-during-e2e-tests) of a given folder. +If a server is running for the given environment, it will be stopped. +If this folder is checked into github all it's changes will be reverted, if it is not checked into github, the folder will be deleted. + +**Environment folder** + +#### @push-based/nx-verdaccio:env-teardown + +## Usage + +// project.json + +```json +{ + "name": "my-project", + "targets": { + "env-teardown": { + "executor": "@push-based/nx-verdaccio:env-teardown" + } + } +} +``` + +By default, the Nx executor will derive the options from the executor options. + +```jsonc +{ + "name": "my-project", + "targets": { + "env-teardown": { + "executor": "@code-pushup/nx-verdaccio:env-teardown", + "options": { + "envRoot": "/tmp/test-npm-workspace" + "verbose": true, + } + } + } +} +``` + +Show what will be executed without actually executing it: + +`nx run my-project:env-teardown --print-config` + +## Options + +| Name | type | description | +| --------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | +| **envRoot** | `string` (REQUIRED) | The folder in which the package should get published. This folder is the environment folder and contains a configured `.npmrc` file. | +| **verbose** | `boolean` | Show more verbose logs | +| **printConfig** | `boolean` | Print config without executing | diff --git a/projects/nx-verdaccio/src/executors/env-teardown/constants.ts b/projects/nx-verdaccio/src/executors/env-teardown/constants.ts new file mode 100644 index 00000000..91647e30 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/constants.ts @@ -0,0 +1 @@ +export const EXECUTOR_ENVIRONMENT_TEARDOWN = 'env-teardown'; diff --git a/projects/nx-verdaccio/src/executors/env-teardown/executor.ts b/projects/nx-verdaccio/src/executors/env-teardown/executor.ts new file mode 100644 index 00000000..c0ed66f1 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/executor.ts @@ -0,0 +1,42 @@ +import {type ExecutorContext, logger} from '@nx/devkit'; +import type {TeardownExecutorOptions} from './schema'; +import {teardownEnvironment,} from './teardown-env'; +import {PACKAGE_NAME} from '../../plugin/constants'; +import {EXECUTOR_ENVIRONMENT_TEARDOWN} from "./constants"; +import {ExecutorOutput} from "../internal/executor-output"; + +export async function teardownExecutor( + options: TeardownExecutorOptions, + context: ExecutorContext +): Promise { + const { environmentRoot, verbose } = options; + + if (verbose) { + logger.info( + `Execute ${PACKAGE_NAME}:${EXECUTOR_ENVIRONMENT_TEARDOWN} with options: ${JSON.stringify( + options, + null, + 2 + )}` + ); + } + try { + await teardownEnvironment(context, { + environmentRoot, + verbose, + }); + } catch (error) { + logger.error(error); + return { + success: false, + command: error?.message ?? (error as Error).toString(), + }; + } + + return { + success: true, + command: 'Teared down environment successfully.', + }; +} + +export default teardownExecutor; diff --git a/projects/nx-verdaccio/src/executors/env-teardown/git.ts b/projects/nx-verdaccio/src/executors/env-teardown/git.ts new file mode 100644 index 00000000..07ab2f57 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/git.ts @@ -0,0 +1,54 @@ +import type {SimpleGit} from "simple-git"; +import {simpleGit} from "simple-git"; +import {resolve} from "node:path"; + +export const gitClient: SimpleGit = simpleGit(); + +export async function cleanGitHistoryForFolder( + environmentRoot: string, + options?: { verbose?: boolean }, + git: SimpleGit = gitClient +): Promise { + + await git.show(['--oneline']); + +} + +export async function isFolderInRepo( + folderPath: string +): Promise { + // Initialize simple-git with the folder path + const git = simpleGit(folderPath); + + try { + // Check if the folder is a git repository + const isRepo = (await git.checkIgnore(folderPath)).length === 0 + // console.log(`${folderPath} is ${isRepo ? '' : 'not '} in Git repository.`); + return isRepo; + } catch (error) { + console.log(`${error}`); + return false; + } +} + +export async function isSubfolderInGitRepository(subFolderPath: string, baseFolderPath = process.cwd()) { + // Resolve the full path for the subfolder + const fullSubFolderPath = resolve(baseFolderPath, subFolderPath); + + // Initialize simple-git with the full subfolder path + const git = simpleGit(fullSubFolderPath); + + try { + // Check if the subfolder path is within a Git repository + const isRepo = await git.checkIsRepo(); + if (isRepo) { + console.log(`${fullSubFolderPath} is inside a Git repository.`); + } else { + console.log(`${fullSubFolderPath} is not inside a Git repository.`); + } + return isRepo; + } catch (error) { + console.log(`${fullSubFolderPath} is not inside a Git repository.`); + return false; + } +} diff --git a/projects/nx-verdaccio/src/executors/env-teardown/git.unit-test.ts b/projects/nx-verdaccio/src/executors/env-teardown/git.unit-test.ts new file mode 100644 index 00000000..70ddbcb0 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/git.unit-test.ts @@ -0,0 +1,11 @@ +import {describe, expect, it, vi} from 'vitest'; +import {cleanGitHistoryForFolder} from './git'; +import {execSync} from 'node:child_process'; + +describe('cleanGitHistoryForFolder', () => { + + it('should clean up given folder', () => { + cleanGitHistoryForFolder('tmp'); + }); + +}); diff --git a/projects/nx-verdaccio/src/executors/env-teardown/schema.json b/projects/nx-verdaccio/src/executors/env-teardown/schema.json new file mode 100644 index 00000000..8ded7571 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "TeardownExecutorOptions", + "title": "Environment teardown executor options", + "type": "object", + "properties": { + "environmentRoot": { + "type": "string", + "description": "The root directory of the environment", + "aliases": ["envRoot", "e"] + }, + "verbose": { + "type": "boolean", + "description": "Print additional logs" + } + }, + "additionalProperties": true, + "required": ["environmentRoot"] +} diff --git a/projects/nx-verdaccio/src/executors/env-teardown/schema.ts b/projects/nx-verdaccio/src/executors/env-teardown/schema.ts new file mode 100644 index 00000000..bb461708 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/schema.ts @@ -0,0 +1,5 @@ +import {Environment} from "../env-bootstrap/npm"; + +export type TeardownExecutorOptions = Partial; diff --git a/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.ts b/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.ts new file mode 100644 index 00000000..56678252 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.ts @@ -0,0 +1,54 @@ +import {Environment} from "../env-bootstrap/npm"; +import {simpleGit, type SimpleGit} from "simple-git"; +import {isFolderInRepo} from "./git"; +import {ExecutorContext, logger} from "@nx/devkit"; +import {runSingleExecutor} from "../../internal/run-executor"; +import {join} from "node:path"; +import {VERDACCIO_REGISTRY_JSON} from "../env-bootstrap/constants"; +import {TARGET_ENVIRONMENT_VERDACCIO_STOP} from "@push-based/nx-verdaccio"; +import {fileExists} from "../../internal/file-system"; + +export const gitClient: SimpleGit = simpleGit(process.cwd()); +export type TeardownEnvironmentOptions = Environment & { verbose?: boolean }; + +export async function teardownEnvironment( + context: ExecutorContext, + options: TeardownEnvironmentOptions, + git: SimpleGit = gitClient +): Promise { + const {verbose, environmentRoot} = options; + + // kill verdaccio process if running + const registryJsonExists = await fileExists(join(environmentRoot, VERDACCIO_REGISTRY_JSON)); + if (registryJsonExists) { + await runSingleExecutor( + { + project: context.projectName, + target: TARGET_ENVIRONMENT_VERDACCIO_STOP, + }, + { + ...(verbose ? {verbose} : {}), + filePath: join(environmentRoot, VERDACCIO_REGISTRY_JSON), + }, + context + ); + } else { + logger.info(`No verdaccio-registry.json file found in ${environmentRoot}.`); + } + + // clean environmentRoot + + const environmentRootInRepo = await isFolderInRepo(environmentRoot); + if (environmentRootInRepo) { + await git.checkout([environmentRoot]); + logger.info(`Cleaned git history in ${environmentRoot}.`); + } else { + try { + + } catch (error) { + // throw new Error(`Error cleaning history of folder ${environmentRoot}. ${error.message}`); + } + } + + +} diff --git a/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.unit-test.ts b/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.unit-test.ts new file mode 100644 index 00000000..362ec6b4 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/env-teardown/teardown-env.unit-test.ts @@ -0,0 +1,5 @@ +import { describe, expect, it, vi } from 'vitest'; +import { teardownEnvironment } from './bootstrap-env'; +import * as verdaccioRegistryModule from './verdaccio-registry'; +import * as npmModule from './npm'; +import * as fs from 'node:fs/promises'; diff --git a/projects/nx-verdaccio/src/executors/internal/executor-output.ts b/projects/nx-verdaccio/src/executors/internal/executor-output.ts new file mode 100644 index 00000000..fd6dca30 --- /dev/null +++ b/projects/nx-verdaccio/src/executors/internal/executor-output.ts @@ -0,0 +1,5 @@ +export type ExecutorOutput = { + success: boolean; + command?: string; + error?: Error; +}; diff --git a/projects/nx-verdaccio/src/internal/file-system.ts b/projects/nx-verdaccio/src/internal/file-system.ts index 63f3175b..b6ad6b99 100644 --- a/projects/nx-verdaccio/src/internal/file-system.ts +++ b/projects/nx-verdaccio/src/internal/file-system.ts @@ -1,4 +1,4 @@ -import { mkdir } from 'node:fs/promises'; +import {mkdir, stat} from 'node:fs/promises'; export async function ensureDirectoryExists(baseDir: string) { try { @@ -11,3 +11,12 @@ export async function ensureDirectoryExists(baseDir: string) { } } } + +export async function fileExists(path: string): Promise { + try { + const stats = await stat(path); + return stats.isFile(); + } catch { + return false; + } +}