@friggframework/devtools 2.0.0-next.47 → 2.0.0-next.48

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 (69) hide show
  1. package/frigg-cli/README.md +1290 -0
  2. package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
  3. package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
  4. package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
  5. package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
  6. package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
  7. package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
  8. package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
  9. package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
  10. package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
  11. package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
  12. package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
  13. package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
  14. package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
  15. package/frigg-cli/__tests__/utils/test-setup.js +287 -0
  16. package/frigg-cli/build-command/index.js +66 -0
  17. package/frigg-cli/db-setup-command/index.js +193 -0
  18. package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
  19. package/frigg-cli/deploy-command/index.js +302 -0
  20. package/frigg-cli/doctor-command/index.js +335 -0
  21. package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
  22. package/frigg-cli/generate-command/azure-generator.js +43 -0
  23. package/frigg-cli/generate-command/gcp-generator.js +47 -0
  24. package/frigg-cli/generate-command/index.js +332 -0
  25. package/frigg-cli/generate-command/terraform-generator.js +555 -0
  26. package/frigg-cli/generate-iam-command.js +118 -0
  27. package/frigg-cli/index.js +173 -0
  28. package/frigg-cli/index.test.js +158 -0
  29. package/frigg-cli/init-command/backend-first-handler.js +756 -0
  30. package/frigg-cli/init-command/index.js +93 -0
  31. package/frigg-cli/init-command/template-handler.js +143 -0
  32. package/frigg-cli/install-command/backend-js.js +33 -0
  33. package/frigg-cli/install-command/commit-changes.js +16 -0
  34. package/frigg-cli/install-command/environment-variables.js +127 -0
  35. package/frigg-cli/install-command/environment-variables.test.js +136 -0
  36. package/frigg-cli/install-command/index.js +54 -0
  37. package/frigg-cli/install-command/install-package.js +13 -0
  38. package/frigg-cli/install-command/integration-file.js +30 -0
  39. package/frigg-cli/install-command/logger.js +12 -0
  40. package/frigg-cli/install-command/template.js +90 -0
  41. package/frigg-cli/install-command/validate-package.js +75 -0
  42. package/frigg-cli/jest.config.js +124 -0
  43. package/frigg-cli/package.json +63 -0
  44. package/frigg-cli/repair-command/index.js +564 -0
  45. package/frigg-cli/start-command/index.js +149 -0
  46. package/frigg-cli/start-command/start-command.test.js +297 -0
  47. package/frigg-cli/test/init-command.test.js +180 -0
  48. package/frigg-cli/test/npm-registry.test.js +319 -0
  49. package/frigg-cli/ui-command/index.js +154 -0
  50. package/frigg-cli/utils/app-resolver.js +319 -0
  51. package/frigg-cli/utils/backend-path.js +25 -0
  52. package/frigg-cli/utils/database-validator.js +154 -0
  53. package/frigg-cli/utils/error-messages.js +257 -0
  54. package/frigg-cli/utils/npm-registry.js +167 -0
  55. package/frigg-cli/utils/process-manager.js +199 -0
  56. package/frigg-cli/utils/repo-detection.js +405 -0
  57. package/infrastructure/create-frigg-infrastructure.js +125 -12
  58. package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
  59. package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
  60. package/infrastructure/domains/shared/resource-discovery.js +31 -2
  61. package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
  62. package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
  63. package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
  64. package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
  65. package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
  66. package/infrastructure/infrastructure-composer.js +22 -0
  67. package/layers/prisma/.build-complete +3 -0
  68. package/package.json +18 -7
  69. package/management-ui/package-lock.json +0 -16517
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Version Detection Wrapper
4
+ // This code runs when frigg-cli is installed globally
5
+ // It checks for a local installation and prefers it if newer
6
+ (function versionDetection() {
7
+ // Skip version detection if explicitly disabled (prevents recursion)
8
+ if (process.env.FRIGG_CLI_SKIP_VERSION_CHECK === 'true') {
9
+ return;
10
+ }
11
+
12
+ const path = require('path');
13
+ const fs = require('fs');
14
+ const semver = require('semver');
15
+ const { spawn } = require('child_process');
16
+
17
+ // Get the directory from which the command was invoked
18
+ const cwd = process.cwd();
19
+
20
+ // Try to find local frigg-cli installation
21
+ const localCliPath = path.join(cwd, 'node_modules', '@friggframework', 'frigg-cli');
22
+ const localCliPackageJson = path.join(localCliPath, 'package.json');
23
+ const localCliIndex = path.join(localCliPath, 'index.js');
24
+
25
+ // Get global version (this package)
26
+ const globalVersion = require('./package.json').version;
27
+
28
+ // Check if local installation exists
29
+ if (fs.existsSync(localCliPackageJson) && fs.existsSync(localCliIndex)) {
30
+ try {
31
+ const localPackage = JSON.parse(fs.readFileSync(localCliPackageJson, 'utf8'));
32
+ const localVersion = localPackage.version;
33
+
34
+ // Compare versions
35
+ const comparison = semver.compare(localVersion, globalVersion);
36
+
37
+ if (comparison >= 0) {
38
+ // Local version is newer or equal - use it
39
+ console.log(`Using local frigg-cli@${localVersion} (global: ${globalVersion})`);
40
+
41
+ // Execute local CLI as subprocess to avoid module resolution issues
42
+ const args = process.argv.slice(2); // Remove 'node' and script path
43
+ const child = spawn(process.execPath, [localCliIndex, ...args], {
44
+ stdio: 'inherit',
45
+ env: {
46
+ ...process.env,
47
+ FRIGG_CLI_SKIP_VERSION_CHECK: 'true', // Prevent recursion
48
+ },
49
+ });
50
+
51
+ child.on('exit', (code) => {
52
+ process.exit(code || 0);
53
+ });
54
+
55
+ // Signal that we've delegated to local CLI
56
+ process.on('SIGINT', () => {
57
+ child.kill('SIGINT');
58
+ });
59
+
60
+ // Prevent further execution
61
+ return true; // Indicates we've delegated
62
+ } else {
63
+ // Global version is newer - warn user
64
+ console.warn(`⚠️ Version mismatch: global frigg-cli@${globalVersion} is newer than local frigg-cli@${localVersion}`);
65
+ console.warn(` Consider updating local version: npm install @friggframework/frigg-cli@latest`);
66
+ }
67
+ } catch (error) {
68
+ // Failed to read local package.json or compare versions
69
+ // Continue with global version silently
70
+ }
71
+ }
72
+
73
+ // Return false to indicate we should continue with global version
74
+ return false;
75
+ })();
76
+
77
+ const { Command } = require('commander');
78
+ const { initCommand } = require('./init-command');
79
+ const { installCommand } = require('./install-command');
80
+ const { startCommand } = require('./start-command'); // Assuming you have a startCommand module
81
+ const { buildCommand } = require('./build-command');
82
+ const { deployCommand } = require('./deploy-command');
83
+ const { generateIamCommand } = require('./generate-iam-command');
84
+ const { uiCommand } = require('./ui-command');
85
+ const { dbSetupCommand } = require('./db-setup-command');
86
+ const { doctorCommand } = require('./doctor-command');
87
+ const { repairCommand } = require('./repair-command');
88
+
89
+ const program = new Command();
90
+
91
+ program
92
+ .command('init [templateName]')
93
+ .description('Initialize a new Frigg application')
94
+ .option('-t, --template <template>', 'template to use', 'backend-only')
95
+ .option('-n, --name <name>', 'project name')
96
+ .option('-d, --directory <directory>', 'target directory')
97
+ .action(initCommand);
98
+
99
+ program
100
+ .command('install [apiModuleName]')
101
+ .description('Install an API module')
102
+ .action(installCommand);
103
+
104
+ program
105
+ .command('start')
106
+ .description('Run the backend and optional frontend')
107
+ .option('-s, --stage <stage>', 'deployment stage', 'dev')
108
+ .option('-v, --verbose', 'enable verbose output')
109
+ .action(startCommand);
110
+
111
+ program
112
+ .command('build')
113
+ .description('Build the serverless application')
114
+ .option('-s, --stage <stage>', 'deployment stage', 'dev')
115
+ .option('-v, --verbose', 'enable verbose output')
116
+ .option('-p, --production', 'build for production (enables AWS discovery)')
117
+ .action(buildCommand);
118
+
119
+ program
120
+ .command('deploy')
121
+ .description('Deploy the serverless application')
122
+ .option('-s, --stage <stage>', 'deployment stage', 'dev')
123
+ .option('-v, --verbose', 'enable verbose output')
124
+ .option('-f, --force', 'force deployment (bypasses caching for layers and functions)')
125
+ .option('--skip-doctor', 'skip post-deployment health check')
126
+ .action(deployCommand);
127
+
128
+ program
129
+ .command('generate-iam')
130
+ .description('Generate IAM CloudFormation template based on app definition')
131
+ .option('-o, --output <path>', 'output directory', 'backend/infrastructure')
132
+ .option('-u, --user <name>', 'deployment user name', 'frigg-deployment-user')
133
+ .option('-s, --stack-name <name>', 'CloudFormation stack name', 'frigg-deployment-iam')
134
+ .option('-v, --verbose', 'enable verbose output')
135
+ .action(generateIamCommand);
136
+
137
+ program
138
+ .command('ui')
139
+ .description('Launch the Frigg Management UI')
140
+ .option('-p, --port <port>', 'port to run the UI on', '3210')
141
+ .option('--no-open', 'do not open browser automatically')
142
+ .action(uiCommand);
143
+
144
+ program
145
+ .command('db:setup')
146
+ .description('Set up database schema and generate Prisma client')
147
+ .option('-s, --stage <stage>', 'deployment stage', 'development')
148
+ .option('-v, --verbose', 'enable verbose output')
149
+ .action(dbSetupCommand);
150
+
151
+ program
152
+ .command('doctor [stackName]')
153
+ .description('Run health check on deployed CloudFormation stack')
154
+ .option('-r, --region <region>', 'AWS region (defaults to AWS_REGION env var or us-east-1)')
155
+ .option('-f, --format <format>', 'output format (console or json)', 'console')
156
+ .option('-o, --output <path>', 'save report to file')
157
+ .option('-v, --verbose', 'enable verbose output')
158
+ .action(doctorCommand);
159
+
160
+ program
161
+ .command('repair <stackName>')
162
+ .description('Repair infrastructure issues (import orphaned resources, reconcile property drift)')
163
+ .option('-r, --region <region>', 'AWS region (defaults to AWS_REGION env var or us-east-1)')
164
+ .option('--import', 'import orphaned resources into stack')
165
+ .option('--reconcile', 'reconcile property drift')
166
+ .option('--mode <mode>', 'reconciliation mode (template or resource)', 'template')
167
+ .option('-y, --yes', 'skip confirmation prompts')
168
+ .option('-v, --verbose', 'enable verbose output')
169
+ .action(repairCommand);
170
+
171
+ program.parse(process.argv);
172
+
173
+ module.exports = { initCommand, installCommand, startCommand, buildCommand, deployCommand, generateIamCommand, uiCommand, dbSetupCommand, doctorCommand, repairCommand };
@@ -0,0 +1,158 @@
1
+ const { Command } = require('commander');
2
+ const { installCommand } = require('./index');
3
+ const { validatePackageExists } = require('./install-command/validate-package');
4
+ const { findNearestBackendPackageJson, validateBackendPath } = require('@friggframework/core');
5
+ const { installPackage } = require('./install-command/install-package');
6
+ const { createIntegrationFile } = require('./install-command/integration-file');
7
+ const { updateBackendJsFile } = require('./install-command/backend-js');
8
+ const { commitChanges } = require('./install-command/commit-changes');
9
+ const { logInfo, logError } = require('./install-command/logger');
10
+
11
+ describe('CLI Command Tests', () => {
12
+ it('should successfully install an API module when all steps complete without errors', async () => {
13
+ const mockApiModuleName = 'testModule';
14
+ const mockPackageName = `@friggframework/api-module-${mockApiModuleName}`;
15
+ const mockBackendPath = '/mock/backend/path';
16
+
17
+ jest.mock('./install-command/validate-package', () => ({
18
+ validatePackageExists: jest.fn().mockResolvedValue(true),
19
+ }));
20
+ jest.mock('./utils/backend-path', () => ({
21
+ findNearestBackendPackageJson: jest
22
+ .fn()
23
+ .mockReturnValue(mockBackendPath),
24
+ validateBackendPath: jest.fn().mockReturnValue(true),
25
+ }));
26
+ jest.mock('./install-command/install-package', () => ({
27
+ installPackage: jest.fn().mockReturnValue(true),
28
+ }));
29
+ jest.mock('./install-command/integration-file', () => ({
30
+ createIntegrationFile: jest.fn().mockReturnValue(true),
31
+ }));
32
+ jest.mock('./install-command/backend-js', () => ({
33
+ updateBackendJsFile: jest.fn().mockReturnValue(true),
34
+ }));
35
+ jest.mock('./install-command/commit-changes', () => ({
36
+ commitChanges: jest.fn().mockReturnValue(true),
37
+ }));
38
+ jest.mock('./install-command/logger', () => ({
39
+ logInfo: jest.fn(),
40
+ logError: jest.fn(),
41
+ }));
42
+
43
+ const program = new Command();
44
+ program
45
+ .command('install <apiModuleName>')
46
+ .description('Install an API module')
47
+ .action(installCommand);
48
+
49
+ await program.parseAsync(['node', 'install', mockApiModuleName]);
50
+
51
+ expect(validatePackageExists).toHaveBeenCalledWith(mockPackageName);
52
+ expect(findNearestBackendPackageJson).toHaveBeenCalled();
53
+ expect(validateBackendPath).toHaveBeenCalledWith(mockBackendPath);
54
+ expect(installPackage).toHaveBeenCalledWith(
55
+ mockBackendPath,
56
+ mockPackageName
57
+ );
58
+ expect(createIntegrationFile).toHaveBeenCalledWith(
59
+ mockBackendPath,
60
+ mockApiModuleName
61
+ );
62
+ expect(updateBackendJsFile).toHaveBeenCalledWith(
63
+ mockBackendPath,
64
+ mockApiModuleName
65
+ );
66
+ expect(commitChanges).toHaveBeenCalledWith(
67
+ mockBackendPath,
68
+ mockApiModuleName
69
+ );
70
+ expect(logInfo).toHaveBeenCalledWith(
71
+ `Successfully installed ${mockPackageName} and updated the project.`
72
+ );
73
+ });
74
+
75
+ it('should log an error and exit with code 1 if the package does not exist', async () => {
76
+ const mockExit = jest
77
+ .spyOn(process, 'exit')
78
+ .mockImplementation(() => {});
79
+ const mockLogError = jest
80
+ .spyOn(require('./install-command/logger'), 'logError')
81
+ .mockImplementation(() => {});
82
+ const mockValidatePackageExists = jest
83
+ .spyOn(
84
+ require('./install-command/validate-package'),
85
+ 'validatePackageExists'
86
+ )
87
+ .mockImplementation(() => {
88
+ throw new Error('Package not found');
89
+ });
90
+
91
+ const program = new Command();
92
+ program
93
+ .command('install <apiModuleName>')
94
+ .description('Install an API module')
95
+ .action(installCommand);
96
+
97
+ await program.parseAsync(['node', 'install', 'nonexistent-package']);
98
+
99
+ expect(mockValidatePackageExists).toHaveBeenCalledWith(
100
+ '@friggframework/api-module-nonexistent-package'
101
+ );
102
+ expect(mockLogError).toHaveBeenCalledWith(
103
+ 'An error occurred:',
104
+ expect.any(Error)
105
+ );
106
+ expect(mockExit).toHaveBeenCalledWith(1);
107
+
108
+ mockExit.mockRestore();
109
+ mockLogError.mockRestore();
110
+ mockValidatePackageExists.mockRestore();
111
+ });
112
+
113
+ it('should log an error and exit with code 1 if the backend path is invalid', async () => {
114
+ const mockLogError = jest
115
+ .spyOn(require('./install-command/logger'), 'logError')
116
+ .mockImplementation(() => {});
117
+ const mockProcessExit = jest
118
+ .spyOn(process, 'exit')
119
+ .mockImplementation(() => {});
120
+ const mockValidatePackageExists = jest
121
+ .spyOn(
122
+ require('./install-command/validate-package'),
123
+ 'validatePackageExists'
124
+ )
125
+ .mockResolvedValue(true);
126
+ const mockFindNearestBackendPackageJson = jest
127
+ .spyOn(
128
+ require('./utils/backend-path'),
129
+ 'findNearestBackendPackageJson'
130
+ )
131
+ .mockReturnValue('/invalid/path');
132
+ const mockValidateBackendPath = jest
133
+ .spyOn(require('./utils/backend-path'), 'validateBackendPath')
134
+ .mockImplementation(() => {
135
+ throw new Error('Invalid backend path');
136
+ });
137
+
138
+ const program = new Command();
139
+ program
140
+ .command('install <apiModuleName>')
141
+ .description('Install an API module')
142
+ .action(installCommand);
143
+
144
+ await program.parseAsync(['node', 'install', 'test-module']);
145
+
146
+ expect(mockLogError).toHaveBeenCalledWith(
147
+ 'An error occurred:',
148
+ expect.any(Error)
149
+ );
150
+ expect(mockProcessExit).toHaveBeenCalledWith(1);
151
+
152
+ mockLogError.mockRestore();
153
+ mockProcessExit.mockRestore();
154
+ mockValidatePackageExists.mockRestore();
155
+ mockFindNearestBackendPackageJson.mockRestore();
156
+ mockValidateBackendPath.mockRestore();
157
+ });
158
+ });