Skip to content

Commit f403302

Browse files
committed
Merge remote-tracking branch 'origin/trunk' into add/persistent-cards-update-notification
# Conflicts: # apps/ui/src/components/sidebar-layout/index.tsx
2 parents aef7e7f + 41d3b5f commit f403302

99 files changed

Lines changed: 3345 additions & 2125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/build-php-cli-binaries.yml‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,11 @@ jobs:
9797

9898
steps:
9999
- name: Checkout Studio
100-
uses: actions/checkout@v7
100+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
101101

102102
- name: Checkout static-php-cli
103103
if: runner.os == 'macOS' || runner.os == 'Linux'
104-
uses: actions/checkout@v7
104+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
105105
with:
106106
repository: crazywhalecc/static-php-cli
107107
ref: ${{ env.SPC_REF }}
@@ -572,14 +572,14 @@ jobs:
572572
(Get-FileHash $archive -Algorithm SHA256).Hash.ToLower() | Set-Content -NoNewline "$archive.sha256"
573573
574574
- name: Upload PHP CLI artifact
575-
uses: actions/upload-artifact@v7
575+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
576576
with:
577577
name: php-${{ env.PHP_VERSION }}-cli-${{ matrix.target }}
578578
path: out/php-binaries/*
579579

580580
- name: Upload static-php-cli logs
581581
if: ${{ failure() && (runner.os == 'macOS' || runner.os == 'Linux') }}
582-
uses: actions/upload-artifact@v7
582+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
583583
with:
584584
name: spc-logs-${{ matrix.target }}
585585
path: .cache/static-php-cli/log/*.log
@@ -601,15 +601,15 @@ jobs:
601601
WPCOM_API_TOKEN: ${{ secrets.WPCOM_API_TOKEN }}
602602
steps:
603603
- name: Checkout Studio
604-
uses: actions/checkout@v7
604+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
605605

606606
- name: Setup Ruby
607607
uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0
608608
with:
609609
bundler-cache: true
610610

611611
- name: Download PHP CLI artifacts
612-
uses: actions/download-artifact@v8
612+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
613613
with:
614614
pattern: php-${{ env.PHP_VERSION }}-cli-*
615615
path: out/php-binaries

‎.github/workflows/check-php-cli-versions.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ jobs:
2323
GH_TOKEN: ${{ github.token }}
2424
steps:
2525
- name: Checkout Studio
26-
uses: actions/checkout@v7
26+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
2727

2828
- name: Setup Node
29-
uses: actions/setup-node@v7
29+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
3030
with:
3131
node-version-file: '.nvmrc'
3232

‎.github/workflows/gardening.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ jobs:
2626

2727
steps:
2828
- name: Checkout
29-
uses: actions/checkout@v7
29+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
3030

3131
- name: Setup Node
32-
uses: actions/setup-node@v7
32+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
3333
with:
3434
node-version-file: '.nvmrc'
3535

‎.github/workflows/stale-pr-notifications.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ jobs:
2323

2424
steps:
2525
- name: Checkout repository
26-
uses: actions/checkout@v7
26+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
2727

2828
- name: Setup Node.js
29-
uses: actions/setup-node@v7
29+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
3030
with:
3131
node-version-file: '.nvmrc'
3232

‎apps/cli/lib/snapshots.ts‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { HOUR_MS, DAY_MS, DEMO_SITE_EXPIRATION_DAYS } from '@studio/common/constants';
2-
import { Snapshot } from '@studio/common/types/snapshot';
32
import { __ } from '@wordpress/i18n';
43
import { addDays, addHours, DurationUnit, formatDuration, intervalToDuration } from 'date-fns';
54

@@ -11,11 +10,7 @@ export {
1110
pruneExpiredOrphanedSnapshots,
1211
} from 'cli/lib/cli-config/snapshots';
1312

14-
export function isSnapshotExpired( snapshot: Snapshot ) {
15-
const now = new Date();
16-
const endDate = addDays( snapshot.date, DEMO_SITE_EXPIRATION_DAYS );
17-
return endDate < now;
18-
}
13+
export { isSnapshotExpired } from '@studio/common/lib/snapshots';
1914

2015
export function formatDurationUntilExpiry( lastUpdatedAt: number ) {
2116
const now = new Date();

‎apps/local/src/index.ts‎

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ import {
3434
import {
3535
arePathsEqual,
3636
isEmptyDir,
37+
isPathWithin,
3738
isWordPressDirectory,
3839
recursiveCopyDirectory,
3940
} from '@studio/common/lib/fs-utils';
4041
import { generateNumberedName, generateSiteName } from '@studio/common/lib/generate-site-name';
42+
import { importIpcEventSchema } from '@studio/common/lib/import-export-events';
4143
import { isErrnoException } from '@studio/common/lib/is-errno-exception';
4244
import { getAuthenticationUrl } from '@studio/common/lib/oauth';
4345
import { decodePassword } from '@studio/common/lib/passwords';
@@ -185,9 +187,7 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
185187
const cliRunner = createCliRunner( { cliBinary, nodeBinary } );
186188
const execute = cliRunner.executeCliCommand;
187189

188-
// --- Server-Sent Events: one stream carries every run's output ------------
189-
// The web connector expects the same envelope on both channels: agent run
190-
// events on `agent`, session-placement updates on `placement`.
190+
// --- Server-Sent Events: one stream carries live UI updates ----------------
191191
const sseClients = new Set< Response >();
192192
function sseSend( message: { channel: string; payload: unknown } ): void {
193193
if ( sseClients.size === 0 ) {
@@ -526,7 +526,15 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
526526

527527
api.post( '/paths/compare', ( req: Request, res: Response ) => {
528528
const { path1, path2 } = req.body as { path1?: string; path2?: string };
529-
res.json( { equal: !! path1 && !! path2 && arePathsEqual( path1, path2 ) } );
529+
// Confine both operands to `sitesRoot`: nothing outside it can be a site, and
530+
// this keeps untrusted input from reaching statSync (in arePathsEqual).
531+
const equal =
532+
!! path1 &&
533+
!! path2 &&
534+
isPathWithin( sitesRoot, path1 ) &&
535+
isPathWithin( sitesRoot, path2 ) &&
536+
arePathsEqual( path1, path2 );
537+
res.json( { equal } );
530538
} );
531539

532540
api.post(
@@ -837,9 +845,8 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
837845
} )
838846
);
839847

840-
// Import a backup archive into an already-created site — the same CLI `import`
841-
// the desktop forks. The archive path is normally an upload from `/uploads`,
842-
// which is cleaned up afterwards.
848+
// Import a backup into an already-created site. Uploaded files are cleaned up
849+
// after each attempt, so a retry must upload the selected File again.
843850
api.post(
844851
'/sites/:id/import',
845852
asyncHandler( async ( req: Request, res: Response ) => {
@@ -855,8 +862,18 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
855862
}
856863
try {
857864
await new Promise< void >( ( resolve, reject ) => {
858-
const [ emitter ] = execute( [ 'import', '--path', site.path, archivePath ], {
859-
output: 'capture',
865+
const [ emitter ] = execute(
866+
[ 'import', '--path', site.path, archivePath, '--start-server' ],
867+
{ output: 'capture' }
868+
);
869+
emitter.on( 'data', ( { data } ) => {
870+
const parsed = importIpcEventSchema.safeParse( data );
871+
if ( parsed.success ) {
872+
sseSend( {
873+
channel: 'import',
874+
payload: { siteId: site.id, event: parsed.data.event },
875+
} );
876+
}
860877
} );
861878
emitter.on( 'success', () => resolve() );
862879
emitter.on( 'failure', ( { error } ) => reject( error ) );
@@ -867,12 +884,14 @@ export async function startLocalServer( options: LocalServerOptions ): Promise<
867884
// direct child of the OS temp dir. Resolve first so `..` in the
868885
// supplied path can't escape the temp root and delete elsewhere.
869886
const uploadDir = path.dirname( path.resolve( archivePath ) );
870-
if ( path.dirname( uploadDir ) === path.resolve( os.tmpdir() ) ) {
887+
if (
888+
path.dirname( uploadDir ) === path.resolve( os.tmpdir() ) &&
889+
path.basename( uploadDir ).startsWith( 'studio-upload-' )
890+
) {
871891
rm( uploadDir, { recursive: true, force: true }, () => undefined );
872892
}
873893
}
874-
const imported = ( await listSites( execute ) ).find( ( s ) => s.id === req.params.id );
875-
res.json( toSiteDetails( imported ?? site ) );
894+
res.status( 204 ).end();
876895
} )
877896
);
878897

‎apps/studio/e2e/page-objects/add-site-modal.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type Page } from '@playwright/test';
2-
import { ACCEPTED_IMPORT_FILE_TYPES } from '@studio/common/constants';
2+
import { ACCEPTED_ADD_SITE_FILE_TYPES } from '@studio/common/constants';
33
import { type SiteRuntime } from '@studio/common/lib/site-runtime';
44
import SiteForm from './site-form';
55

@@ -33,7 +33,7 @@ export default class AddSiteModal {
3333
}
3434

3535
get backupFileInput() {
36-
const fileTypes = ACCEPTED_IMPORT_FILE_TYPES.join( ',' );
36+
const fileTypes = ACCEPTED_ADD_SITE_FILE_TYPES.join( ',' );
3737
return this.page.locator( `input[type="file"][accept="${ fileTypes }"]` );
3838
}
3939

‎apps/studio/src/components/add-site-with-blueprint-button.tsx‎

Lines changed: 0 additions & 37 deletions
This file was deleted.

‎apps/studio/src/components/content-tab-import-export.tsx‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ACCEPTED_IMPORT_FILE_TYPES } from '@studio/common/constants';
2+
import { isSupportedBackupFilename } from '@studio/common/lib/backup-files';
23
import { speak } from '@wordpress/a11y';
34
import { Notice } from '@wordpress/components';
45
import { createInterpolateElement } from '@wordpress/element';
@@ -161,11 +162,7 @@ const InitialImportButton = ( {
161162
};
162163

163164
const isValidImportFile = ( file: File ): boolean => {
164-
const fileName = file.name.toLowerCase();
165-
return (
166-
ACCEPTED_IMPORT_FILE_TYPES.some( ( ext ) => fileName.endsWith( ext ) ) ||
167-
fileName.endsWith( '.sql' )
168-
);
165+
return isSupportedBackupFilename( file.name );
169166
};
170167

171168
const ImportSite = ( {
@@ -329,7 +326,7 @@ const ImportSite = ( {
329326
className="hidden"
330327
type="file"
331328
data-testid="backup-file"
332-
accept={ `${ ACCEPTED_IMPORT_FILE_TYPES.join( ',' ) },.sql` }
329+
accept={ ACCEPTED_IMPORT_FILE_TYPES.join( ',' ) }
333330
onChange={ onFileSelected }
334331
/>
335332
</div>

‎apps/studio/src/components/tests/content-tab-import-export.test.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ describe( 'ContentTabImportExport Import', () => {
9999
} );
100100

101101
const dropZone = screen.getByText( /Drag a file here, or click to select a file/i );
102-
const file = new File( [ 'file contents' ], 'backup.zip', { type: 'application/zip' } );
102+
const file = new File( [ 'file contents' ], 'backup.sql', { type: 'application/sql' } );
103103

104104
fireEvent.dragEnter( dropZone );
105105
fireEvent.dragOver( dropZone );
@@ -120,7 +120,7 @@ describe( 'ContentTabImportExport Import', () => {
120120
const fileInput = screen.getByTestId( 'backup-file' );
121121
expect( fileInput ).toBeInTheDocument();
122122

123-
const file = new File( [ 'file contents' ], 'backup.zip', { type: 'application/zip' } );
123+
const file = new File( [ 'file contents' ], 'backup.sql', { type: 'application/sql' } );
124124

125125
await userEvent.upload( fileInput, file );
126126

0 commit comments

Comments
 (0)