@friggframework/devtools 2.0.0--canary.474.a74bb09.0 → 2.0.0--canary.474.295ae1f.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.
- package/frigg-cli/{CLI.md → README.md} +99 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/index.js +74 -0
- package/frigg-cli/package.json +6 -2
- package/infrastructure/DOCTOR.md +468 -0
- package/package.json +6 -9
- package/FRIGG_DOCTOR_USAGE.md +0 -217
- package/TDD_PROOF.md +0 -254
- package/infrastructure/MULTI_CLOUD_ARCHITECTURE.md +0 -703
|
@@ -1189,3 +1189,102 @@ frigg --version # Show version
|
|
|
1189
1189
|
- GitHub Issues: https://github.com/friggframework/frigg/issues
|
|
1190
1190
|
- Documentation: https://docs.friggframework.org
|
|
1191
1191
|
- Slack: Join via https://friggframework.org/#contact
|
|
1192
|
+
|
|
1193
|
+
---
|
|
1194
|
+
|
|
1195
|
+
## Package Structure & Global Installation
|
|
1196
|
+
|
|
1197
|
+
### Overview
|
|
1198
|
+
|
|
1199
|
+
The Frigg CLI is a standalone globally-installable npm package that can be installed via `npm i -g @friggframework/frigg-cli`. It includes version detection logic to automatically prefer local project installations when available.
|
|
1200
|
+
|
|
1201
|
+
### Package Structure
|
|
1202
|
+
|
|
1203
|
+
**@friggframework/frigg-cli** (standalone package)
|
|
1204
|
+
```
|
|
1205
|
+
├── package.json
|
|
1206
|
+
│ ├── dependencies: @friggframework/core@^2.0.0-next.0
|
|
1207
|
+
│ ├── dependencies: @friggframework/devtools@^2.0.0-next.0
|
|
1208
|
+
│ └── publishConfig: { access: "public" }
|
|
1209
|
+
└── index.js (with version detection wrapper)
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
**Key Changes from Previous Structure:**
|
|
1213
|
+
- ✅ Replaced `workspace:*` with concrete versions (`^2.0.0-next.0`)
|
|
1214
|
+
- ✅ Added `@friggframework/devtools` as dependency
|
|
1215
|
+
- ✅ Added `publishConfig` for public npm publishing
|
|
1216
|
+
- ✅ Removed bin entry from devtools package.json
|
|
1217
|
+
|
|
1218
|
+
**Note:** Since `@friggframework/devtools` is a dependency, the global install size includes devtools. The main benefit is proper version resolution (no `workspace:*` errors) and automatic local CLI preference.
|
|
1219
|
+
|
|
1220
|
+
### Version Detection Logic
|
|
1221
|
+
|
|
1222
|
+
When you run `frigg` (globally installed), the CLI:
|
|
1223
|
+
|
|
1224
|
+
1. **Checks for skip flag**
|
|
1225
|
+
- If `FRIGG_CLI_SKIP_VERSION_CHECK=true`, skips detection (prevents recursion)
|
|
1226
|
+
|
|
1227
|
+
2. **Looks for local installation**
|
|
1228
|
+
- Searches for `node_modules/@friggframework/frigg-cli` in current directory
|
|
1229
|
+
|
|
1230
|
+
3. **Compares versions**
|
|
1231
|
+
- Uses `semver.compare(localVersion, globalVersion)`
|
|
1232
|
+
|
|
1233
|
+
4. **Makes decision**:
|
|
1234
|
+
- **Local ≥ Global**: Uses local CLI (spawns subprocess)
|
|
1235
|
+
- **Global > Local**: Warns and uses global
|
|
1236
|
+
- **No local**: Uses global silently
|
|
1237
|
+
|
|
1238
|
+
### Benefits
|
|
1239
|
+
|
|
1240
|
+
| Aspect | Before | After |
|
|
1241
|
+
|--------|--------|-------|
|
|
1242
|
+
| **Global Install** | `npm i -g @friggframework/devtools` | `npm i -g @friggframework/frigg-cli` |
|
|
1243
|
+
| **Dependencies** | workspace:* (broken) | Concrete versions (works) |
|
|
1244
|
+
| **Version Preference** | No detection | Automatic local preference |
|
|
1245
|
+
| **Version Warnings** | None | Mismatch alerts |
|
|
1246
|
+
| **Publishability** | ❌ Fails | ✅ Works |
|
|
1247
|
+
| **Local Project Isolation** | ❌ Uses global versions | ✅ Uses local versions when available |
|
|
1248
|
+
|
|
1249
|
+
### Publishing Workflow
|
|
1250
|
+
|
|
1251
|
+
```bash
|
|
1252
|
+
# Publish to npm
|
|
1253
|
+
cd packages/devtools/frigg-cli
|
|
1254
|
+
npm version patch # or minor, major
|
|
1255
|
+
npm publish
|
|
1256
|
+
|
|
1257
|
+
# Users can now install globally
|
|
1258
|
+
npm install -g @friggframework/frigg-cli
|
|
1259
|
+
|
|
1260
|
+
# And it will automatically use local versions when available
|
|
1261
|
+
```
|
|
1262
|
+
|
|
1263
|
+
### Migration Path
|
|
1264
|
+
|
|
1265
|
+
**For existing users:**
|
|
1266
|
+
|
|
1267
|
+
```bash
|
|
1268
|
+
# Step 1: Uninstall old global devtools
|
|
1269
|
+
npm uninstall -g @friggframework/devtools
|
|
1270
|
+
|
|
1271
|
+
# Step 2: Install new global CLI
|
|
1272
|
+
npm install -g @friggframework/frigg-cli
|
|
1273
|
+
|
|
1274
|
+
# Step 3: Update local project dependencies
|
|
1275
|
+
npm install @friggframework/frigg-cli@latest
|
|
1276
|
+
```
|
|
1277
|
+
|
|
1278
|
+
**For new projects:**
|
|
1279
|
+
|
|
1280
|
+
```bash
|
|
1281
|
+
# Global CLI (once per machine)
|
|
1282
|
+
npm install -g @friggframework/frigg-cli
|
|
1283
|
+
|
|
1284
|
+
# Local project dependencies
|
|
1285
|
+
npx create-frigg-app my-app
|
|
1286
|
+
# (Will automatically include @friggframework/frigg-cli in package.json)
|
|
1287
|
+
```
|
|
1288
|
+
|
|
1289
|
+
**Status:** ✅ Complete and tested (15 passing tests)
|
|
1290
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Detection Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the CLI version detection wrapper that prefers local installations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const semver = require('semver');
|
|
10
|
+
|
|
11
|
+
describe('Version Detection Logic', () => {
|
|
12
|
+
describe('semver comparison', () => {
|
|
13
|
+
test('should prefer local when local version is newer', () => {
|
|
14
|
+
const localVersion = '2.1.0';
|
|
15
|
+
const globalVersion = '2.0.0';
|
|
16
|
+
|
|
17
|
+
const comparison = semver.compare(localVersion, globalVersion);
|
|
18
|
+
|
|
19
|
+
expect(comparison).toBeGreaterThan(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should prefer local when versions are equal', () => {
|
|
23
|
+
const localVersion = '2.0.0';
|
|
24
|
+
const globalVersion = '2.0.0';
|
|
25
|
+
|
|
26
|
+
const comparison = semver.compare(localVersion, globalVersion);
|
|
27
|
+
|
|
28
|
+
expect(comparison).toBe(0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should warn when global is newer than local', () => {
|
|
32
|
+
const localVersion = '2.0.0';
|
|
33
|
+
const globalVersion = '2.1.0';
|
|
34
|
+
|
|
35
|
+
const comparison = semver.compare(localVersion, globalVersion);
|
|
36
|
+
|
|
37
|
+
expect(comparison).toBeLessThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should handle prerelease versions correctly', () => {
|
|
41
|
+
const localVersion = '2.0.0-next.1';
|
|
42
|
+
const globalVersion = '2.0.0-next.0';
|
|
43
|
+
|
|
44
|
+
const comparison = semver.compare(localVersion, globalVersion);
|
|
45
|
+
|
|
46
|
+
expect(comparison).toBeGreaterThan(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('should prefer release over prerelease', () => {
|
|
50
|
+
const localVersion = '2.0.0';
|
|
51
|
+
const globalVersion = '2.0.0-next.0';
|
|
52
|
+
|
|
53
|
+
const comparison = semver.compare(localVersion, globalVersion);
|
|
54
|
+
|
|
55
|
+
expect(comparison).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('local CLI detection', () => {
|
|
60
|
+
test('should find local installation in node_modules', () => {
|
|
61
|
+
const cwd = process.cwd();
|
|
62
|
+
const localCliPath = path.join(cwd, 'node_modules', '@friggframework', 'frigg-cli');
|
|
63
|
+
|
|
64
|
+
// This test validates the path construction logic
|
|
65
|
+
expect(localCliPath).toContain('node_modules');
|
|
66
|
+
expect(localCliPath).toContain('@friggframework');
|
|
67
|
+
expect(localCliPath).toContain('frigg-cli');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('should construct correct paths for package.json and index.js', () => {
|
|
71
|
+
const cwd = process.cwd();
|
|
72
|
+
const localCliPath = path.join(cwd, 'node_modules', '@friggframework', 'frigg-cli');
|
|
73
|
+
const localCliPackageJson = path.join(localCliPath, 'package.json');
|
|
74
|
+
const localCliIndex = path.join(localCliPath, 'index.js');
|
|
75
|
+
|
|
76
|
+
expect(localCliPackageJson).toContain('package.json');
|
|
77
|
+
expect(localCliIndex).toContain('index.js');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('environment variable check', () => {
|
|
82
|
+
test('should skip version check when FRIGG_CLI_SKIP_VERSION_CHECK is true', () => {
|
|
83
|
+
const originalEnv = process.env.FRIGG_CLI_SKIP_VERSION_CHECK;
|
|
84
|
+
|
|
85
|
+
process.env.FRIGG_CLI_SKIP_VERSION_CHECK = 'true';
|
|
86
|
+
const shouldSkip = process.env.FRIGG_CLI_SKIP_VERSION_CHECK === 'true';
|
|
87
|
+
expect(shouldSkip).toBe(true);
|
|
88
|
+
|
|
89
|
+
// Restore
|
|
90
|
+
if (originalEnv !== undefined) {
|
|
91
|
+
process.env.FRIGG_CLI_SKIP_VERSION_CHECK = originalEnv;
|
|
92
|
+
} else {
|
|
93
|
+
delete process.env.FRIGG_CLI_SKIP_VERSION_CHECK;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should not skip when environment variable is not set', () => {
|
|
98
|
+
const originalEnv = process.env.FRIGG_CLI_SKIP_VERSION_CHECK;
|
|
99
|
+
delete process.env.FRIGG_CLI_SKIP_VERSION_CHECK;
|
|
100
|
+
|
|
101
|
+
const shouldSkip = process.env.FRIGG_CLI_SKIP_VERSION_CHECK === 'true';
|
|
102
|
+
expect(shouldSkip).toBe(false);
|
|
103
|
+
|
|
104
|
+
// Restore
|
|
105
|
+
if (originalEnv !== undefined) {
|
|
106
|
+
process.env.FRIGG_CLI_SKIP_VERSION_CHECK = originalEnv;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
describe('version detection decision matrix', () => {
|
|
112
|
+
const scenarios = [
|
|
113
|
+
{
|
|
114
|
+
name: 'local newer than global',
|
|
115
|
+
local: '2.1.0',
|
|
116
|
+
global: '2.0.0',
|
|
117
|
+
expected: 'use_local',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'local equal to global',
|
|
121
|
+
local: '2.0.0',
|
|
122
|
+
global: '2.0.0',
|
|
123
|
+
expected: 'use_local',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'global newer than local',
|
|
127
|
+
local: '2.0.0',
|
|
128
|
+
global: '2.1.0',
|
|
129
|
+
expected: 'warn_and_use_global',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'local prerelease newer',
|
|
133
|
+
local: '2.0.0-next.1',
|
|
134
|
+
global: '2.0.0-next.0',
|
|
135
|
+
expected: 'use_local',
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
scenarios.forEach(({ name, local, global, expected }) => {
|
|
140
|
+
test(`should ${expected.replace('_', ' ')} when ${name}`, () => {
|
|
141
|
+
const comparison = semver.compare(local, global);
|
|
142
|
+
|
|
143
|
+
if (expected === 'use_local') {
|
|
144
|
+
expect(comparison).toBeGreaterThanOrEqual(0);
|
|
145
|
+
} else if (expected === 'warn_and_use_global') {
|
|
146
|
+
expect(comparison).toBeLessThan(0);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('argument forwarding', () => {
|
|
153
|
+
test('should extract arguments correctly from process.argv', () => {
|
|
154
|
+
// Simulate process.argv
|
|
155
|
+
const mockArgv = ['node', '/path/to/frigg', 'doctor', 'my-stack', '--region', 'us-east-1'];
|
|
156
|
+
const args = mockArgv.slice(2);
|
|
157
|
+
|
|
158
|
+
expect(args).toEqual(['doctor', 'my-stack', '--region', 'us-east-1']);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('should forward all arguments to local CLI', () => {
|
|
162
|
+
const mockArgv = ['node', '/path/to/frigg', 'repair', 'my-stack', '--import', '--yes'];
|
|
163
|
+
const args = mockArgv.slice(2);
|
|
164
|
+
|
|
165
|
+
expect(args).toContain('repair');
|
|
166
|
+
expect(args).toContain('my-stack');
|
|
167
|
+
expect(args).toContain('--import');
|
|
168
|
+
expect(args).toContain('--yes');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|
package/frigg-cli/index.js
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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
|
+
|
|
3
77
|
const { Command } = require('commander');
|
|
4
78
|
const { initCommand } = require('./init-command');
|
|
5
79
|
const { installCommand } = require('./install-command');
|
package/frigg-cli/package.json
CHANGED
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@babel/parser": "^7.25.3",
|
|
14
14
|
"@babel/traverse": "^7.25.3",
|
|
15
|
-
"@friggframework/core": "
|
|
16
|
-
"@friggframework/
|
|
15
|
+
"@friggframework/core": "^2.0.0-next.0",
|
|
16
|
+
"@friggframework/devtools": "^2.0.0-next.0",
|
|
17
|
+
"@friggframework/schemas": "^2.0.0-next.0",
|
|
17
18
|
"@inquirer/prompts": "^5.3.8",
|
|
18
19
|
"axios": "^1.7.2",
|
|
19
20
|
"chalk": "^4.1.2",
|
|
@@ -50,5 +51,8 @@
|
|
|
50
51
|
"homepage": "https://github.com/friggframework/frigg#readme",
|
|
51
52
|
"engines": {
|
|
52
53
|
"node": ">=18"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
53
57
|
}
|
|
54
58
|
}
|