@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.
@@ -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
+ });
@@ -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');
@@ -12,8 +12,9 @@
12
12
  "dependencies": {
13
13
  "@babel/parser": "^7.25.3",
14
14
  "@babel/traverse": "^7.25.3",
15
- "@friggframework/core": "workspace:*",
16
- "@friggframework/schemas": "workspace:*",
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
  }