Skip to content

Commit 998f1e9

Browse files
committed
Adds graph link/unlink script
1 parent 38ccbe5 commit 998f1e9

File tree

2 files changed

+200
-4
lines changed

2 files changed

+200
-4
lines changed

package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26066,10 +26066,8 @@
2606626066
"clean:lint": "pnpx rimraf .eslintcache",
2606726067
"clean:deps": "pnpx rimraf node_modules",
2606826068
"copy:images": "webpack --config webpack.config.images.mjs",
26069-
"graph:link": "pnpm link @gitkraken/gitkraken-components",
26070-
"graph:link:main": "pushd \"../GitKrakenComponents\" && pnpm link && popd && pnpm graph:link",
26071-
"graph:unlink": "pnpm unlink @gitkraken/gitkraken-components && pnpm install --force",
26072-
"graph:unlink:main": "pnpm graph:unlink && pushd \"../GitKrakenComponents\" && pnpm unlink && popd",
26069+
"graph:link": "node ./scripts/linkComponents.mjs link",
26070+
"graph:unlink": "node ./scripts/linkComponents.mjs unlink",
2607326071
"icons:apply": "node ./scripts/applyIconsContribution.mjs",
2607426072
"icons:export": "node ./scripts/export-codicons.mjs",
2607526073
"icons:svgo": "svgo -q -f ./images/icons/ --config svgo.config.js",

