From 4757e20256c3e6c9beca66b2a60f55066c47d796 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 27 May 2026 11:29:24 -0400 Subject: [PATCH 1/3] `--no-build` --- index.js | 49 +++++++++++-- tests/smoke-tests/--no-build.test.ts | 104 +++++++++++++++++++++++++++ 2 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 tests/smoke-tests/--no-build.test.ts diff --git a/index.js b/index.js index ed1e5b9..b7ce836 100644 --- a/index.js +++ b/index.js @@ -11,8 +11,8 @@ function stringifyAndNormalize(contents) { } const replacers = { - 'package.json'(content) { - return this.updatePackageJson(content); + 'package.json'(locals, content) { + return this.updatePackageJson(locals, content); }, }; @@ -34,6 +34,9 @@ module.exports = { ); } + // noBuild doesn't exist because of how ember-cli normalizes args + let noBuild = options.build === false; + return { name: options.name, blueprintVersion: require('./package.json').version, @@ -51,8 +54,11 @@ module.exports = { '--pnpm': isPnpm(options), '--npm': isNpm(options), '--typescript': options.typescript, + '--no-build': options.noBuild, }), ciProvider: options.ciProvider, + noBuild, + hasBuild: !noBuild, }; }, @@ -77,12 +83,43 @@ module.exports = { ); } + if (options.noBuild) { + files = files.filter((filename) => { + if (filename.endsWith('rollup.config.mjs')) return false; + if (filename.endsWith('babel.publish.config.cjs')) return false; + if (filename.endsWith('tsconfig.publish.json')) return false; + + return true; + }); + } + return files; }, - updatePackageJson(content) { + updatePackageJson(locals, content) { let contents = JSON.parse(content); - return stringifyAndNormalize(sortPackageJson(contents)); + + if (locals.noBuild) { + let extSuffix = locals.typescript ? 'ts' : 'js'; + + contents.exports = { + '.': `./src/index.${extSuffix}`, + './addon-main.js': './addon-main.cjs', + './*': './src/*', + './*.css': './src/*.css', + }; + + delete contents.scripts.build; + delete contents.scripts.prepack; + delete contents.devDependencies['@embroider/addon-dev']; + delete contents.devDependencies['@ember/library-tsconfig']; + delete contents.devDependencies['@rollup/plugin-babel']; + delete contents.devDependencies['rollup']; + } + + let sorted = sortPackageJson(contents); + let normalized = stringifyAndNormalize(sorted); + return normalized; }, /** @@ -100,11 +137,11 @@ module.exports = { * _js_eslint.config.mjs is deleted * _ts_eslint.config.mjs is renamed to eslint.config.mjs */ - buildFileInfo(_intoDir, _templateVariables, file, _commandOptions) { + buildFileInfo(_intoDir, locals, file, _commandOptions) { let fileInfo = this._super.buildFileInfo.apply(this, arguments); if (file in replacers) { - fileInfo.replacer = replacers[file].bind(this); + fileInfo.replacer = replacers[file].bind(this, locals); } return fileInfo; diff --git a/tests/smoke-tests/--no-build.test.ts b/tests/smoke-tests/--no-build.test.ts new file mode 100644 index 0000000..cc8e60e --- /dev/null +++ b/tests/smoke-tests/--no-build.test.ts @@ -0,0 +1,104 @@ +import path, { join } from 'node:path'; +import tmp from 'tmp-promise'; +import fs from 'node:fs/promises'; +import fixturify from 'fixturify'; +import { execa } from 'execa'; +import { beforeAll, beforeEach, describe, expect, it } from 'vitest'; + +import { + assertGeneratedCorrectly, + filesMatching, + SUPPORTED_PACKAGE_MANAGERS, + safeExecaEnv, +} from '../helpers.js'; +const blueprintPath = path.join(__dirname, '../..'); +let localEmberCli = require.resolve('ember-cli/bin/ember'); + +for (let packageManager of SUPPORTED_PACKAGE_MANAGERS) { + describe(`--no-build with ${packageManager}`, () => { + let tmpDir: string; + let addonDir: string; + let addonName = 'my-addon'; + + async function commandSucceeds(command: string) { + let result = await execa({ + cwd: addonDir, + shell: true, + preferLocal: true, + extendEnv: false, + env: safeExecaEnv(), + // Allows us to not fail yet when the command fails + // but we'd still fail appropriately with the exitCode check below. + // When we fail, we want to check for git diffs for debugging purposes. + reject: false, + })(command); + + if (result.exitCode !== 0) { + console.log(result); + console.log(`\n\n${command} exited with code ${result.exitCode}\n\n`); + console.log(result.stdout); + console.log(result.stderr); + console.log(`\n\n git diff \n\n`); + await execa({ cwd: addonDir, stdio: 'inherit' })`git diff`; + } + + expect(result.exitCode, `\`${command}\` succeeds`).toEqual(0); + + return result; + } + + beforeAll(async () => { + tmpDir = (await tmp.dir()).path; + addonDir = join(tmpDir, addonName); + await execa({ + cwd: tmpDir, + extendEnv: false, + })`${localEmberCli} addon ${addonName} -b ${blueprintPath} --skip-npm --prefer-local true --${packageManager} --no-build`; + // Have to use --force because NPM is *stricter* when you use tags in package.json + // than pnpm (in that tags don't match any specified stable version) + if (packageManager === 'npm') { + await execa({ cwd: addonDir, extendEnv: false })`npm install --force`; + } else if (packageManager === 'pnpm') { + await execa({ cwd: addonDir, extendEnv: false })`pnpm install`; + } + }); + + it('was generated correctly', async () => { + assertGeneratedCorrectly({ + projectRoot: addonDir, + packageManager, + typeScript: false, + }); + }); + + // Tests are additive, so when running them in order, we want to check linting + // before we add files from fixtures + it('lints pass', async () => { + await commandSucceeds(`${packageManager} run lint`); + }); + + describe('with fixture', () => { + beforeEach(async () => { + let addonFixture = fixturify.readSync('./fixtures/typescript'); + fixturify.writeSync(addonDir, addonFixture); + + // It's important that we ensure that dist directory is empty for these tests, + // troll-y things can happen with shared dists + await fs.rm(join(addonDir, 'dist'), { recursive: true, force: true }); + }); + + it('lint:fix', async () => { + await commandSucceeds(`${packageManager} run lint:fix`); + }); + + it('test', async () => { + let testResult = await commandSucceeds(`${packageManager} run test`); + + console.log(testResult.stdout); + expect(testResult.stdout).to.include('# tests 2'); + expect(testResult.stdout).to.include('# pass 2'); + expect(testResult.stdout).to.include('# fail 0'); + }); + }); + }); +} From ed93d26cb805cca59137ade568cd0985314e6777 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 27 May 2026 12:38:46 -0400 Subject: [PATCH 2/3] Update CI --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b88c9ff..bc07b2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,6 +81,8 @@ jobs: - defaults with npm - defaults with pnpm - try-scenarios + - no-build with npm + - no-build with pnpm steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 From 2dd1c61f73148cdbf51cc9ee4f1488a2f90731e1 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com> Date: Wed, 27 May 2026 13:15:44 -0400 Subject: [PATCH 3/3] Get tests green --- index.js | 3 ++- tests/smoke-tests/--no-build.test.ts | 37 ++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index b7ce836..55ae228 100644 --- a/index.js +++ b/index.js @@ -113,8 +113,9 @@ module.exports = { delete contents.scripts.prepack; delete contents.devDependencies['@embroider/addon-dev']; delete contents.devDependencies['@ember/library-tsconfig']; - delete contents.devDependencies['@rollup/plugin-babel']; delete contents.devDependencies['rollup']; + + contents.scripts['lint:publish'] = 'publint run --level error'; } let sorted = sortPackageJson(contents); diff --git a/tests/smoke-tests/--no-build.test.ts b/tests/smoke-tests/--no-build.test.ts index cc8e60e..6400f9f 100644 --- a/tests/smoke-tests/--no-build.test.ts +++ b/tests/smoke-tests/--no-build.test.ts @@ -53,7 +53,7 @@ for (let packageManager of SUPPORTED_PACKAGE_MANAGERS) { await execa({ cwd: tmpDir, extendEnv: false, - })`${localEmberCli} addon ${addonName} -b ${blueprintPath} --skip-npm --prefer-local true --${packageManager} --no-build`; + })`${localEmberCli} addon ${addonName} -b ${blueprintPath} --skip-npm true --${packageManager} --no-build`; // Have to use --force because NPM is *stricter* when you use tags in package.json // than pnpm (in that tags don't match any specified stable version) if (packageManager === 'npm') { @@ -77,14 +77,35 @@ for (let packageManager of SUPPORTED_PACKAGE_MANAGERS) { await commandSucceeds(`${packageManager} run lint`); }); + // Tests are additive, so when running them in order, we want to check linting + // before we add files from fixtures + it('lints with no fixtures all pass', async () => { + let { exitCode } = await execa({ cwd: addonDir, extendEnv: false })`pnpm lint`; + + expect(exitCode).toEqual(0); + }); + + it('lint:fix with no fixtures', async () => { + let { exitCode } = await execa({ + cwd: addonDir, + extendEnv: false, + })`${packageManager} run lint:fix`; + + expect(exitCode).toEqual(0); + }); + describe('with fixture', () => { beforeEach(async () => { - let addonFixture = fixturify.readSync('./fixtures/typescript'); - fixturify.writeSync(addonDir, addonFixture); + let addonFixture = fixturify.readSync('./fixtures/addon'); + fixturify.writeSync(join(addonDir, 'src'), addonFixture); + + let testFixture = fixturify.readSync('./fixtures/rendering-tests'); + fixturify.writeSync(join(addonDir, 'tests/rendering'), testFixture); - // It's important that we ensure that dist directory is empty for these tests, - // troll-y things can happen with shared dists - await fs.rm(join(addonDir, 'dist'), { recursive: true, force: true }); + fixturify.writeSync( + join(addonDir, 'tests/unit'), + fixturify.readSync('./fixtures/build-mode-tests'), + ); }); it('lint:fix', async () => { @@ -95,8 +116,8 @@ for (let packageManager of SUPPORTED_PACKAGE_MANAGERS) { let testResult = await commandSucceeds(`${packageManager} run test`); console.log(testResult.stdout); - expect(testResult.stdout).to.include('# tests 2'); - expect(testResult.stdout).to.include('# pass 2'); + expect(testResult.stdout).to.include('# tests 6'); + expect(testResult.stdout).to.include('# pass 6'); expect(testResult.stdout).to.include('# fail 0'); }); });