@teardown/cli 1.2.38 → 2.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/bin/teardown.js +11 -1
  2. package/package.json +77 -57
  3. package/src/cli/commands/init.ts +254 -0
  4. package/src/cli/commands/plugins.ts +93 -0
  5. package/src/cli/commands/prebuild.ts +168 -0
  6. package/src/cli/commands/run.ts +727 -0
  7. package/src/cli/commands/start.ts +87 -0
  8. package/src/cli/commands/validate.ts +62 -0
  9. package/src/cli/index.ts +59 -0
  10. package/src/config/index.ts +45 -0
  11. package/src/config/loader.ts +366 -0
  12. package/src/config/schema.ts +235 -0
  13. package/src/config/types.ts +322 -0
  14. package/src/index.ts +177 -0
  15. package/src/pipeline/cache.ts +179 -0
  16. package/src/pipeline/index.ts +10 -0
  17. package/src/pipeline/stages.ts +692 -0
  18. package/src/plugins/base.ts +370 -0
  19. package/src/plugins/capabilities/biometrics.ts +64 -0
  20. package/src/plugins/capabilities/bluetooth.ts +86 -0
  21. package/src/plugins/capabilities/calendar.ts +57 -0
  22. package/src/plugins/capabilities/camera.ts +77 -0
  23. package/src/plugins/capabilities/contacts.ts +57 -0
  24. package/src/plugins/capabilities/deep-linking.ts +124 -0
  25. package/src/plugins/capabilities/firebase.ts +138 -0
  26. package/src/plugins/capabilities/index.ts +96 -0
  27. package/src/plugins/capabilities/location.ts +87 -0
  28. package/src/plugins/capabilities/photo-library.ts +80 -0
  29. package/src/plugins/capabilities/push-notifications.ts +98 -0
  30. package/src/plugins/capabilities/sign-in-with-apple.ts +53 -0
  31. package/src/plugins/context.ts +220 -0
  32. package/src/plugins/index.ts +26 -0
  33. package/src/plugins/resolver.ts +321 -0
  34. package/src/templates/generator.ts +507 -0
  35. package/src/templates/index.ts +9 -0
  36. package/src/templates/paths.ts +25 -0
  37. package/src/transformers/android/gradle.ts +400 -0
  38. package/src/transformers/android/index.ts +19 -0
  39. package/src/transformers/android/manifest.ts +506 -0
  40. package/src/transformers/index.ts +39 -0
  41. package/src/transformers/ios/entitlements.ts +283 -0
  42. package/src/transformers/ios/index.ts +10 -0
  43. package/src/transformers/ios/pbxproj.ts +267 -0
  44. package/src/transformers/ios/plist.ts +198 -0
  45. package/src/utils/fs.ts +429 -0
  46. package/src/utils/index.ts +21 -0
  47. package/src/utils/logger.ts +203 -0
  48. package/templates/.gitignore +63 -0
  49. package/templates/Gemfile +3 -0
  50. package/templates/android/app/build.gradle.kts +97 -0
  51. package/templates/android/app/proguard-rules.pro +10 -0
  52. package/templates/android/app/src/main/AndroidManifest.xml +26 -0
  53. package/templates/android/app/src/main/java/com/appname/MainActivity.kt +22 -0
  54. package/templates/android/app/src/main/java/com/appname/MainApplication.kt +44 -0
  55. package/templates/android/app/src/main/res/values/strings.xml +3 -0
  56. package/templates/android/app/src/main/res/values/styles.xml +7 -0
  57. package/templates/android/build.gradle.kts +44 -0
  58. package/templates/android/gradle.properties +39 -0
  59. package/templates/android/settings.gradle.kts +12 -0
  60. package/templates/babel.config.js +15 -0
  61. package/templates/index.js +7 -0
  62. package/templates/ios/.xcode.env +11 -0
  63. package/templates/ios/AppName/AppDelegate.swift +25 -0
  64. package/templates/ios/AppName/AppName-Bridging-Header.h +4 -0
  65. package/templates/ios/AppName/AppName.entitlements +6 -0
  66. package/templates/ios/AppName/Images.xcassets/AppIcon.appiconset/Contents.json +35 -0
  67. package/templates/ios/AppName/Images.xcassets/Contents.json +6 -0
  68. package/templates/ios/AppName/Info.plist +49 -0
  69. package/templates/ios/AppName/LaunchScreen.storyboard +38 -0
  70. package/templates/ios/AppName.xcodeproj/project.pbxproj +402 -0
  71. package/templates/ios/AppName.xcodeproj/xcshareddata/xcschemes/AppName.xcscheme +78 -0
  72. package/templates/ios/Podfile +35 -0
  73. package/templates/metro.config.js +41 -0
  74. package/templates/package.json +57 -0
  75. package/templates/react-native.config.js +8 -0
  76. package/templates/src/app/index.tsx +34 -0
  77. package/templates/src/assets/fonts/.gitkeep +1 -0
  78. package/templates/src/assets/images/.gitkeep +1 -0
  79. package/templates/src/components/ui/accordion.tsx +114 -0
  80. package/templates/src/components/ui/avatar.tsx +75 -0
  81. package/templates/src/components/ui/button.tsx +93 -0
  82. package/templates/src/components/ui/card.tsx +120 -0
  83. package/templates/src/components/ui/checkbox.tsx +133 -0
  84. package/templates/src/components/ui/chip.tsx +95 -0
  85. package/templates/src/components/ui/dialog.tsx +134 -0
  86. package/templates/src/components/ui/divider.tsx +67 -0
  87. package/templates/src/components/ui/error-view.tsx +82 -0
  88. package/templates/src/components/ui/form-field.tsx +101 -0
  89. package/templates/src/components/ui/index.ts +100 -0
  90. package/templates/src/components/ui/popover.tsx +92 -0
  91. package/templates/src/components/ui/pressable-feedback.tsx +88 -0
  92. package/templates/src/components/ui/radio-group.tsx +153 -0
  93. package/templates/src/components/ui/scroll-shadow.tsx +108 -0
  94. package/templates/src/components/ui/select.tsx +165 -0
  95. package/templates/src/components/ui/skeleton-group.tsx +97 -0
  96. package/templates/src/components/ui/skeleton.tsx +87 -0
  97. package/templates/src/components/ui/spinner.tsx +87 -0
  98. package/templates/src/components/ui/surface.tsx +95 -0
  99. package/templates/src/components/ui/switch.tsx +124 -0
  100. package/templates/src/components/ui/tabs.tsx +154 -0
  101. package/templates/src/components/ui/text-field.tsx +106 -0
  102. package/templates/src/components/ui/toast.tsx +129 -0
  103. package/templates/src/contexts/.gitkeep +2 -0
  104. package/templates/src/core/clients/api/api.client.ts +113 -0
  105. package/templates/src/core/clients/api/index.ts +1 -0
  106. package/templates/src/core/clients/storage/index.ts +1 -0
  107. package/templates/src/core/clients/storage/storage.client.ts +121 -0
  108. package/templates/src/core/constants/index.ts +19 -0
  109. package/templates/src/core/core.ts +40 -0
  110. package/templates/src/core/index.ts +10 -0
  111. package/templates/src/global.css +87 -0
  112. package/templates/src/hooks/index.ts +6 -0
  113. package/templates/src/hooks/use-debounce.ts +23 -0
  114. package/templates/src/hooks/use-mounted.ts +21 -0
  115. package/templates/src/index.ts +28 -0
  116. package/templates/src/lib/index.ts +5 -0
  117. package/templates/src/lib/utils.ts +115 -0
  118. package/templates/src/modules/.gitkeep +6 -0
  119. package/templates/src/navigation/index.ts +8 -0
  120. package/templates/src/navigation/navigation-provider.tsx +36 -0
  121. package/templates/src/navigation/router.tsx +137 -0
  122. package/templates/src/providers/app.provider.tsx +29 -0
  123. package/templates/src/providers/index.ts +5 -0
  124. package/templates/src/routes/(tabs)/_layout.tsx +42 -0
  125. package/templates/src/routes/(tabs)/explore.tsx +161 -0
  126. package/templates/src/routes/(tabs)/home.tsx +138 -0
  127. package/templates/src/routes/(tabs)/profile.tsx +151 -0
  128. package/templates/src/routes/_layout.tsx +18 -0
  129. package/templates/src/routes/settings.tsx +194 -0
  130. package/templates/src/screens/auth/index.ts +6 -0
  131. package/templates/src/screens/auth/login.tsx +165 -0
  132. package/templates/src/screens/auth/register.tsx +203 -0
  133. package/templates/src/screens/home.tsx +204 -0
  134. package/templates/src/screens/index.ts +17 -0
  135. package/templates/src/screens/profile.tsx +210 -0
  136. package/templates/src/screens/settings.tsx +216 -0
  137. package/templates/src/screens/welcome.tsx +101 -0
  138. package/templates/src/styles/index.ts +103 -0
  139. package/templates/src/types/common.ts +71 -0
  140. package/templates/src/types/index.ts +5 -0
  141. package/templates/tsconfig.json +14 -0
  142. package/README.md +0 -15
  143. package/assets/favicon.ico +0 -0
  144. package/dist/commands/dev/dev.js +0 -55
  145. package/dist/commands/init/init-teardown.js +0 -26
  146. package/dist/index.js +0 -20
  147. package/dist/modules/dev/dev-menu/keyboard-handler.js +0 -138
  148. package/dist/modules/dev/dev-menu/open-debugger-keyboard-handler.js +0 -105
  149. package/dist/modules/dev/dev-server/cdp/cdp.adapter.js +0 -12
  150. package/dist/modules/dev/dev-server/cdp/index.js +0 -18
  151. package/dist/modules/dev/dev-server/cdp/types.js +0 -2
  152. package/dist/modules/dev/dev-server/dev-server-checker.js +0 -72
  153. package/dist/modules/dev/dev-server/dev-server.js +0 -269
  154. package/dist/modules/dev/dev-server/inspector/device.event-reporter.js +0 -165
  155. package/dist/modules/dev/dev-server/inspector/device.js +0 -577
  156. package/dist/modules/dev/dev-server/inspector/inspector.js +0 -204
  157. package/dist/modules/dev/dev-server/inspector/types.js +0 -2
  158. package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js +0 -61
  159. package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js +0 -64
  160. package/dist/modules/dev/dev-server/plugins/devtools.plugin.js +0 -50
  161. package/dist/modules/dev/dev-server/plugins/favicon.plugin.js +0 -19
  162. package/dist/modules/dev/dev-server/plugins/multipart.plugin.js +0 -62
  163. package/dist/modules/dev/dev-server/plugins/systrace.plugin.js +0 -28
  164. package/dist/modules/dev/dev-server/plugins/types.js +0 -2
  165. package/dist/modules/dev/dev-server/plugins/wss/index.js +0 -19
  166. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-api.server.js +0 -66
  167. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-debugger.server.js +0 -128
  168. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-dev-client.server.js +0 -75
  169. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-events.server.js +0 -198
  170. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-hmr.server.js +0 -120
  171. package/dist/modules/dev/dev-server/plugins/wss/servers/web-socket-message.server.js +0 -357
  172. package/dist/modules/dev/dev-server/plugins/wss/types.js +0 -2
  173. package/dist/modules/dev/dev-server/plugins/wss/web-socket-router.js +0 -57
  174. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server-adapter.js +0 -26
  175. package/dist/modules/dev/dev-server/plugins/wss/web-socket-server.js +0 -46
  176. package/dist/modules/dev/dev-server/plugins/wss/wss.plugin.js +0 -55
  177. package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js +0 -36
  178. package/dist/modules/dev/dev-server/sybmolicate/types.js +0 -2
  179. package/dist/modules/dev/terminal/base.terminal.reporter.js +0 -78
  180. package/dist/modules/dev/terminal/terminal.reporter.js +0 -76
  181. package/dist/modules/dev/types.js +0 -2
  182. package/dist/modules/dev/utils/log.js +0 -73
