Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
cdb311a
Read site data from CLI instead of appdata (#2701)
bcotrim Mar 11, 2026
9e72f3f
Merge remote-tracking branch 'origin/trunk' into stu-1350-decoupled-c…
bcotrim Mar 12, 2026
8daf4a9
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 13, 2026
b2ce79e
Move CLI site data to dedicated config file (#2731)
bcotrim Mar 13, 2026
7748599
Merge remote-tracking branch 'origin/trunk' into stu-1350-decoupled-c…
bcotrim Mar 13, 2026
a87533b
Merge trunk into stu-1350-decoupled-config-dev
bcotrim Mar 13, 2026
b4b6e42
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 13, 2026
304a3da
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 16, 2026
163b971
Fix AI tools test mocks to use cli-config instead of appdata
bcotrim Mar 16, 2026
147bd6d
Merge trunk and resolve conflicts for cli-config decoupling
bcotrim Mar 17, 2026
ae5be29
Apply decoupled config strategy to preview sites (#2807)
bcotrim Mar 17, 2026
cfa000f
Migrate appdata to ~/.studio/appdata.json with extensible migration f…
bcotrim Mar 17, 2026
9d5ead9
Merge stu-1350-decoupled-config-dev and resolve conflicts
bcotrim Mar 17, 2026
12b98f1
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 18, 2026
aeb2e3a
Move appdata migration from CLI to Studio Desktop
bcotrim Mar 18, 2026
1081ea7
Wire auth and locale to shared.json, AI settings to cli.json (#2821)
bcotrim Mar 18, 2026
7a34728
Merge remote-tracking branch 'origin/stu-1350-decoupled-config-dev' i…
bcotrim Mar 18, 2026
a6a3838
Rewrite appdata migration to split into shared.json, cli.json, and ap…
bcotrim Mar 18, 2026
63b8b8e
trigger ci
bcotrim Mar 18, 2026
46b0358
Ensure .studio directory exists before lockfile and fix import order
bcotrim Mar 19, 2026
4dd6080
Fix readFile mock type to return Buffer instead of string
bcotrim Mar 19, 2026
852cdc9
Refactor appdata to store only Desktop-specific state with sites as R…
bcotrim Mar 19, 2026
92d5be2
Address PR feedback: rename to app.json and siteMetadata, use zod par…
bcotrim Mar 19, 2026
29fa739
Fix prettier formatting in user-data test
bcotrim Mar 19, 2026
a317a82
Fix e2e migration by respecting E2E_APP_DATA_PATH in getOldAppdataPath
bcotrim Mar 19, 2026
a1b7c38
Merge trunk and resolve conflicts in _events.ts and user-settings ipc…
bcotrim Mar 19, 2026
6db60dd
Merge remote-tracking branch 'origin/stu-1350-decoupled-config-dev' i…
bcotrim Mar 19, 2026
0bd4c58
new migration interface
bcotrim Mar 20, 2026
f8e9258
studio migrations
bcotrim Mar 20, 2026
aca873d
studio migrations
bcotrim Mar 20, 2026
ef52842
cli migrations
bcotrim Mar 20, 2026
23d18ed
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 20, 2026
cea5b56
Rename appdata references, centralize config paths and lockfile const…
bcotrim Mar 20, 2026
f211f3a
Isolate e2e config directory to fix test failures
bcotrim Mar 20, 2026
6e95cb5
Merge branch 'trunk' into stu-1350-decoupled-config-dev
bcotrim Mar 20, 2026
e58b6d4
Merge branch 'stu-1350-decoupled-config-dev' into stu-1350-decoupled-…
bcotrim Mar 20, 2026
ec6f7e1
Move site metadata cleanup to SiteServer, fix typecheck and test fail…
bcotrim Mar 23, 2026
8c18041
HTTPS certificate migration
fredrikekelund Mar 23, 2026
205f8e6
Rename file
fredrikekelund Mar 23, 2026
9cd3740
Add APP_CONFIG_LOCKFILE_NAME constant and fix test failures
bcotrim Mar 23, 2026
c19fc13
Merge branch 'stu-1350-decoupled-config-dev-v3' into stu-1350-decoupl…
fredrikekelund Mar 23, 2026
9789cb4
Merge branch 'trunk' into stu-1350-decoupled-config-dev-v4
fredrikekelund Mar 23, 2026
94946a0
Fix merge conflict
fredrikekelund Mar 23, 2026
462a336
Load sites from `SiteServer.getAll()`
fredrikekelund Mar 23, 2026
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/cli/lib/certificate-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import fs from 'node:fs';
import path from 'node:path';
import { domainToASCII } from 'node:url';
import { promisify } from 'node:util';
import { getCertificatesPath } from '@studio/common/lib/config-paths';
import sudo from '@vscode/sudo-prompt';
import { __ } from '@wordpress/i18n';
import forge from 'node-forge';
import { getAppdataDirectory } from 'cli/lib/server-files';

const execFilePromise = promisify( execFile );

Expand Down Expand Up @@ -67,7 +67,7 @@ function createNameConstraintsExtension( domains: string[] ) {
const CA_NAME = 'WordPress Studio CA';
const CA_CERT_VALIDITY_DAYS = 3650; // 10 years
const SITE_CERT_VALIDITY_DAYS = 825; // a little over 2 years
const CERT_DIRECTORY = path.join( getAppdataDirectory(), 'certificates' );
const CERT_DIRECTORY = getCertificatesPath();
const CA_CERT_PATH = path.join( CERT_DIRECTORY, 'studio-ca.crt' );
const CA_KEY_PATH = path.join( CERT_DIRECTORY, 'studio-ca.key' );

Expand Down
14 changes: 7 additions & 7 deletions apps/studio/src/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ export async function installWordPressSkills(
export async function getWordPressSkillsStatusAllSites(
_event: IpcMainInvokeEvent
): Promise< SkillStatus[] > {
const { sites } = await loadUserData();
const sites = SiteServer.getAll();
if ( ! sites.length ) {
return BUNDLED_SKILLS.map( ( skill ) => ( { ...skill, installed: false } ) );
}
const allSiteStatuses = await Promise.all(
sites.map( ( site ) => getSkillsStatus( site.path ) )
sites.map( ( site ) => getSkillsStatus( site.details.path ) )
);
return BUNDLED_SKILLS.map( ( skill ) => ( {
...skill,
Expand All @@ -222,14 +222,14 @@ export async function installWordPressSkillsToAllSites(
_event: IpcMainInvokeEvent,
options?: { skillId?: string; overwrite?: boolean }
): Promise< void > {
const { sites } = await loadUserData();
const sites = SiteServer.getAll();
const overwrite = options?.overwrite ?? false;
const bundledPath = getAiInstructionsPath();
const tasks = sites.flatMap( ( site ) =>
options?.skillId
? [ installSkillToSite( site.path, bundledPath, options.skillId, overwrite ) ]
? [ installSkillToSite( site.details.path, bundledPath, options.skillId, overwrite ) ]
: BUNDLED_SKILLS.map( ( skill ) =>
installSkillToSite( site.path, bundledPath, skill.id, overwrite )
installSkillToSite( site.details.path, bundledPath, skill.id, overwrite )
)
);
const results = await Promise.allSettled( tasks );
Expand All @@ -244,8 +244,8 @@ export async function removeWordPressSkillFromAllSites(
_event: IpcMainInvokeEvent,
skillId: string
): Promise< void > {
const { sites } = await loadUserData();
const tasks = sites.map( ( site ) => removeSkillFromSite( site.path, skillId ) );
const sites = SiteServer.getAll();
const tasks = sites.map( ( site ) => removeSkillFromSite( site.details.path, skillId ) );
const results = await Promise.allSettled( tasks );
results.forEach( ( result ) => {
if ( result.status === 'rejected' ) {
Expand Down
5 changes: 2 additions & 3 deletions apps/studio/src/lib/certificate-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import fs from 'node:fs';
import path from 'node:path';
import { promisify } from 'node:util';
import * as Sentry from '@sentry/electron/main';
import { getCertificatesPath } from '@studio/common/lib/config-paths';
import sudo from '@vscode/sudo-prompt';
import { getUserDataCertificatesPath } from 'src/storage/paths';

const execFilePromise = promisify( execFile );

// Certificate configuration
const CERT_DIRECTORY = getUserDataCertificatesPath();
const CA_CERT_PATH = path.join( CERT_DIRECTORY, 'studio-ca.crt' );
const CA_CERT_PATH = path.join( getCertificatesPath(), 'studio-ca.crt' );

export async function openCertificate() {
shell.showItemInFolder( CA_CERT_PATH );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,6 @@ import { BackupContents, BackupArchiveInfo } from 'src/lib/import-export/import/
import { Validator } from 'src/lib/import-export/import/validators/validator';
import type { Stats } from 'fs';

vi.mock( 'src/storage/paths', () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/path/to/app/appData/App Name' ),
getUserDataCertificatesPath: vi
.fn()
.mockReturnValue( '/path/to/app/appData/App Name/certificates' ),
} ) );
vi.mock( 'src/lib/import-export/import/handlers/backup-handler-factory' );
vi.mock( 'fs/promises', () => ( {
default: {
Expand Down
30 changes: 30 additions & 0 deletions apps/studio/src/migrations/03-copy-https-certs-to-well-known.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Migrates the `certificates` directory from the platform-specific Electron location to a
* well-known location at `~/.studio/certificates`.
*
* The old directory is intentionally not deleted. It'll be cleaned up in a future migration.
*/

import fs from 'node:fs';
import { getCertificatesPath } from '@studio/common/lib/config-paths';
import { getOldUserDataCertificatesPath } from 'src/storage/paths';
import type { Migration } from '@studio/common/lib/migration';

const NEW_CERT_DIR = getCertificatesPath();
const OLD_CERT_DIR = getOldUserDataCertificatesPath();

export const copyHttpsCertsToWellKnown: Migration = {
needsToRun: async () => {
return fs.existsSync( OLD_CERT_DIR ) && ! fs.existsSync( NEW_CERT_DIR );
},
run: async () => {
await fs.promises.mkdir( NEW_CERT_DIR, { recursive: true } );

if ( fs.existsSync( OLD_CERT_DIR ) ) {
await fs.promises.cp( OLD_CERT_DIR, NEW_CERT_DIR, {
recursive: true,
verbatimSymlinks: true,
} );
}
},
};
2 changes: 2 additions & 0 deletions apps/studio/src/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { migrateFromWpNowFolder } from './00-migrate-from-wp-now-folder';
import { renameLaunchUniquesStat } from './01-rename-launch-uniques-stat';
import { migrateAppConfig } from './02-migrate-to-split-config';
import { copyHttpsCertsToWellKnown } from './03-copy-https-certs-to-well-known';
import type { Migration } from '@studio/common/lib/migration';

export const migrations: Migration[] = [
migrateAppConfig,
migrateFromWpNowFolder,
renameLaunchUniquesStat,
copyHttpsCertsToWellKnown,
];
2 changes: 1 addition & 1 deletion apps/studio/src/storage/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function getServerFilesPath(): string {
return path.join( getAppDataPath(), getAppName(), 'server-files' );
}

export function getUserDataCertificatesPath(): string {
export function getOldUserDataCertificatesPath(): string {
return path.join( getAppDataPath(), getAppName(), 'certificates' );
}

Expand Down
10 changes: 0 additions & 10 deletions apps/studio/src/tests/index.test.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

src/storage/paths is already mocked globally in apps/studio/vitest.setup.ts

Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,6 @@ vi.mock( 'atomically', () => ( {
readFile: vi.fn().mockResolvedValue( Buffer.from( JSON.stringify( { sites: [] } ) ) ),
writeFile: vi.fn(),
} ) );
vi.mock( 'src/storage/paths', () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/mock/resources' ),
getUserDataFilePath: vi.fn().mockReturnValue( '/mock/userdata.json' ),
getUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getServerFilesPath: vi.fn().mockReturnValue( '/mock/server/files' ),
getCliPath: vi.fn().mockReturnValue( '/mock/cli/path' ),
getBundledNodeBinaryPath: vi.fn().mockReturnValue( '/mock/node/binary' ),
getSiteThumbnailPath: vi.fn().mockReturnValue( '/mock/thumbnail.png' ),
DEFAULT_SITE_PATH: '/mock/default/site/path',
} ) );
vi.mock( 'src/modules/cli/lib/execute-command', () => {
const mockEventEmitter = {
on: vi.fn().mockImplementation( ( event: string, callback: ( ...args: any[] ) => void ) => {
Expand Down
10 changes: 0 additions & 10 deletions apps/studio/src/tests/ipc-handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@ vi.mock( '@sentry/electron/main', () => ( {
captureException: vi.fn(),
captureMessage: vi.fn(),
} ) );
vi.mock( 'src/storage/paths', () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/mock/resources' ),
getUserDataFilePath: vi.fn().mockReturnValue( '/mock/userdata.json' ),
getUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getServerFilesPath: vi.fn().mockReturnValue( '/mock/server/files' ),
getCliPath: vi.fn().mockReturnValue( '/mock/cli/path' ),
getBundledNodeBinaryPath: vi.fn().mockReturnValue( '/mock/node/binary' ),
getSiteThumbnailPath: vi.fn().mockReturnValue( '/mock/thumbnail.png' ),
DEFAULT_SITE_PATH: '/mock/default/site/path',
} ) );
vi.mock( 'src/site-server' );
vi.mock( 'src/lib/wordpress-setup', () => ( {
setupWordPressFilesOnly: vi.fn().mockResolvedValue( undefined ),
Expand Down
10 changes: 0 additions & 10 deletions apps/studio/src/tests/main-window.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,6 @@ vi.mock( 'atomically' );
vi.mock( 'src/lib/app-globals', () => ( {
saveWindowBounds: vi.fn(),
} ) );
vi.mock( 'src/storage/paths', () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/mock/resources' ),
getUserDataFilePath: vi.fn().mockReturnValue( '/mock/userdata.json' ),
getUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getServerFilesPath: vi.fn().mockReturnValue( '/mock/server/files' ),
getCliPath: vi.fn().mockReturnValue( '/mock/cli/path' ),
getBundledNodeBinaryPath: vi.fn().mockReturnValue( '/mock/node/binary' ),
getSiteThumbnailPath: vi.fn().mockReturnValue( '/mock/thumbnail.png' ),
DEFAULT_SITE_PATH: '/mock/default/site/path',
} ) );

// Create a simpler mock that tracks event handlers
const mockEventHandlers = new Map< string, ( ( ...args: any[] ) => void )[] >();
Expand Down
10 changes: 0 additions & 10 deletions apps/studio/src/tests/open-file-in-ide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,6 @@ vi.mock( '@sentry/electron/main', () => ( {
captureException: vi.fn(),
captureMessage: vi.fn(),
} ) );
vi.mock( 'src/storage/paths', () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/mock/resources' ),
getUserDataFilePath: vi.fn().mockReturnValue( '/mock/userdata.json' ),
getUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getServerFilesPath: vi.fn().mockReturnValue( '/mock/server/files' ),
getCliPath: vi.fn().mockReturnValue( '/mock/cli/path' ),
getBundledNodeBinaryPath: vi.fn().mockReturnValue( '/mock/node/binary' ),
getSiteThumbnailPath: vi.fn().mockReturnValue( '/mock/thumbnail.png' ),
DEFAULT_SITE_PATH: '/mock/default/site/path',
} ) );
vi.mock( 'src/site-server' );
vi.mock( 'src/lib/is-installed' );
vi.mock( 'src/lib/shell-open-external-wrapper' );
Expand Down
11 changes: 8 additions & 3 deletions apps/studio/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,18 @@ vi.mock( 'electron', () => {
};
} );

vi.mock( 'src/storage/paths', () => ( {
vi.mock( import( './src/storage/paths' ), () => ( {
getResourcesPath: vi.fn().mockReturnValue( '/mock/resources' ),
getUserDataFilePath: vi.fn().mockReturnValue( '/mock/userdata.json' ),
getUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getUserDataLockFilePath: vi.fn().mockReturnValue( '/mock/userdata.json.lock' ),
getOldUserDataCertificatesPath: vi.fn().mockReturnValue( '/mock/certificates' ),
getServerFilesPath: vi.fn().mockReturnValue( '/mock/server/files' ),
getCliPath: vi.fn().mockReturnValue( '/mock/cli/path' ),
getBundledNodeBinaryPath: vi.fn().mockReturnValue( '/mock/node/binary' ),
getSiteThumbnailPath: vi.fn().mockReturnValue( '/mock/thumbnail.png' ),
DEFAULT_SITE_PATH: '/mock/default/site/path',
} ) );


vi.mock( 'lockfile', () => {
const lock = vi.fn( ( file, options, callback ) => callback( null ) );
const unlock = vi.fn( ( file, callback ) => callback( null ) );
Expand Down
5 changes: 4 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ export default defineConfig(
],
'import-x/no-named-as-default-member': 'off',
// @wp-playground/blueprints ships blueprint-schema-validator outside its package.json exports map
'import-x/no-unresolved': [ 'error', { ignore: [ '@wp-playground/blueprints/blueprint-schema-validator' ] } ],
'import-x/no-unresolved': [
'error',
{ ignore: [ '@wp-playground/blueprints/blueprint-schema-validator' ] },
],
'import-x/order': [
'error',
{
Expand Down
4 changes: 4 additions & 0 deletions tools/common/lib/config-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ export function getCliConfigPath(): string {
}
return path.join( getConfigDirectory(), 'cli.json' );
}

export function getCertificatesPath(): string {
return path.join( getConfigDirectory(), 'certificates' );
}
Loading