linchpin-cli 0.2.2 → 0.2.3
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.
|
@@ -4,6 +4,13 @@ const check_1 = require("../commands/check");
|
|
|
4
4
|
// Mock dependencies
|
|
5
5
|
jest.mock('../lib/files', () => ({
|
|
6
6
|
getPackageJson: jest.fn(),
|
|
7
|
+
detectProjectContext: jest.fn().mockReturnValue({
|
|
8
|
+
isExpo: false,
|
|
9
|
+
expoVersion: undefined,
|
|
10
|
+
isMonorepo: false,
|
|
11
|
+
monorepoTool: undefined,
|
|
12
|
+
enginesNode: undefined,
|
|
13
|
+
}),
|
|
7
14
|
}));
|
|
8
15
|
jest.mock('../lib/npm', () => ({
|
|
9
16
|
getRegistryVersion: jest.fn(),
|
|
@@ -29,3 +29,67 @@ describe('getPackageJson', () => {
|
|
|
29
29
|
expect(typeof pkg?.devDependencies).toBe('object');
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
|
+
describe('detectProjectContext', () => {
|
|
33
|
+
it('detects Expo projects', () => {
|
|
34
|
+
const pkg = {
|
|
35
|
+
name: 'my-expo-app',
|
|
36
|
+
version: '1.0.0',
|
|
37
|
+
dependencies: {
|
|
38
|
+
'expo': '^52.0.0',
|
|
39
|
+
'react': '^18.2.0',
|
|
40
|
+
'react-native': '0.76.0',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const context = (0, files_1.detectProjectContext)(pkg);
|
|
44
|
+
expect(context.isExpo).toBe(true);
|
|
45
|
+
expect(context.expoVersion).toBe('52.0.0');
|
|
46
|
+
});
|
|
47
|
+
it('detects non-Expo projects', () => {
|
|
48
|
+
const pkg = {
|
|
49
|
+
name: 'my-web-app',
|
|
50
|
+
version: '1.0.0',
|
|
51
|
+
dependencies: {
|
|
52
|
+
'react': '^18.2.0',
|
|
53
|
+
'next': '^14.0.0',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const context = (0, files_1.detectProjectContext)(pkg);
|
|
57
|
+
expect(context.isExpo).toBe(false);
|
|
58
|
+
expect(context.expoVersion).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
it('detects Turborepo monorepo', () => {
|
|
61
|
+
const pkg = {
|
|
62
|
+
name: 'my-monorepo',
|
|
63
|
+
version: '1.0.0',
|
|
64
|
+
devDependencies: {
|
|
65
|
+
'turbo': '^2.0.0',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
const context = (0, files_1.detectProjectContext)(pkg);
|
|
69
|
+
expect(context.isMonorepo).toBe(true);
|
|
70
|
+
expect(context.monorepoTool).toBe('turborepo');
|
|
71
|
+
});
|
|
72
|
+
it('detects Nx monorepo', () => {
|
|
73
|
+
const pkg = {
|
|
74
|
+
name: 'my-nx-workspace',
|
|
75
|
+
version: '1.0.0',
|
|
76
|
+
devDependencies: {
|
|
77
|
+
'nx': '^18.0.0',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const context = (0, files_1.detectProjectContext)(pkg);
|
|
81
|
+
expect(context.isMonorepo).toBe(true);
|
|
82
|
+
expect(context.monorepoTool).toBe('nx');
|
|
83
|
+
});
|
|
84
|
+
it('detects Node.js engine requirement', () => {
|
|
85
|
+
const pkg = {
|
|
86
|
+
name: 'my-app',
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
engines: {
|
|
89
|
+
node: '>=20.0.0',
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const context = (0, files_1.detectProjectContext)(pkg);
|
|
93
|
+
expect(context.enginesNode).toBe('>=20.0.0');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -59,6 +59,17 @@ async function checkCommand(options = {}) {
|
|
|
59
59
|
console.log(chalk_1.default.yellow('⚠️ Found package.json, but no dependencies listed.'));
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
+
// Detect project context
|
|
63
|
+
const projectContext = (0, files_1.detectProjectContext)(pkg);
|
|
64
|
+
// Show Expo detection
|
|
65
|
+
if (projectContext.isExpo) {
|
|
66
|
+
console.log(chalk_1.default.magenta.bold(`📱 Expo project detected`) + chalk_1.default.magenta(` (SDK ${projectContext.expoVersion || 'unknown'})`));
|
|
67
|
+
console.log(chalk_1.default.gray(' Expo-specific compatibility rules will be applied.\n'));
|
|
68
|
+
}
|
|
69
|
+
// Show monorepo detection
|
|
70
|
+
if (projectContext.isMonorepo) {
|
|
71
|
+
console.log(chalk_1.default.cyan(`📦 Monorepo detected (${projectContext.monorepoTool || 'workspaces'})\n`));
|
|
72
|
+
}
|
|
62
73
|
console.log(chalk_1.default.yellow(`⚡ Checking ${depEntries.length} packages via ${source}...\n`));
|
|
63
74
|
// Collect version info
|
|
64
75
|
const packageData = [];
|
|
@@ -81,7 +92,7 @@ async function checkCommand(options = {}) {
|
|
|
81
92
|
if (isDeep && useAI) {
|
|
82
93
|
const modeLabel = isTechnical ? 'technical' : 'plain English';
|
|
83
94
|
console.log(chalk_1.default.yellow(`\n🧠 Analyzing upgrade risks (${modeLabel} mode)...\n`));
|
|
84
|
-
const risks = await (0, ai_1.getBatchRiskAnalysis)(packageData, isTechnical);
|
|
95
|
+
const risks = await (0, ai_1.getBatchRiskAnalysis)(packageData, isTechnical, projectContext);
|
|
85
96
|
risks.forEach(r => riskMap.set(r.name, r));
|
|
86
97
|
}
|
|
87
98
|
// Build table
|
|
@@ -137,6 +148,16 @@ async function checkCommand(options = {}) {
|
|
|
137
148
|
chalk_1.default.yellow(`${minorCount} minor`) + ` · ` +
|
|
138
149
|
chalk_1.default.green(`${patchCount} patch`));
|
|
139
150
|
}
|
|
151
|
+
// Ambient marketing - show when value delivered (critical issues found), ~30% of runs
|
|
152
|
+
if (majorCount > 0 && Math.random() < 0.3) {
|
|
153
|
+
const messages = [
|
|
154
|
+
`🛡️ Caught ${majorCount} breaking change${majorCount > 1 ? 's' : ''} before production. Share: linchpin.dev`,
|
|
155
|
+
`💡 Found this useful? Tell a friend: npx linchpin-cli`,
|
|
156
|
+
`🚀 Avoiding broken deploys? Share Linchpin: linchpin.dev`,
|
|
157
|
+
];
|
|
158
|
+
const msg = messages[Math.floor(Math.random() * messages.length)];
|
|
159
|
+
console.log(chalk_1.default.cyan(`\n ${msg}`));
|
|
160
|
+
}
|
|
140
161
|
// Mode indicator
|
|
141
162
|
if (!useAI) {
|
|
142
163
|
console.log(chalk_1.default.gray('\n ℹ️ Free mode (npm registry). Run `linchpin login` for AI features.'));
|
|
@@ -173,6 +194,14 @@ async function checkCommand(options = {}) {
|
|
|
173
194
|
warningCount: minorCount,
|
|
174
195
|
scanResults,
|
|
175
196
|
deepAnalysis: isDeep,
|
|
197
|
+
// Include project context for Expo-aware analysis
|
|
198
|
+
projectContext: {
|
|
199
|
+
isExpo: projectContext.isExpo,
|
|
200
|
+
expoVersion: projectContext.expoVersion,
|
|
201
|
+
isMonorepo: projectContext.isMonorepo,
|
|
202
|
+
monorepoTool: projectContext.monorepoTool,
|
|
203
|
+
enginesNode: projectContext.enginesNode,
|
|
204
|
+
},
|
|
176
205
|
}).then(result => {
|
|
177
206
|
if (result.error && process.env.DEBUG) {
|
|
178
207
|
console.error('Failed to sync scan:', result.error);
|
package/dist/src/lib/ai.js
CHANGED
|
@@ -99,7 +99,7 @@ Will this break my app? Is it worth the headache? Give me the honest truth in pl
|
|
|
99
99
|
return 'Could not fetch risks.';
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
|
-
async function getBatchRiskAnalysis(packages, technical = false) {
|
|
102
|
+
async function getBatchRiskAnalysis(packages, technical = false, projectContext) {
|
|
103
103
|
const outdatedPkgs = packages.filter(p => {
|
|
104
104
|
const cleanCurrent = p.current.replace(/^[\^~]/, '');
|
|
105
105
|
return cleanCurrent !== p.latest && p.latest !== 'Error' && p.latest !== 'Unknown';
|
|
@@ -113,6 +113,7 @@ async function getBatchRiskAnalysis(packages, technical = false) {
|
|
|
113
113
|
action: 'batch_risk',
|
|
114
114
|
packages: outdatedPkgs,
|
|
115
115
|
technical,
|
|
116
|
+
projectContext, // Include Expo/monorepo context
|
|
116
117
|
});
|
|
117
118
|
if (result.error) {
|
|
118
119
|
console.error('Cloud API error:', result.error);
|
package/dist/src/lib/files.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getPackageJson = getPackageJson;
|
|
4
|
+
exports.detectProjectContext = detectProjectContext;
|
|
4
5
|
const promises_1 = require("fs/promises");
|
|
5
6
|
const path_1 = require("path");
|
|
6
7
|
async function getPackageJson(dir = process.cwd()) {
|
|
@@ -14,3 +15,28 @@ async function getPackageJson(dir = process.cwd()) {
|
|
|
14
15
|
return null;
|
|
15
16
|
}
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Detect project context (Expo, monorepo, Node version)
|
|
20
|
+
*/
|
|
21
|
+
function detectProjectContext(pkg) {
|
|
22
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
23
|
+
// Detect Expo
|
|
24
|
+
const expoVersion = allDeps['expo'];
|
|
25
|
+
const isExpo = !!expoVersion;
|
|
26
|
+
// Detect monorepo (basic detection)
|
|
27
|
+
const hasWorkspaces = !!pkg.workspaces;
|
|
28
|
+
const hasTurbo = !!allDeps['turbo'];
|
|
29
|
+
const hasNx = !!allDeps['nx'] || !!allDeps['@nx/workspace'];
|
|
30
|
+
const hasLerna = !!allDeps['lerna'];
|
|
31
|
+
const isMonorepo = hasWorkspaces || hasTurbo || hasNx || hasLerna;
|
|
32
|
+
const monorepoTool = hasTurbo ? 'turborepo' : hasNx ? 'nx' : hasLerna ? 'lerna' : hasWorkspaces ? 'workspaces' : undefined;
|
|
33
|
+
// Get Node.js engine requirement
|
|
34
|
+
const enginesNode = pkg.engines?.node;
|
|
35
|
+
return {
|
|
36
|
+
isExpo,
|
|
37
|
+
expoVersion: expoVersion?.replace(/^[\^~]/, ''),
|
|
38
|
+
isMonorepo,
|
|
39
|
+
monorepoTool,
|
|
40
|
+
enginesNode,
|
|
41
|
+
};
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linchpin-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Linchpin: The 'Don't Break My App' Tool - AI-powered dependency management for solo founders",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -50,12 +50,20 @@
|
|
|
50
50
|
"resend": "^6.7.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
+
"@babel/core": "^7.28.5",
|
|
53
54
|
"@types/inquirer": "^9.0.9",
|
|
54
55
|
"@types/jest": "^30.0.0",
|
|
55
|
-
"@types/node": "^
|
|
56
|
+
"@types/node": "^25.0.6",
|
|
57
|
+
"@types/react": "^19.2.8",
|
|
58
|
+
"@types/react-dom": "^19.2.3",
|
|
59
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
60
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
61
|
+
"eslint": "^9.39.2",
|
|
62
|
+
"eslint-config-expo": "^10.0.0",
|
|
63
|
+
"eslint-config-prettier": "^10.1.8",
|
|
56
64
|
"jest": "^30.2.0",
|
|
57
65
|
"ts-jest": "^29.4.6",
|
|
58
66
|
"ts-node": "^10.9.1",
|
|
59
|
-
"typescript": "^5.3
|
|
67
|
+
"typescript": "^5.9.3"
|
|
60
68
|
}
|
|
61
69
|
}
|