refactor(services): move org-data VFS ownership from apex-testing to services - W-23596477 - #7912
refactor(services): move org-data VFS ownership from apex-testing to services - W-23596477#7912peternhale wants to merge 3 commits into
Conversation
…services - W-23596477 Migrate the org-data virtual filesystem (scheme sf-org-data) so services owns the provider, scheme registration, and org lifecycle, while apex-testing consumes it through the FsService typed API as a side effect of test discovery. Services now owns: - orgVfs/ (provider, decoration provider, URI bijection, org lifecycle reconcile/reap) - FsService org-data write API (writeOrgData/createOrgDataDir/deleteOrgData/clearOrgData) via a shared onOrgDataProvider helper - fileSystemProviderRegistry + shared closeMatchingTabs tab-reaper (vscode/tabs.ts), exposed on the services API surface apex-testing now: - writes discovered classes through api.services.FsService.* (no provider of its own) - centralizes the class<->URI bijection in discoveryVfs/apexTestingClassUri.ts - deletes its bespoke VFS provider, decoration provider, and provider-ref tag Provider/editor surface stays read-only (isReadonly + FileSystemProvider throws); owners populate via the internal *Internal methods, bypassing the read-only contract.
|
Heads-up: the |
| @@ -54,28 +53,27 @@ const toStat = (entry: Entry): vscode.FileStat => ({ | |||
|
|
|||
| const pathParts = (uri: URI): string[] => uri.path.split('/').filter(Boolean); | |||
There was a problem hiding this comment.
if we're working in vscode-uri, why not use its Utils for the "path math" stuff? (basename, pathParts, getParent, etc).
| ) { | ||
| const baseUri = resolveFindFilesBase(include); | ||
| const filePath = isString(include) ? include : include.pattern; | ||
| if (!baseUri) { |
There was a problem hiding this comment.
This changes the existing vscode.workspace.findFiles no-workspace behavior from an empty result to a rejected FsServiceError. VS Code documents that workspace.findFiles resolves with no results when no workspace folders are open (@types/vscode/index.d.ts:13302-13305), and the previous desktop implementation delegated directly to that API. Can this preserve [] for a string glob without a workspace rather than introduce a new error contract?
|
|
||
| export class FsService extends Effect.Service<FsService>()('FsService', { | ||
| accessors: true, | ||
| dependencies: [], |
There was a problem hiding this comment.
FsService declares no dependency on FileSystemProviderRegistry, then obtains it with serviceOption, so FsService.Default can construct a partially functional service whose org-data methods fail only when called. servicesLayers.ts compensates with usage-site Layer.provide. Can the registry be a required declared service dependency (or the org-data operations move to an OrgDataService) so missing wiring fails during layer construction and FsService.Default has one complete meaning?
| const sanitizeOrgKey = (orgKey: string): string => encodeURIComponent(orgKey.trim().toLowerCase()); | ||
| const sanitizePathPart = (part: string): string => encodeURIComponent(part.trim()); | ||
|
|
||
| export const orgRoot = (orgKey: string): URI => |
There was a problem hiding this comment.
jsdoc on these would be nice so consumers understand that orgKey is what...the orgId? [Or maybe better param naming if it's meant to be orgOd]
| yield* fsService.clearOrgData({ orgKey, owner: OWNER }); | ||
| const ownerRoot = api.services.orgDataOwnerRoot({ orgKey, owner: OWNER }); | ||
| const classesRoot = api.services.orgDataUri({ orgKey, owner: OWNER, segments: [CLASSES_ROOT] }); | ||
| yield* fsService.createOrgDataDir(ownerRoot); |
There was a problem hiding this comment.
are these really necessary? Why not use the equivalent of safeWriteFile (from the fsService) which recursively writes the parent dirs (for example, if you had apexTestingClassDir construct the full URI?)
| }); | ||
| return fsService | ||
| .createOrgDataDir(Utils.dirname(classUri)) | ||
| .pipe(Effect.andThen(fsService.writeOrgData(classUri, content))); |
There was a problem hiding this comment.
hmm, so there's separate write methods special for org data?
why not have fsService be smarter and say
- look at full URI
- figure out which fs that goes to based on scheme
- write there
so then consumers wanting to write "orgData" would just need to know the scheme and we could maybe provide just a helper for constructing URIs correctly ex: buildOrgUri(orgId, owner, subpath)
| @@ -323,7 +322,7 @@ | |||
| }, | |||
| { | |||
| "command": "sf.apex.test.orgOnlyClass.retrieve", | |||
There was a problem hiding this comment.
aww, I didn't know this command had been created and that we'd put it in the palette.
If we didn't palette it, could we simplify this a lot?
| ExtensionContextService, | ||
| ExtensionContextServiceLayer, | ||
| FileChangePubSub, | ||
| closeMatchingTabs, |
There was a problem hiding this comment.
nobody is using this outside of services
| * unavailable (older hosts) or nothing matches. Shared by any feature that reaps editor tabs backed | ||
| * by transient/virtual documents (e.g. the org-data VFS lifecycle and apex-testing retrieve flow). | ||
| */ | ||
| export const closeMatchingTabs = Effect.fn('closeMatchingTabs')(function* (predicate: (uri: URI) => boolean) { |
There was a problem hiding this comment.
Is there a scenario where you wouldn't want this to happen?
Like, I think exposing this as a "service" and making the consumers do it imperatively is maybe unnecessary, if instead services was like
- oh, I see you changed orgs
- lemme close all the org-fs-backed tabs for it
etc
| setExtensionContext(context); | ||
| const extensionScope = Effect.runSync(getExtensionScope()); | ||
|
|
||
| const providerRegistry = makeFileSystemProviderRegistry(); |
There was a problem hiding this comment.
I'd like to keep activate from growing a lot.
idea: have an effect to create your fsp, call it from the activate effect.
attach those to the extension scope with finalizers so vscode doesn't have to manage the context subscriptions (or if you really want to, that fsp setup can have a dep on the extensionContext that's already stashed by setExtensionContext)
There was a problem hiding this comment.
oh, I see, it has to done before fileSystemSetup. OK, the rest of the comment still stands.
| const excludePattern = isString(exclude) ? exclude : exclude?.pattern; | ||
| const results: URI[] = []; | ||
| const visit = async (directory: URI, relativeDirectory: string): Promise<void> => { | ||
| if (token?.isCancellationRequested || results.length >= (maxResults ?? Number.POSITIVE_INFINITY)) return; |
There was a problem hiding this comment.
why not put default on the optional param instead of doing this inline each time?
|
|
||
| export type RegisteredFileSystemProvider = { | ||
| readonly provider: vscode.FileSystemProvider; | ||
| readonly findFiles?: ( |
There was a problem hiding this comment.
would be simpler to have fsService make memfs an exception, rather than making people pass these in? at least until we have another vfs use case that requires that level of abstraction?
see fsServiceL218 already uses the memfs schema as a conditional to use this bit at all.
What does this PR do?
Consolidates all org-derived virtual-filesystem data under a single scheme (
sf-org-data) owned by salesforcedx-vscode-services, and migrates apex-testing onto it as a pure consumer.Services now owns:
orgVfs/— the VFS provider, decoration provider, org-first URI layout (sf-org-data:/orgs/<orgKey>/<owner>/…), and a single org-change lifecycle reactor (close stale tabs, then purge the foreign org subtree).FsServiceorg-data write API (writeOrgData/createOrgDataDir/deleteOrgData/clearOrgData) via a sharedonOrgDataProviderhelper. Writes route to the provider's*Internalmethods; a non-sf-org-data/unregistered URI fails with a typedFsServiceErrorrather than a silent no-op.FileSystemProviderRegistry(replacing the module-levelfsProviderRef) and a sharedcloseMatchingTabstab-reaper, both exposed on the services API surface.apex-testing now:
api.services.FsService.*— it holds no provider, registers no scheme, and no longer parses org-data paths.discoveryVfs/apexTestingClassUri.ts(encode + decode co-located).sf:orgDataOwnercontext key +orgDataDocumentSelector).The provider/editor surface stays read-only (
isReadonly+FileSystemProvidermutators throwNoPermissions); owners populate the tree through the internal write API, which bypasses the read-only contract by design. This also fixes a latent dead-lens bug: a previewed non-Apex file could show a no-op "retrieve org-only test class" lens under the old per-scheme path parsing.What issues does this PR fix or reference?
@W-23596477@