diff --git a/tools/build-env/README.md b/tools/build-env/README.md index 2654203f..f6d76fe1 100644 --- a/tools/build-env/README.md +++ b/tools/build-env/README.md @@ -26,6 +26,7 @@ - outputs - list of packages in registry under `.../storage/.verdaccio-db.json` e.g.: `{"list":[""],"secret":"esKM34zA53wetObgi5f0Uu1e7iObmm+f"}`` - tarball of package under `.../storage/@org/-.tgz` + e.g.: `{workspaceRoot}/${environmentsDir}/{args.environmentProject}/storage/@org/${packageName}`, - `package.json` of package under `.../storage/@org//package.json` - `npm-install` - outputs diff --git a/tools/build-env/executors.json b/tools/build-env/executors.json index d717b1a1..72b5bff6 100644 --- a/tools/build-env/executors.json +++ b/tools/build-env/executors.json @@ -1,24 +1,29 @@ { "executors": { - "setup-env": { - "implementation": "./src/executors/setup-env/executor", - "schema": "./src/executors/setup-env/schema.json", - "description": "Generate and install test environments in your workspace. Cached and ready for use." - }, "bootstrap": { "implementation": "./src/executors/bootstrap/executor", "schema": "./src/executors/bootstrap/schema.json", "description": "Bootstraps a test environments in your workspace. Cached and ready for use." }, - "kill-process": { - "implementation": "./src/executors/kill-process/executor", - "schema": "./src/executors/kill-process/schema.json", - "description": "Kills process by PID, command or file path." + "setup": { + "implementation": "./src/executors/setup/executor", + "schema": "./src/executors/setup/schema.json", + "description": "Generate and install test environments in your workspace. Cached and ready for use." + }, + "npm-publish": { + "implementation": "./src/executors/npm-publish/executor", + "schema": "./src/executors/npm-publish/schema.json", + "description": "Publishes npm packages to a configured registry." }, "npm-install": { "implementation": "./src/executors/npm-install/executor", "schema": "./src/executors/npm-install/schema.json", "description": "Installs npm packages in your workspace." + }, + "kill-process": { + "implementation": "./src/executors/kill-process/executor", + "schema": "./src/executors/kill-process/schema.json", + "description": "Kills process by PID, command or file path." } } } diff --git a/tools/build-env/src/executors/bootstrap/executor.ts b/tools/build-env/src/executors/bootstrap/executor.ts index 218e0a2b..8d520383 100644 --- a/tools/build-env/src/executors/bootstrap/executor.ts +++ b/tools/build-env/src/executors/bootstrap/executor.ts @@ -3,6 +3,7 @@ import type { BootstrapExecutorOptions } from './schema'; import { bootstrapEnvironment } from '../../internal/verdaccio/verdaccio-npm-env'; import { join } from 'node:path'; import { DEFAULT_ENVIRONMENTS_OUTPUT_DIR } from '../../internal/constants'; +import { normalizeOptions } from '../internal/normalize-options'; export type BootstrapExecutorOutput = { success: boolean; @@ -11,31 +12,28 @@ export type BootstrapExecutorOutput = { }; export default async function runBootstrapExecutor( - terminalAndExecutorOptions: BootstrapExecutorOptions, + options: BootstrapExecutorOptions, context: ExecutorContext ) { - const { projectName } = context; - const normalizedOptions = { - ...terminalAndExecutorOptions, - environmentRoot: join(DEFAULT_ENVIRONMENTS_OUTPUT_DIR, projectName), - }; + const { projectName, options: normalizedOptions } = normalizeOptions( + context, + options + ); logger.info( `Execute @org/build-env:build with options: ${JSON.stringify( - terminalAndExecutorOptions, + options, null, 2 )}` ); - let envResult; try { - envResult = await bootstrapEnvironment({ + await bootstrapEnvironment({ ...normalizedOptions, projectName, readyWhen: 'Environment ready under', }); } catch (error) { - // nx build-env cli-e2e logger.error(error); return { success: false, @@ -45,6 +43,6 @@ export default async function runBootstrapExecutor( return Promise.resolve({ success: true, - command: JSON.stringify(envResult, null, 2), + command: 'Bootstraped environemnt successfully.', } satisfies BootstrapExecutorOutput); } diff --git a/tools/build-env/src/executors/internal/normalize-options.ts b/tools/build-env/src/executors/internal/normalize-options.ts new file mode 100644 index 00000000..e21308b5 --- /dev/null +++ b/tools/build-env/src/executors/internal/normalize-options.ts @@ -0,0 +1,22 @@ +import { join } from 'node:path'; +import { DEFAULT_ENVIRONMENTS_OUTPUT_DIR } from '../../internal/constants'; +import { ExecutorContext } from '@nx/devkit'; + +export function normalizeOptions< + T extends ExecutorContext, + I extends Record +>( + context: T, + options: I +): T & { + options: I & { environmentRoot: string }; +} { + const { projectName } = context; + return { + ...context, + options: { + ...options, + environmentRoot: join(DEFAULT_ENVIRONMENTS_OUTPUT_DIR, projectName), + }, + }; +} diff --git a/tools/build-env/src/executors/kill-process/executor.ts b/tools/build-env/src/executors/kill-process/executor.ts index 5901af28..c3fe2453 100644 --- a/tools/build-env/src/executors/kill-process/executor.ts +++ b/tools/build-env/src/executors/kill-process/executor.ts @@ -3,6 +3,7 @@ import { type ExecutorContext, logger } from '@nx/devkit'; import type { KillProcessExecutorOptions } from './schema'; import { join } from 'node:path'; import { killProcessFromPid } from '../../internal/utils/process'; +import { normalizeOptions } from '../internal/normalize-options'; export type ExecutorOutput = { success: boolean; @@ -11,25 +12,22 @@ export type ExecutorOutput = { }; export default async function runKillProcessExecutor( - terminalAndExecutorOptions: KillProcessExecutorOptions, + options: KillProcessExecutorOptions, context: ExecutorContext ) { - const { projectName } = context; + const { options: opt } = normalizeOptions(context, options); const { - workspaceRoot, - filePath = join(workspaceRoot ?? '', 'process.json'), + environmentRoot, pid, cleanFs = true, dryRun = false, verbose = false, - } = { - ...terminalAndExecutorOptions, - workspaceRoot: join('tmp', 'environments', projectName), - }; + filePath = join(environmentRoot ?? '', 'process.json'), + } = opt; logger.info( `Execute @org/stop-verdaccio-env:kill-process with options: ${JSON.stringify( - terminalAndExecutorOptions, + options, null, 2 )}` @@ -45,6 +43,6 @@ export default async function runKillProcessExecutor( } return Promise.resolve({ success: true, - command: '????????', + command: 'Process killed successfully.', } satisfies ExecutorOutput); } diff --git a/tools/build-env/src/executors/npm-install/executor.ts b/tools/build-env/src/executors/npm-install/executor.ts index 078636d4..eece5960 100644 --- a/tools/build-env/src/executors/npm-install/executor.ts +++ b/tools/build-env/src/executors/npm-install/executor.ts @@ -1,71 +1,54 @@ -import { - type ExecutorContext, - readJsonFile, - type TargetConfiguration, -} from '@nx/devkit'; +import { type ExecutorContext, logger, readJsonFile } from '@nx/devkit'; import type { NpmInstallExecutorOptions } from './schema'; import { join, relative } from 'node:path'; import { executeProcess } from '../../internal/utils/execute-process'; import { objectToCliArgs } from '../../internal/utils/terminal-command'; import { PackageJson } from 'nx/src/utils/package-json'; -import { DEFAULT_ENVIRONMENTS_OUTPUT_DIR } from '../../internal/constants'; +import { getBuildOutput } from '../../internal/utils/utils'; +import { normalizeOptions } from '../internal/normalize-options'; -export type ExecutorOutput = { +export type NpmInstallExecutorOutput = { success: boolean; command?: string; error?: Error; }; -const relativeFromPath = (dir: string) => - relative(join(process.cwd(), dir), join(process.cwd())); - export default async function runNpmInstallExecutor( - terminalAndExecutorOptions: NpmInstallExecutorOptions, + options: NpmInstallExecutorOptions, context: ExecutorContext ) { - const { projectName, projectsConfigurations } = context; - const { environmentProject = projectName, pkgVersion } = - terminalAndExecutorOptions; - // @TODO DEFAULT_ENVIRONMENTS_OUTPUT_DIR is configured in the registered plugin thing about how to get that value - const environmentRoot = join( - DEFAULT_ENVIRONMENTS_OUTPUT_DIR, - environmentProject + const { + projectName, + projectsConfigurations, + options: opt, + } = normalizeOptions(context, options); + + const packageDistPath = getBuildOutput( + projectsConfigurations.projects[projectName]?.targets['build'] ); - const packageDistPath = getBuildOutput(projectsConfigurations[projectName]); - const { name: packageName, version } = readJsonFile( join(packageDistPath, 'package.json') ); - const userconfig = relativeFromPath( - join(packageDistPath, environmentRoot, '.npmrc') - ); + const { pkgVersion = version, environmentRoot } = opt; + + logger.info(`Installing ${packageName}@${pkgVersion} in ${environmentRoot}`); await executeProcess({ command: 'npm', args: objectToCliArgs({ - _: ['install', `${packageName}@${pkgVersion ?? version}`], + _: ['install', `${packageName}@${pkgVersion}`], 'no-fund': true, 'no-shrinkwrap': true, save: true, prefix: environmentRoot, - userconfig, + userconfig: join(environmentRoot, '.npmrc'), }), - cwd: process.cwd(), verbose: true, }); return Promise.resolve({ success: true, command: 'Installed dependencies successfully.', - } satisfies ExecutorOutput); -} - -function getBuildOutput(target: TargetConfiguration) { - const { options } = target ?? {}; - const { outputPath } = options ?? {}; - if (!outputPath) { - throw new Error('outputPath is required'); - } - return outputPath; + } satisfies NpmInstallExecutorOutput); } diff --git a/tools/build-env/src/executors/npm-publish/executor.ts b/tools/build-env/src/executors/npm-publish/executor.ts new file mode 100644 index 00000000..78c98e2a --- /dev/null +++ b/tools/build-env/src/executors/npm-publish/executor.ts @@ -0,0 +1,57 @@ +import { logger, type ExecutorContext } from '@nx/devkit'; + +import type { NpmPublishExecutorOptions } from './schema'; +import { join, relative } from 'node:path'; +import { executeProcess } from '../../internal/utils/execute-process'; +import { objectToCliArgs } from '../../internal/utils/terminal-command'; +import { DEFAULT_ENVIRONMENTS_OUTPUT_DIR } from '../../internal/constants'; +import { getBuildOutput } from '../../internal/utils/utils'; +import { normalizeOptions } from '../internal/normalize-options'; + +export type NpmPublishExecutorOutput = { + success: boolean; + command?: string; + error?: Error; +}; + +const relativeFromDist = (dir: string) => + relative(join(process.cwd(), dir), join(process.cwd())); + +export default async function runNpmPublishExecutor( + options: NpmPublishExecutorOptions, + context: ExecutorContext +) { + const { + projectName, + projectsConfigurations, + options: opt, + } = normalizeOptions(context, options); + const { environmentRoot } = opt; + + const { targets } = projectsConfigurations.projects[projectName]; + const packageDistPath = getBuildOutput(targets['build']); + const userconfig = join( + relativeFromDist(packageDistPath), + join(environmentRoot, '.npmrc') + ); + + logger.info( + `Publishing package from ${packageDistPath} to ${environmentRoot} with userconfig ${userconfig}` + ); + + // @TODO: try leverage nx-release-publish + await executeProcess({ + command: 'npm', + args: objectToCliArgs({ + _: ['publish'], + userconfig, + }), + cwd: packageDistPath, + verbose: true, + }); + + return Promise.resolve({ + success: true, + command: 'Published package successfully.', + } satisfies NpmPublishExecutorOutput); +} diff --git a/tools/build-env/src/executors/npm-publish/schema.json b/tools/build-env/src/executors/npm-publish/schema.json new file mode 100644 index 00000000..f5e9e810 --- /dev/null +++ b/tools/build-env/src/executors/npm-publish/schema.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "KillProcessExecutorOptions", + "title": "A executor to kill processes by PID, command, or file", + "type": "object", + "properties": { + "dryRun": { + "type": "boolean", + "description": "Print the commands that would be run, but don't actually run them", + "default": false + }, + "environmentProject": { + "type": "string", + "description": "The project to use for the environment" + }, + "verbose": { + "type": "boolean", + "description": "Print additional logs" + } + }, + "additionalProperties": true +} diff --git a/tools/build-env/src/executors/npm-publish/schema.ts b/tools/build-env/src/executors/npm-publish/schema.ts new file mode 100644 index 00000000..3ba9478e --- /dev/null +++ b/tools/build-env/src/executors/npm-publish/schema.ts @@ -0,0 +1,4 @@ +export type NpmPublishExecutorOptions = Partial<{ + environmentProject: string; + verbose: boolean; +}>; diff --git a/tools/build-env/src/executors/setup-env/executor.ts b/tools/build-env/src/executors/setup/executor.ts similarity index 90% rename from tools/build-env/src/executors/setup-env/executor.ts rename to tools/build-env/src/executors/setup/executor.ts index 876831ce..d54235f8 100644 --- a/tools/build-env/src/executors/setup-env/executor.ts +++ b/tools/build-env/src/executors/setup/executor.ts @@ -7,6 +7,7 @@ import { executeProcess } from '../../internal/utils/execute-process'; import { objectToCliArgs } from '../../internal/utils/terminal-command'; import { VerdaccioProcessResult } from '../../internal/verdaccio/verdaccio-registry'; import { SetupEnvironmentExecutorOptions } from './schema'; +import { normalizeOptions } from '../internal/normalize-options'; export type ExecutorOutput = { success: boolean; @@ -19,10 +20,11 @@ export default async function runSetupEnvironmentExecutor( context: ExecutorContext ) { const { projectName } = context; - const normalizedOptions = { - ...terminalAndExecutorOptions, - environmentRoot: join('tmp', 'environments', projectName), - }; + const normalizedContext = normalizeOptions( + context, + terminalAndExecutorOptions + ); + const { options: normalizedOptions } = normalizedContext; try { await runBuildExecutor( diff --git a/tools/build-env/src/executors/setup-env/schema.json b/tools/build-env/src/executors/setup/schema.json similarity index 70% rename from tools/build-env/src/executors/setup-env/schema.json rename to tools/build-env/src/executors/setup/schema.json index 1a7e8b13..559558fc 100644 --- a/tools/build-env/src/executors/setup-env/schema.json +++ b/tools/build-env/src/executors/setup/schema.json @@ -4,15 +4,9 @@ "title": "CodePushup CLI build executor", "type": "object", "properties": { - "project": { + "environmentRoot": { "type": "string", - "description": "The name of the project.", - "x-prompt": "Which project should configure Code Pushup?", - "x-dropdown": "projects", - "$default": { - "$source": "argv", - "index": 0 - } + "description": "The root directory of the environment" }, "dryRun": { "type": "boolean", diff --git a/tools/build-env/src/executors/setup-env/schema.ts b/tools/build-env/src/executors/setup/schema.ts similarity index 82% rename from tools/build-env/src/executors/setup-env/schema.ts rename to tools/build-env/src/executors/setup/schema.ts index 745802d6..5cd6dc5f 100644 --- a/tools/build-env/src/executors/setup-env/schema.ts +++ b/tools/build-env/src/executors/setup/schema.ts @@ -1,5 +1,5 @@ export type SetupEnvironmentExecutorOptions = Partial<{ - workspaceRoot: string; + environmentRoot: string; keepServerOn: boolean; progress: boolean; verbose: boolean; diff --git a/tools/build-env/src/internal/utils/utils.ts b/tools/build-env/src/internal/utils/utils.ts index a6f70d02..ef1cd78f 100644 --- a/tools/build-env/src/internal/utils/utils.ts +++ b/tools/build-env/src/internal/utils/utils.ts @@ -1,4 +1,14 @@ import { mkdir } from 'node:fs/promises'; +import type { TargetConfiguration } from '@nx/devkit'; + +export function getBuildOutput(target?: TargetConfiguration) { + const { options } = target ?? {}; + const { outputPath } = options ?? {}; + if (!outputPath) { + throw new Error('outputPath is required'); + } + return outputPath; +} export function uniquePort(): number { return Number((6000 + Number(Math.random() * 1000)).toFixed(0)); diff --git a/tools/build-env/src/plugin/verdaccio-env.plugin.ts b/tools/build-env/src/plugin/verdaccio-env.plugin.ts index efa8cdd2..2e811850 100644 --- a/tools/build-env/src/plugin/verdaccio-env.plugin.ts +++ b/tools/build-env/src/plugin/verdaccio-env.plugin.ts @@ -13,6 +13,14 @@ import { } from '../internal/verdaccio/verdaccio-npm-env'; import { StarVerdaccioOptions } from '../internal/verdaccio/verdaccio-registry'; +export function isPublishable(tags: string[]): boolean { + return tags.some((target) => target === 'publishable'); +} + +export function isNpmEnv(tags: string[]): boolean { + return tags.some((tag) => tag === 'npm-env'); +} + export type BuildEnvPluginCreateNodeOptions = { environmentsDir?: string; }; @@ -22,8 +30,6 @@ export const createNodes: CreateNodes = [ const { environmentsDir = DEFAULT_ENVIRONMENTS_OUTPUT_DIR } = (opt as BuildEnvPluginCreateNodeOptions) ?? {}; - console.log('projectConfigurationFile', projectConfigurationFile); - const root = dirname(projectConfigurationFile); const projectConfiguration: ProjectConfiguration = readJsonFile( join(process.cwd(), projectConfigurationFile) ); @@ -34,30 +40,22 @@ export const createNodes: CreateNodes = [ ) { throw new Error('Project name is required'); } - const { name: envProjectName } = - readJsonFile('project.json'); const projectName = projectConfiguration.name; const tags = projectConfiguration?.tags ?? []; - const isPublishable = tags.some((target) => target === 'publishable'); - const isNpmEnv = tags.some((target) => target === 'npm-env'); - const workspaceRoot = join(environmentsDir, projectName); + const projectRoot = dirname(projectConfigurationFile); + const environmentRoot = join(environmentsDir, projectName); return { projects: { - [root]: { + [projectRoot]: { targets: { - // === e2e project // start-verdaccio, stop-verdaccio - ...(isNpmEnv && verdaccioTargets({ root: workspaceRoot })), - // setup-npm-env, setup-env, setup-deps - ...(isNpmEnv && envTargets({ root: workspaceRoot, projectName })), + ...(isNpmEnv(tags) && verdaccioTargets({ environmentRoot })), + // bootstrap-env, setup-env, install-deps (intermediate task to run dependency tasks) + ...(isNpmEnv(tags) && envTargets({ environmentRoot, projectName })), // === dependency project // npm-publish, npm-install - ...(isPublishable && - npmTargets( - { ...projectConfiguration, root, environmentsDir }, - envProjectName - )), + ...(isPublishable(tags) && npmTargets(projectName)), }, }, }, @@ -66,15 +64,18 @@ export const createNodes: CreateNodes = [ ]; function verdaccioTargets({ - root, + environmentRoot, ...options -}: Environment & StarVerdaccioOptions): Record { +}: StarVerdaccioOptions & { + environmentRoot: string; +}): Record { return { + // @TODO: consider using the executor function directly to reduce the number of targets 'start-verdaccio': { executor: '@nx/js:verdaccio', options: { config: '.verdaccio/config.yml', - storage: join(root, 'storage'), + storage: join(environmentRoot, 'storage'), clear: true, ...options, }, @@ -82,7 +83,7 @@ function verdaccioTargets({ 'stop-verdaccio': { executor: '@org/build-env:kill-process', options: { - filePath: join(root, 'verdaccio-registry.json'), + filePath: join(environmentRoot, 'verdaccio-registry.json'), ...options, }, }, @@ -90,24 +91,21 @@ function verdaccioTargets({ } function envTargets({ - root: environmentRoot, + environmentRoot, projectName, -}: Environment & { +}: { environmentRoot: string } & { projectName: string; }): Record { return { - 'build-env': { - executor: '@org/build-env:build', - options: { - environmentRoot, - }, + 'bootstrap-env': { + executor: '@org/build-env:bootstrap', }, 'setup-env': { - inputs: ['default', '^production'], - executor: '@org/build-env:setup-env', + executor: '@org/build-env:setup', + options: { environmentRoot }, }, // just here to execute dependent npm-install tasks with the correct environmentProject - 'install-deps': { + 'install-env': { dependsOn: [ { projects: 'dependencies', @@ -115,65 +113,23 @@ function envTargets({ params: 'forward', }, ], - options: { - environmentRoot, - environmentProject: projectName, - }, + options: { environmentProject: projectName }, command: 'echo Dependencies installed!', }, }; } -const relativeFromPath = (dir: string) => - relative(join(process.cwd(), dir), join(process.cwd())); - function npmTargets( - projectConfiguration: ProjectConfiguration & { - root: string; - environmentsDir: string; - }, environmentProject: string ): Record { - const { root, targets, environmentsDir } = projectConfiguration; - const { build } = - (targets as Record<'build', TargetConfiguration<{ outputPath: string }>>) ?? - {}; - const { options } = build ?? {}; - const { outputPath } = options ?? {}; - if (outputPath == null) { - throw new Error('outputPath is required'); - } - - const { name: packageName, version: pkgVersion } = readJsonFile( - join(root, 'package.json') - ); - const userconfig = `${relativeFromPath( - outputPath - )}/${environmentsDir}/{args.environmentProject}/.npmrc`; - const prefix = `${environmentsDir}/{args.environmentProject}`; - return { - // @TODO: try leverage nx-release-publish - // nx npm-publish models --environmentProject=cli-e2e 'npm-publish': { dependsOn: [ { projects: 'self', target: 'build', params: 'forward' }, - { - projects: 'dependencies', - target: 'npm-publish', - params: 'forward', - }, + { projects: 'dependencies', target: 'npm-publish', params: 'forward' }, ], - inputs: [{ dependentTasksOutputFiles: `**/{options.outputPath}/**` }], - /*outputs: [ - // - `{workspaceRoot}/${environmentsDir}/{args.environmentProject}/storage/@org/${packageName}`, - ],*/ - // cache: true, - command: `npm publish --userconfig=${userconfig}`, - options: { - cwd: outputPath, - }, + executor: '@org/build-env:npm-publish', + options: { environmentProject }, }, 'npm-install': { dependsOn: [ @@ -181,20 +137,7 @@ function npmTargets( { projects: 'dependencies', target: 'npm-install', params: 'forward' }, ], executor: '@org/build-env:npm-install', - options: { - environmentProject, - }, - }, - 'npm-install-old': { - dependsOn: [ - { projects: 'self', target: 'npm-publish', params: 'forward' }, - { projects: 'dependencies', target: 'npm-install', params: 'forward' }, - ], - command: `npm install --no-fund --no-shrinkwrap --save ${packageName}@{args.pkgVersion} --prefix=${prefix} --userconfig=${userconfig}`, - options: { - pkgVersion, - environmentProject: environmentProject, - }, + options: { environmentProject }, }, }; }