Skip to content

Conversation

mshanemc
Copy link
Contributor

@mshanemc mshanemc commented Oct 25, 2022

What does this PR do?

string replacements during deploy

define file via glob or filename
define what to replace via exact strings or regex
define new text from env or file
replacements could happen conditional based on one or more envs

provide output in DeployResponse for plugins to use for display

What issues does this PR fix or reference?

@W-11949818@

const conversionPipeline = pipeline(
new ComponentReader(components),
Readable.from(components),
!targetFormatIsSource && (process.env.DEBUG_REPLACEMENTS_VIA_CONVERT === 'true' || output.type === 'zip')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

want to keep this env var in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning toward yes. It would also enable a use case I don't want to officially encourage (do replacements to create a mdapi folder that you can mdapi deploy)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but I renamed it to match sf style

{
"name": "componentSetCreate",
"duration": 644.1402209999505
"duration": 702.3725049999775
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add a replacement perf test?

@mshanemc mshanemc marked this pull request as ready for review October 26, 2022 19:16
@mshanemc mshanemc requested review from a team as code owners October 26, 2022 19:16
@mshanemc mshanemc requested review from jeffb-sfdc and removed request for a team and jeffb-sfdc October 26, 2022 19:16
// used during replacement stream to limit warnings to explicit filenames, not globs
singleFile: Boolean(r.filename),
// Config is json which might use the regex. If so, turn it into an actual regex
toReplace: r.stringToReplace ?? new RegExp(r.regexToReplace, 'g'),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 57 above is looking at toReplace checking if it is a string and if so creating a regex from the string. If toReplace needs to be a regex, why not convert the string into the regex here?

*/
const readReplacementsFromProject = async (): Promise<ReplacementConfig[]> => {
const proj = await SfProject.resolve();
const projJson = (await proj.resolveProjectConfig()) as { replacements?: ReplacementConfig[] };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mshanemc is your work going to include updating sfdx-project.schema.json in @salesforce/schemas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should. good call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a WI for it

| { replaceWithEnv?: never; replaceWithFile: string };

type ReplacementTarget =
| { stringToReplace: string; regexToReplace?: never }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why carry a string and a regex, when a single property would suffice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exact-match-on-strings is simpler, less error-prone, and require no escaping beyond what json requires for \

considering the common scenarios like emails and urls, user@company.com or https://foo.com/api, it's gonna result in more success for users.

import { matchingContentFile } from '../mock';
import * as replacementsForMock from '../../src/convert/replacements';

describe('file matching', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that regexes are loaded from json, there will be nothing that verifies regexes while modifying the project json, allowing an invalid regex to be saved. At runtime the, the regex will fail parse. How will this error bubble out with enough information to properly identify the source of the error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ERROR running force:source:deploy:  Component conversion failed: Invalid regular expression: /BOOM\/: \ at end of pattern

How's that?

mshanemc and others added 5 commits October 28, 2022 13:11
* chore: auto-update metadata coverage in METADATA_SUPPORT.md

* test: use filenames from mocks

* test: record perf

Co-authored-by: svc-cli-bot <svc_cli_bot@salesforce.com>
@WillieRuemmele
Copy link
Member

QA Notes


Replace with ENV VAR

    {
      "glob": "force-app/**/*.cls",
      "stringToReplace": "replaceMeWithEnv",
      "replaceWithEnv": "ENVIRONMENT"
    }

✅ : confirmed in the org string was replaced with ENV VAR value
✅ : when ENVIRONMENT not set: ERROR running force:source:deploy: Component conversion failed: "ENVIRONMENT" is in sfdx-project.json as a value for "replaceWithEnv" property, but it's not set in your environment.
✅ : in output of source:push

=== Metadata Replacements

 PROJECT PATH                                        TEXT REPLACED    
 ─────────────────────────────────────────────────── ──────────────── 
 force-app/main/default/classes/GeocodingService.cls replaceMeWithEnv 

😐 : in output of deploy --verbose identical to source:push but not when --verbose is excluded
😐 : with env set, but nothing matching in file, no warning nothing was replaced
✅ : new replacements in json
✅ : present in --json even without specifying --verbose
✅ : source:retrieve no affect - pulls whats in org
😐 : "glob": "force-app/**/abc.cls", with env set, no warning no file was found?
😐 : "stringToReplace": "string not in file", no warning about not finding string?

Optionally Replace

    {
          "glob": "force-app/**/*.cls",
          "stringToReplace": "replaceThis",
          "replaceWithEnv": "ENVIRONMENT",
          "replaceWhenEnv": [
            {
              "env": "ENVIRONMENT",
              "value": "bar"
            }
          ]
        }

✅ : nothing replaced when ENVIRONEMNT=prod
✅ : replaced with bar when ENVIRONEMNT=bar
⁉️ : invalid regex allowed, "glob": "[", should be invalid when parsed as RegEx

Replace with File

        {
          "glob": "force-app/**/*.cls",
          "stringToReplace": "replaceMeWithFile",
          "replaceWithFile": "replacements.txt"
        }

✅ : replaced string with file contents
🏆 : nice error message when file not found The file "nonexistant.csv" specified in the "replacements" property of sfdx-project.json could not be read.

Replace with RegEx

        {
          "glob": "force-app/**/*.cls",
          "regexToReplace": "\\d{4}@W",
          "replaceWithEnv": "ENV"
        }

✅ : replaced // 1234@W in the Apex Class to bar (set at command level)
❓ : had to escape \ as invalid json so the RegEx became \\d to represent \d - Should make a note of this when releasing
🏆 : error message indicates invalid RegEx Invalid regular expression: /[/: Unterminated character class when "regexToReplace": "[",

Replace String with File

        {
          "glob": "force-app/main/default/classes/**/*",
          "stringToReplace": "find this string",
          "replaceWithFile": "replacements.csv"
        }

✅ : replaces all of the matching strings with content of file

Replace String with Env Var

        {
          "glob": "force-app/main/default/classes/**/*",
          "stringToReplace": "find this string",
          "replaceWithEnv": "ENV"
        }

✅ : replaces the string with env var
✅ : replaces all of the strings that match with that env var

@mshanemc
Copy link
Contributor Author

QA responses:

invalid regex allowed, "glob": "[", should be invalid when parsed as RegEx

globs are not regex.

"glob": "force-app/**/abc.cls", with env set, no warning no file was found

yeah, because these are streamed at the component level, there's not some top-level context where the stream is concerned with "has there ever been any file that matched this?" It's possibly, but not elegantly.

❓ : had to escape \ as invalid json so the RegEx became \d to represent \d - Should make a note of this when releasing

it's going to be in the docs.

@mshanemc mshanemc merged commit a23c6b3 into main Oct 31, 2022
@mshanemc mshanemc deleted the replacements-deploy branch October 31, 2022 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants