@creact-labs/creact 0.1.0

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 (103) hide show
  1. package/LICENSE +212 -0
  2. package/README.md +379 -0
  3. package/dist/cli/commands/BuildCommand.d.ts +40 -0
  4. package/dist/cli/commands/BuildCommand.js +151 -0
  5. package/dist/cli/commands/DeployCommand.d.ts +38 -0
  6. package/dist/cli/commands/DeployCommand.js +194 -0
  7. package/dist/cli/commands/DevCommand.d.ts +52 -0
  8. package/dist/cli/commands/DevCommand.js +385 -0
  9. package/dist/cli/commands/PlanCommand.d.ts +39 -0
  10. package/dist/cli/commands/PlanCommand.js +164 -0
  11. package/dist/cli/commands/index.d.ts +36 -0
  12. package/dist/cli/commands/index.js +43 -0
  13. package/dist/cli/core/ArgumentParser.d.ts +46 -0
  14. package/dist/cli/core/ArgumentParser.js +127 -0
  15. package/dist/cli/core/BaseCommand.d.ts +75 -0
  16. package/dist/cli/core/BaseCommand.js +95 -0
  17. package/dist/cli/core/CLIContext.d.ts +68 -0
  18. package/dist/cli/core/CLIContext.js +183 -0
  19. package/dist/cli/core/CommandRegistry.d.ts +64 -0
  20. package/dist/cli/core/CommandRegistry.js +89 -0
  21. package/dist/cli/core/index.d.ts +36 -0
  22. package/dist/cli/core/index.js +43 -0
  23. package/dist/cli/index.d.ts +35 -0
  24. package/dist/cli/index.js +100 -0
  25. package/dist/cli/output.d.ts +204 -0
  26. package/dist/cli/output.js +437 -0
  27. package/dist/cli/utils.d.ts +59 -0
  28. package/dist/cli/utils.js +76 -0
  29. package/dist/context/createContext.d.ts +90 -0
  30. package/dist/context/createContext.js +113 -0
  31. package/dist/context/index.d.ts +30 -0
  32. package/dist/context/index.js +35 -0
  33. package/dist/core/CReact.d.ts +409 -0
  34. package/dist/core/CReact.js +1127 -0
  35. package/dist/core/CloudDOMBuilder.d.ts +429 -0
  36. package/dist/core/CloudDOMBuilder.js +1198 -0
  37. package/dist/core/ContextDependencyTracker.d.ts +165 -0
  38. package/dist/core/ContextDependencyTracker.js +448 -0
  39. package/dist/core/ErrorRecoveryManager.d.ts +145 -0
  40. package/dist/core/ErrorRecoveryManager.js +443 -0
  41. package/dist/core/EventBus.d.ts +91 -0
  42. package/dist/core/EventBus.js +185 -0
  43. package/dist/core/ProviderOutputTracker.d.ts +211 -0
  44. package/dist/core/ProviderOutputTracker.js +476 -0
  45. package/dist/core/ReactiveUpdateQueue.d.ts +76 -0
  46. package/dist/core/ReactiveUpdateQueue.js +121 -0
  47. package/dist/core/Reconciler.d.ts +415 -0
  48. package/dist/core/Reconciler.js +1037 -0
  49. package/dist/core/RenderScheduler.d.ts +153 -0
  50. package/dist/core/RenderScheduler.js +519 -0
  51. package/dist/core/Renderer.d.ts +276 -0
  52. package/dist/core/Renderer.js +791 -0
  53. package/dist/core/Runtime.d.ts +246 -0
  54. package/dist/core/Runtime.js +640 -0
  55. package/dist/core/StateBindingManager.d.ts +121 -0
  56. package/dist/core/StateBindingManager.js +309 -0
  57. package/dist/core/StateMachine.d.ts +424 -0
  58. package/dist/core/StateMachine.js +787 -0
  59. package/dist/core/StructuralChangeDetector.d.ts +140 -0
  60. package/dist/core/StructuralChangeDetector.js +363 -0
  61. package/dist/core/Validator.d.ts +127 -0
  62. package/dist/core/Validator.js +279 -0
  63. package/dist/core/errors.d.ts +153 -0
  64. package/dist/core/errors.js +202 -0
  65. package/dist/core/index.d.ts +38 -0
  66. package/dist/core/index.js +64 -0
  67. package/dist/core/types.d.ts +263 -0
  68. package/dist/core/types.js +48 -0
  69. package/dist/hooks/context.d.ts +147 -0
  70. package/dist/hooks/context.js +334 -0
  71. package/dist/hooks/useContext.d.ts +113 -0
  72. package/dist/hooks/useContext.js +169 -0
  73. package/dist/hooks/useEffect.d.ts +105 -0
  74. package/dist/hooks/useEffect.js +540 -0
  75. package/dist/hooks/useInstance.d.ts +139 -0
  76. package/dist/hooks/useInstance.js +441 -0
  77. package/dist/hooks/useState.d.ts +120 -0
  78. package/dist/hooks/useState.js +298 -0
  79. package/dist/index.d.ts +46 -0
  80. package/dist/index.js +70 -0
  81. package/dist/jsx.d.ts +64 -0
  82. package/dist/jsx.js +76 -0
  83. package/dist/providers/DummyBackendProvider.d.ts +193 -0
  84. package/dist/providers/DummyBackendProvider.js +189 -0
  85. package/dist/providers/DummyCloudProvider.d.ts +128 -0
  86. package/dist/providers/DummyCloudProvider.js +157 -0
  87. package/dist/providers/IBackendProvider.d.ts +177 -0
  88. package/dist/providers/IBackendProvider.js +31 -0
  89. package/dist/providers/ICloudProvider.d.ts +146 -0
  90. package/dist/providers/ICloudProvider.js +31 -0
  91. package/dist/providers/index.d.ts +31 -0
  92. package/dist/providers/index.js +31 -0
  93. package/dist/test-event-callbacks.d.ts +0 -0
  94. package/dist/test-event-callbacks.js +1 -0
  95. package/dist/utils/Logger.d.ts +144 -0
  96. package/dist/utils/Logger.js +220 -0
  97. package/dist/utils/Output.d.ts +161 -0
  98. package/dist/utils/Output.js +401 -0
  99. package/dist/utils/deepEqual.d.ts +71 -0
  100. package/dist/utils/deepEqual.js +276 -0
  101. package/dist/utils/naming.d.ts +241 -0
  102. package/dist/utils/naming.js +376 -0
  103. package/package.json +87 -0
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ /**
3
+
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+
6
+ * you may not use this file except in compliance with the License.
7
+
8
+ * You may obtain a copy of the License at
9
+
10
+ *
11
+
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ *
15
+
16
+ * Unless required by applicable law or agreed to in writing, software
17
+
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+
22
+ * See the License for the specific language governing permissions and
23
+
24
+ * limitations under the License.
25
+
26
+ *
27
+
28
+ * Copyright 2025 Daniel Coutinho Ribeiro
29
+
30
+ */
31
+ var __importDefault = (this && this.__importDefault) || function (mod) {
32
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.DevCommand = void 0;
36
+ /**
37
+ * Dev Command - hot reload development mode with auto/manual approval
38
+ */
39
+ const BaseCommand_1 = require("../core/BaseCommand");
40
+ const CLIContext_1 = require("../core/CLIContext");
41
+ const Output_1 = require("../../utils/Output");
42
+ const fs_1 = require("fs");
43
+ const path_1 = require("path");
44
+ const readline_1 = require("readline");
45
+ const Reconciler_1 = require("../../core/Reconciler");
46
+ const Logger_1 = require("../../utils/Logger");
47
+ const chalk_1 = __importDefault(require("chalk"));
48
+ const logger = Logger_1.LoggerFactory.getLogger('cli');
49
+ class DevCommand extends BaseCommand_1.BaseCommand {
50
+ constructor() {
51
+ super(...arguments);
52
+ this.isWatching = false;
53
+ this.watchTimeout = null;
54
+ this.autoApprove = false;
55
+ this.currentReadline = null;
56
+ this.state = {
57
+ lastCloudDOM: null,
58
+ lastStackName: null,
59
+ instance: null,
60
+ };
61
+ }
62
+ getName() {
63
+ return 'dev';
64
+ }
65
+ getDescription() {
66
+ return 'Hot reload development mode with auto/manual approval';
67
+ }
68
+ async execute() {
69
+ this.output = (0, Output_1.createOutputManager)({
70
+ json: this.json,
71
+ quiet: !!this.context.flags.quiet,
72
+ verbose: this.verbose,
73
+ });
74
+ try {
75
+ // Check for auto-approve flag
76
+ this.autoApprove = !!this.context.flags['auto-approve'] || !!this.context.flags.auto;
77
+ // Find entry file
78
+ logger.debug('DevCommand: Starting execution');
79
+ let entryPath;
80
+ try {
81
+ entryPath = CLIContext_1.CLIContextManager.findEntryFile(this.context.flags.entry);
82
+ logger.debug(`DevCommand: Entry file resolved to ${entryPath}`);
83
+ }
84
+ catch (error) {
85
+ this.output.showError('Entry file resolution failed', {
86
+ cause: error.message,
87
+ stackTrace: this.verbose ? error.stack : undefined,
88
+ });
89
+ return { exitCode: 1 };
90
+ }
91
+ // Show mode information
92
+ this.output.showInfo('Starting CReact development mode...');
93
+ const mode = this.autoApprove ? chalk_1.default.yellow('Auto-approve') : chalk_1.default.blue('Manual approval');
94
+ this.output.showInfo(`Mode: ${mode}`);
95
+ if (!this.autoApprove) {
96
+ this.output.showInfo('Tip: Use --auto-approve to automatically deploy changes');
97
+ }
98
+ // Start file watching BEFORE initial deploy
99
+ this.startWatching(entryPath);
100
+ // Initial build and deploy
101
+ await this.performInitialDeploy(entryPath);
102
+ // Keep process alive
103
+ this.output.showInfo('Watching for changes... (Press Ctrl+C to stop)');
104
+ // Handle graceful shutdown
105
+ process.on('SIGINT', () => {
106
+ this.output.showInfo('\nStopping development mode...');
107
+ this.stopWatching();
108
+ process.exit(0);
109
+ });
110
+ // Keep the process alive indefinitely
111
+ await this.keepAlive();
112
+ return { exitCode: 0 };
113
+ }
114
+ catch (error) {
115
+ this.output.showError('Dev mode failed to start', {
116
+ cause: error.message,
117
+ stackTrace: this.verbose ? error.stack : undefined,
118
+ });
119
+ return { exitCode: 1 };
120
+ }
121
+ }
122
+ async performInitialDeploy(entryPath) {
123
+ try {
124
+ this.output.showInfo('Building initial state...');
125
+ const result = await CLIContext_1.CLIContextManager.createCLIInstance(entryPath, this.verbose);
126
+ logger.debug(`DevCommand: Initial CloudDOM built with ${result.cloudDOM.length} resources`);
127
+ // Load backend state for comparison
128
+ let previousCloudDOM = [];
129
+ try {
130
+ const backendState = await result.instance.getBackendProvider().getState(result.stackName);
131
+ previousCloudDOM = backendState?.cloudDOM || [];
132
+ logger.debug(`DevCommand: Previous CloudDOM loaded with ${previousCloudDOM.length} resources`);
133
+ }
134
+ catch (error) {
135
+ logger.debug(`DevCommand: Could not load previous state: ${error.message}`);
136
+ }
137
+ // Compute diff using Reconciler
138
+ const reconciler = new Reconciler_1.Reconciler();
139
+ const changeSet = reconciler.reconcile(previousCloudDOM, result.cloudDOM);
140
+ const totalChanges = (0, Reconciler_1.getTotalChanges)(changeSet);
141
+ logger.debug(`DevCommand: Total changes: ${totalChanges}`);
142
+ // Store state for future comparisons
143
+ this.state.lastCloudDOM = result.cloudDOM;
144
+ this.state.lastStackName = result.stackName;
145
+ this.state.instance = result.instance;
146
+ // Check if there are any changes
147
+ if (totalChanges === 0) {
148
+ this.output.showSuccess('No changes detected - infrastructure is up to date');
149
+ return;
150
+ }
151
+ this.output.showSuccess(`Changes detected: ${totalChanges} changes`);
152
+ // Show plan
153
+ this.output.showPlanHeader(result.stackName);
154
+ this.output.showPlanChanges(changeSet);
155
+ this.output.showPlanSummary(changeSet);
156
+ if (this.autoApprove) {
157
+ this.output.showWarning('Auto-approving changes...');
158
+ await this.deployChanges(result, changeSet);
159
+ }
160
+ else {
161
+ const shouldDeploy = await this.promptForApproval();
162
+ if (shouldDeploy) {
163
+ await this.deployChanges(result, changeSet);
164
+ }
165
+ else {
166
+ this.output.showInfo('Deployment skipped');
167
+ }
168
+ }
169
+ }
170
+ catch (error) {
171
+ this.output.showError('Initial deployment failed', {
172
+ cause: error.message,
173
+ stackTrace: this.verbose ? error.stack : undefined,
174
+ });
175
+ }
176
+ }
177
+ async performHotReload(entryPath) {
178
+ try {
179
+ this.output.showHotReloadStart();
180
+ if (!this.state.lastCloudDOM || !this.state.instance) {
181
+ this.output.showError('No previous state found');
182
+ return;
183
+ }
184
+ // Create new instance
185
+ const result = await CLIContext_1.CLIContextManager.createCLIInstance(entryPath, this.verbose);
186
+ logger.debug(`DevCommand: Hot reload CloudDOM built with ${result.cloudDOM.length} resources`);
187
+ // Compute diff using Reconciler
188
+ const reconciler = new Reconciler_1.Reconciler();
189
+ const changeSet = reconciler.reconcile(this.state.lastCloudDOM, result.cloudDOM);
190
+ const totalChanges = (0, Reconciler_1.getTotalChanges)(changeSet);
191
+ logger.debug(`DevCommand: Hot reload total changes: ${totalChanges}`);
192
+ // Always update state to preserve reactive changes
193
+ this.state.lastCloudDOM = result.cloudDOM;
194
+ this.state.instance = result.instance;
195
+ // Check if there are any changes
196
+ if (totalChanges === 0) {
197
+ this.output.showSuccess('No changes detected');
198
+ return;
199
+ }
200
+ this.output.showSuccess(`Changes detected: ${totalChanges} changes`);
201
+ // Show plan
202
+ this.output.showPlanHeader(result.stackName);
203
+ this.output.showPlanChanges(changeSet);
204
+ this.output.showPlanSummary(changeSet);
205
+ if (this.autoApprove) {
206
+ this.output.showWarning('Auto-approving changes...');
207
+ await this.deployChanges(result, changeSet);
208
+ }
209
+ else {
210
+ const shouldDeploy = await this.promptForApproval();
211
+ if (shouldDeploy) {
212
+ await this.deployChanges(result, changeSet);
213
+ }
214
+ else {
215
+ this.output.showInfo('Changes skipped');
216
+ }
217
+ }
218
+ }
219
+ catch (error) {
220
+ this.output.showError('Hot reload failed', {
221
+ cause: error.message,
222
+ stackTrace: this.verbose ? error.stack : undefined,
223
+ });
224
+ }
225
+ }
226
+ async deployChanges(result, changeSet) {
227
+ try {
228
+ // Reactive deployment loop - continue until no more changes
229
+ let deploymentCycle = 1;
230
+ let hasMoreChanges = true;
231
+ let currentCloudDOM = result.cloudDOM;
232
+ while (hasMoreChanges) {
233
+ if (deploymentCycle > 1) {
234
+ this.output.showInfo(`\nReactive deployment cycle #${deploymentCycle}`);
235
+ }
236
+ this.output.showDeployHeader();
237
+ const startTime = Date.now();
238
+ // Deploy
239
+ await result.instance.deploy(currentCloudDOM, result.stackName, 'dev-user');
240
+ const duration = (Date.now() - startTime) / 1000;
241
+ // Update state
242
+ this.state.lastCloudDOM = currentCloudDOM;
243
+ const deployResult = {
244
+ resourceCount: currentCloudDOM.length,
245
+ duration,
246
+ creates: changeSet.creates.length,
247
+ updates: changeSet.updates.length,
248
+ deletes: changeSet.deletes.length,
249
+ };
250
+ this.output.showDeploySummary(deployResult);
251
+ // Check if reactive changes were detected
252
+ const reactiveInfo = await result.instance.getReactiveDeploymentInfo(result.stackName);
253
+ if (reactiveInfo && (0, Reconciler_1.getTotalChanges)(reactiveInfo.changeSet) > 0) {
254
+ this.output.showReactiveChangesDetected();
255
+ this.output.showPlanChanges(reactiveInfo.changeSet);
256
+ this.output.showPlanSummary(reactiveInfo.changeSet);
257
+ let shouldContinue = this.autoApprove;
258
+ if (!this.autoApprove) {
259
+ shouldContinue = await this.promptForApproval();
260
+ }
261
+ else {
262
+ this.output.showWarning('Auto-approving reactive changes...');
263
+ }
264
+ if (shouldContinue) {
265
+ currentCloudDOM = reactiveInfo.cloudDOM;
266
+ changeSet = reactiveInfo.changeSet;
267
+ deploymentCycle++;
268
+ }
269
+ else {
270
+ this.output.showInfo('Reactive changes skipped');
271
+ hasMoreChanges = false;
272
+ }
273
+ }
274
+ else {
275
+ hasMoreChanges = false;
276
+ }
277
+ }
278
+ }
279
+ catch (error) {
280
+ this.output.showError('Deployment failed', {
281
+ cause: error.message,
282
+ stackTrace: this.verbose ? error.stack : undefined,
283
+ });
284
+ }
285
+ }
286
+ async promptForApproval() {
287
+ // Close any existing readline interface
288
+ if (this.currentReadline) {
289
+ this.currentReadline.close();
290
+ this.currentReadline = null;
291
+ }
292
+ const rl = (0, readline_1.createInterface)({
293
+ input: process.stdin,
294
+ output: process.stdout,
295
+ });
296
+ this.currentReadline = rl;
297
+ return new Promise((resolve) => {
298
+ rl.question(chalk_1.default.bold('Deploy these changes? (y/N/a=auto-approve): '), (answer) => {
299
+ this.currentReadline = null;
300
+ rl.close();
301
+ const response = answer.toLowerCase().trim();
302
+ if (response === 'a' || response === 'auto') {
303
+ this.output.showWarning('Switching to auto-approve mode');
304
+ this.autoApprove = true;
305
+ resolve(true);
306
+ }
307
+ else if (response === 'y' || response === 'yes') {
308
+ resolve(true);
309
+ }
310
+ else {
311
+ resolve(false);
312
+ }
313
+ });
314
+ });
315
+ }
316
+ startWatching(entryPath) {
317
+ if (this.isWatching)
318
+ return;
319
+ this.isWatching = true;
320
+ const absoluteEntryPath = (0, path_1.resolve)(process.cwd(), entryPath);
321
+ const entryDir = (0, path_1.dirname)(absoluteEntryPath);
322
+ logger.debug(`DevCommand: Watching directory: ${entryDir}`);
323
+ try {
324
+ (0, fs_1.watch)(entryDir, { recursive: true }, (eventType, filename) => {
325
+ if (!filename)
326
+ return;
327
+ // Skip certain file types
328
+ if (this.shouldIgnoreFile(filename)) {
329
+ return;
330
+ }
331
+ logger.debug(`DevCommand: File event: ${eventType} - ${filename}`);
332
+ // Debounce file changes
333
+ if (this.watchTimeout) {
334
+ clearTimeout(this.watchTimeout);
335
+ }
336
+ this.watchTimeout = setTimeout(() => {
337
+ // Skip hot reload if we don't have initial state yet
338
+ if (!this.state.lastCloudDOM) {
339
+ logger.debug('DevCommand: Skipping hot reload - waiting for initial deployment');
340
+ return;
341
+ }
342
+ // Cancel any pending prompts
343
+ if (this.currentReadline) {
344
+ this.output.showInfo('\nNew changes detected, canceling previous prompt...');
345
+ this.currentReadline.close();
346
+ this.currentReadline = null;
347
+ }
348
+ this.output.showFileChanged(filename);
349
+ this.performHotReload(entryPath);
350
+ }, 300);
351
+ });
352
+ logger.debug('DevCommand: File watcher started successfully');
353
+ }
354
+ catch (error) {
355
+ logger.warn(`DevCommand: Could not watch ${entryDir}: ${error.message}`);
356
+ }
357
+ }
358
+ shouldIgnoreFile(filename) {
359
+ // Only watch TypeScript and JavaScript files
360
+ const allowedExtensions = ['.ts', '.tsx', '.js', '.jsx'];
361
+ const hasAllowedExtension = allowedExtensions.some((ext) => filename.endsWith(ext));
362
+ if (!hasAllowedExtension) {
363
+ return true;
364
+ }
365
+ // Ignore certain patterns
366
+ const ignoredPatterns = ['node_modules', '.git', 'dist', '.next', '.cache', 'test', 'spec'];
367
+ if (ignoredPatterns.some((pattern) => filename.includes(pattern))) {
368
+ return true;
369
+ }
370
+ return false;
371
+ }
372
+ async keepAlive() {
373
+ return new Promise(() => {
374
+ // This promise never resolves, keeping the process alive
375
+ });
376
+ }
377
+ stopWatching() {
378
+ this.isWatching = false;
379
+ if (this.watchTimeout) {
380
+ clearTimeout(this.watchTimeout);
381
+ this.watchTimeout = null;
382
+ }
383
+ }
384
+ }
385
+ exports.DevCommand = DevCommand;
@@ -0,0 +1,39 @@
1
+ /**
2
+
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+
5
+ * you may not use this file except in compliance with the License.
6
+
7
+ * You may obtain a copy of the License at
8
+
9
+ *
10
+
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ *
14
+
15
+ * Unless required by applicable law or agreed to in writing, software
16
+
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+
21
+ * See the License for the specific language governing permissions and
22
+
23
+ * limitations under the License.
24
+
25
+ *
26
+
27
+ * Copyright 2025 Daniel Coutinho Ribeiro
28
+
29
+ */
30
+ /**
31
+ * Plan Command - shows diff between current and previous CloudDOM
32
+ */
33
+ import { BaseCommand, CommandResult } from '../core/BaseCommand';
34
+ export declare class PlanCommand extends BaseCommand {
35
+ getName(): string;
36
+ getDescription(): string;
37
+ execute(): Promise<CommandResult>;
38
+ private printVerboseOutput;
39
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /**
3
+
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+
6
+ * you may not use this file except in compliance with the License.
7
+
8
+ * You may obtain a copy of the License at
9
+
10
+ *
11
+
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ *
15
+
16
+ * Unless required by applicable law or agreed to in writing, software
17
+
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+
22
+ * See the License for the specific language governing permissions and
23
+
24
+ * limitations under the License.
25
+
26
+ *
27
+
28
+ * Copyright 2025 Daniel Coutinho Ribeiro
29
+
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.PlanCommand = void 0;
33
+ /**
34
+ * Plan Command - shows diff between current and previous CloudDOM
35
+ */
36
+ const BaseCommand_1 = require("../core/BaseCommand");
37
+ const CLIContext_1 = require("../core/CLIContext");
38
+ const Output_1 = require("../../utils/Output");
39
+ const Reconciler_1 = require("../../core/Reconciler");
40
+ const Logger_1 = require("../../utils/Logger");
41
+ const logger = Logger_1.LoggerFactory.getLogger('cli');
42
+ class PlanCommand extends BaseCommand_1.BaseCommand {
43
+ getName() {
44
+ return 'plan';
45
+ }
46
+ getDescription() {
47
+ return 'Show diff between current and previous CloudDOM';
48
+ }
49
+ async execute() {
50
+ const output = (0, Output_1.createOutputManager)({
51
+ json: this.json,
52
+ quiet: !!this.context.flags.quiet,
53
+ verbose: this.verbose,
54
+ });
55
+ try {
56
+ // Find entry file
57
+ logger.debug('PlanCommand: Starting execution');
58
+ let entryPath;
59
+ try {
60
+ entryPath = CLIContext_1.CLIContextManager.findEntryFile(this.context.flags.entry);
61
+ logger.debug(`PlanCommand: Entry file resolved to ${entryPath}`);
62
+ }
63
+ catch (error) {
64
+ output.showError('Entry file resolution failed', {
65
+ cause: error.message,
66
+ stackTrace: this.verbose ? error.stack : undefined,
67
+ });
68
+ return { exitCode: 1 };
69
+ }
70
+ // Load entry file and create CLI instance
71
+ output.showInfo('Loading entry file and configuring providers...');
72
+ let result;
73
+ try {
74
+ result = await CLIContext_1.CLIContextManager.createCLIInstance(entryPath, this.verbose);
75
+ logger.debug(`PlanCommand: Current CloudDOM built with ${result.cloudDOM.length} resources`);
76
+ }
77
+ catch (error) {
78
+ output.showError('Failed to load entry file and configure providers', {
79
+ cause: error.message,
80
+ stackTrace: this.verbose ? error.stack : undefined,
81
+ });
82
+ return { exitCode: 1 };
83
+ }
84
+ output.showSuccess('Entry file loaded and providers configured');
85
+ // Load previous CloudDOM from backend
86
+ output.showInfo('Loading previous state...');
87
+ let previousCloudDOM = [];
88
+ try {
89
+ const previousState = await result.instance.getBackendProvider().getState(result.stackName);
90
+ if (previousState && previousState.cloudDOM) {
91
+ previousCloudDOM = previousState.cloudDOM;
92
+ logger.debug(`PlanCommand: Previous CloudDOM loaded with ${previousCloudDOM.length} resources`);
93
+ }
94
+ else {
95
+ logger.debug('PlanCommand: No previous state found (first deployment)');
96
+ }
97
+ }
98
+ catch (stateError) {
99
+ logger.debug(`PlanCommand: Could not load previous state: ${stateError.message}`);
100
+ // Continue with empty previous state
101
+ }
102
+ output.showSuccess('Previous state loaded');
103
+ // Compute diff using Reconciler
104
+ output.showInfo('Computing changes...');
105
+ const reconciler = new Reconciler_1.Reconciler();
106
+ const changeSet = reconciler.reconcile(previousCloudDOM, result.cloudDOM);
107
+ logger.debug(`PlanCommand: Changes computed: ${changeSet.creates.length} creates, ${changeSet.updates.length} updates, ${changeSet.deletes.length} deletes, ${changeSet.replacements.length} replacements`);
108
+ output.showSuccess('Changes computed');
109
+ // Check if there are any changes
110
+ const hasChanges = changeSet.creates.length > 0 ||
111
+ changeSet.updates.length > 0 ||
112
+ changeSet.deletes.length > 0 ||
113
+ changeSet.replacements.length > 0 ||
114
+ changeSet.moves.length > 0;
115
+ // Display plan
116
+ output.showPlanHeader(result.stackName);
117
+ output.showPlanChanges(changeSet);
118
+ output.showPlanSummary(changeSet);
119
+ if (this.verbose && !this.json) {
120
+ this.printVerboseOutput(changeSet);
121
+ }
122
+ if (!hasChanges) {
123
+ return { exitCode: 0 };
124
+ }
125
+ // Output results
126
+ const totalChanges = changeSet.creates.length +
127
+ changeSet.updates.length +
128
+ changeSet.deletes.length +
129
+ changeSet.replacements.length +
130
+ changeSet.moves.length;
131
+ if (this.json) {
132
+ const diffVisualization = reconciler.generateDiffVisualization(changeSet);
133
+ console.log(JSON.stringify({
134
+ status: 'success',
135
+ message: `Plan complete: ${totalChanges} changes`,
136
+ summary: diffVisualization.summary,
137
+ changes: diffVisualization.changes,
138
+ deployment: diffVisualization.deployment,
139
+ }, null, 2));
140
+ }
141
+ return { exitCode: 0 };
142
+ }
143
+ catch (error) {
144
+ output.showError('Plan failed', {
145
+ cause: error.message,
146
+ stackTrace: this.verbose ? error.stack : undefined,
147
+ });
148
+ return { exitCode: 1 };
149
+ }
150
+ }
151
+ printVerboseOutput(changeSet) {
152
+ console.log('\nDeployment Order:');
153
+ changeSet.deploymentOrder.forEach((nodeId, index) => {
154
+ console.log(` ${String(index + 1).padStart(2)}. ${nodeId}`);
155
+ });
156
+ if (changeSet.parallelBatches.length > 1) {
157
+ console.log('\nParallel Batches:');
158
+ changeSet.parallelBatches.forEach((batch, index) => {
159
+ console.log(` Batch ${index + 1}: ${batch.join(', ')}`);
160
+ });
161
+ }
162
+ }
163
+ }
164
+ exports.PlanCommand = PlanCommand;
@@ -0,0 +1,36 @@
1
+ /**
2
+
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+
5
+ * you may not use this file except in compliance with the License.
6
+
7
+ * You may obtain a copy of the License at
8
+
9
+ *
10
+
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ *
14
+
15
+ * Unless required by applicable law or agreed to in writing, software
16
+
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+
19
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+
21
+ * See the License for the specific language governing permissions and
22
+
23
+ * limitations under the License.
24
+
25
+ *
26
+
27
+ * Copyright 2025 Daniel Coutinho Ribeiro
28
+
29
+ */
30
+ /**
31
+ * Command exports - centralized command registration
32
+ */
33
+ export { BuildCommand } from './BuildCommand';
34
+ export { PlanCommand } from './PlanCommand';
35
+ export { DeployCommand } from './DeployCommand';
36
+ export { DevCommand } from './DevCommand';
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+
6
+ * you may not use this file except in compliance with the License.
7
+
8
+ * You may obtain a copy of the License at
9
+
10
+ *
11
+
12
+ * http://www.apache.org/licenses/LICENSE-2.0
13
+
14
+ *
15
+
16
+ * Unless required by applicable law or agreed to in writing, software
17
+
18
+ * distributed under the License is distributed on an "AS IS" BASIS,
19
+
20
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+
22
+ * See the License for the specific language governing permissions and
23
+
24
+ * limitations under the License.
25
+
26
+ *
27
+
28
+ * Copyright 2025 Daniel Coutinho Ribeiro
29
+
30
+ */
31
+ Object.defineProperty(exports, "__esModule", { value: true });
32
+ exports.DevCommand = exports.DeployCommand = exports.PlanCommand = exports.BuildCommand = void 0;
33
+ /**
34
+ * Command exports - centralized command registration
35
+ */
36
+ var BuildCommand_1 = require("./BuildCommand");
37
+ Object.defineProperty(exports, "BuildCommand", { enumerable: true, get: function () { return BuildCommand_1.BuildCommand; } });
38
+ var PlanCommand_1 = require("./PlanCommand");
39
+ Object.defineProperty(exports, "PlanCommand", { enumerable: true, get: function () { return PlanCommand_1.PlanCommand; } });
40
+ var DeployCommand_1 = require("./DeployCommand");
41
+ Object.defineProperty(exports, "DeployCommand", { enumerable: true, get: function () { return DeployCommand_1.DeployCommand; } });
42
+ var DevCommand_1 = require("./DevCommand");
43
+ Object.defineProperty(exports, "DevCommand", { enumerable: true, get: function () { return DevCommand_1.DevCommand; } });