diff --git a/lib/Api.js b/lib/Api.js index 4147c81e9..bf38c53b8 100644 --- a/lib/Api.js +++ b/lib/Api.js @@ -239,9 +239,8 @@ class Api { .addPlugin(plugin, installOptions) .then(() => { if (plugin != null && isSwiftPackagePlugin(plugin)) { - const packagePath = path.join(this.locations.root, 'CordovaPlugins', 'Package.swift'); - const spm = new SwiftPackage(packagePath); - spm.addPlugin(plugin); + const spm = new SwiftPackage(this.locations.root); + spm.addPlugin(plugin, installOptions); } }) .then(() => { @@ -293,8 +292,7 @@ class Api { .removePlugin(plugin, uninstallOptions) .then(() => { if (plugin != null && isSwiftPackagePlugin(plugin)) { - const packagePath = path.join(this.locations.root, 'CordovaPlugins', 'Package.swift'); - const spm = new SwiftPackage(packagePath); + const spm = new SwiftPackage(this.locations.root); spm.removePlugin(plugin); } }) diff --git a/lib/SwiftPackage.js b/lib/SwiftPackage.js index 460c86dc4..2ecf17b3c 100644 --- a/lib/SwiftPackage.js +++ b/lib/SwiftPackage.js @@ -18,38 +18,67 @@ */ const fs = require('node:fs'); +const path = require('node:path'); const CordovaError = require('cordova-common').CordovaError; class SwiftPackage { - constructor (packagePath) { - this.path = packagePath; + constructor (projectRoot) { + this.root = projectRoot; + this.path = path.join(this.root, 'CordovaPlugins', 'Package.swift'); if (!fs.existsSync(this.path)) { throw new CordovaError('Package.swift is not found.'); } } - _pluginReference (plugin) { + _pluginReference (plugin, opts = {}) { + let pluginPath = path.relative(path.dirname(this.path), path.join(this.root, 'packages', plugin.id)); + if (opts.link) { + pluginPath = path.relative(path.dirname(this.path), plugin.dir); + } + return ` -package.dependencies.append(.package(name: "${plugin.id}", path: "../../../plugins/${plugin.id}")) +package.dependencies.append(.package(name: "${plugin.id}", path: "${pluginPath.replaceAll(path.sep, path.posix.sep)}")) package.targets.first?.dependencies.append(.product(name: "${plugin.id}", package: "${plugin.id}")) `; } - addPlugin (plugin) { + addPlugin (plugin, opts = {}) { + if (!opts.link) { + // Copy the plugin into the packages directory + fs.cpSync(plugin.dir, path.join(this.root, 'packages', plugin.id), { recursive: true }); + + const pkgSwiftPath = path.join(this.root, 'packages', plugin.id, 'Package.swift'); + const pkg_fd = fs.openSync(pkgSwiftPath, 'r+'); + + let packageContent = fs.readFileSync(pkg_fd, 'utf8'); + packageContent = packageContent.replace(/\(.*url.+cordova-ios.+\)/gm, '(name: "cordova-ios", path: "../cordova-ios")'); + + fs.ftruncateSync(pkg_fd); + fs.writeSync(pkg_fd, packageContent, 0, 'utf8'); + fs.closeSync(pkg_fd); + } + const fd = fs.openSync(this.path, 'a'); - fs.writeFileSync(fd, this._pluginReference(plugin), 'utf8'); + fs.writeFileSync(fd, this._pluginReference(plugin, opts), 'utf8'); fs.closeSync(fd); } removePlugin (plugin) { const fd = fs.openSync(this.path, 'r+'); + if (fs.existsSync(path.join(this.root, 'packages', plugin.id))) { + fs.rmSync(path.join(this.root, 'packages', plugin.id), { recursive: true, force: true }); + } + let packageContent = fs.readFileSync(fd, 'utf8'); + + // We don't know if it was originally linked or not, so try to remove both packageContent = packageContent.replace(this._pluginReference(plugin), ''); + packageContent = packageContent.replace(this._pluginReference(plugin, { link: true }), ''); fs.ftruncateSync(fd); - fs.writeFileSync(fd, packageContent, 'utf8'); + fs.writeSync(fd, packageContent, 0, 'utf8'); fs.closeSync(fd); } } diff --git a/tests/spec/unit/Api.spec.js b/tests/spec/unit/Api.spec.js index 2c3b90fe8..d2498d067 100644 --- a/tests/spec/unit/Api.spec.js +++ b/tests/spec/unit/Api.spec.js @@ -167,7 +167,7 @@ describe('Platform Api', () => { it('should add the plugin reference to Package.swift', () => { return api.addPlugin(swift_plugin) .then(() => { - expect(swiftPackage_mock.addPlugin).toHaveBeenCalledWith(swift_plugin); + expect(swiftPackage_mock.addPlugin).toHaveBeenCalledWith(swift_plugin, jasmine.any(Object)); }); }); }); diff --git a/tests/spec/unit/SwiftPackage.spec.js b/tests/spec/unit/SwiftPackage.spec.js index da11dc461..016969e81 100644 --- a/tests/spec/unit/SwiftPackage.spec.js +++ b/tests/spec/unit/SwiftPackage.spec.js @@ -27,56 +27,102 @@ tmp.setGracefulCleanup(); const fixturePackage = fs.readFileSync(path.join(__dirname, 'fixtures', 'test-Package.swift'), 'utf-8'); describe('SwiftPackage', () => { + let tmpDir; + beforeEach(() => { + tmpDir = tmp.dirSync(); + }); + + afterEach(() => { + fs.rmSync(tmpDir.name, { recursive: true, force: true }); + }); + it('should error if Package.swift file does not exist', () => { expect(() => { - const _ = new SwiftPackage('dummypath'); + const _ = new SwiftPackage(tmpDir.name); expect(_).not.toEqual(null); // To avoid ESLINT error "Do not use 'new' for side effects" }).toThrow(); }); describe('addPlugin', () => { const my_plugin = { - id: 'my-plugin' + id: 'my-plugin', + dir: path.join(__dirname, 'fixtures', 'org.test.plugins.swiftpackageplugin') }; let pkg; - let tmpFile; beforeEach(() => { - tmpFile = tmp.fileSync({ discardDescriptor: true }); - fs.writeFileSync(tmpFile.name, fixturePackage, 'utf8'); + fs.mkdirSync(path.join(tmpDir.name, 'CordovaPlugins')); + fs.writeFileSync(path.join(tmpDir.name, 'CordovaPlugins', 'Package.swift'), fixturePackage, 'utf8'); - pkg = new SwiftPackage(tmpFile.name); + pkg = new SwiftPackage(tmpDir.name); }); it('should add plugin references to the package file', () => { pkg.addPlugin(my_plugin); - const content = fs.readFileSync(tmpFile.name, 'utf8'); - expect(content).toContain('.package(name: "my-plugin"'); + const pkgPath = path.join(tmpDir.name, 'CordovaPlugins', 'Package.swift'); + const content = fs.readFileSync(pkgPath, 'utf8'); + expect(content).toContain('.package(name: "my-plugin", path: "../packages/my-plugin")'); expect(content).toContain('.product(name: "my-plugin", package: "my-plugin")'); }); + + it('should copy the plugin into the packages directory', () => { + pkg.addPlugin(my_plugin); + + expect(fs.existsSync(path.join(tmpDir.name, 'packages', 'my-plugin'))).toBeTruthy(); + }); + + it('should add plugin references to the package file when linked', () => { + pkg.addPlugin(my_plugin, { link: true }); + + const pkgPath = path.join(tmpDir.name, 'CordovaPlugins', 'Package.swift'); + const content = fs.readFileSync(pkgPath, 'utf8'); + + expect(content).toContain('.package(name: "my-plugin", path: "'); + expect(content).not.toContain('.package(name: "my-plugin", path: "../packages/my-plugin")'); + expect(content).toContain('.product(name: "my-plugin", package: "my-plugin")'); + }); + + it('should copy a linked plugin into the packages directory', () => { + pkg.addPlugin(my_plugin, { link: true }); + + expect(fs.existsSync(path.join(tmpDir.name, 'packages', 'my-plugin'))).toBeFalsy(); + }); }); describe('removePlugin', () => { const my_plugin = { - id: 'my-plugin' + id: 'my-plugin', + dir: path.join(__dirname, 'fixtures', 'org.test.plugins.swiftpackageplugin') }; let pkg; - let tmpFile; beforeEach(() => { - tmpFile = tmp.fileSync({ discardDescriptor: true }); + fs.mkdirSync(path.join(tmpDir.name, 'CordovaPlugins')); + const pkgPath = path.join(tmpDir.name, 'CordovaPlugins', 'Package.swift'); + fs.writeFileSync(pkgPath, fixturePackage, 'utf8'); - pkg = new SwiftPackage(tmpFile.name); - fs.writeFileSync(tmpFile.name, fixturePackage + pkg._pluginReference(my_plugin), 'utf8'); + pkg = new SwiftPackage(tmpDir.name); + fs.writeFileSync(pkgPath, fixturePackage + pkg._pluginReference(my_plugin), 'utf8'); }); - it('should add plugin references to the package file', () => { + it('should remove plugin references to the package file', () => { pkg.removePlugin(my_plugin); - const content = fs.readFileSync(tmpFile.name, 'utf8'); + const content = fs.readFileSync(path.join(tmpDir.name, 'CordovaPlugins', 'Package.swift'), 'utf8'); expect(content).not.toContain('.package(name: "my-plugin"'); expect(content).not.toContain('.product(name: "my-plugin", package: "my-plugin")'); }); + + it('should remove the plugin from the packages directory', () => { + fs.mkdirSync(path.join(tmpDir.name, 'packages', 'my-plugin'), { recursive: true }); + fs.writeFileSync(path.join(tmpDir.name, 'packages', 'my-plugin', 'test.txt'), 'Test', 'utf-8'); + + expect(fs.existsSync(path.join(tmpDir.name, 'packages', 'my-plugin'))).toBeTruthy(); + + pkg.removePlugin(my_plugin); + + expect(fs.existsSync(path.join(tmpDir.name, 'packages', 'my-plugin'))).toBeFalsy(); + }); }); });