@@ -0,0 +1,220 @@
1
+ import type { PrebuildWarning, ProcessedAppConfig, TeardownConfig } from "../config/types";
2
+ import type { VirtualFileSystem } from "../utils/fs";
3
+ import type { Logger } from "../utils/logger";
4
+
5
+ /**
6
+ * Plugin context provides shared state and utilities for plugins
7
+ */
8
+ export interface PluginContext {
9
+ /**
10
+ * Shared state between plugins - allows plugins to communicate
11
+ */
12
+ readonly sharedState: Map<string, unknown>;
13
+
14
+ /**
15
+ * Access another plugin's output by name
16
+ */
17
+ getPluginOutput<T>(pluginName: string): T | undefined;
18
+
19
+ /**
20
+ * Set output for this plugin (accessible by other plugins)
21
+ */
22
+ setPluginOutput<T>(pluginName: string, output: T): void;
23
+
24
+ /**
25
+ * Register a capability that this plugin provides
26
+ */
27
+ registerCapability(capability: string): void;
28
+
29
+ /**
30
+ * Check if a capability has been registered by any plugin
31
+ */
32
+ hasCapability(capability: string): boolean;
33
+
34
+ /**
35
+ * Get all registered capabilities
36
+ */
37
+ getCapabilities(): string[];
38
+
39
+ /**
40
+ * Project root directory path
41
+ */
42
+ readonly projectRoot: string;
43
+
44
+ /**
45
+ * Target platform(s) for this prebuild
46
+ */
47
+ readonly platform: "ios" | "android" | "both";
48
+
49
+ /**
50
+ * Virtual file system for dry-run support
51
+ */
52
+ readonly fs: VirtualFileSystem;
53
+
54
+ /**
55
+ * Logger instance
56
+ */
57
+ readonly log: Logger;
58
+
59
+ /**
60
+ * Original configuration (before plugin modifications)
61
+ */
62
+ readonly originalConfig: TeardownConfig;
63
+
64
+ /**
65
+ * Current processed configuration (accumulates changes)
66
+ */
67
+ config: ProcessedAppConfig;
68
+
69
+ /**
70
+ * Add a warning to the prebuild output
71
+ */
72
+ addWarning(warning: PrebuildWarning): void;
73
+
74
+ /**
75
+ * Get all accumulated warnings
76
+ */
77
+ getWarnings(): PrebuildWarning[];
78
+
79
+ /**
80
+ * Check if running in dry-run mode
81
+ */
82
+ readonly isDryRun: boolean;
83
+
84
+ /**
85
+ * Check if verbose logging is enabled
86
+ */
87
+ readonly isVerbose: boolean;
88
+
89
+ /**
90
+ * iOS-specific paths
91
+ */
92
+ readonly iosPaths: {
93
+ projectRoot: string;
94
+ infoPlist: string;
95
+ entitlements: string;
96
+ pbxproj: string;
97
+ podfile: string;
98
+ appDelegate: string;
99
+ };
100
+
101
+ /**
102
+ * Android-specific paths
103
+ */
104
+ readonly androidPaths: {
105
+ projectRoot: string;
106
+ manifest: string;
107
+ buildGradle: string;
108
+ appBuildGradle: string;
109
+ mainActivity: string;
110
+ mainApplication: string;
111
+ stringsXml: string;
112
+ networkSecurityConfig: string;
113
+ };
114
+ }
115
+
116
+ /**
117
+ * Create a new plugin context
118
+ */
119
+ export function createPluginContext(options: {
120
+ projectRoot: string;
121
+ platform: "ios" | "android" | "both";
122
+ config: TeardownConfig;
123
+ fs: VirtualFileSystem;
124
+ log: Logger;
125
+ isDryRun: boolean;
126
+ isVerbose: boolean;
127
+ }): PluginContext {
128
+ const { projectRoot, platform, config, fs, log, isDryRun, isVerbose } = options;
129
+
130
+ const sharedState = new Map<string, unknown>();
131
+ const pluginOutputs = new Map<string, unknown>();
132
+ const capabilities = new Set<string>();
133
+ const warnings: PrebuildWarning[] = [];
134
+
135
+ // Derive app name for paths
136
+ const appName = config.name.replace(/[^a-zA-Z0-9]/g, "");
137
+ const packagePath = config.android?.packageName?.replace(/\./g, "/") ?? "com/example/app";
138
+
139
+ // Create processed config with empty resolved fields
140
+ const processedConfig: ProcessedAppConfig = {
141
+ ...config,
142
+ resolvedInfoPlist: {},
143
+ resolvedEntitlements: {},
144
+ resolvedAndroidPermissions: [],
145
+ resolvedAndroidFeatures: [],
146
+ resolvedAndroidMetadata: {},
147
+ resolvedGradleDependencies: [],
148
+ resolvedFrameworks: [],
149
+ resolvedPodDependencies: [],
150
+ resolvedBackgroundModes: [],
151
+ resolvedUrlSchemes: [],
152
+ resolvedIntentFilters: [],
153
+ };
154
+
155
+ const iosPaths = {
156
+ projectRoot: `${projectRoot}/ios`,
157
+ infoPlist: `${projectRoot}/ios/${appName}/Info.plist`,
158
+ entitlements: `${projectRoot}/ios/${appName}/${appName}.entitlements`,
159
+ pbxproj: `${projectRoot}/ios/${appName}.xcodeproj/project.pbxproj`,
160
+ podfile: `${projectRoot}/ios/Podfile`,
161
+ appDelegate: `${projectRoot}/ios/${appName}/AppDelegate.swift`,
162
+ };
163
+
164
+ const androidPaths = {
165
+ projectRoot: `${projectRoot}/android`,
166
+ manifest: `${projectRoot}/android/app/src/main/AndroidManifest.xml`,
167
+ buildGradle: `${projectRoot}/android/build.gradle.kts`,
168
+ appBuildGradle: `${projectRoot}/android/app/build.gradle.kts`,
169
+ mainActivity: `${projectRoot}/android/app/src/main/java/${packagePath}/MainActivity.kt`,
170
+ mainApplication: `${projectRoot}/android/app/src/main/java/${packagePath}/MainApplication.kt`,
171
+ stringsXml: `${projectRoot}/android/app/src/main/res/values/strings.xml`,
172
+ networkSecurityConfig: `${projectRoot}/android/app/src/main/res/xml/network_security_config.xml`,
173
+ };
174
+
175
+ return {
176
+ sharedState,
177
+
178
+ getPluginOutput<T>(pluginName: string): T | undefined {
179
+ return pluginOutputs.get(pluginName) as T | undefined;
180
+ },
181
+
182
+ setPluginOutput<T>(pluginName: string, output: T): void {
183
+ pluginOutputs.set(pluginName, output);
184
+ },
185
+
186
+ registerCapability(capability: string): void {
187
+ capabilities.add(capability);
188
+ log.debug(`Registered capability: ${capability}`);
189
+ },
190
+
191
+ hasCapability(capability: string): boolean {
192
+ return capabilities.has(capability);
193
+ },
194
+
195
+ getCapabilities(): string[] {
196
+ return Array.from(capabilities);
197
+ },
198
+
199
+ projectRoot,
200
+ platform,
201
+ fs,
202
+ log,
203
+ originalConfig: config,
204
+ config: processedConfig,
205
+ isDryRun,
206
+ isVerbose,
207
+
208
+ addWarning(warning: PrebuildWarning): void {
209
+ warnings.push(warning);
210
+ log.warn(`[${warning.code}] ${warning.message}`);
211
+ },
212
+
213
+ getWarnings(): PrebuildWarning[] {
214
+ return [...warnings];
215
+ },
216
+
217
+ iosPaths,
218
+ androidPaths,
219
+ };
220
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Plugin system for Teardown Launchpad
3
+ *
4
+ * Provides the base plugin class, context, and dependency resolution
5
+ * for implementing native capability plugins.
6
+ */
7
+
8
+ export type {
9
+ AndroidActivity,
10
+ AndroidIntentData,
11
+ AndroidIntentFilter,
12
+ AndroidManifest,
13
+ AndroidProvider,
14
+ AndroidReceiver,
15
+ AndroidService,
16
+ Entitlements,
17
+ InfoPlist,
18
+ PluginConstructor,
19
+ } from "./base";
20
+ export { BasePlugin } from "./base";
21
+ // Re-export capability plugins
22
+ export * from "./capabilities";
23
+ export type { PluginContext } from "./context";
24
+ export { createPluginContext } from "./context";
25
+ export type { CycleInfo } from "./resolver";
26
+ export { DependencyResolver, PluginResolutionError } from "./resolver";
@@ -0,0 +1,321 @@
1
+ import type { Logger } from "../utils/logger";
2
+ import type { BasePlugin, PluginConstructor } from "./base";
3
+
4
+ /**
5
+ * Plugin resolution error
6
+ */
7
+ export class PluginResolutionError extends Error {
8
+ constructor(
9
+ message: string,
10
+ public readonly code: string,
11
+ public readonly details?: unknown
12
+ ) {
13
+ super(message);
14
+ this.name = "PluginResolutionError";
15
+ }
16
+ }
17
+
18
+ /**
19
+ * Cycle detection result
20
+ */
21
+ export interface CycleInfo {
22
+ plugins: string[];
23
+ path: string;
24
+ }
25
+
26
+ /**
27
+ * Plugin registry and dependency resolver
28
+ *
29
+ * Handles plugin registration, dependency resolution, and topological sorting
30
+ */
31
+ export class DependencyResolver {
32
+ private plugins: Map<string, BasePlugin> = new Map();
33
+ private pluginConfigs: Map<string, unknown> = new Map();
34
+ private logger: Logger;
35
+
36
+ constructor(logger: Logger) {
37
+ this.logger = logger;
38
+ }
39
+
40
+ /**
41
+ * Register a plugin instance
42
+ */
43
+ registerPlugin<TConfig>(plugin: BasePlugin<TConfig>, config?: TConfig): void {
44
+ if (this.plugins.has(plugin.name)) {
45
+ throw new PluginResolutionError(`Plugin "${plugin.name}" is already registered`, "DUPLICATE_PLUGIN");
46
+ }
47
+
48
+ // Validate config if provided
49
+ if (config !== undefined) {
50
+ try {
51
+ plugin.validateConfig(config);
52
+ } catch (error) {
53
+ throw new PluginResolutionError(
54
+ `Invalid configuration for plugin "${plugin.name}": ${error instanceof Error ? error.message : String(error)}`,
55
+ "INVALID_CONFIG",
56
+ error
57
+ );
58
+ }
59
+ }
60
+
61
+ this.plugins.set(plugin.name, plugin);
62
+ if (config !== undefined) {
63
+ this.pluginConfigs.set(plugin.name, config);
64
+ }
65
+
66
+ this.logger.debug(`Registered plugin: ${plugin.name} v${plugin.version}`);
67
+ }
68
+
69
+ /**
70
+ * Register a plugin from constructor and config
71
+ */
72
+ registerPluginFromConstructor<TConfig>(Constructor: PluginConstructor<TConfig>, config?: TConfig): void {
73
+ const plugin = new Constructor();
74
+ this.registerPlugin(plugin, config);
75
+ }
76
+
77
+ /**
78
+ * Get a registered plugin by name
79
+ */
80
+ getPlugin(name: string): BasePlugin | undefined {
81
+ return this.plugins.get(name);
82
+ }
83
+
84
+ /**
85
+ * Get plugin configuration
86
+ */
87
+ getPluginConfig<TConfig>(name: string): TConfig | undefined {
88
+ return this.pluginConfigs.get(name) as TConfig | undefined;
89
+ }
90
+
91
+ /**
92
+ * Check if a plugin is registered
93
+ */
94
+ hasPlugin(name: string): boolean {
95
+ return this.plugins.has(name);
96
+ }
97
+
98
+ /**
99
+ * Get all registered plugin names
100
+ */
101
+ getPluginNames(): string[] {
102
+ return Array.from(this.plugins.keys());
103
+ }
104
+
105
+ /**
106
+ * Resolve plugin execution order using topological sort
107
+ * Returns plugins in order such that dependencies come before dependents
108
+ */
109
+ resolveOrder(): BasePlugin[] {
110
+ // First, check for cycles
111
+ const cycles = this.detectCycles();
112
+ if (cycles.length > 0) {
113
+ const cycleStr = cycles.map((c) => c.path).join("; ");
114
+ throw new PluginResolutionError(`Circular dependencies detected: ${cycleStr}`, "CIRCULAR_DEPENDENCY", cycles);
115
+ }
116
+
117
+ // Check for missing dependencies
118
+ const missingDeps = this.findMissingDependencies();
119
+ if (missingDeps.length > 0) {
120
+ const missingStr = missingDeps.map((m) => `${m.plugin} requires ${m.dependency}`).join(", ");
121
+ throw new PluginResolutionError(`Missing dependencies: ${missingStr}`, "MISSING_DEPENDENCY", missingDeps);
122
+ }
123
+
124
+ // Perform topological sort using Kahn's algorithm
125
+ const inDegree = new Map<string, number>();
126
+ const adjacency = new Map<string, string[]>();
127
+
128
+ // Initialize
129
+ for (const [name] of this.plugins) {
130
+ inDegree.set(name, 0);
131
+ adjacency.set(name, []);
132
+ }
133
+
134
+ // Build graph
135
+ for (const [name, plugin] of this.plugins) {
136
+ for (const dep of plugin.dependencies) {
137
+ if (this.plugins.has(dep)) {
138
+ adjacency.get(dep)?.push(name);
139
+ inDegree.set(name, (inDegree.get(name) || 0) + 1);
140
+ }
141
+ }
142
+ }
143
+
144
+ // Find all nodes with no incoming edges
145
+ const queue: string[] = [];
146
+ for (const [name, degree] of inDegree) {
147
+ if (degree === 0) {
148
+ queue.push(name);
149
+ }
150
+ }
151
+
152
+ // Process queue
153
+ const result: BasePlugin[] = [];
154
+ while (queue.length > 0) {
155
+ const current = queue.shift()!;
156
+ result.push(this.plugins.get(current)!);
157
+
158
+ for (const neighbor of adjacency.get(current) || []) {
159
+ const newDegree = (inDegree.get(neighbor) || 1) - 1;
160
+ inDegree.set(neighbor, newDegree);
161
+ if (newDegree === 0) {
162
+ queue.push(neighbor);
163
+ }
164
+ }
165
+ }
166
+
167
+ this.logger.debug(`Plugin execution order: ${result.map((p) => p.name).join(" -> ")}`);
168
+ return result;
169
+ }
170
+
171
+ /**
172
+ * Detect circular dependencies using Tarjan's algorithm
173
+ */
174
+ detectCycles(): CycleInfo[] {
175
+ const cycles: CycleInfo[] = [];
176
+ const visited = new Set<string>();
177
+ const recursionStack = new Set<string>();
178
+ const path: string[] = [];
179
+
180
+ const dfs = (name: string): boolean => {
181
+ visited.add(name);
182
+ recursionStack.add(name);
183
+ path.push(name);
184
+
185
+ const plugin = this.plugins.get(name);
186
+ if (plugin) {
187
+ for (const dep of plugin.dependencies) {
188
+ if (!this.plugins.has(dep)) {
189
+ continue; // Skip missing deps (handled separately)
190
+ }
191
+
192
+ if (!visited.has(dep)) {
193
+ if (dfs(dep)) {
194
+ return true;
195
+ }
196
+ } else if (recursionStack.has(dep)) {
197
+ // Found a cycle
198
+ const cycleStart = path.indexOf(dep);
199
+ const cyclePath = path.slice(cycleStart);
200
+ cycles.push({
201
+ plugins: [...cyclePath],
202
+ path: `${cyclePath.join(" -> ")} -> ${dep}`,
203
+ });
204
+ return true;
205
+ }
206
+ }
207
+ }
208
+
209
+ path.pop();
210
+ recursionStack.delete(name);
211
+ return false;
212
+ };
213
+
214
+ for (const name of this.plugins.keys()) {
215
+ if (!visited.has(name)) {
216
+ dfs(name);
217
+ }
218
+ }
219
+
220
+ return cycles;
221
+ }
222
+
223
+ /**
224
+ * Find dependencies that are not registered
225
+ */
226
+ findMissingDependencies(): Array<{ plugin: string; dependency: string }> {
227
+ const missing: Array<{ plugin: string; dependency: string }> = [];
228
+
229
+ for (const [name, plugin] of this.plugins) {
230
+ for (const dep of plugin.dependencies) {
231
+ if (!this.plugins.has(dep)) {
232
+ missing.push({ plugin: name, dependency: dep });
233
+ }
234
+ }
235
+ }
236
+
237
+ return missing;
238
+ }
239
+
240
+ /**
241
+ * Get plugins that depend on a specific plugin
242
+ */
243
+ getDependents(pluginName: string): BasePlugin[] {
244
+ const dependents: BasePlugin[] = [];
245
+
246
+ for (const plugin of this.plugins.values()) {
247
+ if (plugin.dependencies.includes(pluginName)) {
248
+ dependents.push(plugin);
249
+ }
250
+ }
251
+
252
+ return dependents;
253
+ }
254
+
255
+ /**
256
+ * Get plugins that a specific plugin depends on
257
+ */
258
+ getDependencies(pluginName: string): BasePlugin[] {
259
+ const plugin = this.plugins.get(pluginName);
260
+ if (!plugin) {
261
+ return [];
262
+ }
263
+
264
+ return plugin.dependencies.map((dep) => this.plugins.get(dep)).filter((p): p is BasePlugin => p !== undefined);
265
+ }
266
+
267
+ /**
268
+ * Clear all registered plugins
269
+ */
270
+ clear(): void {
271
+ this.plugins.clear();
272
+ this.pluginConfigs.clear();
273
+ }
274
+
275
+ /**
276
+ * Get all registered plugins (unordered)
277
+ */
278
+ getAllPlugins(): BasePlugin[] {
279
+ return Array.from(this.plugins.values());
280
+ }
281
+
282
+ /**
283
+ * Validate all plugin configurations
284
+ */
285
+ validateAllConfigs(): Array<{ plugin: string; error: string }> {
286
+ const errors: Array<{ plugin: string; error: string }> = [];
287
+
288
+ for (const [name, plugin] of this.plugins) {
289
+ const config = this.pluginConfigs.get(name);
290
+ if (config !== undefined) {
291
+ try {
292
+ plugin.validateConfig(config);
293
+ } catch (error) {
294
+ errors.push({
295
+ plugin: name,
296
+ error: error instanceof Error ? error.message : String(error),
297
+ });
298
+ }
299
+ }
300
+ }
301
+
302
+ return errors;
303
+ }
304
+
305
+ /**
306
+ * Get all capabilities provided by registered plugins
307
+ */
308
+ getAllProvidedCapabilities(): Map<string, string[]> {
309
+ const capabilities = new Map<string, string[]>();
310
+
311
+ for (const plugin of this.plugins.values()) {
312
+ for (const capability of plugin.provides) {
313
+ const providers = capabilities.get(capability) || [];
314
+ providers.push(plugin.name);
315
+ capabilities.set(capability, providers);
316
+ }
317
+ }
318
+
319
+ return capabilities;
320
+ }
321
+ }