From 8453adbc84d1bb0ef1a31799f6ffa9ae54580bf6 Mon Sep 17 00:00:00 2001 From: Chris Manson Date: Fri, 3 Jul 2026 13:25:28 +0100 Subject: [PATCH] drop support for ancient broccoli version --- .github/workflows/ci.yml | 4 +- package.json | 4 - pnpm-lock.yaml | 6 -- src/index.ts | 45 ---------- src/read_compat.ts | 125 --------------------------- test/integration_test.js | 177 --------------------------------------- test/unit_test.js | 19 ----- yarn.lock | 2 +- 8 files changed, 3 insertions(+), 379 deletions(-) delete mode 100644 src/read_compat.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab1d9d7..27d2ead 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - broccoli-version: ["0.16.9", "1", "2", "3"] + broccoli-version: ["1", "2", "3"] # when github updates their latest 24.x to include this patch number we can remove the specific version here node-version: [14.x, 16.x, 18.x, 20.x, 22.x, 24.13.1] @@ -77,7 +77,7 @@ jobs: strategy: fail-fast: false matrix: - broccoli-version: ["0.16.9", "1", "2", "3", "4"] + broccoli-version: ["1", "2", "3", "4"] steps: - uses: actions/checkout@v6 diff --git a/package.json b/package.json index caf96b7..8a6626f 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,11 @@ "test": "tsc && mocha", "watch": "tsc --watch" }, - "resolutions": { - "quick-temp": "0.1.8" - }, "dependencies": { "broccoli-node-api": "^1.7.0", "broccoli-output-wrapper": "^3.2.5", "fs-merger": "^3.2.1", "promise-map-series": "^0.3.0", - "quick-temp": "^0.1.8", "symlink-or-copy": "^1.3.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dd98de..5df76db 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,9 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - quick-temp: 0.1.8 - importers: .: @@ -23,9 +20,6 @@ importers: promise-map-series: specifier: ^0.3.0 version: 0.3.0 - quick-temp: - specifier: 0.1.8 - version: 0.1.8 symlink-or-copy: specifier: ^1.3.1 version: 1.3.1 diff --git a/src/index.ts b/src/index.ts index 8a8e82a..114b463 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,7 +8,6 @@ import { import type { MapSeriesIterator, PluginOptions } from './interfaces'; -import ReadCompat from './read_compat.js'; import { FSOutput } from 'broccoli-output-wrapper'; // @ts-expect-error the types are declared wrong upstream here @@ -67,8 +66,6 @@ export default class Plugin implements TransformNode { private _volatile: boolean; private _trackInputChanges: boolean; private _instantiationError: Error; - private _readCompatError?: Error; - private _readCompat?: ReadCompat | false; private rebuild?: () => void; __broccoliFeatures__: FeatureSet; @@ -118,8 +115,6 @@ export default class Plugin implements TransformNode { this._volatile = !!options.volatile; this._trackInputChanges = !!options.trackInputChanges; - this._checkOverrides(); - // For future extensibility, we version the API using feature flags this.__broccoliFeatures__ = BROCCOLI_FEATURES; } @@ -168,18 +163,6 @@ export default class Plugin implements TransformNode { return FSFACADE.get(this).output; } - private _checkOverrides() { - if (typeof this.rebuild === 'function') { - throw new Error('For compatibility, plugins must not define a plugin.rebuild() function'); - } - if (this.read !== Plugin.prototype.read) { - throw new Error('For compatibility, plugins must not define a plugin.read() function'); - } - if (this.cleanup !== Plugin.prototype.cleanup) { - throw new Error('For compatibility, plugins must not define a plugin.cleanup() function'); - } - } - // The Broccoli builder calls plugin.__broccoliGetInfo__ __broccoliGetInfo__( builderFeatures: FeatureSet = { persistentOutputFlag: true, sourceDirectories: true } @@ -291,32 +274,4 @@ export default class Plugin implements TransformNode { build(): Promise | void { throw new Error('Plugin subclasses must implement a .build() function'); } - - // Compatibility code so plugins can run on old, .read-based Broccoli: - read(readTree: MapSeriesIterator): Promise | undefined { - if (this._readCompat == null) { - try { - this._initializeReadCompat(); // call this.__broccoliGetInfo__() - } catch (err) { - // Prevent trying to initialize again on next .read - this._readCompat = false; - // Remember error so we can throw it on all subsequent .read calls - this._readCompatError = err as unknown as Error; - } - } - - if (this._readCompatError != null) throw this._readCompatError; - - if (this._readCompat) { - return this._readCompat.read(readTree); - } - } - - async cleanup(): Promise { - if (this._readCompat) return this._readCompat.cleanup(); - } - - private _initializeReadCompat() { - this._readCompat = new ReadCompat(this); - } } diff --git a/src/read_compat.ts b/src/read_compat.ts deleted file mode 100644 index 0a81a1f..0000000 --- a/src/read_compat.ts +++ /dev/null @@ -1,125 +0,0 @@ -import Plugin from './index'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as quickTemp from 'quick-temp'; -import mapSeries from 'promise-map-series'; -import * as symlinkOrCopy from 'symlink-or-copy'; -const symlinkOrCopySync = symlinkOrCopy.sync; - -import { InputNode, TransformNodeInfo, CallbackObject } from 'broccoli-node-api'; -import { MapSeriesIterator } from './interfaces'; - -export interface PluginWithDescription extends Omit { - description?: string; -} - -const READ_COMPAT_FEATURES = Object.freeze({ - // these two features are supported by the old builders which still utilize read() - persistentOutputFlag: true, - sourceDirectories: true, - // ReadCompat provides this capability as the builder relying on ReadCompat may not - needsCacheFlag: true, -}); - -// Mimic how a Broccoli builder would call a plugin, using quickTemp to create -// directories -export default class ReadCompat { - pluginInterface: TransformNodeInfo; - inputPaths: string[]; - inputBasePath!: string; - cachePath?: string; - outputPath!: string; - - private _priorBuildInputNodeOutputPaths: string[]; - callbackObject: CallbackObject; - - constructor(plugin: PluginWithDescription) { - this.pluginInterface = plugin.__broccoliGetInfo__(READ_COMPAT_FEATURES); - - quickTemp.makeOrReuse(this, 'outputPath', this.pluginInterface.name); - - if (this.pluginInterface.needsCache) { - quickTemp.makeOrReuse(this, 'cachePath', this.pluginInterface.name); - } else { - this.cachePath = undefined; - } - - quickTemp.makeOrReuse(this, 'inputBasePath', this.pluginInterface.name); - - this.inputPaths = []; - this._priorBuildInputNodeOutputPaths = []; - - if (this.pluginInterface.inputNodes.length === 1) { - this.inputPaths.push(this.inputBasePath); - this._priorBuildInputNodeOutputPaths.push(this.inputBasePath); - } else { - for (let i = 0; i < this.pluginInterface.inputNodes.length; i++) { - this.inputPaths.push(path.join(this.inputBasePath, i + '')); - } - } - - this.pluginInterface.setup( - {}, - { - inputPaths: this.inputPaths, - outputPath: this.outputPath, - cachePath: this.cachePath, - } - ); - - this.callbackObject = this.pluginInterface.getCallbackObject(); - - if (plugin.description == null) { - plugin.description = this.pluginInterface.name; - if (this.pluginInterface.annotation != null) { - plugin.description += ': ' + this.pluginInterface.annotation; - } - } - } - - read(readTree: MapSeriesIterator): Promise { - if (!this.pluginInterface.persistentOutput) { - fs.rmSync(this.outputPath, { recursive: true, force: true }); - fs.mkdirSync(this.outputPath); - } - - return mapSeries(this.pluginInterface.inputNodes, readTree) - .then((outputPaths) => { - const priorBuildInputNodeOutputPaths = this._priorBuildInputNodeOutputPaths; - // In old .read-based Broccoli, the inputNodes's outputPaths can change - // on each rebuild. But the new API requires that our plugin sees fixed - // input paths. Therefore, we symlink the inputNodes' outputPaths to our - // (fixed) inputPaths on each .read. - for (let i = 0; i < outputPaths.length; i++) { - const priorPath = priorBuildInputNodeOutputPaths[i]; - const currentPath = outputPaths[i]; - - // if this output path is different from last builds or - // if we cannot symlink then clear and symlink/copy manually - const hasDifferentPath = priorPath !== currentPath; - const forceReSymlinking = !symlinkOrCopy.canSymlink || hasDifferentPath; - - if (forceReSymlinking) { - // avoid `fs.rmSync` for initial build - if (priorPath) { - fs.rmSync(this.inputPaths[i], { recursive: true, force: true }); - } - - symlinkOrCopySync(currentPath as string, this.inputPaths[i]); - } - } - - // save for next builds comparison - this._priorBuildInputNodeOutputPaths = outputPaths as string[]; - - return this.callbackObject.build(); - }) - .then(() => this.outputPath); - } - - cleanup(): void { - quickTemp.remove(this, 'outputPath'); - quickTemp.remove(this, 'cachePath'); - quickTemp.remove(this, 'inputBasePath'); - } -} diff --git a/test/integration_test.js b/test/integration_test.js index 3361ae4..d03bd3f 100644 --- a/test/integration_test.js +++ b/test/integration_test.js @@ -5,7 +5,6 @@ import Fixturify from 'broccoli-fixturify'; import chai from 'chai'; import chaiAsPromised from 'chai-as-promised'; import broccoli from 'broccoli'; -import quickTemp from 'quick-temp'; import symlinkOrCopy from 'symlink-or-copy'; import Plugin from '../dist/index.js'; @@ -61,182 +60,6 @@ describe('integration test', function () { } }); - describe('.read compatibility code', function () { - const Builder = broccoli.Builder; - - before(function () { - if (broccoliVersion !== '0.16.9') { - this.skip(); - } - }); - - it('sets description', function () { - const node = new AnnotatingPlugin([], { - name: 'SomePlugin', - annotation: 'some annotation', - }); - builder = new Builder(node); - return builder.build().then(function (hash) { - return expect(hash.graph.toJSON().description).to.equal('SomePlugin: some annotation'); - }); - }); - - it('handles readCompat initialization errors', function () { - const node = new AnnotatingPlugin([]); - let initializeReadCompatCalls = 0; - node._initializeReadCompat = function () { - // stub - throw new Error('someError ' + ++initializeReadCompatCalls); - }; - builder = new Builder(node); - return Promise.resolve() - .then(function () { - return expect(builder.build()).to.be.rejectedWith(Error, 'someError 1'); - }) - .then(function () { - return expect(builder.build()).to.be.rejectedWith(Error, 'someError 1'); - }); - }); - - describe('stable inputPaths', function () { - let tmp, inputPaths, originalCanSymlink, builder; - - beforeEach(function () { - inputPaths = []; - - originalCanSymlink = symlinkOrCopy.canSymlink; - tmp = {}; - }); - - afterEach(function () { - symlinkOrCopy._setOptions({ - fs, - isWindows: process.platform === 'win32', - canSymlink: originalCanSymlink, - }); - - quickTemp.remove(tmp, 'fixtures'); - - return builder && builder.cleanup(); - }); - - function UnstableOutputPathTree(inputTree) { - this._inputTree = inputTree; - quickTemp.makeOrReuse(this, 'outputBasePath'); - this._buildCount = 0; - } - UnstableOutputPathTree.prototype.read = async function (readTree) { - quickTemp.makeOrRemake(this, 'outputBasePath'); - - const inputTreesOutputPath = await readTree(this._inputTree); - const outputPath = path.join(this.outputBasePath, '' + this._buildCount++); - fs.mkdirSync(outputPath); - - copyFilesWithAnnotation(0, inputTreesOutputPath, outputPath); - - return outputPath; - }; - UnstableOutputPathTree.prototype.cleanup = function () { - quickTemp.remove(this, 'outputBasePath'); - }; - - function StableOutputPathTree(inputTree) { - this._inputTree = inputTree; - quickTemp.makeOrReuse(this, 'outputPath'); - } - StableOutputPathTree.prototype.read = async function (readTree) { - quickTemp.makeOrRemake(this, 'outputPath'); - - const inputTreesOutputPath = await readTree(this._inputTree); - - copyFilesWithAnnotation(0, inputTreesOutputPath, this.outputPath); - - return this.outputPath; - }; - - StableOutputPathTree.prototype.cleanup = function () { - quickTemp.remove(this, 'outputPath'); - }; - - class InputPathTracker extends Plugin { - build(...args) { - inputPaths.push(this.inputPaths[0]); - - return AnnotatingPlugin.prototype.build.apply(this, args); - } - } - - function isConsistent(inputNode) { - builder = new Builder(inputNode); - - function buildAndCheck() { - return Promise.resolve() - .then(function () { - return builder.build(); - }) - .then(function (hash) { - return fixturify.readSync(hash.directory); - }) - .then(function (fixture) { - expect(fixture).to.deep.equal({ - 'foo.txt': 'foo contents - from input node #0 - from input node #0', - }); - - return fixture; - }); - } - return buildAndCheck() - .then(buildAndCheck) - .then(function () { - expect(inputPaths[0]).to.equal(inputPaths[1]); - }); - } - - it('provides stable inputPaths when upstream output path changes', function () { - const unstableNode = new UnstableOutputPathTree(node1); - const inputTracker = new InputPathTracker([unstableNode]); - - return isConsistent(inputTracker); - }); - - it('provides stable inputPaths when upstream output path is consistent', function () { - const unstableNode = new StableOutputPathTree(node1); - const inputTracker = new InputPathTracker([unstableNode]); - - return isConsistent(inputTracker); - }); - - it('provides stable inputPaths when upstream output path is consistent without symlinking', async function () { - symlinkOrCopy._setOptions({ - fs, - isWindows: process.platform === 'win32', - canSymlink: false, - }); - - quickTemp.makeOrRemake(tmp, 'fixtures'); - fixturify.writeSync(tmp.fixtures, { - 'foo.txt': 'foo contents', - }); - - const unstableNode = new StableOutputPathTree(tmp.fixtures); - const inputTracker = new InputPathTracker([unstableNode]); - - await isConsistent(inputTracker); - - fixturify.writeSync(tmp.fixtures, { - 'foo.txt': 'foo other contents', - }); - - const hash = await builder.build(); - const fixture = fixturify.readSync(hash.directory); - - expect(fixture).to.deep.equal({ - 'foo.txt': 'foo other contents - from input node #0 - from input node #0', - }); - }); - }); - }); - describe('.input/.output functionality', function () { const Builder = broccoli.Builder; class FSFacadePlugin extends Plugin { diff --git a/test/unit_test.js b/test/unit_test.js index 94812a3..1728b5d 100644 --- a/test/unit_test.js +++ b/test/unit_test.js @@ -72,25 +72,6 @@ describe('unit tests', function () { new TestPlugin([new TestPlugin([])]); }); - it('disallows overriding read, cleanup, and rebuild', function () { - const prohibitedNames = ['read', 'rebuild', 'cleanup']; - for (let i = 0; i < prohibitedNames.length; i++) { - class BadPlugin extends Plugin { - build() { - // empty function - } - } - - BadPlugin.prototype[prohibitedNames[i]] = () => { - /* empty function */ - }; - - expect(function () { - new BadPlugin([]); - }).to.throw(/For compatibility, plugins must not define/); - } - }); - it('checks that the inputNodes argument is an array', function () { expect(function () { new NoopPlugin('notAnArray'); diff --git a/yarn.lock b/yarn.lock index f8dbcdf..d2a4890 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3037,7 +3037,7 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== -quick-temp@0.1.8, quick-temp@^0.1.3, quick-temp@^0.1.5, quick-temp@^0.1.8: +quick-temp@^0.1.3, quick-temp@^0.1.5: version "0.1.8" resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408" integrity sha512-YsmIFfD9j2zaFwJkzI6eMG7y0lQP7YeWzgtFgNl38pGWZBSXJooZbOWwkcRot7Vt0Fg9L23pX0tqWU3VvLDsiA==