angular-cli: MergeStrategy seems to be completely broken (mergeWith failure)

Re-post of https://github.com/angular/devkit/issues/745

Bug Report or Feature Request (mark with an x)

- [x] bug report -> please search issues before submitting
- [ ] feature request

Area

- [ ] devkit
- [x] schematics

Versions

Node v8.11.1 NPM 5.8.0 Mac OS High Sierra

Repro steps

Use the following schematic

import { Rule, url, apply, mergeWith, chain, MergeStrategy } from '@angular-devkit/schematics';

export function index(): Rule {
  return chain([
    simpleMerge(),
  ]);
}

function simpleMerge(): Rule {

  const source = url('./files');

  return mergeWith(source, MergeStrategy.Overwrite);
}

The log given by the failure

schematics .:boilerplate

ERROR! /src/app/app.component.html already exists. ERROR! /src/app/app.component.spec.ts already exists. ERROR! /src/app/app.component.ts already exists. ERROR! /src/app/app.module.ts already exists. ERROR! /src/assets/.gitkeep already exists. ERROR! /src/environments/environment.prod.ts already exists. ERROR! /src/environments/environment.ts already exists. ERROR! /src/index.html already exists.

Desired functionality

All files should be overwritten.

Mention any other details that might be useful

I tried every MergeStrategy possible (AllowOverwriteConflict, etc.), same result.

https://github.com/angular/devkit/issues/745 https://stackoverflow.com/questions/48957132/how-to-overwrite-file-with-angular-schematics

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 23
  • Comments: 24 (4 by maintainers)

Commits related to this issue

Most upvoted comments

This is making me cry 😢

@tscislo this is my current workaround:

export function overwriteIfExists(host: Tree): Rule {
  return forEach(fileEntry => {
    if (host.exists(fileEntry.path)) {
      host.overwrite(fileEntry.path, fileEntry.content);
      return null;
    }
    return fileEntry;
  });
}

export function mySchematic(options: MyOptions): Rule {
  return (host: Tree, context: SchematicContext) => {
    /* ...logic... */
    const templateSource = apply(
      url('./files'),
      [
        template({
          ...strings,
          ...options
        }),
        move(options.path),
        options.overwrite ? overwriteIfExists(host) : noop()
      ]
    );

    return mergeWith(templateSource);
  };
}

derived from other variations in answers here https://stackoverflow.com/questions/48957132/how-to-overwrite-file-with-angular-schematics

Is this already fixed? I have following workaround:

const templateSource = apply(url('./project-files'), [
      template({ ...strings, ...options }),
      move(options.path),
      forEach((fileEntry: FileEntry) => {
        if (host.exists(fileEntry.path)) {
          host.overwrite(fileEntry.path, fileEntry.content);
        }
        return fileEntry;
      }),
    ]);

Hi,

I experience the same issue. I setup a minimal reproduction: https://github.com/GregOnNet/merge-strategie-issue

Setup

  • The repository contains a file .prettierrc
  • My schematic overwrite loads another .prettierrc from ./templates
  • MergeStrategy.Overwrite is configured as 2nd parameter of apply.
  • The repository contains a unit test to check if the template overwrites the origin file.

Observed behaviour

  • The unit test succeeds
  • The integration test using schematics fails

    Please refer to section Repro Steps to see the details.

Versions

Node v11.11.0
NPM 6.9.0
Mac MacOS Mojave 10.14.3

@angular-devkit/core: 7.3.6
@angular-devkit/schematics: 7.3.6

Repro Steps

git clone https://github.com/GregOnNet/merge-strategie-issue.git
cd merge-strategie-issue

npm install
npm test # Everything is fine
npm run build

schematics .:overwrite
# ERROR
ERROR! /.prettierrc already exists.
The Schematic workflow failed. See above.

If I can provide any further information, please let me know. 👍

As others have stated, this still seems to be an issue… I’ve reproduced with 0.6.8 and 0.7.2… Based on one of the suggestions in the thread linked by @cgatian, I was able to formulate a workaround for my specific situation…

In my method, I was attempting to call move() within the rules array of apply()… Essentially the source url() was a subdirectory of my ‘files’ directory in my schematic - and the destination in move() was a subdirectory of the project tree.

So, I had the schematics source assets in: \schematics\ng-add\files\help\my-help\index.html

And I wanted my schematics source assets to be dropped in the projects source directory: \src\help\my-help\index.html.

When calling url(‘./files/help’) and move(‘.’, ‘/src/’), I would get “src/help/my-help/index.html already exists. The Schematic workflow failed.”

To work around this, I changed my folder structure in my schematics assets to match the project folder structure - so instead of ‘\schematics\ng-add\files\help…’ I used ‘\schematics\ng-add\files\src\help…’. I then updated url to be “url(‘./files’)” and move to be “move(‘.’)”… So now instead of explicitly setting the URL source as a subfolder containing the assets, it’s just using the whole ‘files’ directory to overlay on top of the root of the project (move(‘.’)).

I ran across this workaround when I by chance notice that I only got the ‘already exists’ error when operating on subfolders of the project - but in my troubleshooting, I realized that things behaved as expected when operating against the root of the project…

Here’s a sample of my updated method which now works as desired in case it assists:

/** Add help assets to application */
// NOTE: work around due to busted MergeStrategy - https://github.com/angular/angular-cli/issues/11337
function addHelpToApplicationRoot(options: Schema) {
  return (tree: Tree, context: SchematicContext) => {
    context.logger.info(`Adding help assets to application help.`);
    const rule = mergeWith(
      apply(url('./files'), [
        forEach((fileEntry) => {
          if (tree.exists(fileEntry.path)) {
            tree.overwrite(fileEntry.path, fileEntry.content);
            return null;
          }
          return fileEntry;
        }),
        move('.')
      ]),
    );
    return rule(tree, context);
  };
}

Seems that the important detail, at least in my case, was to make my ‘files’ assets be structured in a way that matches the project’s folder structure rather than relying on url() and move() to work on specific subfolders.

I have the same issue qith 0.8.6, however if I run schematics my-schematic:my-schematic it will work BUT if I run ng g my-schematic:my-schematic it will give me the already exists error message. Can anyone explain why?

Is there any plan to fix this? Any current workarounds?

@tinesoft I get the exact same behavior with v0.6.8 as with v0.7.3