Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(dev-env): detection of URLs to replace for multisites #2081

Merged
merged 5 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions __tests__/commands/dev-env-sync-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ describe( 'commands/DevEnvSyncSQLCommand', () => {
blogId: 2,
homeUrl: 'https://subsite.com',
},
{
blogId: 3,
homeUrl: 'https://another.com/path',
},
],
},
};
Expand All @@ -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' } );
Expand All @@ -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',
'another.com/path': 'another-com-3.test-slug.vipdev.lndo.site/path',
} );
} );
} );
Expand Down
2 changes: 1 addition & 1 deletion src/commands/dev-env-import-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.` );
Expand Down
91 changes: 76 additions & 15 deletions src/commands/dev-env-sync-sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
*
Expand All @@ -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;
}
Comment on lines +50 to +55
Copy link
Member Author

@sjinks sjinks Nov 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node 18.17.0 has URL.canParse(); hopefully, we can use it instead of this code someday.

}

/**
Expand All @@ -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 );
Expand Down Expand Up @@ -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;
Expand All @@ -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 )
);
}
}

Expand Down Expand Up @@ -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 ) };`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

custom prefixes is what gives me a pause here.

);
}

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
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 3 additions & 2 deletions src/lib/dev-environment/dev-environment-lando.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
}

/**
Expand Down
Loading