diff --git a/packages/compat/tests/stage1.test.ts b/packages/compat/tests/stage1.test.ts
deleted file mode 100644
index b1023cfc4..000000000
--- a/packages/compat/tests/stage1.test.ts
+++ /dev/null
@@ -1,489 +0,0 @@
-import { Project, BuildResult, ExpectFile, expectFilesAt } from '@embroider/test-support';
-import resolve from 'resolve';
-import { dirname } from 'path';
-import merge from 'lodash/merge';
-
-describe('stage1 build', function () {
-  jest.setTimeout(120000);
-
-  describe('max compatibility', function () {
-    let build: BuildResult;
-    let expectFile: ExpectFile;
-
-    beforeAll(async function () {
-      process.env.THROW_UNLESS_PARALLELIZABLE = '1'; // see https://github.com/embroider-build/embroider/pull/924
-      // A simple ember app with no tests
-      let app = Project.emberNew();
-
-      // We create an addon.
-      // Our addon is configured to use ember-auto-import's dynamic import.
-      let addon = app.addAddon(
-        'my-addon',
-        `
-        options: {
-          babel: {
-            plugins: [ require.resolve('ember-auto-import/babel-plugin') ]
-          }
-        },
-      `
-      );
-
-      merge(addon.files, {
-        addon: {
-          components: {
-            'hello-world.js': `
-            import Component from '@ember/component';
-            import layout from '../templates/components/hello-world';
-            import { getOwnConfig } from '@embroider/macros';
-            export default Component.extend({
-              message: 'embroider-sample-transforms-target',
-              config: getOwnConfig(),
-              layout
-            });
-          `,
-            'has-inline-template.js': `
-            import Component from '@ember/component';
-            import { hbs } from 'ember-cli-htmlbars';
-            export default Component.extend({
-              // tagged template form:
-              layout: ${"hbs`<div class={{embroider-sample-transforms-target}}>Inline</div><span>{{macroDependencySatisfies 'ember-source' '>3'}}</span>`"},
-              // call expression form:
-              extra: hbs("<div class={{embroider-sample-transforms-target}}>Extra</div>")
-            });
-          `,
-            'does-dynamic-import.js': `
-            export default function() {
-              return import('some-library');
-            }
-          `,
-          },
-          templates: {
-            components: {
-              'hello-world.hbs': `
-              <div class={{embroider-sample-transforms-target}}>hello world</div>
-              <span>{{macroDependencySatisfies "ember-source" ">3"}}</span>
-            `,
-              'module-name.hbs': `<div class={{embroider-sample-transforms-module}}>hello world</div>`,
-            },
-          },
-        },
-        app: {
-          components: {
-            'hello-world.js': `export { default } from 'my-addon/components/hello-world'`,
-          },
-        },
-      });
-
-      // Our addon will use @embroider/sample-transforms as examples of custom
-      // AST and babel transforms.
-      addon.linkPackage('@embroider/sample-transforms');
-      addon.linkPackage('@embroider/macros');
-      addon.linkPackage('ember-auto-import');
-
-      app.linkPackage('ember-auto-import');
-      app.linkPackage('webpack');
-      // our app will include an in-repo addon
-      app.pkg['ember-addon'] = {
-        paths: ['lib/in-repo-addon'],
-      };
-      app.files.lib = {
-        'in-repo-addon': {
-          'package.json': JSON.stringify(
-            {
-              name: 'in-repo-addon',
-              keywords: ['ember-addon'],
-            },
-            null,
-            2
-          ),
-          'index.js': `module.exports = { name: 'in-repo-addon' };`,
-          addon: {
-            helpers: {
-              'helper-from-in-repo-addon.js': '',
-            },
-          },
-        },
-      };
-
-      build = await BuildResult.build(app, { stage: 1 });
-      expectFile = expectFilesAt(build.outputPath);
-    });
-
-    afterAll(async function () {
-      delete process.env.THROW_UNLESS_PARALLELIZABLE;
-      await build.cleanup();
-    });
-
-    test('component in app tree', function () {
-      expectFile('node_modules/my-addon/_app_/components/hello-world.js').exists();
-    });
-
-    test('addon metadata', function () {
-      let assertMeta = expectFile('node_modules/my-addon/package.json').json('ember-addon');
-      assertMeta.get('app-js').deepEquals({ './components/hello-world.js': './_app_/components/hello-world.js' }); // should have app-js metadata
-      assertMeta
-        .get('implicit-modules')
-        .includes('./components/hello-world', 'staticAddonTrees is off so we should include the component implicitly');
-      assertMeta
-        .get('implicit-modules')
-        .includes(
-          './templates/components/hello-world.hbs',
-          'staticAddonTrees is off so we should include the template implicitly'
-        );
-      assertMeta.get('version').equals(2);
-    });
-
-    test('component in addon tree', function () {
-      let assertFile = expectFile('node_modules/my-addon/components/hello-world.js');
-      assertFile.matches(`getOwnConfig()`, `JS macros have not run yet`);
-      assertFile.matches(`embroider-sample-transforms-result`, `custom babel plugins have run`);
-    });
-
-    test('component template in addon tree', function () {
-      let assertFile = expectFile('node_modules/my-addon/templates/components/hello-world.hbs');
-      assertFile.matches(
-        '<div class={{embroider-sample-transforms-result}}>hello world</div>',
-        'template is still hbs and custom transforms have run'
-      );
-      assertFile.matches(
-        '<span>{{macroDependencySatisfies "ember-source" ">3"}}</span>',
-        'template macros have not run'
-      );
-    });
-
-    test('test module name added', function () {
-      let assertFile = expectFile('node_modules/my-addon/templates/components/module-name.hbs');
-      assertFile.matches(
-        '<div class={{embroider-sample-transforms-module "my-addon/templates/components/module-name.hbs"}}>hello world</div>',
-        'template is still hbs and module name transforms have run'
-      );
-    });
-
-    test('component with inline template', function () {
-      let assertFile = expectFile('node_modules/my-addon/components/has-inline-template.js');
-      assertFile.matches(
-        'hbs`<div class={{embroider-sample-transforms-result}}>Inline</div>',
-        'tagged template is still hbs and custom transforms have run'
-      );
-      assertFile.matches(
-        /hbs\(["']<div class={{embroider-sample-transforms-result}}>Extra<\/div>["']\)/,
-        'called template is still hbs and custom transforms have run'
-      );
-      assertFile.matches(
-        /<span>{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/,
-        'template macros have not run'
-      );
-    });
-
-    test('in-repo-addon is available', function () {
-      resolve.sync('in-repo-addon/helpers/helper-from-in-repo-addon', { basedir: build.outputPath });
-    });
-
-    test('dynamic import is preserved', function () {
-      expectFile('node_modules/my-addon/components/does-dynamic-import.js').matches(
-        /return import\(['"]some-library['"]\)/
-      );
-    });
-  });
-
-  describe('inline hbs, ember-cli-htmlbars@3', function () {
-    let build: BuildResult;
-    let expectFile: ExpectFile;
-
-    beforeAll(async function () {
-      // A simple ember app with no tests
-      let app = Project.emberNew();
-
-      // We create an addon
-      let addon = app.addAddon('my-addon');
-      addon.files.addon = {
-        components: {
-          'has-inline-template.js': `
-              import Component from '@ember/component';
-              import hbs from 'htmlbars-inline-precompile';
-              export default Component.extend({
-                // tagged template form:
-                layout: ${"hbs`<div class={{embroider-sample-transforms-target}}>Inline</div><span>{{macroDependencySatisfies 'ember-source' '>3'}}</span>`"},
-                // call expression form:
-                extra: hbs("<div class={{embroider-sample-transforms-target}}>Extra</div>")
-              });
-            `,
-        },
-      };
-
-      // Our addon will use @embroider/sample-transforms as examples of custom
-      // AST and babel transforms.
-      addon.linkPackage('@embroider/sample-transforms');
-      addon.linkPackage('ember-cli-htmlbars-inline-precompile');
-      addon.linkPackage('ember-cli-htmlbars', dirname(require.resolve('ember-cli-htmlbars-3/package.json')));
-      addon.linkPackage('@embroider/macros');
-
-      build = await BuildResult.build(app, { stage: 1 });
-      expectFile = expectFilesAt(build.outputPath);
-    });
-
-    afterAll(async function () {
-      await build.cleanup();
-    });
-
-    test('component with inline template', function () {
-      let assertFile = expectFile('node_modules/my-addon/components/has-inline-template.js');
-      assertFile.matches(
-        'hbs`<div class={{embroider-sample-transforms-result}}>Inline</div>',
-        'tagged template is still hbs and custom transforms have run'
-      );
-      assertFile.matches(
-        /hbs\(["']<div class={{embroider-sample-transforms-result}}>Extra<\/div>["']\)/,
-        'called template is still hbs and custom transforms have run'
-      );
-      assertFile.matches(
-        /<span>{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/,
-        'template macros have not run'
-      );
-    });
-  });
-
-  describe('addon dummy app', function () {
-    let build: BuildResult;
-
-    beforeAll(async function () {
-      let app = Project.addonNew();
-      (app.files.addon as Project['files']).components = {
-        'hello-world.js': '',
-      };
-
-      build = await BuildResult.build(app, { stage: 1, type: 'addon' });
-    });
-
-    afterAll(async function () {
-      await build.cleanup();
-    });
-
-    test('dummy app can resolve own addon', function () {
-      resolve.sync('my-addon/components/hello-world.js', { basedir: build.outputPath });
-    });
-  });
-
-  describe('problematic addon zoo', function () {
-    let build: BuildResult;
-    let expectFile: ExpectFile;
-
-    beforeAll(async function () {
-      let app = Project.emberNew();
-
-      // an addon that emits a package.json file from its treeForAddon
-      let addon = app.addAddon(
-        'alpha',
-        `
-        treeForAddon() {
-          return require('path').join(__dirname, 'alpha-addon-tree');
-        }
-      `
-      );
-      addon.files['alpha-addon-tree'] = {
-        'package.json': '{}',
-      };
-
-      // an addon that manually extends the Addon base class
-      let hasCustomBase = app.addAddon('has-custom-base');
-      hasCustomBase.files['index.js'] = `
-            const { join } = require('path');
-            const Addon = require('ember-cli/lib/models/addon');
-            module.exports = Addon.extend({
-              name: 'has-custom-base',
-              treeForAddon() {
-                return join(__dirname, 'weird-addon-path');
-              }
-            });
-            `;
-      hasCustomBase.files['weird-addon-path'] = {
-        'has-custom-base': {
-          'file.js': '// weird-addon-path/file.js',
-        },
-      };
-
-      // an addon that nullifies its custom fastboot tree with a custom fastboot hook
-      let undefinedFastboot = app.addAddon('undefined-fastboot');
-      merge(undefinedFastboot.files, {
-        'index.js': `module.exports = {
-          name: 'undefined-fastboot',
-          treeForFastBoot() {}
-        }`,
-        fastboot: {
-          'something.js': '',
-        },
-      });
-
-      // Use one addon to patch the hook on another (yes, this happens in the
-      // wild...), and ensure we still detect the customized hook
-      app.addAddon('externally-customized');
-      let troubleMaker = app.addAddon('trouble-maker');
-      merge(troubleMaker.files, {
-        injected: {
-          hello: { 'world.js': '// hello' },
-        },
-        'index.js': `
-          const { join } = require('path');
-          module.exports = {
-          name: 'trouble-maker',
-          included() {
-            let instance = this.project.addons.find(a => a.name === "externally-customized");
-            let root = this.root;
-            instance.treeForPublic = function() {
-              return join(root, 'injected');
-            }
-          }
-        }`,
-      });
-
-      // an addon that customizes packageJSON['ember-addon'].main and then uses
-      // stock trees. Setting the main actually changes the root for *all* stock
-      // trees.
-      let movedMain = app.addAddon('moved-main');
-      merge(movedMain.files, {
-        custom: {
-          'index.js': `module.exports = { name: 'moved-main'};`,
-          addon: { helpers: { 'hello.js': '// hello-world' } },
-        },
-      });
-      merge(movedMain.pkg, { 'ember-addon': { main: 'custom/index.js' } });
-
-      // an addon that uses treeFor() to sometimes suppress its stock trees
-      let suppressed = app.addAddon(
-        'suppressed',
-        `
-        treeFor(name) {
-          if (name !== 'app') {
-            return this._super.treeFor(name);
-          } else {
-            return undefined;
-          }
-        }
-      `
-      );
-      merge(suppressed.files, {
-        addon: {
-          'addon-example.js': '// example',
-        },
-        app: {
-          'app-example.js': '// example',
-        },
-      });
-
-      // an addon that uses treeFor() to sometimes suppress its custom trees
-      let suppressedCustom = app.addAddon(
-        'suppressed-custom',
-        `
-        treeFor(name) {
-          if (name !== 'app') {
-            return this._super(name);
-          } else {
-            return undefined;
-          }
-        },
-        treeForApp() {
-          return require('path').join(__dirname, 'app-custom');
-        },
-        treeForAddon() {
-          return require('path').join(__dirname, 'addon-custom');
-        },
-      `
-      );
-      merge(suppressedCustom.files, {
-        'addon-custom': {
-          'suppressed-custom': {
-            'addon-example.js': '// example',
-          },
-        },
-        'app-custom': {
-          'app-example.js': '// example',
-        },
-      });
-
-      let blacklistedInRepoAddon = app.addInRepoAddon('blacklisted-in-repo-addon');
-      merge(blacklistedInRepoAddon.files, {
-        addon: {
-          'example.js': '',
-        },
-      });
-
-      let disabledInRepoAddon = app.addInRepoAddon(
-        'disabled-in-repo-addon',
-        `
-          isEnabled() { return false }
-        `
-      );
-      merge(disabledInRepoAddon.files, {
-        addon: {
-          'example.js': '',
-        },
-      });
-
-      build = await BuildResult.build(app, {
-        stage: 1,
-        type: 'app',
-        emberAppOptions: {
-          addons: {
-            blacklist: ['blacklisted-in-repo-addon'],
-          },
-        },
-      });
-      expectFile = expectFilesAt(build.outputPath);
-    });
-
-    afterAll(async function () {
-      await build.cleanup();
-    });
-
-    test('real package.json wins', function () {
-      expectFile('node_modules/alpha/package.json').matches(`alpha`);
-    });
-
-    test('custom tree hooks are detected in addons that manually extend from Addon', function () {
-      let assertFile = expectFile('node_modules/has-custom-base/file.js');
-      assertFile.matches(/weird-addon-path\/file\.js/);
-    });
-
-    test('no fastboot-js is emitted', function () {
-      expectFile('node_modules/undefined-fastboot/package.json')
-        .json()
-        .get('ember-addon.fastboot-js')
-        .equals(undefined);
-    });
-
-    test('custom tree hooks are detected when they have been patched into the addon instance', function () {
-      let assertFile = expectFile('node_modules/externally-customized/public/hello/world.js');
-      assertFile.exists();
-    });
-
-    test('addon with customized ember-addon.main can still use stock trees', function () {
-      expectFile('node_modules/moved-main/helpers/hello.js').matches(/hello-world/);
-    });
-
-    test('addon with customized treeFor can suppress a stock tree', function () {
-      expectFile('node_modules/suppressed/_app_/app-example.js').doesNotExist();
-    });
-
-    test('addon with customized treeFor can pass through a stock tree', function () {
-      expectFile('node_modules/suppressed/addon-example.js').exists();
-    });
-
-    test('addon with customized treeFor can suppress a customized tree', function () {
-      expectFile('node_modules/suppressed-custom/_app_/app-example.js').doesNotExist();
-    });
-
-    test('addon with customized treeFor can pass through a customized tree', function () {
-      expectFile('node_modules/suppressed-custom/addon-example.js').exists();
-    });
-
-    test('blacklisted in-repo addon is present but empty', function () {
-      expectFile('lib/blacklisted-in-repo-addon/package.json').exists();
-      expectFile('lib/blacklisted-in-repo-addon/example.js').doesNotExist();
-    });
-
-    test('disabled in-repo addon is present but empty', function () {
-      expectFile('lib/disabled-in-repo-addon/package.json').exists();
-      expectFile('lib/disabled-in-repo-addon/example.js').doesNotExist();
-    });
-  });
-});
diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/addon/helpers/helper-from-in-repo-addon.js b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/addon/helpers/helper-from-in-repo-addon.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js
new file mode 100644
index 000000000..1d5db98b1
--- /dev/null
+++ b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/index.js
@@ -0,0 +1 @@
+module.exports = { name: 'in-repo-addon' };
diff --git a/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json
new file mode 100644
index 000000000..2fbfb00f5
--- /dev/null
+++ b/tests/fixtures/basic-in-repo-addon/lib/in-repo-addon/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "in-repo-addon",
+  "keywords": ["ember-addon"]
+}
\ No newline at end of file
diff --git a/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js b/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js
new file mode 100644
index 000000000..674c7e024
--- /dev/null
+++ b/tests/fixtures/blacklisted-addon-build-options/ember-cli-build.js
@@ -0,0 +1,21 @@
+'use strict';
+
+const EmberApp = require('ember-cli/lib/broccoli/ember-app');
+
+module.exports = function (defaults) {
+  let app = new EmberApp(defaults, {
+    // Add options here
+    addons: {
+      blacklist: ['blacklisted-in-repo-addon'],
+    },
+  });
+
+  const { Webpack } = require('@embroider/webpack');
+  return require('@embroider/compat').compatBuild(app, Webpack, {
+    skipBabel: [
+      {
+        package: 'qunit',
+      },
+    ],
+  });
+};
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/addon/example.js b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/addon/example.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js
new file mode 100644
index 000000000..3a77c77d6
--- /dev/null
+++ b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+  name: require('./package').name,
+};
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json
new file mode 100644
index 000000000..f9d40ca62
--- /dev/null
+++ b/tests/fixtures/blacklisted-addon-build-options/lib/blacklisted-in-repo-addon/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "blacklisted-in-repo-addon",
+  "keywords": ["ember-addon"]
+}
\ No newline at end of file
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/addon/example.js b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/addon/example.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js
new file mode 100644
index 000000000..b5b01410d
--- /dev/null
+++ b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/index.js
@@ -0,0 +1,6 @@
+module.exports = {
+  name: require('./package').name,
+  isEnabled() {
+    return false;
+  },
+};
diff --git a/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json
new file mode 100644
index 000000000..44410c325
--- /dev/null
+++ b/tests/fixtures/blacklisted-addon-build-options/lib/disabled-in-repo-addon/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "disabled-in-repo-addon",
+  "keywords": ["ember-addon"]
+}
\ No newline at end of file
diff --git a/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js b/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js
new file mode 100644
index 000000000..228c0a8fc
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/addon/components/does-dynamic-import.js
@@ -0,0 +1,3 @@
+export default function () {
+  return import('some-library');
+}
diff --git a/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js b/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js
new file mode 100644
index 000000000..84ce0394d
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/addon/components/has-inline-template.js
@@ -0,0 +1,8 @@
+import Component from '@ember/component';
+import { hbs } from 'ember-cli-htmlbars';
+export default Component.extend({
+  // tagged template form:
+  layout: hbs`<div class={{embroider-sample-transforms-target}}>Inline</div><span>{{macroDependencySatisfies 'ember-source' '>3'}}</span>`,
+  // call expression form:
+  extra: hbs('<div class={{embroider-sample-transforms-target}}>Extra</div>'),
+});
diff --git a/tests/fixtures/hello-world-addon/addon/components/hello-world.js b/tests/fixtures/hello-world-addon/addon/components/hello-world.js
new file mode 100644
index 000000000..674075603
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/addon/components/hello-world.js
@@ -0,0 +1,8 @@
+import Component from '@ember/component';
+import layout from '../templates/components/hello-world';
+import { getOwnConfig } from '@embroider/macros';
+export default Component.extend({
+  message: 'embroider-sample-transforms-target',
+  config: getOwnConfig(),
+  layout,
+});
diff --git a/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs b/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs
new file mode 100644
index 000000000..180916ab4
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/addon/templates/components/hello-world.hbs
@@ -0,0 +1,2 @@
+<div class={{embroider-sample-transforms-target}}>hello world</div>
+<span>{{macroDependencySatisfies "ember-source" ">3"}}</span>
\ No newline at end of file
diff --git a/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs b/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs
new file mode 100644
index 000000000..17aaa1f8d
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/addon/templates/components/module-name.hbs
@@ -0,0 +1 @@
+<div class={{embroider-sample-transforms-module}}>hello world</div>
\ No newline at end of file
diff --git a/tests/fixtures/hello-world-addon/app/components/hello-world.js b/tests/fixtures/hello-world-addon/app/components/hello-world.js
new file mode 100644
index 000000000..0f8e0e70a
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/app/components/hello-world.js
@@ -0,0 +1 @@
+export { default } from 'my-addon/components/hello-world';
diff --git a/tests/fixtures/hello-world-addon/index.js b/tests/fixtures/hello-world-addon/index.js
new file mode 100644
index 000000000..791da73d0
--- /dev/null
+++ b/tests/fixtures/hello-world-addon/index.js
@@ -0,0 +1,8 @@
+module.exports = {
+  name: require('./package').name,
+  options: {
+    babel: {
+      plugins: [require.resolve('ember-auto-import/babel-plugin')],
+    },
+  },
+};
diff --git a/tests/scenarios/package.json b/tests/scenarios/package.json
index e7bf20fc6..930114cb6 100644
--- a/tests/scenarios/package.json
+++ b/tests/scenarios/package.json
@@ -10,6 +10,7 @@
     "jsdom": "^16.2.2",
     "lodash": "^4.17.20",
     "qunit": "^2.16.0",
+    "resolve": "^1.20.0",
     "scenario-tester": "^2.0.1",
     "ts-node": "^9.1.1"
   },
@@ -32,6 +33,8 @@
     "ember-cli-3.20": "npm:ember-cli@~3.20.0",
     "ember-cli-3.24": "npm:ember-cli@~3.24.0",
     "ember-cli-beta": "npm:ember-cli@beta",
+    "ember-cli-htmlbars-inline-precompile": "^2.1.0",
+    "ember-cli-htmlbars-3": "npm:ember-cli-htmlbars@3",
     "ember-cli-latest": "npm:ember-cli@latest",
     "ember-cli-fastboot": "^2.2.3",
     "ember-composable-helpers": "^4.4.1",
diff --git a/tests/scenarios/stage1-test.ts b/tests/scenarios/stage1-test.ts
new file mode 100644
index 000000000..b1042a855
--- /dev/null
+++ b/tests/scenarios/stage1-test.ts
@@ -0,0 +1,444 @@
+import resolve from 'resolve';
+import { join } from 'path';
+import merge from 'lodash/merge';
+import fs from 'fs-extra';
+import { loadFromFixtureData } from './helpers';
+import { appReleaseScenario, dummyAppScenarios, baseAddon } from './scenarios';
+import { PreparedApp } from 'scenario-tester';
+import QUnit from 'qunit';
+const { module: Qmodule, test } = QUnit;
+
+appReleaseScenario
+  .map('stage-1', project => {
+    let addon = baseAddon();
+
+    merge(addon.files, loadFromFixtureData('hello-world-addon'));
+    addon.pkg.name = 'my-addon';
+
+    addon.linkDependency('@embroider/sample-transforms', { baseDir: __dirname });
+    addon.linkDependency('@embroider/macros', { baseDir: __dirname });
+    project.addDependency(addon);
+
+    // our app will include an in-repo addon
+    project.pkg['ember-addon'] = { paths: ['lib/in-repo-addon'] };
+    merge(project.files, loadFromFixtureData('basic-in-repo-addon'));
+  })
+  .forEachScenario(async scenario => {
+    Qmodule(`${scenario.name} max compatibility`, function (hooks) {
+      let app: PreparedApp;
+      let workspaceDir: string;
+
+      hooks.before(async () => {
+        process.env.THROW_UNLESS_PARALLELIZABLE = '1'; // see https://github.com/embroider-build/embroider/pull/924
+        app = await scenario.prepare();
+        await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b');
+        workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8');
+      });
+
+      hooks.after(async () => {
+        delete process.env.THROW_UNLESS_PARALLELIZABLE;
+      });
+
+      test('component in app tree', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/my-addon/_app_/components/hello-world.js')));
+      });
+
+      test('addon metadata', function (assert) {
+        let assertMeta = fs.readJsonSync(join(workspaceDir, 'node_modules/my-addon/package.json'))['ember-addon'];
+        assert.deepEqual(assertMeta['app-js'], { './components/hello-world.js': './_app_/components/hello-world.js' });
+        assert.ok(
+          JSON.stringify(assertMeta['implicit-modules']).includes('./components/hello-world'),
+          'staticAddonTrees is off so we should include the component implicitly'
+        );
+        assert.ok(
+          JSON.stringify(assertMeta['implicit-modules']).includes('./templates/components/hello-world.hbs'),
+          'staticAddonTrees is off so we should include the template implicitly'
+        );
+
+        assert.equal(assertMeta.version, 2);
+      });
+
+      test('component in addon tree', function (assert) {
+        let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/my-addon/components/hello-world.js'));
+
+        assert.ok(fileContents.includes('getOwnConfig()'), 'JS macros have not run yet');
+        assert.ok(fileContents.includes('embroider-sample-transforms-result'), 'custom babel plugins have run');
+      });
+
+      test('component template in addon tree', function (assert) {
+        let fileContents = fs.readFileSync(
+          join(workspaceDir, 'node_modules/my-addon/templates/components/hello-world.hbs')
+        );
+        assert.ok(
+          fileContents.includes('<div class={{embroider-sample-transforms-result}}>hello world</div>'),
+          'template is still hbs and custom transforms have run'
+        );
+        assert.ok(
+          fileContents.includes('<span>{{macroDependencySatisfies "ember-source" ">3"}}</span>'),
+          'template macros have not run'
+        );
+      });
+
+      test('test module name added', function (assert) {
+        let fileContents = fs.readFileSync(
+          join(workspaceDir, 'node_modules/my-addon/templates/components/module-name.hbs')
+        );
+        let searchRegExp = /\\/gi;
+        let replaceWith = '\\\\';
+        assert.ok(
+          fileContents.includes(
+            `<div class={{embroider-sample-transforms-module "${join(
+              'my-addon',
+              'templates',
+              'components',
+              'module-name.hbs'
+            ).replace(searchRegExp, replaceWith)}"}}>hello world</div>`
+          ),
+          'template is still hbs and module name transforms have run'
+        );
+      });
+
+      test('component with inline template', function (assert) {
+        let fileContents = fs.readFileSync(
+          join(workspaceDir, 'node_modules/my-addon/components/has-inline-template.js')
+        );
+        assert.ok(
+          fileContents.includes('hbs`<div class={{embroider-sample-transforms-result}}>Inline</div>'),
+          'tagged template is still hbs and custom transforms have run'
+        );
+        assert.ok(
+          /hbs\(["']<div class={{embroider-sample-transforms-result}}>Extra<\/div>["']\)/.test(fileContents.toString()),
+          'called template is still hbs and custom transforms have run'
+        );
+        assert.ok(
+          /<span>{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/.test(fileContents.toString()),
+          'template macros have not run'
+        );
+      });
+
+      test('in-repo-addon is available', function (assert) {
+        assert.ok(resolve.sync('in-repo-addon/helpers/helper-from-in-repo-addon', { basedir: workspaceDir }));
+      });
+
+      test('dynamic import is preserved', function (assert) {
+        let fileContents = fs.readFileSync(
+          join(workspaceDir, 'node_modules/my-addon/components/does-dynamic-import.js')
+        );
+        assert.ok(/return import\(['"]some-library['"]\)/.test(fileContents.toString()));
+      });
+    });
+  });
+
+appReleaseScenario
+  .map('stage-1-inline-hbs', project => {
+    let addon = baseAddon();
+
+    merge(addon.files, {
+      addon: {
+        components: {
+          'has-inline-template.js': `
+            import Component from '@ember/component';
+            import hbs from 'htmlbars-inline-precompile';
+            export default Component.extend({
+              // tagged template form:
+              layout: ${"hbs`<div class={{embroider-sample-transforms-target}}>Inline</div><span>{{macroDependencySatisfies 'ember-source' '>3'}}</span>`"},
+              // call expression form:
+              extra: hbs("<div class={{embroider-sample-transforms-target}}>Extra</div>")
+            });
+          `,
+        },
+      },
+    });
+    addon.pkg.name = 'my-addon';
+
+    addon.linkDependency('@embroider/sample-transforms', { baseDir: __dirname });
+    addon.linkDependency('@embroider/macros', { baseDir: __dirname });
+    addon.linkDependency('ember-cli-htmlbars-inline-precompile', { baseDir: __dirname });
+    addon.linkDependency('ember-cli-htmlbars-3', { baseDir: __dirname, resolveName: 'ember-cli-htmlbars' });
+    project.addDependency(addon);
+  })
+  .forEachScenario(async scenario => {
+    Qmodule(`${scenario.name} inline hbs, ember-cli-htmlbars@3`, function (hooks) {
+      let app: PreparedApp;
+      let workspaceDir: string;
+
+      hooks.before(async () => {
+        app = await scenario.prepare();
+        await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b');
+        workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8');
+      });
+
+      test('component with inline template', function (assert) {
+        let fileContents = fs.readFileSync(
+          join(workspaceDir, 'node_modules/my-addon/components/has-inline-template.js')
+        );
+        assert.ok(
+          fileContents.includes('hbs`<div class={{embroider-sample-transforms-result}}>Inline</div>'),
+          'tagged template is still hbs and custom transforms have run'
+        );
+        assert.ok(
+          /hbs\(["']<div class={{embroider-sample-transforms-result}}>Extra<\/div>["']\)/.test(fileContents.toString()),
+          'called template is still hbs and custom transforms have run'
+        );
+        assert.ok(
+          /<span>{{macroDependencySatisfies ['"]ember-source['"] ['"]>3['"]}}<\/span>/.test(fileContents.toString()),
+          'template macros have not run'
+        );
+      });
+    });
+  });
+
+appReleaseScenario
+  .map('stage-1-problematic-addon-zoo', project => {
+    let addon = baseAddon();
+
+    // an addon that emits a package.json file from its treeForAddon
+    merge(addon.files, {
+      index: `module.exports = {
+        name: require('./package').name,
+        treeForAddon() {
+          return require('path').join(__dirname, 'alpha-addon-tree');
+        }
+      };`,
+      'alpha-addon-tree': {
+        'package.json': '{}',
+      },
+    });
+
+    addon.pkg.name = 'alpha';
+
+    let hasCustomBase = baseAddon();
+
+    // an addon that manually extends the Addon base class
+    merge(hasCustomBase.files, {
+      'index.js': `
+        const { join } = require('path');
+        const Addon = require('ember-cli/lib/models/addon');
+        module.exports = Addon.extend({
+          name: 'has-custom-base',
+          treeForAddon() {
+            return join(__dirname, 'weird-addon-path');
+          }
+        });`,
+      'weird-addon-path': {
+        'has-custom-base': {
+          'file.js': '// weird-addon-path/file.js',
+        },
+      },
+    });
+
+    hasCustomBase.pkg.name = 'has-custom-base';
+
+    let undefinedFastboot = baseAddon();
+
+    // an addon that nullifies its custom fastboot tree with a custom fastboot hook
+    merge(undefinedFastboot.files, {
+      'index.js': `module.exports = {
+        name: 'undefined-fastboot',
+        treeForFastBoot() {}
+      }`,
+      fastboot: {
+        'something.js': '',
+      },
+    });
+
+    undefinedFastboot.pkg.name = 'undefined-fastboot';
+
+    // Use one addon to patch the hook on another (yes, this happens in the
+    // wild...), and ensure we still detect the customized hook
+    let externallyCustomized = baseAddon();
+    let troubleMaker = baseAddon();
+    merge(troubleMaker.files, {
+      injected: {
+        hello: { 'world.js': '// hello' },
+      },
+      'index.js': `
+        const { join } = require('path');
+        module.exports = {
+        name: 'trouble-maker',
+        included() {
+          let instance = this.project.addons.find(a => a.name === "externally-customized");
+          let root = this.root;
+          instance.treeForPublic = function() {
+            return join(root, 'injected');
+          }
+        }
+      }`,
+    });
+
+    externallyCustomized.pkg.name = 'externally-customized';
+    troubleMaker.pkg.name = 'trouble-maker';
+
+    // an addon that customizes packageJSON['ember-addon'].main and then uses
+    // stock trees. Setting the main actually changes the root for *all* stock
+    // trees.
+    let movedMain = baseAddon();
+    merge(movedMain.files, {
+      custom: {
+        'index.js': `module.exports = { name: 'moved-main'};`,
+        addon: { helpers: { 'hello.js': '// hello-world' } },
+      },
+    });
+    merge(movedMain.pkg, { 'ember-addon': { main: 'custom/index.js' }, name: 'moved-main' });
+
+    // an addon that uses treeFor() to sometimes suppress its stock trees
+    let suppressed = baseAddon();
+    merge(suppressed.files, {
+      'index.js': `module.exports = {
+        name: require('./package').name,
+        treeFor(name) {
+          if (name !== 'app') {
+            return this._super.treeFor(name);
+          } else {
+            return undefined;
+          }
+        }
+      }`,
+      addon: {
+        'addon-example.js': '// example',
+      },
+      app: {
+        'app-example.js': '// example',
+      },
+    });
+
+    suppressed.pkg.name = 'suppressed';
+
+    // an addon that uses treeFor() to sometimes suppress its custom trees
+    let suppressedCustom = baseAddon();
+    merge(suppressedCustom.files, {
+      'index.js': `module.exports = {
+        name: require('./package').name,
+        treeFor(name) {
+          if (name !== 'app') {
+            return this._super(name);
+          } else {
+            return undefined;
+          }
+        },
+        treeForApp() {
+          return require('path').join(__dirname, 'app-custom');
+        },
+        treeForAddon() {
+          return require('path').join(__dirname, 'addon-custom');
+        },
+      }`,
+      'addon-custom': {
+        'suppressed-custom': {
+          'addon-example.js': '// example',
+        },
+      },
+      'app-custom': {
+        'app-example.js': '// example',
+      },
+    });
+
+    suppressedCustom.pkg.name = 'suppressed-custom';
+
+    project.addDependency(addon);
+    project.addDependency(hasCustomBase);
+    project.addDependency(undefinedFastboot);
+    project.addDependency(externallyCustomized);
+    project.addDependency(troubleMaker);
+    project.addDependency(movedMain);
+    project.addDependency(suppressed);
+    project.addDependency(suppressedCustom);
+
+    project.pkg['ember-addon'] = { paths: ['lib/disabled-in-repo-addon', 'lib/blacklisted-in-repo-addon'] };
+    merge(project.files, loadFromFixtureData('blacklisted-addon-build-options'));
+  })
+  .forEachScenario(async scenario => {
+    Qmodule(`${scenario.name} problematic addon zoo`, function (hooks) {
+      let app: PreparedApp;
+      let workspaceDir: string;
+
+      hooks.before(async () => {
+        app = await scenario.prepare();
+        await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b');
+        workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8');
+      });
+
+      test('real package.json wins', function (assert) {
+        let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/alpha/package.json'));
+        assert.ok(fileContents.includes('alpha'));
+      });
+
+      test('custom tree hooks are detected in addons that manually extend from Addon', function (assert) {
+        let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/has-custom-base/file.js'));
+        assert.ok(/weird-addon-path\/file\.js/.test(fileContents.toString()));
+      });
+
+      test('no fastboot-js is emitted', function (assert) {
+        let fileContents = fs.readJsonSync(join(workspaceDir, 'node_modules/undefined-fastboot/package.json'));
+        assert.equal(fileContents['ember-addon']['fastboot-js'], null);
+      });
+
+      test('custom tree hooks are detected when they have been patched into the addon instance', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/externally-customized/public/hello/world.js')));
+      });
+
+      test('addon with customized ember-addon.main can still use stock trees', function (assert) {
+        let fileContents = fs.readFileSync(join(workspaceDir, 'node_modules/moved-main/helpers/hello.js'));
+        assert.ok(/hello-world/.test(fileContents.toString()));
+      });
+
+      test('addon with customized treeFor can suppress a stock tree', function (assert) {
+        assert.notOk(fs.existsSync(join(workspaceDir, 'node_modules/suppressed/_app_/app-example.js')));
+      });
+
+      test('addon with customized treeFor can pass through a stock tree', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/suppressed/addon-example.js')));
+      });
+
+      test('addon with customized treeFor can suppress a customized tree', function (assert) {
+        assert.notOk(fs.existsSync(join(workspaceDir, 'node_modules/suppressed-custom/_app_/app-example.js')));
+      });
+
+      test('addon with customized treeFor can pass through a customized tree', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'node_modules/suppressed-custom/addon-example.js')));
+      });
+
+      test('blacklisted in-repo addon is present but empty', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'lib/blacklisted-in-repo-addon/package.json')));
+        assert.notOk(fs.existsSync(join(workspaceDir, 'lib/blacklisted-in-repo-addon/example.js')));
+      });
+
+      test('disabled in-repo addon is present but empty', function (assert) {
+        assert.ok(fs.existsSync(join(workspaceDir, 'lib/disabled-in-repo-addon/package.json')));
+        assert.notOk(fs.existsSync(join(workspaceDir, 'lib/disabled-in-repo-addon/example.js')));
+      });
+    });
+  });
+
+dummyAppScenarios
+  .map('stage-1-dummy-addon', project => {
+    project.pkg.name = 'my-addon';
+
+    project.linkDependency('@embroider/webpack', { baseDir: __dirname });
+    project.linkDependency('@embroider/core', { baseDir: __dirname });
+    project.linkDependency('@embroider/compat', { baseDir: __dirname });
+
+    merge(project.files, {
+      addon: {
+        components: {
+          'hello-world.js': '',
+        },
+      },
+    });
+  })
+  .forEachScenario(async scenario => {
+    Qmodule(`${scenario.name} addon dummy app`, function (hooks) {
+      let app: PreparedApp;
+      let workspaceDir: string;
+
+      hooks.before(async () => {
+        app = await scenario.prepare();
+        await app.execute('cross-env STAGE1_ONLY=true node ./node_modules/ember-cli/bin/ember b');
+        workspaceDir = fs.readFileSync(join(app.dir, 'dist', '.stage1-output'), 'utf8');
+      });
+
+      test('dummy app can resolve own addon', function (assert) {
+        assert.ok(resolve.sync('my-addon/components/hello-world.js', { basedir: workspaceDir }));
+      });
+    });
+  });