Skip to content

Commit

Permalink
feat(core): add migration script to escape $ in env
Browse files Browse the repository at this point in the history
  • Loading branch information
xiongemi committed Aug 23, 2023
1 parent abc147c commit c8c504d
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/nx/migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Original file line number Diff line number Diff line change
@@ -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`);
});
});
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit c8c504d

Please sign in to comment.