Integrate Project model into loader and decompose into composable stages#7023
Integrate Project model into loader and decompose into composable stages#7023ryancbahan wants to merge 5 commits intomainfrom
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Coverage report
Test suite run success3902 tests passing in 1500 suites. Report generated by 🧪jest coverage report action from 5f12f8e |
6c3198a to
c7fcd8b
Compare
d7d2097 to
ff9497f
Compare
c7fcd8b to
d60ce08
Compare
288c896 to
b17ef80
Compare
|
We detected some changes at Caution DO NOT create changesets for features which you do not wish to be included in the public changelog of the next CLI release. |
b17ef80 to
57e86f9
Compare
d60ce08 to
2edfd38
Compare
18f3516 to
839512e
Compare
509aacf to
8964add
Compare
839512e to
87728e2
Compare
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/public/node/themes/api.d.ts@@ -5,7 +5,6 @@ export type ThemeParams = Partial<Pick<Theme, 'name' | 'role' | 'processing' | '
export type AssetParams = Pick<ThemeAsset, 'key'> & Partial<Pick<ThemeAsset, 'value' | 'attachment'>>;
export declare function fetchTheme(id: number, session: AdminSession): Promise<Theme | undefined>;
export declare function fetchThemes(session: AdminSession): Promise<Theme[]>;
-export declare function findDevelopmentThemeByName(name: string, session: AdminSession): Promise<Theme | undefined>;
export declare function themeCreate(params: ThemeParams, session: AdminSession): Promise<Theme | undefined>;
export declare function fetchThemeAssets(id: number, filenames: Key[], session: AdminSession): Promise<ThemeAsset[]>;
export declare function deleteThemeAssets(id: number, filenames: Key[], session: AdminSession): Promise<Result[]>;
packages/cli-kit/dist/public/node/themes/theme-manager.d.ts@@ -8,8 +8,8 @@ export declare abstract class ThemeManager {
protected abstract removeTheme(): void;
protected abstract context: string;
constructor(adminSession: AdminSession);
- findOrCreate(name?: string, role?: Role): Promise<Theme>;
- fetch(name?: string, role?: Role): Promise<Theme | undefined>;
+ findOrCreate(): Promise<Theme>;
+ fetch(): Promise<Theme | undefined>;
generateThemeName(context: string): string;
create(themeRole?: Role, themeName?: string): Promise<Theme>;
}
\ No newline at end of file
|
87728e2 to
490c047
Compare
8964add to
2929234
Compare
Wires the Project domain model into the existing loading pipeline: - getAppConfigurationState uses Project.load() for filesystem discovery - getAppConfigurationContext returns project + activeConfig + state as independent values (project is never nested inside state) - AppLoader reads from Project's pre-loaded data: extension files, web files, dotenv, hidden config, deps, package manager, workspaces - No duplicate filesystem scanning — Project discovers once, loader reads from it - AppConfigurationState no longer carries project as a field - LoadedAppContextOutput exposes project and activeConfig as top-level fields for commands - All extension/web file discovery filtered to active config's directories via config-selection functions Zero behavioral changes. All 3801 existing tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the monolithic loadApp pipeline with composable stages:
- loadApp is now a thin wrapper: getAppConfigurationContext → loadAppFromContext
- loadAppFromContext takes narrow Project + ActiveConfig directly
- getAppConfigurationContext is discovery-only (no parsing/state construction)
- ReloadState replaces passing entire AppLinkedInterface through reloads
- AppLoader takes reloadState? instead of previousApp?
- link() returns {remoteApp, configFileName, configuration} (no state)
- linkedAppContext uses activeConfig directly, no AppConfigurationState
Remove dead code: AppConfigurationState, toAppConfigurationState,
loadAppConfigurationFromState, loadAppUsingConfigurationState,
loadAppConfiguration, getAppConfigurationState, getAppDirectory,
loadDotEnv, loadHiddenConfig, findWebConfigPaths, loadWebsForAppCreation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
490c047 to
e775694
Compare
…Path - Remove orphaned <<<<<<< HEAD marker and dead loadHiddenConfig tests - Re-add isAppConfigSpecification import (used at line 776) - Add missing configPath field to loadedConfiguration in loadAppFromContext - Fix configuration.path references to use configPath (path extracted from config type) - Fix testApp() call signature in use.test.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…path Same bug as the other two configuration.path references fixed in e4487d9 — configuration is Zod-parsed and has no path property. Without this fix, activeConfigFile always returns undefined, causing loadWebs() and createExtensionInstances() to load all project files instead of filtering by the active config. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

WHY are these changes introduced?
PR #7022 introduced the
ProjectandActiveConfigdomain models, but nothing consumed them (the loader still ran its own filesystem discovery, constructedAppConfigurationStateas an intermediate, and threaded the entire previousAppInterfacethrough reloads). This PR wires the new models into the loading pipeline and uses that as leverage to decompose the monolithicloadAppinto composable stages with narrow interfaces.WHAT is this pull request doing?
Decomposes the loader into three explicit stages:
getAppConfigurationContext(dir, configName)→{project, activeConfig}— discovery only, no parsing or state constructionloadAppFromContext({project, activeConfig, specifications, …})→AppInterface— validation and assembly, for callers that already hold a ProjectloadApp({directory, configName, …})— thin wrapper composing the two above[Interim state] Replaces
previousAppwith narrowReloadState:Instead of threading the entire
AppLinkedInterfacethrough reloads (just to read 2 fields),reloadAppnow constructs aReloadStatewith onlyextensionDevUUIDs: Map<string, string>andpreviousDevURLs. This is a step change away from broad state passing/mutation, but needs more consideration on "permanent home" as we continue to decompose functionality.Updates all consumers:
linkedAppContextusesactiveConfig.isLinkedandactiveConfig.file.contentdirectly instead of going throughAppConfigurationStatelink()returns{remoteApp, configFileName, configuration}— dropsstatefrom its return typeuse()readsactiveConfig.file.contentinstead of callingloadAppConfigurationloadConfigForAppCreationusesactiveConfigandproject.directorydirectlyRemoves dead code (-400 lines):
AppConfigurationState,AppConfigurationStateBasics,toAppConfigurationState,loadAppConfigurationFromState,loadAppUsingConfigurationState,loadAppConfiguration,getAppConfigurationState,getAppDirectory,loadDotEnv,loadHiddenConfig,findWebConfigPaths,loadWebsForAppCreation,getConfigurationPath(de-exported)How to test your changes?
Measuring impact
Checklist
🤖 Generated with Claude Code