From c8c504dac27e527d0d78ede31852c7cfb5638abb Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Wed, 23 Aug 2023 12:56:46 -0400 Subject: [PATCH] feat(core): add migration script to escape $ in env --- packages/nx/migrations.json | 6 + .../escape-dollar-sign-env-variables.spec.ts | 131 ++++++++++++++++++ .../escape-dollar-sign-env-variables.ts | 63 +++++++++ 3 files changed, 200 insertions(+) create mode 100644 packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.spec.ts create mode 100644 packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.ts diff --git a/packages/nx/migrations.json b/packages/nx/migrations.json index cf5fd96773082c..f3dddf4f46893b 100644 --- a/packages/nx/migrations.json +++ b/packages/nx/migrations.json @@ -83,6 +83,12 @@ "version": "16.6.0-beta.6", "description": "Prefix outputs with {workspaceRoot}/{projectRoot} if needed", "implementation": "./src/migrations/update-15-0-0/prefix-outputs" + }, + "16.8.0-escape-dollar-sign-env": { + "cli": "nx", + "version": "16.8.0-beta.3", + "description": "Escape $ in env variables", + "implementation": "./src/migrations/update-16-8-0/escape-dollar-sign-env-variables" } } } diff --git a/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.spec.ts b/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.spec.ts new file mode 100644 index 00000000000000..e73925b1051cd1 --- /dev/null +++ b/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.spec.ts @@ -0,0 +1,131 @@ +import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; +import { addProjectConfiguration } from '../../generators/utils/project-configuration'; +import escapeDollarSignEnvVariables from './escape-dolllar-sign-env-variables'; + +describe('escape $ in env variables', () => { + let tree; + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should escape $ in env variables in .env file', () => { + tree.write( + '.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + escapeDollarSignEnvVariables(tree); + expect(tree.read('.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + }); + + it('should escape $ env variables in .env file under project', () => { + addProjectConfiguration(tree, 'my-app', { + root: 'apps/my-app', + }); + addProjectConfiguration(tree, 'my-app2', { + root: 'apps/my-app2', + }); + tree.write( + 'apps/my-app/.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + tree.write( + 'apps/my-app2/.env', + `dollar=$ +NX_SOME_VAR=$DEF` + ); + escapeDollarSignEnvVariables(tree); + expect(tree.read('apps/my-app/.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + expect(tree.read('apps/my-app2/.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$DEF`); + }); + + it('should escape $ env variables in .env for target', () => { + tree.write('.env', 'dollar=$'); + tree.write('.env.build', 'dollar=$'); + addProjectConfiguration(tree, 'my-app', { + root: 'apps/my-app', + targets: { + build: { + executor: '@nx/node:build', + configurations: { + production: {}, + }, + }, + }, + }); + tree.write( + 'apps/my-app/.build.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + tree.write( + 'apps/my-app/.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + escapeDollarSignEnvVariables(tree); + expect(tree.read('.env', 'utf-8')).toEqual(`dollar=\$`); + expect(tree.read('apps/my-app/.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + expect(tree.read('apps/my-app/.build.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + }); + + it('should escape $ env variables in .env for configuration', () => { + tree.write('.env', 'dollar=$'); + tree.write('.env.production', 'dollar=$'); + addProjectConfiguration(tree, 'my-app', { + root: 'apps/my-app', + targets: { + build: { + executor: '@nx/node:build', + configurations: { + production: {}, + }, + }, + }, + }); + tree.write( + 'apps/my-app/.production.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + tree.write( + 'apps/my-app/.build.production.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + tree.write( + 'apps/my-app/.env', + `dollar=$ +NX_SOME_VAR=$ABC` + ); + escapeDollarSignEnvVariables(tree); + expect(tree.read('.env', 'utf-8')).toEqual(`dollar=\$`); + expect(tree.read('apps/my-app/.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + expect(tree.read('apps/my-app/.build.production.env', 'utf-8')) + .toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + expect(tree.read('apps/my-app/.production.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + }); + + it('should not escape $ env variables if it is already escaped', () => { + addProjectConfiguration(tree, 'my-app', { + root: 'apps/my-app', + }); + tree.write( + 'apps/my-app/.env', + `dollar=\$ +NX_SOME_VAR=\$ABC` + ); + escapeDollarSignEnvVariables(tree); + expect(tree.read('apps/my-app/.env', 'utf-8')).toEqual(`dollar=\$ +NX_SOME_VAR=\$ABC`); + }); +}); diff --git a/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.ts b/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.ts new file mode 100644 index 00000000000000..dc4843a6e574ef --- /dev/null +++ b/packages/nx/src/migrations/update-16-8-0/escape-dollar-sign-env-variables.ts @@ -0,0 +1,63 @@ +import { Tree } from '../../generators/tree'; +import { getProjects } from '../../generators/utils/project-configuration'; + +/** + * This function escapes dollar sign in env variables + * It will go through: + * - '.env', '.local.env', '.env.local' + * - .env.[target-name], .[target-name].env + * - .env.[target-name].[configuration-name], .[target-name].[configuration-name].env + * - .env.[configuration-name], .[configuration-name].env + * at each project root and workspace root + * @param tree + */ +export default async function escapeDollarSignEnvVariables(tree: Tree) { + for (const [_, configuration] of getProjects(tree).entries()) { + const envFiles = ['.env', '.local.env', '.env.local']; + for (const targetName in configuration.targets) { + const task = configuration.targets[targetName]; + envFiles.push(`.env.${targetName}`, `.${targetName}.env`); + + if (task.configurations) { + for (const configurationName in task.configurations) { + envFiles.push( + `.env.${targetName}.${configurationName}`, + `.${targetName}.${configurationName}.env`, + `.env.${configurationName}`, + `.${configurationName}.env` + ); + } + } + } + + for (const envFile of envFiles) { + parseEnvFile(tree, `${configuration.root}/${envFile}`); // parse at project root + parseEnvFile(tree, envFile); // parse at workspace root + } + } +} + +/** + * This function parse the env file and escape dollar sign + * @param tree + * @param envFilePath + * @returns + */ +function parseEnvFile(tree: Tree, envFilePath: string) { + if (!tree.exists(envFilePath)) { + return; + } + let envFileContent = tree.read(envFilePath, 'utf-8'); + envFileContent = envFileContent + .split('\n\r') + .map((line) => { + const declarations = line.split('='); + if (declarations[1].includes('$') && !declarations[1].includes(`\$`)) { + declarations[1] = declarations[1].replace('$', `\$`); + line = declarations.join('='); + } + return line; + }) + .join('\n\r'); + tree.write(envFilePath, envFileContent); +}