Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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: 9 additions & 0 deletions cli/__mocks__/cli-table3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default class MockTable {
constructor() {}
push() {
return this;
}
toString() {
return 'table output';
}
}
17 changes: 6 additions & 11 deletions cli/__mocks__/ora.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
jest.mock( 'ora', () => {
return {
__esModule: true,
default: () => ( {
start: jest.fn().mockReturnThis(),
stop: jest.fn().mockReturnThis(),
succeed: jest.fn().mockReturnThis(),
fail: jest.fn().mockReturnThis(),
} ),
};
} );
export default {
start: jest.fn().mockReturnThis(),
stop: jest.fn().mockReturnThis(),
succeed: jest.fn().mockReturnThis(),
fail: jest.fn().mockReturnThis(),
};
2 changes: 1 addition & 1 deletion cli/commands/preview/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function runCommand( siteFolder: string ): Promise< void > {
logger.reportSuccess( __( 'Archive created' ) );

logger.reportStart( LoggerAction.UPLOAD, __( 'Uploading archive…' ) );
const wordpressVersion = await getWordPressVersion( siteFolder );
const wordpressVersion = getWordPressVersion( siteFolder );
const uploadResponse = await uploadArchive( archivePath, token.accessToken, wordpressVersion );
logger.reportSuccess( __( 'Archive uploaded' ) );

Expand Down
47 changes: 41 additions & 6 deletions cli/commands/preview/list.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { __, _n, sprintf } from '@wordpress/i18n';
import Table from 'cli-table3';
import { PreviewCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import { format } from 'date-fns';
import { getAuthToken } from 'cli/lib/appdata';
import {
getSnapshotCliJson,
getSnapshotCliTable,
formatDurationUntilExpiry,
getSnapshotsFromAppdata,
isSnapshotExpired,
} from 'cli/lib/snapshots';
import { getColumnWidths } from 'cli/lib/utils';
import { validateSiteFolder } from 'cli/lib/validation';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

export async function runCommand( siteFolder: string, format: 'table' | 'json' ): Promise< void > {
export async function runCommand(
siteFolder: string,
outputFormat: 'table' | 'json'
): Promise< void > {
const logger = new Logger< LoggerAction >();

try {
Expand Down Expand Up @@ -46,11 +51,41 @@ export async function runCommand( siteFolder: string, format: 'table' | 'json' )
logger.reportSuccess( snapshotsMessage );
}

if ( format === 'table' ) {
const table = getSnapshotCliTable( snapshots );
if ( outputFormat === 'table' ) {
const colWidths = getColumnWidths( [ 0.4, 0.25, 0.175, 0.175 ] );
const table = new Table( {
head: [ __( 'URL' ), __( 'Site Name' ), __( 'Updated' ), __( 'Expires in' ) ],
wordWrap: true,
wrapOnWordBoundary: false,
colWidths,
style: {
head: [],
border: [],
},
} );

for ( const snapshot of snapshots ) {
const durationUntilExpiry = formatDurationUntilExpiry( snapshot.date );
const url = `https://${ snapshot.url }`;

table.push( [
{ href: url, content: url },
snapshot.name,
format( snapshot.date, 'yyyy-MM-dd HH:mm' ),
durationUntilExpiry,
] );
}

console.log( table.toString() );
} else {
console.log( JSON.stringify( getSnapshotCliJson( snapshots ), null, 2 ) );
const output = snapshots.map( ( snapshot ) => ( {
url: `https://${ snapshot.url }`,
name: snapshot.name,
date: format( snapshot.date, 'yyyy-MM-dd HH:mm' ),
expiresIn: formatDurationUntilExpiry( snapshot.date ),
} ) );

console.log( JSON.stringify( output, null, 2 ) );
}
} catch ( error ) {
if ( error instanceof LoggerError ) {
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/preview/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export async function runCommand(
logger.reportSuccess( __( 'Archive created' ) );

logger.reportStart( LoggerAction.UPLOAD, __( 'Uploading archive…' ) );
const wordpressVersion = await getWordPressVersion( siteFolder );
const wordpressVersion = getWordPressVersion( siteFolder );
const uploadResponse = await uploadArchive(
archivePath,
token.accessToken,
Expand Down
93 changes: 60 additions & 33 deletions cli/commands/site/list.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,94 @@
import os from 'node:os';
import { __, _n, sprintf } from '@wordpress/i18n';
import Table from 'cli-table3';
import { PreviewCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import { readAppdata, type SiteData } from 'cli/lib/appdata';
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import { getSiteUrl, readAppdata, type SiteData } from 'cli/lib/appdata';
import { connect, disconnect } from 'cli/lib/pm2-manager';
import { getColumnWidths, getPrettyPath } from 'cli/lib/utils';
import { isServerRunning } from 'cli/lib/wordpress-server-manager';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

interface SiteTable {
id: string;
interface SiteListEntry {
status: string;
name: string;
path: string;
url: string;
phpVersion: string;
}

function getSitesCliTable( sites: SiteData[] ) {
const table = new Table( {
head: [ __( 'Name' ), __( 'Path' ), __( 'ID' ), __( 'PHP' ) ],
style: {
head: [ 'cyan' ],
border: [ 'grey' ],
},
wordWrap: true,
wrapOnWordBoundary: false,
} );
async function getSiteListData( sites: SiteData[] ) {
const result: SiteListEntry[] = [];

sites.forEach( ( site ) => {
table.push( [ site.name, site.path, site.id, site.phpVersion ] );
} );
for await ( const site of sites ) {
const isOnline = await isServerRunning( site.id );
const status = isOnline ? '🟢 Online' : '🔴 Offline';
const url = getSiteUrl( site );

return table;
}
result.push( {
status,
name: site.name,
path: getPrettyPath( site.path ),
url,
phpVersion: site.phpVersion,
} );
}

function getSitesCliJson( sites: SiteData[] ): SiteTable[] {
return sites.map( ( site ) => ( {
id: site.id,
name: site.name,
path: site.path,
phpVersion: site.phpVersion,
} ) );
return result;
}

export async function runCommand( format: 'table' | 'json' ): Promise< void > {
const logger = new Logger< LoggerAction >();

try {
logger.reportStart( LoggerAction.LOAD, __( 'Loading sites…' ) );
logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading sites…' ) );
const appdata = await readAppdata();
const allSites = appdata.sites;

if ( allSites.length === 0 ) {
if ( appdata.sites.length === 0 ) {
logger.reportSuccess( __( 'No sites found' ) );
return;
}

const sitesMessage = sprintf(
_n( 'Found %d site', 'Found %d sites', allSites.length ),
allSites.length
_n( 'Found %d site', 'Found %d sites', appdata.sites.length ),
appdata.sites.length
);

logger.reportSuccess( sitesMessage );

logger.reportStart( LoggerAction.START_DAEMON, __( 'Connecting to process daemon...' ) );
await connect();
logger.reportSuccess( __( 'Connected to process daemon' ) );

const sitesData = await getSiteListData( appdata.sites );

if ( format === 'table' ) {
const table = getSitesCliTable( allSites );
const colWidths = getColumnWidths( [ 0.1, 0.2, 0.25, 0.4, 0.05 ] );

const table = new Table( {
head: [ __( 'Status' ), __( 'Name' ), __( 'Path' ), __( 'URL' ), __( 'PHP' ) ],
wordWrap: true,
wrapOnWordBoundary: false,
colWidths,
style: {
head: [],
border: [],
},
} );

table.push(
...sitesData.map( ( site ) => [
site.status,
site.name,
site.path,
{ href: new URL( site.url ).toString(), content: site.url },
site.phpVersion,
] )
);

console.log( table.toString() );
} else {
console.log( JSON.stringify( getSitesCliJson( allSites ), null, 2 ) );
console.log( JSON.stringify( sitesData, null, 2 ) );
}
} catch ( error ) {
if ( error instanceof LoggerError ) {
Expand All @@ -72,6 +97,8 @@ export async function runCommand( format: 'table' | 'json' ): Promise< void > {
const loggerError = new LoggerError( __( 'Failed to load sites' ), error );
logger.reportError( loggerError );
}
} finally {
disconnect();
}
}

Expand Down
7 changes: 3 additions & 4 deletions cli/commands/site/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ async function openSiteInBrowser( site: SiteData ) {

function logSiteDetails( site: SiteData ) {
const siteUrl = getSiteUrl( site );
console.log( __( 'Site URL: ' ), siteUrl );
console.log( __( 'Username: ' ), 'admin' );
console.log( __( 'Password: ' ), site.adminPassword );
console.log( __( 'Site URL:' ), siteUrl );
console.log( __( 'Admin Username:' ), 'admin' );
console.log( __( 'Admin Password:' ), site.adminPassword );
}

export async function runCommand( siteFolder: string, skipBrowser = false ): Promise< void > {
Expand Down Expand Up @@ -112,7 +112,6 @@ export async function runCommand( siteFolder: string, skipBrowser = false ): Pro
const loggerError = new LoggerError( __( 'Failed to start site infrastructure' ), error );
logger.reportError( loggerError );
}
process.exit( 1 );
} finally {
disconnect();
}
Expand Down
91 changes: 91 additions & 0 deletions cli/commands/site/status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { __, _n } from '@wordpress/i18n';
import CliTable3 from 'cli-table3';
import { getWordPressVersion } from 'common/lib/get-wordpress-version';
import { SiteCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import { getSiteByFolder, getSiteUrl } from 'cli/lib/appdata';
import { connect, disconnect } from 'cli/lib/pm2-manager';
import { getPrettyPath } from 'cli/lib/utils';
import { isServerRunning } from 'cli/lib/wordpress-server-manager';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

export async function runCommand( siteFolder: string, format: 'table' | 'json' ): Promise< void > {
const logger = new Logger< LoggerAction >();

try {
logger.reportStart( LoggerAction.LOAD_SITES, __( 'Loading site…' ) );
const site = await getSiteByFolder( siteFolder, false );
logger.reportSuccess( __( 'Site loaded' ) );

await connect();

const isOnline = Boolean( await isServerRunning( site.id ) );
const status = isOnline ? '🟢 Online' : '🔴 Offline';
const siteUrl = getSiteUrl( site );
const sitePath = getPrettyPath( site.path );
const wpVersion = getWordPressVersion( site.path );
const autoLoginUrl = new URL( siteUrl );
autoLoginUrl.pathname = `/studio-auto-login`;
autoLoginUrl.searchParams.set( 'redirect_to', `/wp-admin/` );

const siteData: { key: string; value: string | undefined; type?: string; hidden?: boolean }[] =
[
{ key: 'Site URL', value: new URL( siteUrl ).toString(), type: 'url' },
{ key: 'Auto Login URL', value: autoLoginUrl.toString(), type: 'url', hidden: ! isOnline },
{ key: 'Site Path', value: sitePath },
{ key: 'Status', value: status },
{ key: 'PHP Version', value: site.phpVersion },
{ key: 'WP Version', value: wpVersion },
{ key: 'Admin Username', value: 'admin' },
{ key: 'Admin Password', value: site.adminPassword },
].filter( ( { value, hidden } ) => value && ! hidden );

if ( format === 'table' ) {
const table = new CliTable3( {
wordWrap: true,
wrapOnWordBoundary: false,
style: {
head: [],
border: [],
},
} );

for ( const { key, value, type } of siteData ) {
table.push( [ key, type === 'url' ? { href: value, content: value } : value ] );
}

console.table( table.toString() );
} else {
const logData = Object.fromEntries( siteData.map( ( { key, value } ) => [ key, value ] ) );

console.log( JSON.stringify( logData, null, 2 ) );
}
} catch ( error ) {
if ( error instanceof LoggerError ) {
logger.reportError( error );
} else {
const loggerError = new LoggerError( __( 'Failed to load site' ), error );
logger.reportError( loggerError );
}
} finally {
disconnect();
}
}

export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'status',
describe: __( 'Get status of local site' ),
builder: ( yargs ) => {
return yargs.option( 'format', {
type: 'string',
choices: [ 'table', 'json' ],
default: 'table',
description: __( 'Output format' ),
} );
},
handler: async ( argv ) => {
await runCommand( argv.path, argv.format as 'table' | 'json' );
},
} );
};
Loading