From 653934c5690c4f4a0116cc285cdafc31e0c1f34d Mon Sep 17 00:00:00 2001 From: Volodymyr Kolesnykov Date: Fri, 8 Nov 2024 23:24:23 +0200 Subject: [PATCH 1/2] fix(dev-env): detection of URLs to replace for multisites (#2081) * fix(dev-env): detection of URLs to replace for multisites * fix: replacement takes paths into consideration * refactor: simplify `replaceDomain()` * fix: update `wp_blogs` * Fix test cases - we don't have id's anymore --------- Co-authored-by: Rinat Khaziev --- __tests__/commands/dev-env-sync-sql.ts | 11 ++- src/commands/dev-env-import-sql.ts | 2 +- src/commands/dev-env-sync-sql.ts | 91 ++++++++++++++++--- .../dev-environment/dev-environment-lando.ts | 5 +- 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/__tests__/commands/dev-env-sync-sql.ts b/__tests__/commands/dev-env-sync-sql.ts index a0392447d..72ad0813a 100644 --- a/__tests__/commands/dev-env-sync-sql.ts +++ b/__tests__/commands/dev-env-sync-sql.ts @@ -34,6 +34,10 @@ describe( 'commands/DevEnvSyncSQLCommand', () => { blogId: 2, homeUrl: 'https://subsite.com', }, + { + blogId: 3, + homeUrl: 'https://another.com/path', + }, ], }, }; @@ -56,7 +60,7 @@ describe( 'commands/DevEnvSyncSQLCommand', () => { it( 'should return a map of search-replace values', () => { const cmd = new DevEnvSyncSQLCommand( app, env, 'test-slug', lando ); cmd.slug = 'test-slug'; - cmd.siteUrls = [ 'test.go-vip.com' ]; + cmd.siteUrls = [ 'http://test.go-vip.com' ]; cmd.generateSearchReplaceMap(); expect( cmd.searchReplaceMap ).toEqual( { 'test.go-vip.com': 'test-slug.vipdev.lndo.site' } ); @@ -65,12 +69,13 @@ describe( 'commands/DevEnvSyncSQLCommand', () => { it( 'should return a map of search-replace values for multisite', () => { const cmd = new DevEnvSyncSQLCommand( app, msEnv, 'test-slug', lando ); cmd.slug = 'test-slug'; - cmd.siteUrls = [ 'test.go-vip.com', 'subsite.com' ]; + cmd.siteUrls = [ 'https://test.go-vip.com', 'http://subsite.com', 'http://another.com/path' ]; cmd.generateSearchReplaceMap(); expect( cmd.searchReplaceMap ).toEqual( { 'test.go-vip.com': 'test-slug.vipdev.lndo.site', - 'subsite.com': 'subsite-com-2.test-slug.vipdev.lndo.site', + 'subsite.com': 'subsite-com.test-slug.vipdev.lndo.site', + 'another.com/path': 'another-com.test-slug.vipdev.lndo.site/path', } ); } ); } ); diff --git a/src/commands/dev-env-import-sql.ts b/src/commands/dev-env-import-sql.ts index c561d3953..3be537500 100644 --- a/src/commands/dev-env-import-sql.ts +++ b/src/commands/dev-env-import-sql.ts @@ -108,7 +108,7 @@ export class DevEnvImportSQLCommand { * Therefore, for the things to work, we have to pretend that stdin is not a TTY :-) */ process.stdin.isTTY = false; - await exec( lando, this.slug, importArg, { stdio: [ fd, 'pipe', 'pipe' ] } ); + await exec( lando, this.slug, importArg, { stdio: [ fd.fd, 'pipe', 'pipe' ] } ); if ( ! this.options.quiet ) { console.log( `${ chalk.green.bold( 'Success:' ) } Database imported.` ); diff --git a/src/commands/dev-env-sync-sql.ts b/src/commands/dev-env-sync-sql.ts index 4de7c557b..554ec731d 100644 --- a/src/commands/dev-env-sync-sql.ts +++ b/src/commands/dev-env-sync-sql.ts @@ -5,7 +5,6 @@ import chalk from 'chalk'; import fs from 'fs'; import Lando from 'lando'; import { pipeline } from 'node:stream/promises'; -import urlLib from 'url'; import { DevEnvImportSQLCommand, DevEnvImportSQLOptions } from './dev-env-import-sql'; import { ExportSQLCommand } from './export-sql'; @@ -18,6 +17,27 @@ import { fixMyDumperTransform, getSqlDumpDetails, SqlDumpType } from '../lib/dat import { makeTempDir } from '../lib/utils'; import { getReadInterface } from '../lib/validations/line-by-line'; +/** + * Replaces the domain in the given URL + * + * @param string str The URL to replace the domain in. + * @param string domain The new domain + * @return The URL with the new domain + */ +const replaceDomain = ( str: string, domain: string ): string => + str.replace( /^([^:]+:\/\/)([^:/]+)/, `$1${ domain }` ); + +/** + * Strips the protocol from the URL + * + * @param string url The URL to strip the protocol from + * @return The URL without the protocol + */ +function stripProtocol( url: string ): string { + const parts = url.split( '//', 2 ); + return parts.length > 1 ? parts[ 1 ] : parts[ 0 ]; +} + /** * Finds the site home url from the SQL line * @@ -27,8 +47,12 @@ import { getReadInterface } from '../lib/validations/line-by-line'; function findSiteHomeUrl( sql: string ): string | null { const regex = `['"](siteurl|home)['"],\\s?['"](.*?)['"]`; const url = sql.match( regex )?.[ 2 ] || ''; - - return urlLib.parse( url ).hostname || null; + try { + new URL( url ); + return url; + } catch { + return null; + } } /** @@ -42,17 +66,17 @@ async function extractSiteUrls( sqlFile: string ): Promise< string[] > { const readInterface = await getReadInterface( sqlFile ); return new Promise( ( resolve, reject ) => { - const domains: Set< string > = new Set(); + const urls: Set< string > = new Set(); readInterface.on( 'line', line => { - const domain = findSiteHomeUrl( line ); - if ( domain ) { - domains.add( domain ); + const url = findSiteHomeUrl( line ); + if ( url ) { + urls.add( url ); } } ); readInterface.on( 'close', () => { - // Soring by length so that longest domains are replaced first - resolve( Array.from( domains ).sort( ( dom1, dom2 ) => dom2.length - dom1.length ) ); + // Soring by length so that longest URLs are replaced first + resolve( Array.from( urls ).sort( ( url1, url2 ) => url2.length - url1.length ) ); } ); readInterface.on( 'error', reject ); @@ -168,7 +192,9 @@ export class DevEnvSyncSQLCommand { this.searchReplaceMap = {}; for ( const url of this.siteUrls ) { - this.searchReplaceMap[ url ] = this.landoDomain; + this.searchReplaceMap[ stripProtocol( url ) ] = stripProtocol( + replaceDomain( url, this.landoDomain ) + ); } const networkSites = this.env.wpSitesSDS?.nodes; @@ -177,12 +203,18 @@ export class DevEnvSyncSQLCommand { for ( const site of networkSites ) { if ( ! site?.blogId || site.blogId === 1 ) continue; - const url = site?.homeUrl?.replace( /https?:\/\//, '' ); - if ( ! url || ! this.searchReplaceMap[ url ] ) continue; + const url = site?.homeUrl; + if ( ! url ) continue; + + const strippedUrl = stripProtocol( url ); + if ( ! this.searchReplaceMap[ strippedUrl ] ) continue; + + const domain = new URL( url ).hostname; + const newDomain = `${ this.slugifyDomain( domain ) }.${ this.landoDomain }`; - this.searchReplaceMap[ url ] = `${ this.slugifyDomain( url ) }-${ site.blogId }.${ - this.landoDomain - }`; + this.searchReplaceMap[ stripProtocol( url ) ] = stripProtocol( + replaceDomain( url, newDomain ) + ); } } @@ -213,6 +245,34 @@ export class DevEnvSyncSQLCommand { await importCommand.run(); } + public async fixBlogsTable(): Promise< void > { + const networkSites = this.env.wpSitesSDS?.nodes; + if ( ! networkSites ) { + return; + } + + const queries: string[] = []; + for ( const site of networkSites ) { + if ( ! site?.blogId || ! site?.homeUrl ) { + continue; + } + + const oldDomain = new URL( site.homeUrl ).hostname; + const newDomain = + site.blogId !== 1 + ? `${ this.slugifyDomain( oldDomain ) }.${ this.landoDomain }` + : this.landoDomain; + + queries.push( + `UPDATE wp_blogs SET domain = '${ newDomain }' WHERE blog_id = ${ Number( site.blogId ) };` + ); + } + + if ( queries.length ) { + await fs.promises.appendFile( this.sqlFile, queries.join( '\n' ) ); + } + } + /** * Sequentially runs the commands to export, search-replace, and import the SQL file * to the local environment @@ -273,6 +333,7 @@ export class DevEnvSyncSQLCommand { } await this.runSearchReplace(); + await this.fixBlogsTable(); console.log( `${ chalk.green( '✓' ) } Search-replace operation is complete` ); } catch ( err ) { const error = err as Error; diff --git a/src/lib/dev-environment/dev-environment-lando.ts b/src/lib/dev-environment/dev-environment-lando.ts index 2f508c3c4..0d6c7090f 100644 --- a/src/lib/dev-environment/dev-environment-lando.ts +++ b/src/lib/dev-environment/dev-environment-lando.ts @@ -6,7 +6,7 @@ import Lando, { type LandoConfig } from 'lando/lib/lando'; import landoUtils, { type AppInfo } from 'lando/plugins/lando-core/lib/utils'; import landoBuildTask from 'lando/plugins/lando-tooling/lib/build'; import { lookup } from 'node:dns/promises'; -import { FileHandle, mkdir, rename } from 'node:fs/promises'; +import { mkdir, rename } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path, { dirname } from 'node:path'; import { satisfies } from 'semver'; @@ -24,9 +24,10 @@ import UserError from '../user-error'; import type { NetworkInspectInfo } from 'dockerode'; import type Landerode from 'lando/lib/docker'; +import type { StdioOptions } from 'node:child_process'; export interface LandoExecOptions { - stdio?: string | [ FileHandle, string, string ]; + stdio?: StdioOptions; } /** From 55aa511aeafe3e0cc2d7e8fdc8a986b14ec79a8f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:24:27 +0000 Subject: [PATCH 2/2] build(deps-dev): bump nock from 13.5.5 to 13.5.6 Bumps [nock](https://github.com/nock/nock) from 13.5.5 to 13.5.6. - [Release notes](https://github.com/nock/nock/releases) - [Changelog](https://github.com/nock/nock/blob/main/CHANGELOG.md) - [Commits](https://github.com/nock/nock/compare/v13.5.5...v13.5.6) --- updated-dependencies: - dependency-name: nock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- npm-shrinkwrap.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index f214ee89b..1fbbaa2c5 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -136,7 +136,7 @@ "dockerode": "^4.0.0", "eslint": "^8.35.0", "jest": "^29.7.0", - "nock": "13.5.5", + "nock": "13.5.6", "prettier": "npm:wp-prettier@2.8.5", "rimraf": "6.0.1", "typescript": "^5.2.2" @@ -10103,9 +10103,9 @@ "dev": true }, "node_modules/nock": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.5.tgz", - "integrity": "sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "dev": true, "dependencies": { "debug": "^4.1.0", @@ -20602,9 +20602,9 @@ "dev": true }, "nock": { - "version": "13.5.5", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.5.tgz", - "integrity": "sha512-XKYnqUrCwXC8DGG1xX4YH5yNIrlh9c065uaMZZHUoeUUINTOyt+x/G+ezYk0Ft6ExSREVIs+qBJDK503viTfFA==", + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", "dev": true, "requires": { "debug": "^4.1.0", diff --git a/package.json b/package.json index a76415898..7a4cd413d 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "dockerode": "^4.0.0", "eslint": "^8.35.0", "jest": "^29.7.0", - "nock": "13.5.5", + "nock": "13.5.6", "prettier": "npm:wp-prettier@2.8.5", "rimraf": "6.0.1", "typescript": "^5.2.2"