scripts/linkComponents.mjs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'node:fs';
2+
import { fileURLToPath } from 'node:url';
3+
import { dirname, join, resolve, relative } from 'node:path';
4+
import { globSync } from 'glob';
5+
import { execSync } from 'node:child_process';
6+
import * as readline from 'node:readline/promises';
7+
8+
const __dirname = dirname(fileURLToPath(import.meta.url));
9+
const action = process.argv[2]; // 'link' | 'unlink'
10+
const root = join(__dirname, '..');
11+
const packageJsonPath = join(root, 'package.json');
12+
const versionStateFile = join(root, '.gitkraken-components.version');
13+
14+
function getCurrentBranch(repoPath) {
15+
try {
16+
// Try normal branch first; fallback to detached HEAD short sha
17+
let branch = execSync('git rev-parse --abbrev-ref HEAD', {
18+
cwd: repoPath,
19+
encoding: 'utf8',
20+
stdio: ['pipe', 'pipe', 'ignore'],
21+
}).trim();
22+
if (branch === 'HEAD') {
23+
branch = execSync('git rev-parse --short HEAD', {
24+
cwd: repoPath,
25+
encoding: 'utf8',
26+
stdio: ['pipe', 'pipe', 'ignore'],
27+
}).trim();
28+
}
29+
return branch || 'unknown';
30+
} catch {
31+
return 'unknown';
32+
}
33+
}
34+
35+
function findComponentsPaths() {
36+
const results = [];
37+
38+
let parentRoot;
39+
try {
40+
const gitCommonDir = execSync('git rev-parse --git-common-dir', {
41+
cwd: root,
42+
encoding: 'utf8',
43+
stdio: ['pipe', 'pipe', 'ignore'],
44+
}).trim();
45+
if (gitCommonDir === '.git') {
46+
parentRoot = join(root, '..');
47+
} else {
48+
parentRoot = join(gitCommonDir, '../..');
49+
}
50+
} catch {
51+
parentRoot = resolve(root, '..');
52+
}
53+
54+
// Main repo
55+
const mainRoot = join(parentRoot, 'GitKrakenComponents');
56+
if (existsSync(join(mainRoot, 'package.json'))) {
57+
results.push({
58+
path: '../GitKrakenComponents',
59+
description: `${getCurrentBranch(mainRoot)} (default)`,
60+
abs: mainRoot,
61+
});
62+
}
63+
64+
// Worktrees
65+
const worktreesRoot = join(parentRoot, 'GitKrakenComponents.worktrees');
66+
if (existsSync(worktreesRoot)) {
67+
try {
68+
const pkgs = globSync('**/package.json', { cwd: worktreesRoot, ignore: ['**/node_modules/**'] });
69+
for (const pkg of pkgs) {
70+
const dir = join(worktreesRoot, dirname(pkg));
71+
const rel = relative(worktreesRoot, dir).replace(/\\/g, '/');
72+
results.push({
73+
path: `../GitKrakenComponents.worktrees/${rel}`,
74+
description: `${getCurrentBranch(dir)} (worktree)`,
75+
abs: dir,
76+
});
77+
}
78+
} catch {}
79+
}
80+
81+
return results.sort((a, b) => {
82+
if (a.description.includes('(default)')) return -1;
83+
if (b.description.includes('(default)')) return 1;
84+
return a.description.localeCompare(b.description);
85+
});
86+
}
87+
88+
async function promptForPath() {
89+
// Check if path provided via environment variable or argument
90+
const envPath = process.env.GK_COMPONENTS_PATH;
91+
const argPath = process.argv[3];
92+
93+
if (argPath) {
94+
console.log(`Using path from argument: ${argPath}`);
95+
return argPath;
96+
}
97+
98+
if (envPath) {
99+
console.log(`Using path from GK_COMPONENTS_PATH: ${envPath}`);
100+
return envPath;
101+
}
102+
103+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
104+
105+
const availablePaths = findComponentsPaths();
106+
107+
if (availablePaths.length === 0) {
108+
console.log('\n⚠️ No GitKrakenComponents directories found.');
109+
const customPath = await rl.question('Enter path to GitKrakenComponents: ');
110+
rl.close();
111+
return customPath.trim() || '../GitKrakenComponents';
112+
}
113+
114+
console.log('\n┌─ Select a GitKrakenComponents Path ───────────────────────────────────┐');
115+
console.log('│');
116+
for (let i = 0; i < availablePaths.length; i++) {
117+
const num = String(i + 1).padStart(2);
118+
const label = availablePaths[i].description;
119+
console.log(`│ ${num}. ${label}`);
120+
}
121+
const customNum = String(availablePaths.length + 1).padStart(2);
122+
console.log(`│ ${customNum}. Custom path`);
123+
console.log('│');
124+
console.log('└───────────────────────────────────────────────────────────────────────┘');
125+
126+
const choice = await rl.question(`\nChoice [1-${availablePaths.length + 1}] (default: 1): `);
127+
128+
let selectedPath;
129+
const trimmedChoice = choice.trim() || '1';
130+
const choiceNum = parseInt(trimmedChoice, 10);
131+
132+
if (choiceNum > 0 && choiceNum <= availablePaths.length) {
133+
selectedPath = availablePaths[choiceNum - 1].path;
134+
console.log(`\n✓ ${availablePaths[choiceNum - 1].description}`);
135+
} else if (choiceNum === availablePaths.length + 1) {
136+
const customPath = await rl.question('\nEnter custom path: ');
137+
selectedPath = customPath.trim() || '../GitKrakenComponents';
138+
console.log(`\n✓ Custom: ${selectedPath}`);
139+
} else {
140+
selectedPath = availablePaths[0].path;
141+
console.log(`\n✓ ${availablePaths[0].description}`);
142+
}
143+
144+
rl.close();
145+
return selectedPath;
146+
}
147+
148+
try {
149+
if (!['link', 'unlink'].includes(action)) {
150+
console.error('Usage: node linkComponents.mjs [link|unlink] [path]');
151+
process.exit(1);
152+
}
153+
154+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
155+
const depSection = pkg.dependencies || {};
156+
157+
if (action === 'link') {
158+
const targetPath = (await promptForPath()).replace(/\\/g, '/');
159+
const currentSpec = depSection['@gitkraken/gitkraken-components'];
160+
if (!currentSpec) {
161+
console.error('Dependency @gitkraken/gitkraken-components not found in package.json');
162+
process.exit(1);
163+
}
164+
165+
// Save original version if not already saved
166+
if (!existsSync(versionStateFile)) {
167+
writeFileSync(versionStateFile, currentSpec, 'utf8');
168+
}
169+
170+
console.log(`Linking @gitkraken/gitkraken-components -> file:${targetPath}`);
171+
execSync(`pnpm add @gitkraken/gitkraken-components@file:${targetPath}`, { stdio: 'inherit' });
172+
console.log('✓ Link complete');
173+
process.exit(0);
174+
}
175+
176+
// unlink
177+
if (!existsSync(versionStateFile)) {
178+
console.error('No saved version found (dotfile missing). Cannot restore.');
179+
process.exit(1);
180+
}
181+
182+
const originalVersion = readFileSync(versionStateFile, 'utf8').trim();
183+
if (!originalVersion) {
184+
console.error('Saved version file is empty. Aborting.');
185+
process.exit(1);
186+
}
187+
188+
console.log(`Restoring @gitkraken/gitkraken-components to ${originalVersion}`);
189+
execSync(`pnpm add @gitkraken/gitkraken-components@${originalVersion}`, { stdio: 'inherit' });
190+
191+
try {
192+
unlinkSync(versionStateFile);
193+
} catch {}
194+
console.log('✓ Unlink complete');
195+
} catch (ex) {
196+
console.error('Error:', ex.message);
197+
process.exit(1);
198+
}

0 commit comments

Comments
 (0)