-
Notifications
You must be signed in to change notification settings - Fork 86
Share connected WP.com sites between Studio app and CLI #3227
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
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
b54464a
Move connected WP.com sites to cli.json and teach CLI push/pull to re…
youknowriad 4556faa
Bump APP_CONFIG_VERSION to 2 and prompt upgrade on mismatch
youknowriad 7a93c7f
Move `connectedWpcomSites` to shared.json
fredrikekelund d622fae
Simplify logic for updating `connectedWpcomSites`
fredrikekelund 9a98fa6
Minor tweaks
fredrikekelund dd14d49
Fix
fredrikekelund 123baf0
Address review comments
fredrikekelund 7c5bd1f
Merge branch 'trunk' into claude/festive-feynman-fa1aa3
fredrikekelund 60ab166
Merge branch 'trunk' into claude/festive-feynman-fa1aa3
fredrikekelund 5ab0970
Upgrade ESLint dependencies
fredrikekelund 3762e7c
Split `lint` npm script per workspace to save memory
fredrikekelund a3772ad
Fix lint error
fredrikekelund 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
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { tool } from '@anthropic-ai/claude-agent-sdk'; | ||
| import { getConnectedWpcomSitesForLocalSite } from '@studio/common/lib/connected-sites'; | ||
| import { z } from 'zod/v4'; | ||
| import { errorResult, resolveSite, textResult } from './utils'; | ||
|
|
||
| export const listConnectedRemoteSitesTool = tool( | ||
| 'site_connected_remote_sites', | ||
| 'Lists the WordPress.com sites that are already connected/attached to a local Studio site. ' + | ||
| 'Use this before calling site_push to determine how to ask the user which remote site to push to. ' + | ||
| 'Returns an empty array when the user has no connections for that local site.', | ||
| { | ||
| nameOrPath: z.string().describe( 'The local site name or file system path' ), | ||
| }, | ||
| async ( args ) => { | ||
| try { | ||
| const site = await resolveSite( args.nameOrPath ); | ||
| const connected = await getConnectedWpcomSitesForLocalSite( site.id ); | ||
| const summary = connected.map( ( s ) => ( { | ||
| id: s.id, | ||
| name: s.name, | ||
| url: s.url, | ||
| isStaging: s.isStaging, | ||
| syncSupport: s.syncSupport, | ||
| lastPushTimestamp: s.lastPushTimestamp, | ||
| lastPullTimestamp: s.lastPullTimestamp, | ||
| } ) ); | ||
| return textResult( JSON.stringify( summary, null, 2 ) ); | ||
| } catch ( error ) { | ||
| return errorResult( | ||
| `Failed to list connected remote sites: ${ | ||
| error instanceof Error ? error.message : String( error ) | ||
| }` | ||
| ); | ||
| } | ||
| } | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
apps/cli/migrations/05-migrate-connected-sites-to-shared.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| /** | ||
| * Copies `connectedWpcomSites` from the Desktop-owned `app.json` into the | ||
| * shared `shared.json` so the CLI can read and write it through the helpers | ||
| * in `tools/common/lib/connected-sites.ts`. | ||
| * | ||
| * Runs only when app.json carries the legacy field AND shared.json does not | ||
| * already have it — the CLI must never modify app.json. The Studio app runs | ||
| * its own migration to strip the field once it boots; until then the legacy | ||
| * copy in app.json is harmless because both the new Studio and the new CLI | ||
| * read connections from shared.json. | ||
| */ | ||
|
|
||
| import fs from 'node:fs'; | ||
| import { | ||
| lockSharedConfig, | ||
| readSharedConfig, | ||
| saveSharedConfig, | ||
| unlockSharedConfig, | ||
| } from '@studio/common/lib/shared-config'; | ||
| import { getAppConfigPath } from '@studio/common/lib/well-known-paths'; | ||
| import { syncSiteSchema } from '@studio/common/types/sync'; | ||
| import { readFile } from 'atomically'; | ||
| import { z } from 'zod'; | ||
| import type { Migration } from '@studio/common/lib/migration'; | ||
|
|
||
| const appConnectedShapeSchema = z | ||
| .object( { | ||
| connectedWpcomSites: z.record( z.string(), z.array( syncSiteSchema ) ).optional(), | ||
| } ) | ||
| .loose(); | ||
|
|
||
| async function readAppConnectedSites(): Promise< Record< | ||
| string, | ||
| z.infer< typeof syncSiteSchema >[] | ||
| > | null > { | ||
| const appPath = getAppConfigPath(); | ||
| if ( ! fs.existsSync( appPath ) ) { | ||
| return null; | ||
| } | ||
| try { | ||
| const raw = await readFile( appPath, { encoding: 'utf8' } ); | ||
| const parsed = appConnectedShapeSchema.safeParse( JSON.parse( raw ) ); | ||
| if ( ! parsed.success ) { | ||
| return null; | ||
| } | ||
| return parsed.data.connectedWpcomSites ?? null; | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export const migrateConnectedSitesToShared: Migration = { | ||
| async needsToRun() { | ||
| const appConnected = await readAppConnectedSites(); | ||
| if ( ! appConnected || Object.keys( appConnected ).length === 0 ) { | ||
| return false; | ||
| } | ||
| const shared = await readSharedConfig().catch( () => null ); | ||
| return ! shared?.connectedWpcomSites; | ||
| }, | ||
|
|
||
| async run() { | ||
| const appConnected = await readAppConnectedSites(); | ||
| if ( ! appConnected || Object.keys( appConnected ).length === 0 ) { | ||
| return; | ||
| } | ||
| try { | ||
| await lockSharedConfig(); | ||
| const shared = await readSharedConfig(); | ||
| if ( shared.connectedWpcomSites ) { | ||
| return; | ||
| } | ||
| await saveSharedConfig( { ...shared, connectedWpcomSites: appConnected } ); | ||
| } finally { | ||
| await unlockSharedConfig(); | ||
| } | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
apps/studio/src/migrations/04-migrate-connected-sites-to-shared.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /** | ||
| * Moves `connectedWpcomSites` from `app.json` into `shared.json` so both the | ||
| * Studio app and the Studio CLI can read and write it through the helpers in | ||
| * `tools/common/lib/connected-sites.ts`. | ||
| * | ||
| * Runs whenever app.json still carries the legacy `connectedWpcomSites` | ||
| * field. The copy into shared.json is skipped if shared.json already has the | ||
| * field (e.g. the CLI's own migration ran first), but the field is always | ||
| * stripped from app.json — once both halves of Studio agree that | ||
| * connections live in shared.json there's no reason to keep the stale copy | ||
| * around. Data loss is bounded: an older Studio that boots after this | ||
| * migration will simply re-add an empty `connectedWpcomSites` to app.json, | ||
| * which the next run of this migration will clean up. | ||
| */ | ||
|
|
||
| import fs from 'node:fs'; | ||
| import { | ||
| lockSharedConfig, | ||
| readSharedConfig, | ||
| saveSharedConfig, | ||
| unlockSharedConfig, | ||
| } from '@studio/common/lib/shared-config'; | ||
| import { getAppConfigPath } from '@studio/common/lib/well-known-paths'; | ||
| import { syncSiteSchema } from '@studio/common/types/sync'; | ||
| import { readFile, writeFile } from 'atomically'; | ||
| import { z } from 'zod'; | ||
| import { lockAppdata, unlockAppdata } from 'src/storage/user-data'; | ||
| import type { Migration } from '@studio/common/lib/migration'; | ||
|
|
||
| const appConnectedShapeSchema = z | ||
| .object( { | ||
| connectedWpcomSites: z.record( z.string(), z.array( syncSiteSchema ) ).optional(), | ||
| } ) | ||
| .loose(); | ||
|
|
||
| async function readAppConfigWithConnectedWpcomSites() { | ||
| const appPath = getAppConfigPath(); | ||
| if ( ! fs.existsSync( appPath ) ) { | ||
| return null; | ||
| } | ||
| try { | ||
| const raw = await readFile( appPath, { encoding: 'utf8' } ); | ||
| const parsed = JSON.parse( raw ); | ||
| return appConnectedShapeSchema.parse( parsed ); | ||
| } catch { | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| export const migrateConnectedSitesToShared: Migration = { | ||
| async needsToRun() { | ||
| const parsed = await readAppConfigWithConnectedWpcomSites(); | ||
| return !! parsed?.connectedWpcomSites && Object.keys( parsed.connectedWpcomSites ).length > 0; | ||
| }, | ||
| async run() { | ||
| const parsed = await readAppConfigWithConnectedWpcomSites(); | ||
| if ( ! parsed ) { | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await lockSharedConfig(); | ||
| const shared = await readSharedConfig(); | ||
| if ( ! shared.connectedWpcomSites ) { | ||
| await saveSharedConfig( { | ||
| ...shared, | ||
| connectedWpcomSites: parsed.connectedWpcomSites, | ||
| } ); | ||
| } | ||
| } finally { | ||
| await unlockSharedConfig(); | ||
| } | ||
|
|
||
| try { | ||
| await lockAppdata(); | ||
| const { connectedWpcomSites: _legacy, ...rest } = parsed; | ||
| await writeFile( getAppConfigPath(), JSON.stringify( rest, null, 2 ) + '\n', { | ||
| encoding: 'utf8', | ||
| } ); | ||
| } finally { | ||
| await unlockAppdata(); | ||
| } | ||
| }, | ||
| }; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is better in the push tool instead of the system prompt to keep the system prompt leaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't really evaluate how this is integrated into Studio Code – I focused almost exclusively on the data storage. The changes in this file come from the initial commit to this PR, b54464a.